summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2009-10-19 20:20:09 -0400
committerJason A. Donenfeld <Jason@zx2c4.com>2009-10-19 20:20:09 -0400
commit2d6dd2c5ade3f5fad3e2257dce52a6e188fe7535 (patch)
treeda9c93d2f87df6d2b688a455a31e69859117ba1e
downloadFramedPrototype-2d6dd2c5ade3f5fad3e2257dce52a6e188fe7535.tar.xz
FramedPrototype-2d6dd2c5ade3f5fad3e2257dce52a6e188fe7535.zip
Initial import.
-rw-r--r--api.py49
-rw-r--r--app.yaml9
-rw-r--r--framed.py82
-rw-r--r--google_appengine/BUGS3
-rw-r--r--google_appengine/LICENSE95
-rw-r--r--google_appengine/README91
-rw-r--r--google_appengine/RELEASE_NOTES421
-rw-r--r--google_appengine/VERSION3
-rwxr-xr-xgoogle_appengine/appcfg.py60
-rwxr-xr-xgoogle_appengine/bulkload_client.py60
-rwxr-xr-xgoogle_appengine/bulkloader.py60
-rw-r--r--google_appengine/demos/guestbook/app.yaml8
-rwxr-xr-xgoogle_appengine/demos/guestbook/guestbook.py79
-rw-r--r--google_appengine/demos/guestbook/index.yaml11
-rwxr-xr-xgoogle_appengine/dev_appserver.py60
-rwxr-xr-xgoogle_appengine/google/__init__.py16
-rw-r--r--google_appengine/google/__init__.pycbin0 -> 142 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/__init__.py16
-rw-r--r--google_appengine/google/appengine/__init__.pycbin0 -> 152 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/__init__.py16
-rw-r--r--google_appengine/google/appengine/api/__init__.pycbin0 -> 156 bytes
-rw-r--r--google_appengine/google/appengine/api/api_base_pb.py582
-rw-r--r--google_appengine/google/appengine/api/api_base_pb.pycbin0 -> 26242 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/apiproxy_rpc.py150
-rw-r--r--google_appengine/google/appengine/api/apiproxy_rpc.pycbin0 -> 5367 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/apiproxy_stub.py80
-rw-r--r--google_appengine/google/appengine/api/apiproxy_stub.pycbin0 -> 2862 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/apiproxy_stub_map.py470
-rw-r--r--google_appengine/google/appengine/api/apiproxy_stub_map.pycbin0 -> 19038 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/app_logging.py99
-rwxr-xr-xgoogle_appengine/google/appengine/api/appinfo.py430
-rw-r--r--google_appengine/google/appengine/api/appinfo.pycbin0 -> 13746 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/appinfo_errors.py46
-rw-r--r--google_appengine/google/appengine/api/appinfo_errors.pycbin0 -> 2564 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/capabilities/__init__.py172
-rw-r--r--google_appengine/google/appengine/api/capabilities/__init__.pycbin0 -> 5952 bytes
-rw-r--r--google_appengine/google/appengine/api/capabilities/capability_service_pb.py366
-rw-r--r--google_appengine/google/appengine/api/capabilities/capability_service_pb.pycbin0 -> 18033 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/capabilities/capability_stub.py53
-rw-r--r--google_appengine/google/appengine/api/capabilities/capability_stub.pycbin0 -> 1762 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/croninfo.py132
-rw-r--r--google_appengine/google/appengine/api/croninfo.pycbin0 -> 4778 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/datastore.py2170
-rw-r--r--google_appengine/google/appengine/api/datastore.pycbin0 -> 73429 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/datastore_admin.py213
-rw-r--r--google_appengine/google/appengine/api/datastore_admin.pycbin0 -> 7403 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/datastore_entities.py343
-rwxr-xr-xgoogle_appengine/google/appengine/api/datastore_errors.py105
-rw-r--r--google_appengine/google/appengine/api/datastore_errors.pycbin0 -> 6056 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/datastore_file_stub.py1061
-rw-r--r--google_appengine/google/appengine/api/datastore_file_stub.pycbin0 -> 36485 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/datastore_types.py1788
-rw-r--r--google_appengine/google/appengine/api/datastore_types.pycbin0 -> 64168 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/images/__init__.py827
-rw-r--r--google_appengine/google/appengine/api/images/__init__.pycbin0 -> 29034 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/images/images_not_implemented_stub.py36
-rw-r--r--google_appengine/google/appengine/api/images/images_not_implemented_stub.pycbin0 -> 1326 bytes
-rw-r--r--google_appengine/google/appengine/api/images/images_service_pb.py1988
-rw-r--r--google_appengine/google/appengine/api/images/images_service_pb.pycbin0 -> 92677 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/images/images_stub.py411
-rw-r--r--google_appengine/google/appengine/api/images/images_stub.pycbin0 -> 13183 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/labs/__init__.py16
-rw-r--r--google_appengine/google/appengine/api/labs/__init__.pycbin0 -> 161 bytes
-rw-r--r--google_appengine/google/appengine/api/labs/taskqueue/__init__.py20
-rw-r--r--google_appengine/google/appengine/api/labs/taskqueue/__init__.pycbin0 -> 258 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/labs/taskqueue/taskqueue.py633
-rw-r--r--google_appengine/google/appengine/api/labs/taskqueue/taskqueue.pycbin0 -> 25279 bytes
-rw-r--r--google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.py1645
-rw-r--r--google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.pycbin0 -> 80247 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.py327
-rw-r--r--google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.pycbin0 -> 10766 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/mail.py1127
-rw-r--r--google_appengine/google/appengine/api/mail.pycbin0 -> 36719 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/mail_errors.py55
-rw-r--r--google_appengine/google/appengine/api/mail_errors.pycbin0 -> 3365 bytes
-rw-r--r--google_appengine/google/appengine/api/mail_service_pb.py584
-rw-r--r--google_appengine/google/appengine/api/mail_service_pb.pycbin0 -> 26434 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/mail_stub.py233
-rw-r--r--google_appengine/google/appengine/api/mail_stub.pycbin0 -> 7980 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/memcache/__init__.py931
-rw-r--r--google_appengine/google/appengine/api/memcache/__init__.pycbin0 -> 35018 bytes
-rw-r--r--google_appengine/google/appengine/api/memcache/memcache_service_pb.py2002
-rw-r--r--google_appengine/google/appengine/api/memcache/memcache_service_pb.pycbin0 -> 96235 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/memcache/memcache_stub.py293
-rw-r--r--google_appengine/google/appengine/api/memcache/memcache_stub.pycbin0 -> 10214 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/namespace_manager/__init__.py75
-rw-r--r--google_appengine/google/appengine/api/namespace_manager/__init__.pycbin0 -> 2413 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/queueinfo.py143
-rw-r--r--google_appengine/google/appengine/api/queueinfo.pycbin0 -> 4721 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/quota.py71
-rwxr-xr-xgoogle_appengine/google/appengine/api/urlfetch.py361
-rw-r--r--google_appengine/google/appengine/api/urlfetch.pycbin0 -> 12400 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/urlfetch_errors.py60
-rw-r--r--google_appengine/google/appengine/api/urlfetch_errors.pycbin0 -> 2546 bytes
-rw-r--r--google_appengine/google/appengine/api/urlfetch_service_pb.py823
-rw-r--r--google_appengine/google/appengine/api/urlfetch_service_pb.pycbin0 -> 37893 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/urlfetch_stub.py270
-rw-r--r--google_appengine/google/appengine/api/urlfetch_stub.pycbin0 -> 8446 bytes
-rw-r--r--google_appengine/google/appengine/api/user_service_pb.py491
-rw-r--r--google_appengine/google/appengine/api/user_service_pb.pycbin0 -> 22376 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/user_service_stub.py106
-rw-r--r--google_appengine/google/appengine/api/user_service_stub.pycbin0 -> 3831 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/users.py230
-rw-r--r--google_appengine/google/appengine/api/users.pycbin0 -> 8366 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/validation.py928
-rw-r--r--google_appengine/google/appengine/api/validation.pycbin0 -> 35134 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/xmpp/__init__.py332
-rw-r--r--google_appengine/google/appengine/api/xmpp/__init__.pycbin0 -> 11264 bytes
-rw-r--r--google_appengine/google/appengine/api/xmpp/xmpp_service_pb.py826
-rw-r--r--google_appengine/google/appengine/api/xmpp/xmpp_service_pb.pycbin0 -> 37790 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/xmpp/xmpp_service_stub.py154
-rw-r--r--google_appengine/google/appengine/api/xmpp/xmpp_service_stub.pycbin0 -> 4894 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/yaml_builder.py432
-rw-r--r--google_appengine/google/appengine/api/yaml_builder.pycbin0 -> 15897 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/yaml_errors.py96
-rw-r--r--google_appengine/google/appengine/api/yaml_errors.pycbin0 -> 4937 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/yaml_listener.py218
-rw-r--r--google_appengine/google/appengine/api/yaml_listener.pycbin0 -> 9436 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/api/yaml_object.py294
-rw-r--r--google_appengine/google/appengine/api/yaml_object.pycbin0 -> 10844 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/base/__init__.py16
-rw-r--r--google_appengine/google/appengine/base/__init__.pycbin0 -> 157 bytes
-rw-r--r--google_appengine/google/appengine/base/capabilities_pb.py451
-rw-r--r--google_appengine/google/appengine/base/capabilities_pb.pycbin0 -> 20294 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/cron/GrocLexer.py1669
-rw-r--r--google_appengine/google/appengine/cron/GrocLexer.pycbin0 -> 22472 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/cron/GrocParser.py1008
-rw-r--r--google_appengine/google/appengine/cron/GrocParser.pycbin0 -> 18129 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/cron/__init__.py17
-rw-r--r--google_appengine/google/appengine/cron/__init__.pycbin0 -> 222 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/cron/groc.py74
-rw-r--r--google_appengine/google/appengine/cron/groc.pycbin0 -> 2413 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/cron/groctimespecification.py304
-rw-r--r--google_appengine/google/appengine/cron/groctimespecification.pycbin0 -> 10574 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/datastore/__init__.py16
-rw-r--r--google_appengine/google/appengine/datastore/__init__.pycbin0 -> 162 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/datastore/action_pb.py24
-rw-r--r--google_appengine/google/appengine/datastore/action_pb.pycbin0 -> 615 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/datastore/datastore_index.py438
-rw-r--r--google_appengine/google/appengine/datastore/datastore_index.pycbin0 -> 14075 bytes
-rw-r--r--google_appengine/google/appengine/datastore/datastore_pb.py4673
-rw-r--r--google_appengine/google/appengine/datastore/datastore_pb.pycbin0 -> 214173 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/datastore/datastore_v3_pb.py26
-rw-r--r--google_appengine/google/appengine/datastore/datastore_v3_pb.pycbin0 -> 533 bytes
-rw-r--r--google_appengine/google/appengine/datastore/entity_pb.py2599
-rw-r--r--google_appengine/google/appengine/datastore/entity_pb.pycbin0 -> 117476 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/dist/__init__.py36
-rw-r--r--google_appengine/google/appengine/dist/__init__.pycbin0 -> 487 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/dist/_library.py284
-rw-r--r--google_appengine/google/appengine/dist/_library.pycbin0 -> 9435 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/dist/ftplib.py16
-rwxr-xr-xgoogle_appengine/google/appengine/dist/httplib.py388
-rw-r--r--google_appengine/google/appengine/dist/httplib.pycbin0 -> 15982 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/dist/neo_cgi.py16
-rwxr-xr-xgoogle_appengine/google/appengine/dist/py_imp.py142
-rw-r--r--google_appengine/google/appengine/dist/py_imp.pycbin0 -> 5838 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/dist/py_zipimport.py291
-rw-r--r--google_appengine/google/appengine/dist/py_zipimport.pycbin0 -> 10960 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/dist/select.py16
-rwxr-xr-xgoogle_appengine/google/appengine/dist/socket.py45
-rw-r--r--google_appengine/google/appengine/dist/socket.pycbin0 -> 1618 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/dist/subprocess.py16
-rwxr-xr-xgoogle_appengine/google/appengine/dist/tempfile.py65
-rw-r--r--google_appengine/google/appengine/dist/tempfile.pycbin0 -> 1621 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/__init__.py16
-rw-r--r--google_appengine/google/appengine/ext/__init__.pycbin0 -> 156 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/admin/__init__.py1297
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/base.html96
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/cron.html85
-rwxr-xr-xgoogle_appengine/google/appengine/ext/admin/templates/css/ae.css170
-rwxr-xr-xgoogle_appengine/google/appengine/ext/admin/templates/css/base.css2
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/cron.css26
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/datastore.css71
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/form.css20
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/inboundmail.css19
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/memcache.css54
-rwxr-xr-xgoogle_appengine/google/appengine/ext/admin/templates/css/nav.css88
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/pager.css7
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/queues.css26
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/tasks.css26
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/css/xmpp.css19
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/datastore.html183
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/datastore_edit.html162
-rwxr-xr-xgoogle_appengine/google/appengine/ext/admin/templates/images/google.gifbin0 -> 1470 bytes
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/inboundmail.html158
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/interactive-output.html36
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/interactive.html104
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/js/multipart_form_data.js125
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/js/rfc822_date.js70
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/js/webhook.js87
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/memcache.html119
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/pager.html9
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/queues.html75
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/tasks.html103
-rw-r--r--google_appengine/google/appengine/ext/admin/templates/xmpp.html234
-rwxr-xr-xgoogle_appengine/google/appengine/ext/bulkload/__init__.py435
-rwxr-xr-xgoogle_appengine/google/appengine/ext/bulkload/constants.py24
-rwxr-xr-xgoogle_appengine/google/appengine/ext/db/__init__.py2959
-rw-r--r--google_appengine/google/appengine/ext/db/__init__.pycbin0 -> 107352 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/db/djangoforms.py886
-rwxr-xr-xgoogle_appengine/google/appengine/ext/db/polymodel.py355
-rw-r--r--google_appengine/google/appengine/ext/db/polymodel.pycbin0 -> 13673 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/deferred/__init__.py22
-rwxr-xr-xgoogle_appengine/google/appengine/ext/deferred/deferred.py267
-rwxr-xr-xgoogle_appengine/google/appengine/ext/ereporter/__init__.py18
-rwxr-xr-xgoogle_appengine/google/appengine/ext/ereporter/ereporter.py261
-rwxr-xr-xgoogle_appengine/google/appengine/ext/ereporter/report_generator.py184
-rw-r--r--google_appengine/google/appengine/ext/ereporter/templates/report.html15
-rwxr-xr-xgoogle_appengine/google/appengine/ext/gql/__init__.py1151
-rw-r--r--google_appengine/google/appengine/ext/gql/__init__.pycbin0 -> 41358 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/key_range/__init__.py570
-rw-r--r--google_appengine/google/appengine/ext/key_range/__init__.pycbin0 -> 19668 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/preload/__init__.py213
-rw-r--r--google_appengine/google/appengine/ext/remote_api/__init__.py16
-rw-r--r--google_appengine/google/appengine/ext/remote_api/__init__.pycbin0 -> 167 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/remote_api/handler.py319
-rw-r--r--google_appengine/google/appengine/ext/remote_api/remote_api_pb.py809
-rw-r--r--google_appengine/google/appengine/ext/remote_api/remote_api_pb.pycbin0 -> 37288 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/remote_api/remote_api_stub.py499
-rw-r--r--google_appengine/google/appengine/ext/remote_api/remote_api_stub.pycbin0 -> 20575 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/remote_api/throttle.py637
-rw-r--r--google_appengine/google/appengine/ext/remote_api/throttle.pycbin0 -> 24719 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/search/__init__.py420
-rwxr-xr-xgoogle_appengine/google/appengine/ext/webapp/__init__.py580
-rw-r--r--google_appengine/google/appengine/ext/webapp/__init__.pycbin0 -> 22445 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/webapp/mail_handlers.py78
-rwxr-xr-xgoogle_appengine/google/appengine/ext/webapp/template.py219
-rw-r--r--google_appengine/google/appengine/ext/webapp/template.pycbin0 -> 7270 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/webapp/util.py90
-rw-r--r--google_appengine/google/appengine/ext/webapp/util.pycbin0 -> 3085 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/ext/webapp/xmpp_handlers.py119
-rwxr-xr-xgoogle_appengine/google/appengine/ext/zipserve/__init__.py173
-rwxr-xr-xgoogle_appengine/google/appengine/runtime/__init__.py33
-rw-r--r--google_appengine/google/appengine/runtime/__init__.pycbin0 -> 768 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/runtime/apiproxy.py184
-rw-r--r--google_appengine/google/appengine/runtime/apiproxy.pycbin0 -> 6433 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/runtime/apiproxy_errors.py84
-rw-r--r--google_appengine/google/appengine/runtime/apiproxy_errors.pycbin0 -> 4918 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/__init__.py16
-rw-r--r--google_appengine/google/appengine/tools/__init__.pycbin0 -> 158 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/adaptive_thread_pool.py460
-rw-r--r--google_appengine/google/appengine/tools/adaptive_thread_pool.pycbin0 -> 17325 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/appcfg.py2525
-rw-r--r--google_appengine/google/appengine/tools/appcfg.pycbin0 -> 91877 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/appengine_rpc.py435
-rw-r--r--google_appengine/google/appengine/tools/appengine_rpc.pycbin0 -> 16715 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/bulkload_client.py297
-rwxr-xr-xgoogle_appengine/google/appengine/tools/bulkloader.py3827
-rw-r--r--google_appengine/google/appengine/tools/bulkloader.pycbin0 -> 135278 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/dev_appserver.py3542
-rw-r--r--google_appengine/google/appengine/tools/dev_appserver.pycbin0 -> 119171 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/dev_appserver_index.py277
-rw-r--r--google_appengine/google/appengine/tools/dev_appserver_index.pycbin0 -> 8685 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/dev_appserver_info.py160
-rwxr-xr-xgoogle_appengine/google/appengine/tools/dev_appserver_login.py297
-rw-r--r--google_appengine/google/appengine/tools/dev_appserver_login.pycbin0 -> 8740 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/dev_appserver_main.py498
-rwxr-xr-xgoogle_appengine/google/appengine/tools/os_compat.py46
-rw-r--r--google_appengine/google/appengine/tools/os_compat.pycbin0 -> 1180 bytes
-rwxr-xr-xgoogle_appengine/google/appengine/tools/remote_api_shell.py94
-rwxr-xr-xgoogle_appengine/google/appengine/tools/requeue.py219
-rw-r--r--google_appengine/google/appengine/tools/requeue.pycbin0 -> 7934 bytes
-rwxr-xr-xgoogle_appengine/google/net/__init__.py16
-rw-r--r--google_appengine/google/net/__init__.pycbin0 -> 146 bytes
-rw-r--r--google_appengine/google/net/proto/ProtocolBuffer.py532
-rw-r--r--google_appengine/google/net/proto/ProtocolBuffer.pycbin0 -> 23250 bytes
-rwxr-xr-xgoogle_appengine/google/net/proto/RawMessage.py83
-rw-r--r--google_appengine/google/net/proto/RawMessage.pycbin0 -> 3751 bytes
-rwxr-xr-xgoogle_appengine/google/net/proto/__init__.py16
-rw-r--r--google_appengine/google/net/proto/__init__.pycbin0 -> 152 bytes
-rwxr-xr-xgoogle_appengine/google/net/proto/message_set.py291
-rwxr-xr-xgoogle_appengine/google/pyglib/__init__.py16
-rw-r--r--google_appengine/google/pyglib/__init__.pycbin0 -> 149 bytes
-rw-r--r--google_appengine/google/pyglib/gexcept.py39
-rw-r--r--google_appengine/google/pyglib/gexcept.pycbin0 -> 1756 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/AUTHORS2
-rwxr-xr-xgoogle_appengine/lib/antlr3/LICENSE26
-rwxr-xr-xgoogle_appengine/lib/antlr3/MANIFEST.in2
-rw-r--r--google_appengine/lib/antlr3/OWNERS7
-rwxr-xr-xgoogle_appengine/lib/antlr3/README90
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/__init__.py171
-rw-r--r--google_appengine/lib/antlr3/antlr3/__init__.pycbin0 -> 4493 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/compat.py48
-rw-r--r--google_appengine/lib/antlr3/antlr3/compat.pycbin0 -> 591 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/constants.py57
-rw-r--r--google_appengine/lib/antlr3/antlr3/constants.pycbin0 -> 421 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/dfa.py213
-rw-r--r--google_appengine/lib/antlr3/antlr3/dfa.pycbin0 -> 4602 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/dottreegen.py210
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/exceptions.py364
-rw-r--r--google_appengine/lib/antlr3/antlr3/exceptions.pycbin0 -> 12794 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/extras.py47
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/main.py289
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/recognizers.py1511
-rw-r--r--google_appengine/lib/antlr3/antlr3/recognizers.pycbin0 -> 48184 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/streams.py1452
-rw-r--r--google_appengine/lib/antlr3/antlr3/streams.pycbin0 -> 44652 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/tokens.py416
-rw-r--r--google_appengine/lib/antlr3/antlr3/tokens.pycbin0 -> 13856 bytes
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/tree.py2448
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr3/treewizard.py612
-rwxr-xr-xgoogle_appengine/lib/antlr3/antlr_python_runtime.egg-info/PKG-INFO13
-rw-r--r--google_appengine/lib/antlr3/antlr_python_runtime.egg-info/SOURCES.txt23
-rw-r--r--google_appengine/lib/antlr3/antlr_python_runtime.egg-info/dependency_links.txt1
-rw-r--r--google_appengine/lib/antlr3/antlr_python_runtime.egg-info/top_level.txt1
-rwxr-xr-xgoogle_appengine/lib/antlr3/setup.py289
-rw-r--r--google_appengine/lib/django/AUTHORS216
-rw-r--r--google_appengine/lib/django/INSTALL22
-rw-r--r--google_appengine/lib/django/LICENSE27
-rw-r--r--google_appengine/lib/django/PKG-INFO11
-rw-r--r--google_appengine/lib/django/README37
-rwxr-xr-xgoogle_appengine/lib/django/django/__init__.py1
-rw-r--r--google_appengine/lib/django/django/__init__.pycbin0 -> 214 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/compile-messages.py49
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/daily_cleanup.py20
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/django-admin.py5
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/make-messages.py145
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/profiling/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/profiling/gather_profile_stats.py34
-rwxr-xr-xgoogle_appengine/lib/django/django/bin/unique-messages.py28
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/__init__.py149
-rw-r--r--google_appengine/lib/django/django/conf/__init__.pycbin0 -> 6249 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/app_template/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/app_template/models.py3
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/app_template/views.py1
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/global_settings.py330
-rw-r--r--google_appengine/lib/django/django/conf/global_settings.pycbin0 -> 5803 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.mobin0 -> 41884 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.po1989
-rw-r--r--google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.mobin0 -> 1774 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.mobin0 -> 25779 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.po1993
-rw-r--r--google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.mobin0 -> 31909 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.po2383
-rw-r--r--google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.mobin0 -> 1520 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.po121
-rw-r--r--google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.mobin0 -> 37754 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.po2110
-rw-r--r--google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.mobin0 -> 1458 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.po112
-rw-r--r--google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.mobin0 -> 22852 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.po1990
-rw-r--r--google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.mobin0 -> 1009 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.po112
-rw-r--r--google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.mobin0 -> 32010 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.po1927
-rw-r--r--google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.mobin0 -> 40423 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.po2225
-rw-r--r--google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.mobin0 -> 1569 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.po119
-rw-r--r--google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.mobin0 -> 15668 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.po1921
-rw-r--r--google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.mobin0 -> 1810 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.po109
-rw-r--r--google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.mobin0 -> 627 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.po2097
-rw-r--r--google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.mobin0 -> 367 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.po108
-rw-r--r--google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.mobin0 -> 40248 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.po2371
-rw-r--r--google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.mobin0 -> 1427 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.mobin0 -> 40281 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.po2425
-rw-r--r--google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mobin0 -> 1576 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po118
-rw-r--r--google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.mobin0 -> 34867 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.po2036
-rw-r--r--google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.mobin0 -> 1529 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.mobin0 -> 39707 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.po2513
-rw-r--r--google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.mobin0 -> 1533 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.mobin0 -> 32192 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.po1988
-rw-r--r--google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.mobin0 -> 1519 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.mobin0 -> 36826 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.po1999
-rw-r--r--google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.mobin0 -> 1626 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.po111
-rw-r--r--google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.mobin0 -> 30545 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.po2022
-rw-r--r--google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.mobin0 -> 1556 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.mobin0 -> 29355 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.po2042
-rw-r--r--google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.mobin0 -> 1609 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.po109
-rw-r--r--google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.mobin0 -> 40300 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.po2297
-rw-r--r--google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.mobin0 -> 1643 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.po123
-rw-r--r--google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.mobin0 -> 43571 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.po2327
-rw-r--r--google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.mobin0 -> 1604 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.po118
-rw-r--r--google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.mobin0 -> 57878 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.po2533
-rw-r--r--google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.mobin0 -> 2205 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.po116
-rw-r--r--google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.mobin0 -> 26751 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.po2326
-rw-r--r--google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.mobin0 -> 367 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.po118
-rw-r--r--google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.mobin0 -> 50847 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.po2320
-rw-r--r--google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.mobin0 -> 1921 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.po119
-rw-r--r--google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.mobin0 -> 38004 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.po2277
-rw-r--r--google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.mobin0 -> 1507 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.mobin0 -> 27469 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.po2002
-rw-r--r--google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.mobin0 -> 1492 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.po118
-rw-r--r--google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.mobin0 -> 28373 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.po1961
-rw-r--r--google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.mobin0 -> 1564 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.po112
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.mobin0 -> 37681 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.po2125
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.mobin0 -> 1514 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.po108
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.mobin0 -> 28462 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.po2051
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.mobin0 -> 1537 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.po109
-rw-r--r--google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.mobin0 -> 16327 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.po2005
-rw-r--r--google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.mobin0 -> 42820 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.po1906
-rw-r--r--google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.mobin0 -> 1746 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.po111
-rw-r--r--google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.mobin0 -> 32375 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.po2002
-rw-r--r--google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.mobin0 -> 1492 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.po111
-rw-r--r--google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.mobin0 -> 32469 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.po1929
-rw-r--r--google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.mobin0 -> 32246 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.po1916
-rw-r--r--google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.mobin0 -> 1669 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.po109
-rw-r--r--google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.mobin0 -> 39640 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.po2344
-rw-r--r--google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.mobin0 -> 1680 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.po125
-rw-r--r--google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.mobin0 -> 60022 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.po2136
-rw-r--r--google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.mobin0 -> 2336 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.po112
-rw-r--r--google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.mobin0 -> 35017 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.po2106
-rw-r--r--google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.mobin0 -> 2206 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.po110
-rw-r--r--google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.mobin0 -> 38712 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.po2470
-rw-r--r--google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.mobin0 -> 1508 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.po109
-rw-r--r--google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.mobin0 -> 24836 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.po1970
-rw-r--r--google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.mobin0 -> 30798 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.po1879
-rw-r--r--google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mobin0 -> 1505 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po105
-rw-r--r--google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.mobin0 -> 28397 bytes
-rw-r--r--google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.po1974
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/project_template/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/project_template/manage.py11
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/project_template/settings.py80
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/project_template/urls.py9
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/urls/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/urls/defaults.py19
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/urls/i18n.py5
-rwxr-xr-xgoogle_appengine/lib/django/django/conf/urls/shortcut.py5
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/filterspecs.py175
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/base.css14
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/changelists.css50
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/dashboard.css10
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/forms.css60
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/global.css141
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/layout.css29
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/login.css13
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/patch-iewin.css8
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/rtl.css46
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/css/widgets.css101
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-down.gifbin0 -> 80 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-up.gifbin0 -> 838 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/changelist-bg.gifbin0 -> 58 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser-bg.gifbin0 -> 199 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser_stacked-bg.gifbin0 -> 212 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg-reverse.gifbin0 -> 843 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg.gifbin0 -> 844 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/deleted-overlay.gifbin0 -> 45 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-no.gifbin0 -> 176 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-unknown.gifbin0 -> 130 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-yes.gifbin0 -> 299 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_addlink.gifbin0 -> 119 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_alert.gifbin0 -> 145 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_calendar.gifbin0 -> 192 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_changelink.gifbin0 -> 119 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_clock.gifbin0 -> 390 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_deletelink.gifbin0 -> 181 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_error.gifbin0 -> 319 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_searchbox.pngbin0 -> 667 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_success.gifbin0 -> 341 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete-8bit.pngbin0 -> 477 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete.pngbin0 -> 781 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore-8bit.pngbin0 -> 447 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore.pngbin0 -> 623 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-splitter-bg.gifbin0 -> 102 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-grabber.gifbin0 -> 116 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-reverse.gifbin0 -> 186 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg.gifbin0 -> 273 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-add.gifbin0 -> 606 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-addall.gifbin0 -> 358 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-remove.gifbin0 -> 398 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-removeall.gifbin0 -> 355 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-search.gifbin0 -> 552 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-add.gifbin0 -> 612 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-remove.gifbin0 -> 401 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left.gifbin0 -> 197 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left_over.gifbin0 -> 203 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right.gifbin0 -> 198 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right_over.gifbin0 -> 200 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add.gifbin0 -> 932 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add_over.gifbin0 -> 336 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright.gifbin0 -> 351 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gifbin0 -> 354 bytes
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/SelectBox.js109
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter.js81
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter2.js113
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/admin/CollapsedFieldsets.js85
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/admin/DateTimeShortcuts.js241
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/admin/RelatedObjectLookups.js57
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/admin/ordering.js137
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/calendar.js143
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/core.js164
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/dateparse.js233
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/getElementsBySelector.js167
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/timeparse.js94
-rw-r--r--google_appengine/lib/django/django/contrib/admin/media/js/urlify.js15
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/models.py51
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/404.html12
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/500.html12
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/add_form.html28
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/change_password.html52
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/base.html55
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/base_site.html10
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/change_form.html70
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/change_list.html23
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/change_list_results.html17
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/date_hierarchy.html10
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/delete_confirmation.html30
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_stacked.html16
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_tabular.html44
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/field_line.html10
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/filter.html8
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/filters.html7
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/index.html67
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/invalid_setup.html10
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/login.html32
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/object_history.html43
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/pagination.html11
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/search_form.html18
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/submit_line.html8
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin/template_validator.html31
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/bookmarklets.html32
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/index.html28
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/missing_docutils.html17
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_detail.html47
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_index.html45
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_detail.html22
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_filter_index.html48
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_tag_index.html48
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_detail.html26
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_index.html43
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/registration/logged_out.html12
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_done.html14
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_form.html26
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_done.html14
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_email.html15
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_form.html19
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/widget/date_time.html5
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/widget/default.html1
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/widget/file.html4
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/widget/foreign.html20
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/widget/many_to_many.html1
-rw-r--r--google_appengine/lib/django/django/contrib/admin/templates/widget/one_to_one.html2
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/templatetags/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/templatetags/admin_list.py279
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/templatetags/admin_modify.py247
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/templatetags/adminapplist.py79
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/templatetags/adminmedia.py14
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/templatetags/log.py53
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/urls.py43
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/utils.py102
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/views/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/views/auth.py77
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/views/decorators.py73
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/views/doc.py365
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/views/main.py779
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/admin/views/template.py72
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/__init__.py77
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/backends.py21
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/create_superuser.py91
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/decorators.py36
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/forms.py144
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/handlers/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/handlers/modpython.py52
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/management.py49
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/middleware.py12
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/models.py315
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/auth/views.py85
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/feeds.py41
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/models.py285
-rw-r--r--google_appengine/lib/django/django/contrib/comments/templates/comments/form.html38
-rw-r--r--google_appengine/lib/django/django/contrib/comments/templates/comments/freeform.html13
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/templatetags/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/templatetags/comments.py322
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/urls/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/urls/comments.py12
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/views/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/views/comments.py340
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/views/karma.py29
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/comments/views/userflags.py54
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/contenttypes/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/contenttypes/management.py33
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/contenttypes/models.py60
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/csrf/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/csrf/middleware.py92
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/flatpages/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/flatpages/middleware.py18
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/flatpages/models.py33
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/flatpages/urls.py5
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/flatpages/views.py38
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/formtools/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/formtools/preview.py165
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/humanize/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/humanize/templatetags/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/humanize/templatetags/humanize.py69
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/localflavor/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/localflavor/uk/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/localflavor/uk/forms.py19
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/localflavor/usa/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/localflavor/usa/forms.py59
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/localflavor/usa/us_states.py239
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/markup/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/markup/templatetags/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/markup/templatetags/markup.py56
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/redirects/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/redirects/middleware.py27
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/redirects/models.py24
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sessions/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sessions/middleware.py103
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sessions/models.py88
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sitemaps/__init__.py90
-rw-r--r--google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap.xml13
-rw-r--r--google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap_index.xml4
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sitemaps/views.py30
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sites/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sites/management.py17
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sites/managers.py20
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/sites/models.py23
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/syndication/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/syndication/feeds.py122
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/syndication/views.py25
-rwxr-xr-xgoogle_appengine/lib/django/django/core/__init__.py0
-rw-r--r--google_appengine/lib/django/django/core/__init__.pycbin0 -> 158 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/__init__.py54
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/base.py56
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/db.py82
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/dummy.py22
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/filebased.py80
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/locmem.py47
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/memcached.py29
-rwxr-xr-xgoogle_appengine/lib/django/django/core/cache/backends/simple.py64
-rwxr-xr-xgoogle_appengine/lib/django/django/core/context_processors.py69
-rwxr-xr-xgoogle_appengine/lib/django/django/core/exceptions.py25
-rw-r--r--google_appengine/lib/django/django/core/exceptions.pycbin0 -> 1893 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/core/handler.py11
-rwxr-xr-xgoogle_appengine/lib/django/django/core/handlers/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/core/handlers/base.py131
-rwxr-xr-xgoogle_appengine/lib/django/django/core/handlers/modpython.py177
-rwxr-xr-xgoogle_appengine/lib/django/django/core/handlers/profiler-hotshot.py22
-rwxr-xr-xgoogle_appengine/lib/django/django/core/handlers/wsgi.py207
-rwxr-xr-xgoogle_appengine/lib/django/django/core/mail.py108
-rwxr-xr-xgoogle_appengine/lib/django/django/core/management.py1670
-rwxr-xr-xgoogle_appengine/lib/django/django/core/paginator.py88
-rwxr-xr-xgoogle_appengine/lib/django/django/core/serializers/__init__.py90
-rwxr-xr-xgoogle_appengine/lib/django/django/core/serializers/base.py165
-rwxr-xr-xgoogle_appengine/lib/django/django/core/serializers/json.py51
-rwxr-xr-xgoogle_appengine/lib/django/django/core/serializers/python.py101
-rwxr-xr-xgoogle_appengine/lib/django/django/core/serializers/pyyaml.py36
-rwxr-xr-xgoogle_appengine/lib/django/django/core/serializers/xml_serializer.py229
-rwxr-xr-xgoogle_appengine/lib/django/django/core/servers/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/core/servers/basehttp.py664
-rwxr-xr-xgoogle_appengine/lib/django/django/core/servers/fastcgi.py158
-rwxr-xr-xgoogle_appengine/lib/django/django/core/signals.py3
-rwxr-xr-xgoogle_appengine/lib/django/django/core/template_loader.py7
-rwxr-xr-xgoogle_appengine/lib/django/django/core/urlresolvers.py241
-rwxr-xr-xgoogle_appengine/lib/django/django/core/validators.py573
-rwxr-xr-xgoogle_appengine/lib/django/django/core/xheaders.py22
-rwxr-xr-xgoogle_appengine/lib/django/django/db/__init__.py48
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/ado_mssql/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/ado_mssql/base.py167
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/ado_mssql/client.py2
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/ado_mssql/creation.py25
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/ado_mssql/introspection.py13
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/dummy/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/dummy/base.py44
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/dummy/client.py3
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/dummy/creation.py1
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/dummy/introspection.py8
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql/base.py231
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql/client.py27
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql/creation.py29
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql/introspection.py95
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql_old/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql_old/base.py233
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql_old/client.py14
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql_old/creation.py29
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/mysql_old/introspection.py95
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/oracle/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/oracle/base.py151
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/oracle/client.py10
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/oracle/creation.py25
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/oracle/introspection.py50
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql/base.py241
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql/client.py15
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql/creation.py29
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql/introspection.py83
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql_psycopg2/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql_psycopg2/base.py186
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql_psycopg2/client.py1
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql_psycopg2/creation.py1
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/postgresql_psycopg2/introspection.py83
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/sqlite3/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/sqlite3/base.py201
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/sqlite3/client.py6
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/sqlite3/creation.py28
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/sqlite3/introspection.py87
-rwxr-xr-xgoogle_appengine/lib/django/django/db/backends/util.py120
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/__init__.py58
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/base.py448
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/fields/__init__.py892
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/fields/generic.py260
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/fields/related.py801
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/loading.py116
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/manager.py117
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/manipulators.py335
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/options.py274
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/query.py1079
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/related.py142
-rwxr-xr-xgoogle_appengine/lib/django/django/db/models/signals.py12
-rwxr-xr-xgoogle_appengine/lib/django/django/db/transaction.py222
-rwxr-xr-xgoogle_appengine/lib/django/django/dispatch/__init__.py6
-rwxr-xr-xgoogle_appengine/lib/django/django/dispatch/dispatcher.py495
-rwxr-xr-xgoogle_appengine/lib/django/django/dispatch/errors.py10
-rwxr-xr-xgoogle_appengine/lib/django/django/dispatch/robust.py57
-rwxr-xr-xgoogle_appengine/lib/django/django/dispatch/robustapply.py47
-rwxr-xr-xgoogle_appengine/lib/django/django/dispatch/saferef.py165
-rwxr-xr-xgoogle_appengine/lib/django/django/forms/__init__.py1
-rwxr-xr-xgoogle_appengine/lib/django/django/http/__init__.py304
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/cache.py84
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/common.py96
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/doc.py18
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/gzip.py29
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/http.py61
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/locale.py24
-rwxr-xr-xgoogle_appengine/lib/django/django/middleware/transaction.py27
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/__init__.py17
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/extras/__init__.py1
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/extras/widgets.py59
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/fields.py492
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/forms.py309
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/models.py190
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/util.py74
-rwxr-xr-xgoogle_appengine/lib/django/django/newforms/widgets.py353
-rwxr-xr-xgoogle_appengine/lib/django/django/oldforms/__init__.py1015
-rwxr-xr-xgoogle_appengine/lib/django/django/shortcuts/__init__.py32
-rwxr-xr-xgoogle_appengine/lib/django/django/template/__init__.py918
-rw-r--r--google_appengine/lib/django/django/template/__init__.pycbin0 -> 42220 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/template/context.py100
-rw-r--r--google_appengine/lib/django/django/template/context.pycbin0 -> 5563 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/template/defaultfilters.py617
-rw-r--r--google_appengine/lib/django/django/template/defaultfilters.pycbin0 -> 23726 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/template/defaulttags.py969
-rw-r--r--google_appengine/lib/django/django/template/defaulttags.pycbin0 -> 39753 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/template/loader.py118
-rw-r--r--google_appengine/lib/django/django/template/loader.pycbin0 -> 4643 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/template/loader_tags.py177
-rw-r--r--google_appengine/lib/django/django/template/loader_tags.pycbin0 -> 8310 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/template/loaders/__init__.py0
-rw-r--r--google_appengine/lib/django/django/template/loaders/__init__.pycbin0 -> 170 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/template/loaders/app_directories.py41
-rwxr-xr-xgoogle_appengine/lib/django/django/template/loaders/eggs.py25
-rwxr-xr-xgoogle_appengine/lib/django/django/template/loaders/filesystem.py25
-rw-r--r--google_appengine/lib/django/django/template/loaders/filesystem.pycbin0 -> 1283 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/templatetags/__init__.py7
-rwxr-xr-xgoogle_appengine/lib/django/django/templatetags/i18n.py246
-rwxr-xr-xgoogle_appengine/lib/django/django/test/__init__.py6
-rwxr-xr-xgoogle_appengine/lib/django/django/test/client.py256
-rwxr-xr-xgoogle_appengine/lib/django/django/test/doctest.py2669
-rwxr-xr-xgoogle_appengine/lib/django/django/test/signals.py1
-rwxr-xr-xgoogle_appengine/lib/django/django/test/simple.py88
-rwxr-xr-xgoogle_appengine/lib/django/django/test/testcases.py50
-rwxr-xr-xgoogle_appengine/lib/django/django/test/utils.py110
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/__init__.py0
-rw-r--r--google_appengine/lib/django/django/utils/__init__.pycbin0 -> 159 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/_threading_local.py240
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/autoreload.py94
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/cache.py166
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/daemonize.py55
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/datastructures.py262
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/dateformat.py262
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/dates.py29
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/decorators.py33
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/feedgenerator.py273
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/functional.py54
-rw-r--r--google_appengine/lib/django/django/utils/functional.pycbin0 -> 3035 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/html.py115
-rw-r--r--google_appengine/lib/django/django/utils/html.pycbin0 -> 6740 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/images.py22
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/itercompat.py31
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/simplejson/__init__.py252
-rw-r--r--google_appengine/lib/django/django/utils/simplejson/__init__.pycbin0 -> 10204 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/simplejson/decoder.py273
-rw-r--r--google_appengine/lib/django/django/utils/simplejson/decoder.pycbin0 -> 10246 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/simplejson/encoder.py331
-rw-r--r--google_appengine/lib/django/django/utils/simplejson/encoder.pycbin0 -> 11325 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/simplejson/jsonfilter.py40
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/simplejson/scanner.py63
-rw-r--r--google_appengine/lib/django/django/utils/simplejson/scanner.pycbin0 -> 2692 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/stopwords.py42
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/synch.py88
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/termcolors.py68
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/text.py204
-rw-r--r--google_appengine/lib/django/django/utils/text.pycbin0 -> 8484 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/timesince.py57
-rw-r--r--google_appengine/lib/django/django/utils/timesince.pycbin0 -> 3321 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/translation/__init__.py8
-rw-r--r--google_appengine/lib/django/django/utils/translation/__init__.pycbin0 -> 330 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/translation/trans_null.py30
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/translation/trans_real.py524
-rw-r--r--google_appengine/lib/django/django/utils/translation/trans_real.pycbin0 -> 20086 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/tzinfo.py52
-rw-r--r--google_appengine/lib/django/django/utils/tzinfo.pycbin0 -> 3525 bytes
-rwxr-xr-xgoogle_appengine/lib/django/django/utils/xmlutils.py14
-rwxr-xr-xgoogle_appengine/lib/django/django/views/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/views/debug.py661
-rwxr-xr-xgoogle_appengine/lib/django/django/views/decorators/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/views/decorators/cache.py42
-rwxr-xr-xgoogle_appengine/lib/django/django/views/decorators/gzip.py6
-rwxr-xr-xgoogle_appengine/lib/django/django/views/decorators/http.py34
-rwxr-xr-xgoogle_appengine/lib/django/django/views/decorators/vary.py35
-rwxr-xr-xgoogle_appengine/lib/django/django/views/defaults.py89
-rwxr-xr-xgoogle_appengine/lib/django/django/views/generic/__init__.py0
-rwxr-xr-xgoogle_appengine/lib/django/django/views/generic/create_update.py200
-rwxr-xr-xgoogle_appengine/lib/django/django/views/generic/date_based.py344
-rwxr-xr-xgoogle_appengine/lib/django/django/views/generic/list_detail.py131
-rwxr-xr-xgoogle_appengine/lib/django/django/views/generic/simple.py35
-rwxr-xr-xgoogle_appengine/lib/django/django/views/i18n.py172
-rwxr-xr-xgoogle_appengine/lib/django/django/views/static.py125
-rw-r--r--google_appengine/lib/django/docs/add_ons.txt206
-rw-r--r--google_appengine/lib/django/docs/admin_css.txt173
-rw-r--r--google_appengine/lib/django/docs/apache_auth.txt71
-rw-r--r--google_appengine/lib/django/docs/api_stability.txt123
-rw-r--r--google_appengine/lib/django/docs/authentication.txt1024
-rw-r--r--google_appengine/lib/django/docs/cache.txt543
-rw-r--r--google_appengine/lib/django/docs/contributing.txt654
-rw-r--r--google_appengine/lib/django/docs/csrf.txt69
-rw-r--r--google_appengine/lib/django/docs/databases.txt162
-rw-r--r--google_appengine/lib/django/docs/db-api.txt1804
-rw-r--r--google_appengine/lib/django/docs/design_philosophies.txt281
-rw-r--r--google_appengine/lib/django/docs/distributions.txt76
-rw-r--r--google_appengine/lib/django/docs/django-admin.txt544
-rw-r--r--google_appengine/lib/django/docs/documentation.txt148
-rw-r--r--google_appengine/lib/django/docs/email.txt176
-rw-r--r--google_appengine/lib/django/docs/faq.txt669
-rw-r--r--google_appengine/lib/django/docs/fastcgi.txt317
-rw-r--r--google_appengine/lib/django/docs/flatpages.txt115
-rw-r--r--google_appengine/lib/django/docs/forms.txt695
-rw-r--r--google_appengine/lib/django/docs/generic_views.txt1076
-rw-r--r--google_appengine/lib/django/docs/i18n.txt765
-rw-r--r--google_appengine/lib/django/docs/install.txt143
-rw-r--r--google_appengine/lib/django/docs/legacy_databases.txt69
-rw-r--r--google_appengine/lib/django/docs/middleware.txt229
-rw-r--r--google_appengine/lib/django/docs/model-api.txt1921
-rw-r--r--google_appengine/lib/django/docs/modpython.txt244
-rw-r--r--google_appengine/lib/django/docs/newforms.txt875
-rw-r--r--google_appengine/lib/django/docs/outputting_csv.txt119
-rw-r--r--google_appengine/lib/django/docs/outputting_pdf.txt153
-rw-r--r--google_appengine/lib/django/docs/overview.txt301
-rw-r--r--google_appengine/lib/django/docs/redirects.txt71
-rw-r--r--google_appengine/lib/django/docs/release_notes_0.95.txt126
-rw-r--r--google_appengine/lib/django/docs/release_notes_0.96.txt276
-rw-r--r--google_appengine/lib/django/docs/request_response.txt548
-rw-r--r--google_appengine/lib/django/docs/serialization.txt118
-rw-r--r--google_appengine/lib/django/docs/sessions.txt313
-rw-r--r--google_appengine/lib/django/docs/settings.txt1046
-rw-r--r--google_appengine/lib/django/docs/sitemaps.txt321
-rw-r--r--google_appengine/lib/django/docs/sites.txt322
-rw-r--r--google_appengine/lib/django/docs/static_files.txt125
-rw-r--r--google_appengine/lib/django/docs/syndication_feeds.txt767
-rw-r--r--google_appengine/lib/django/docs/templates.txt1277
-rw-r--r--google_appengine/lib/django/docs/templates_python.txt1195
-rw-r--r--google_appengine/lib/django/docs/testing.txt550
-rw-r--r--google_appengine/lib/django/docs/transactions.txt163
-rw-r--r--google_appengine/lib/django/docs/tutorial01.txt575
-rw-r--r--google_appengine/lib/django/docs/tutorial02.txt437
-rw-r--r--google_appengine/lib/django/docs/tutorial03.txt466
-rw-r--r--google_appengine/lib/django/docs/tutorial04.txt259
-rw-r--r--google_appengine/lib/django/docs/url_dispatch.txt481
-rw-r--r--google_appengine/lib/django/scripts/rpm-install.sh19
-rw-r--r--google_appengine/lib/django/setup.cfg4
-rwxr-xr-xgoogle_appengine/lib/django/setup.py46
-rw-r--r--google_appengine/lib/webob/LICENSE20
-rw-r--r--google_appengine/lib/webob/PKG-INFO23
-rw-r--r--google_appengine/lib/webob/WebOb.egg-info/PKG-INFO23
-rw-r--r--google_appengine/lib/webob/WebOb.egg-info/SOURCES.txt41
-rw-r--r--google_appengine/lib/webob/WebOb.egg-info/dependency_links.txt1
-rw-r--r--google_appengine/lib/webob/WebOb.egg-info/top_level.txt1
-rw-r--r--google_appengine/lib/webob/WebOb.egg-info/zip-safe1
-rwxr-xr-xgoogle_appengine/lib/webob/docs/comment-example-code/example.py150
-rw-r--r--google_appengine/lib/webob/docs/comment-example.txt414
-rw-r--r--google_appengine/lib/webob/docs/differences.txt590
-rw-r--r--google_appengine/lib/webob/docs/file-example.txt217
-rw-r--r--google_appengine/lib/webob/docs/index.txt328
-rw-r--r--google_appengine/lib/webob/docs/license.txt20
-rw-r--r--google_appengine/lib/webob/docs/news.txt123
-rw-r--r--google_appengine/lib/webob/docs/reference.txt1001
-rw-r--r--google_appengine/lib/webob/docs/test-file.txt1
-rwxr-xr-xgoogle_appengine/lib/webob/docs/wiki-example-code/example.py200
-rw-r--r--google_appengine/lib/webob/docs/wiki-example.txt693
-rw-r--r--google_appengine/lib/webob/setup.cfg48
-rwxr-xr-xgoogle_appengine/lib/webob/setup.py33
-rwxr-xr-xgoogle_appengine/lib/webob/test8
-rwxr-xr-xgoogle_appengine/lib/webob/tests/__init__.py1
-rwxr-xr-xgoogle_appengine/lib/webob/tests/conftest.py4
-rwxr-xr-xgoogle_appengine/lib/webob/tests/test_request.py94
-rw-r--r--google_appengine/lib/webob/tests/test_request.txt317
-rwxr-xr-xgoogle_appengine/lib/webob/tests/test_response.py37
-rw-r--r--google_appengine/lib/webob/tests/test_response.txt254
-rwxr-xr-xgoogle_appengine/lib/webob/webob/__init__.py2252
-rw-r--r--google_appengine/lib/webob/webob/__init__.pycbin0 -> 80157 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/acceptparse.py293
-rw-r--r--google_appengine/lib/webob/webob/acceptparse.pycbin0 -> 11372 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/byterange.py295
-rw-r--r--google_appengine/lib/webob/webob/byterange.pycbin0 -> 9502 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/cachecontrol.py165
-rw-r--r--google_appengine/lib/webob/webob/cachecontrol.pycbin0 -> 6871 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/datastruct.py58
-rw-r--r--google_appengine/lib/webob/webob/datastruct.pycbin0 -> 3130 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/etag.py214
-rw-r--r--google_appengine/lib/webob/webob/etag.pycbin0 -> 8522 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/exc.py657
-rwxr-xr-xgoogle_appengine/lib/webob/webob/headerdict.py110
-rw-r--r--google_appengine/lib/webob/webob/headerdict.pycbin0 -> 3961 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/multidict.py593
-rw-r--r--google_appengine/lib/webob/webob/multidict.pycbin0 -> 24202 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/statusreasons.py67
-rw-r--r--google_appengine/lib/webob/webob/statusreasons.pycbin0 -> 1964 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/updatedict.py41
-rw-r--r--google_appengine/lib/webob/webob/updatedict.pycbin0 -> 2388 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/util/__init__.py1
-rw-r--r--google_appengine/lib/webob/webob/util/__init__.pycbin0 -> 156 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/util/dictmixin.py102
-rw-r--r--google_appengine/lib/webob/webob/util/dictmixin.pycbin0 -> 4910 bytes
-rwxr-xr-xgoogle_appengine/lib/webob/webob/util/reversed.py4
-rwxr-xr-xgoogle_appengine/lib/webob/webob/util/safegzip.py21
-rwxr-xr-xgoogle_appengine/lib/webob/webob/util/stringtemplate.py128
-rw-r--r--google_appengine/lib/yaml/LICENSE19
-rw-r--r--google_appengine/lib/yaml/PKG-INFO28
-rw-r--r--google_appengine/lib/yaml/README24
-rw-r--r--google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.cfg115
-rwxr-xr-xgoogle_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.py114
-rw-r--r--google_appengine/lib/yaml/ext/_yaml.c9054
-rw-r--r--google_appengine/lib/yaml/ext/_yaml.h3
-rw-r--r--google_appengine/lib/yaml/ext/_yaml.pxd249
-rw-r--r--google_appengine/lib/yaml/ext/_yaml.pyx1344
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/__init__.py290
-rw-r--r--google_appengine/lib/yaml/lib/yaml/__init__.pycbin0 -> 11623 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/composer.py118
-rw-r--r--google_appengine/lib/yaml/lib/yaml/composer.pycbin0 -> 4312 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/constructor.py675
-rw-r--r--google_appengine/lib/yaml/lib/yaml/constructor.pycbin0 -> 22764 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/cyaml.py85
-rw-r--r--google_appengine/lib/yaml/lib/yaml/cyaml.pycbin0 -> 3976 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/dumper.py62
-rw-r--r--google_appengine/lib/yaml/lib/yaml/dumper.pycbin0 -> 2684 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/emitter.py1163
-rw-r--r--google_appengine/lib/yaml/lib/yaml/emitter.pycbin0 -> 33305 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/error.py75
-rw-r--r--google_appengine/lib/yaml/lib/yaml/error.pycbin0 -> 3131 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/events.py86
-rw-r--r--google_appengine/lib/yaml/lib/yaml/events.pycbin0 -> 5324 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/loader.py40
-rw-r--r--google_appengine/lib/yaml/lib/yaml/loader.pycbin0 -> 1995 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/nodes.py49
-rw-r--r--google_appengine/lib/yaml/lib/yaml/nodes.pycbin0 -> 2336 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/parser.py586
-rw-r--r--google_appengine/lib/yaml/lib/yaml/parser.pycbin0 -> 15176 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/reader.py225
-rw-r--r--google_appengine/lib/yaml/lib/yaml/reader.pycbin0 -> 6968 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/representer.py488
-rw-r--r--google_appengine/lib/yaml/lib/yaml/representer.pycbin0 -> 15488 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/resolver.py223
-rw-r--r--google_appengine/lib/yaml/lib/yaml/resolver.pycbin0 -> 6850 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/scanner.py1456
-rw-r--r--google_appengine/lib/yaml/lib/yaml/scanner.pycbin0 -> 34694 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/serializer.py111
-rw-r--r--google_appengine/lib/yaml/lib/yaml/serializer.pycbin0 -> 4530 bytes
-rwxr-xr-xgoogle_appengine/lib/yaml/lib/yaml/tokens.py104
-rw-r--r--google_appengine/lib/yaml/lib/yaml/tokens.pycbin0 -> 6978 bytes
-rw-r--r--google_appengine/lib/yaml/setup.cfg28
-rwxr-xr-xgoogle_appengine/lib/yaml/setup.py53
-rwxr-xr-xgoogle_appengine/lib/yaml/setup_with_libyaml.py31
-rw-r--r--google_appengine/new_project_template/app.yaml8
-rw-r--r--google_appengine/new_project_template/index.yaml12
-rwxr-xr-xgoogle_appengine/new_project_template/main.py40
-rwxr-xr-xgoogle_appengine/remote_api_shell.py60
-rw-r--r--google_appengine/templates/logging_console.js257
-rw-r--r--google_appengine/templates/logging_console_footer.html4
-rw-r--r--google_appengine/templates/logging_console_header.html71
-rw-r--r--google_appengine/templates/logging_console_middle.html4
-rwxr-xr-xgoogle_appengine/tools/bulkload_client.py55
1040 files changed, 251756 insertions, 0 deletions
diff --git a/api.py b/api.py
new file mode 100644
index 0000000..59231ff
--- /dev/null
+++ b/api.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+from google.appengine.api.urlfetch import fetch
+from urllib import quote_plus
+from django.utils.simplejson import loads
+from google.appengine.ext import db
+import re
+import cgitb
+
+class AnyClipAPIData(db.Model):
+ apiKey = db.StringProperty(required=True)
+ token = db.StringProperty()
+
+class AnyClipAPI():
+ def __init__(self, key):
+ apiDataFetch = AnyClipAPIData.all().filter('apiKey = ', key).fetch(1)
+ if (len(apiDataFetch) == 1):
+ self.apiData = apiDataFetch[0]
+ else:
+ self.apiData = AnyClipAPIData(apiKey = key)
+
+ self.errorMatch = re.compile("<code>(.*)</code><description>(.*)</description>")
+ def request(self, method, args = ()):
+ if method == "authenticate":
+ url = "http://api3.anyclip.com/ac_auth/v1/json/authenticate/%s/" % args[0]
+ else:
+ if self.apiData.token == None:
+ self.authenticate()
+ url = "http://api3.anyclip.com/ac_api/v1/%s/json/%s/" % (self.apiData.token, method)
+ for arg in args:
+ url += "%s/" % quote_plus(str(arg))
+ response = fetch(url)
+ if response.status_code != 200:
+ return ("", "")
+ error = self.errorMatch.search(response.content)
+ if error != None:
+ if error.group(1) == '1001':
+ self.authenticate()
+ return self.request(method, args)
+ else:
+ return (error.group(1), error.group(2))
+ try:
+ return loads(response.content)
+ except:
+ return ("", "")
+ def authenticate(self):
+ self.apiData.token = self.request("authenticate", [self.apiData.apiKey])["Token"]
+ self.apiData.put() \ No newline at end of file
diff --git a/app.yaml b/app.yaml
new file mode 100644
index 0000000..4cad643
--- /dev/null
+++ b/app.yaml
@@ -0,0 +1,9 @@
+application: anyclipframed
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /.*
+ script: framed.py
+
diff --git a/framed.py b/framed.py
new file mode 100644
index 0000000..41447fe
--- /dev/null
+++ b/framed.py
@@ -0,0 +1,82 @@
+# -*- coding: iso-8859-1 -*-
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp.util import run_wsgi_app
+from google.appengine.ext import db
+from google.appengine.api.urlfetch import fetch
+from api import AnyClipAPI
+import re
+from time import sleep
+from random import randint
+
+class NewQuestion(webapp.RequestHandler):
+ def get(self):
+ self.response.headers['Content-Type'] = 'application/json'
+ titles = Title.all()
+ title = titles[randint(0, titles.count() - 1)]
+ if len(title.actors) == 0:
+ return self.get()
+
+ url = "http://img.anyclip.com/thumbnails/%s/tmb_%s_480.jpg" % (title.code, "910") #frame hard-coded for now
+ cast = ""
+ for i in range(len(title.actors)):
+ if i != 0:
+ cast += ","
+ cast += "[\"%s\",\"%s\"]" % (title.actors[i], title.characters[i])
+ self.response.out.write("{title:\"%s\",frame:\"%s\",cast:[%s]}" % (title.title, url, cast))
+class Title(db.Model):
+ title = db.StringProperty(required=True)
+ code = db.StringProperty(required=True)
+ actors = db.StringListProperty()
+ characters = db.StringListProperty()
+class LoadNewTitles(webapp.RequestHandler):
+ def get(self):
+ api = AnyClipAPI("CAD58B9E-F045-492F-81B9-22CFE6B00604")
+ index = 0
+ imdbPattern = re.compile("<td class=\"nm\"><a href=\"/name/nm[0-9]{7}/\" onclick=\"\\(new Image\\(\\)\\)\\.src='/rg/castlist/position-[0-9]{1,2}/images/b\\.gif\\?link=/name/nm[0-9]{7}/';\">([^><]+)</a></td><td class=\"ddd\"> \\.\\.\\. </td><td class=\"char\">(?:<a href=\"/character/ch[0-9]{7}/\">([^><]+)</a>[^><]*|([^><]*))</td>")
+ while True:
+ response = api.request("titles", ("recent", index, 20))
+ index += 20
+ for item in response["Items"]:
+ if Title.all().filter('code = ', item["Code"]).count() == 0:
+ self.response.out.write("Adding %s<br>" % item["Name"])
+ newTitle = Title(title=item["Name"], code=item["Code"])
+
+ imdbList = fetch("http://www.imdb.com/title/tt%s/fullcredits" % item["ImdbID"])
+ i = 0
+ while imdbList.status_code != 200:
+ i += 1
+ if i == 5:
+ break
+ sleep(3)
+ imdbList = fetch("http://www.imdb.com/title/tt%s/fullcredits" % item["ImdbID"])
+ if imdbList.status_code != 200:
+ self.response.out.write("Could not add cast of %s<br>" % item["Name"])
+ continue
+ i = 0
+ for match in imdbPattern.finditer(imdbList.content):
+ newTitle.actors.append(match.group(1))
+ character = match.group(2)
+ if character == None:
+ character = match.group(3)
+ newTitle.characters.append(character)
+ i += 1
+ if i == 10:
+ break
+ newTitle.put()
+ else:
+ index = response["TotalItemCount"]
+ break
+ if index >= response["TotalItemCount"]:
+ break
+
+
+application = webapp.WSGIApplication(
+ [('/newquestion', NewQuestion),
+ ('/loadnewtitles', LoadNewTitles)],
+ debug=True)
+
+def main():
+ run_wsgi_app(application)
+
+if __name__ == "__main__":
+ main() \ No newline at end of file
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..47bcc91
--- /dev/null
+++ b/google_appengine/LICENSE
@@ -0,0 +1,95 @@
+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.
diff --git a/google_appengine/README b/google_appengine/README
new file mode 100644
index 0000000..76bab56
--- /dev/null
+++ b/google_appengine/README
@@ -0,0 +1,91 @@
+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
+
+
+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>
+
+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)
+ --datastore_path=PATH Path to use for storing Datastore file stub data.
+ (Default /tmp/dev_appserver.datastore)
+ --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.
+ --disable_static_caching Never allow the browser to cache static files.
+ (Default enable if expiration set in app.yaml)
+
+
+
+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
diff --git a/google_appengine/RELEASE_NOTES b/google_appengine/RELEASE_NOTES
new file mode 100644
index 0000000..0e98ab0
--- /dev/null
+++ b/google_appengine/RELEASE_NOTES
@@ -0,0 +1,421 @@
+Copyright 2008 Google Inc.
+All rights reserved.
+
+App Engine Python SDK - Release Notes
+
+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..90eedc7
--- /dev/null
+++ b/google_appengine/VERSION
@@ -0,0 +1,3 @@
+release: "1.2.5"
+timestamp: 1250206498
+api_versions: ['1']
diff --git a/google_appengine/appcfg.py b/google_appengine/appcfg.py
new file mode 100755
index 0000000..9bda605
--- /dev/null
+++ b/google_appengine/appcfg.py
@@ -0,0 +1,60 @@
+#!/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', 'webob'),
+ os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'),
+]
+
+SCRIPT_EXCEPTIONS = {
+ "dev_appserver.py" : "dev_appserver_main.py"
+}
+
+def run_file(file_path, globals_, script_dir=SCRIPT_DIR):
+ """Execute the file at the specified path with the passed-in globals."""
+ sys.path = EXTRA_PATHS + 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..9bda605
--- /dev/null
+++ b/google_appengine/bulkload_client.py
@@ -0,0 +1,60 @@
+#!/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', 'webob'),
+ os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'),
+]
+
+SCRIPT_EXCEPTIONS = {
+ "dev_appserver.py" : "dev_appserver_main.py"
+}
+
+def run_file(file_path, globals_, script_dir=SCRIPT_DIR):
+ """Execute the file at the specified path with the passed-in globals."""
+ sys.path = EXTRA_PATHS + 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..9bda605
--- /dev/null
+++ b/google_appengine/bulkloader.py
@@ -0,0 +1,60 @@
+#!/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', 'webob'),
+ os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'),
+]
+
+SCRIPT_EXCEPTIONS = {
+ "dev_appserver.py" : "dev_appserver_main.py"
+}
+
+def run_file(file_path, globals_, script_dir=SCRIPT_DIR):
+ """Execute the file at the specified path with the passed-in globals."""
+ sys.path = EXTRA_PATHS + 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('<html><body>')
+
+ greetings = db.GqlQuery("SELECT * "
+ "FROM Greeting "
+ "ORDER BY date DESC LIMIT 10")
+
+ for greeting in greetings:
+ if greeting.author:
+ self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
+ else:
+ self.response.out.write('An anonymous person wrote:')
+ self.response.out.write('<blockquote>%s</blockquote>' %
+ cgi.escape(greeting.content))
+
+ self.response.out.write("""
+ <form action="/sign" method="post">
+ <div><textarea name="content" rows="3" cols="60"></textarea></div>
+ <div><input type="submit" value="Sign Guestbook"></div>
+ </form>
+ </body>
+ </html>""")
+
+
+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/demos/guestbook/index.yaml b/google_appengine/demos/guestbook/index.yaml
new file mode 100644
index 0000000..a3b9e05
--- /dev/null
+++ b/google_appengine/demos/guestbook/index.yaml
@@ -0,0 +1,11 @@
+indexes:
+
+# AUTOGENERATED
+
+# 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.
diff --git a/google_appengine/dev_appserver.py b/google_appengine/dev_appserver.py
new file mode 100755
index 0000000..9bda605
--- /dev/null
+++ b/google_appengine/dev_appserver.py
@@ -0,0 +1,60 @@
+#!/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', 'webob'),
+ os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'),
+]
+
+SCRIPT_EXCEPTIONS = {
+ "dev_appserver.py" : "dev_appserver_main.py"
+}
+
+def run_file(file_path, globals_, script_dir=SCRIPT_DIR):
+ """Execute the file at the specified path with the passed-in globals."""
+ sys.path = EXTRA_PATHS + 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 0000000..5712b54
--- /dev/null
+++ b/google_appengine/google/__init__.pyc
Binary files differ
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 0000000..3215a40
--- /dev/null
+++ b/google_appengine/google/appengine/__init__.pyc
Binary files differ
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 0000000..874041c
--- /dev/null
+++ b/google_appengine/google/appengine/api/__init__.pyc
Binary files differ
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 100644
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 0000000..fbbb0fc
--- /dev/null
+++ b/google_appengine/google/appengine/api/api_base_pb.pyc
Binary files differ
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..2ac8923
--- /dev/null
+++ b/google_appengine/google/appengine/api/apiproxy_rpc.py
@@ -0,0 +1,150 @@
+#!/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 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, e:
+ self.__exception = e
+ finally:
+ self.__state = RPC.FINISHING
+ self.__Callback()
+
+ return True
+
+ def __Callback(self):
+ if self.callback:
+ try:
+ self.callback()
+ except:
+ exc_class, 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 0000000..da77a36
--- /dev/null
+++ b/google_appengine/google/appengine/api/apiproxy_rpc.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/apiproxy_stub.py b/google_appengine/google/appengine/api/apiproxy_stub.py
new file mode 100755
index 0000000..5104ab2
--- /dev/null
+++ b/google_appengine/google/appengine/api/apiproxy_stub.py
@@ -0,0 +1,80 @@
+#!/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 API proxy stubs."""
+
+
+
+
+
+from google.appengine.api import apiproxy_rpc
+from google.appengine.runtime import apiproxy_errors
+
+
+MAX_REQUEST_SIZE = 1 << 20
+
+
+class APIProxyStub(object):
+ """Base class for implementing API proxy stub classes.
+
+ To implement an API proxy stub:
+ - Extend this class.
+ - Override __init__ to pass in appropriate default service name.
+ - Implement service methods as _Dynamic_<method>(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_<call>.
+ 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 0000000..41e7a0c
--- /dev/null
+++ b/google_appengine/google/appengine/api/apiproxy_stub.pyc
Binary files differ
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..716498f
--- /dev/null
+++ b/google_appengine/google/appengine/api/apiproxy_stub_map.py
@@ -0,0 +1,470 @@
+#!/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
+
+ Raises:
+ apiproxy_errors.Error or a subclass.
+ """
+ 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):
+ """Invokes all hooks in this collection.
+
+ 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
+ """
+ for key, function, srv, num_args in self.__content:
+ if srv is None or srv == service:
+ if 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
+
+ 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)
+ rpc.MakeCall(service, call, request, response)
+ rpc.Wait()
+ rpc.CheckSuccess()
+ self.__postcall_hooks.Call(service, call, request, response, rpc)
+ else:
+ self.__precall_hooks.Call(service, call, request, response)
+ stub.MakeSyncCall(service, call, request, response)
+ self.__postcall_hooks.Call(service, call, request, 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()
+ self.__rpc.CheckSuccess()
+ 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 0000000..5d889f2
--- /dev/null
+++ b/google_appengine/google/appengine/api/apiproxy_stub_map.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/app_logging.py b/google_appengine/google/appengine/api/app_logging.py
new file mode 100755
index 0000000..e576d37
--- /dev/null
+++ b/google_appengine/google/appengine/api/app_logging.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.
+#
+
+"""Logging utilities for use by applications.
+
+Classes defined here:
+ AppLogsHandler: StreamHandler subclass
+"""
+
+
+
+
+
+import logging
+import sys
+import types
+
+
+NEWLINE_REPLACEMENT = "\0"
+
+
+class AppLogsHandler(logging.StreamHandler):
+ """Logging handler that will direct output to a persistent store of
+ application logs.
+
+ This handler will output log statements to stderr. This handler is
+ automatically initialized and attached to the Python common logging library.
+ """
+
+
+
+
+ def __init__(self, stream=None):
+ """Constructor.
+
+ Args:
+ # stream is optional. it defaults to sys.stderr.
+ stream: destination for output
+ """
+ logging.StreamHandler.__init__(self, stream)
+
+ def close(self):
+ """Closes the stream.
+
+ This implementation based on the implementation of FileHandler.close()."""
+ self.flush()
+ self.stream.close()
+ logging.StreamHandler.close(self)
+
+ def emit(self, record):
+ """Emit a record.
+
+ This implementation is based on the implementation of
+ StreamHandler.emit()."""
+ try:
+ message = self._AppLogsMessage(record)
+ if isinstance(message, unicode):
+ message = message.encode("UTF-8")
+ self.stream.write(message)
+ self.flush()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handleError(record)
+
+ def _AppLogsMessage(self, record):
+ """Converts the log record into a log line."""
+
+ message = self.format(record).replace("\n", NEWLINE_REPLACEMENT)
+ return "LOG %d %d %s\n" % (self._AppLogsLevel(record.levelno),
+ long(record.created * 1000 * 1000),
+ message)
+
+ def _AppLogsLevel(self, level):
+ """Converts the logging level used in Python to the API logging level"""
+ if level >= 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..6ab406c
--- /dev/null
+++ b/google_appengine/google/appengine/api/appinfo.py
@@ -0,0 +1,430 @@
+#!/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 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'([1-9][0-9]*)([DdHhMm]|[sS]?)'
+_EXPIRATION_REGEX = r'\s*(%s)(\s+%s)*\s*' % (_DELTA_REGEX, _DELTA_REGEX)
+
+_SERVICE_RE_STRING = r'(mail|xmpp_message)'
+
+_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
+
+APPLICATION_RE_STRING = r'(?!-)[a-z\d\-]{1,%d}' % APP_ID_MAX_LEN
+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'
+
+SECURE_HTTP = 'never'
+SECURE_HTTPS = 'always'
+SECURE_HTTP_OR_HTTPS = 'optional'
+
+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'
+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'
+
+
+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:
+
+ <url>/(.*)
+ <static_dir>/\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),
+
+ SECURE: validation.Options(SECURE_HTTP,
+ SECURE_HTTPS,
+ SECURE_HTTP_OR_HTTPS,
+ default=SECURE_HTTP),
+
+
+
+ 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, 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 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)
+ }
+
+ 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 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()
+ 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 0000000..af39ab3
--- /dev/null
+++ b/google_appengine/google/appengine/api/appinfo.pyc
Binary files differ
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 0000000..3207355
--- /dev/null
+++ b/google_appengine/google/appengine/api/appinfo_errors.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/capabilities/__init__.py b/google_appengine/google/appengine/api/capabilities/__init__.py
new file mode 100755
index 0000000..f672cbb
--- /dev/null
+++ b/google_appengine/google/appengine/api/capabilities/__init__.py
@@ -0,0 +1,172 @@
+#!/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.
+#
+
+"""Allows applications to identify API outages and scheduled downtime.
+
+Some examples:
+ def StoreUploadedProfileImage(self):
+ uploaded_image = self.request.get('img')
+ # If the images API is unavailable, we'll just skip the resize.
+ if CapabilitySet('images').is_enabled():
+ uploaded_image = images.resize(uploaded_image, 64, 64)
+ store(uploaded_image)
+
+ def RenderHTMLForm(self):
+ datastore_readonly = CapabilitySet('datastore_v3', capabilities=['write'])
+ if datastore_readonly.may_be_disabled_in(60):
+ # self.response.out('<p>Not accepting submissions right now: %s</p>' %
+ 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 0000000..c8ac026
--- /dev/null
+++ b/google_appengine/google/appengine/api/capabilities/__init__.pyc
Binary files differ
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 100644
index 0000000..9f9ba29
--- /dev/null
+++ b/google_appengine/google/appengine/api/capabilities/capability_service_pb.py
@@ -0,0 +1,366 @@
+#!/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 CapabilityConfig
+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 0000000..d1a68c2
--- /dev/null
+++ b/google_appengine/google/appengine/api/capabilities/capability_service_pb.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/capabilities/capability_stub.py b/google_appengine/google/appengine/api/capabilities/capability_stub.py
new file mode 100755
index 0000000..6d33d7e
--- /dev/null
+++ b/google_appengine/google/appengine/api/capabilities/capability_stub.py
@@ -0,0 +1,53 @@
+#!/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 capability service API, everything is always enabled."""
+
+
+
+from google.appengine.api import apiproxy_stub
+from google.appengine.api import capabilities
+
+IsEnabledRequest = capabilities.IsEnabledRequest
+IsEnabledResponse = capabilities.IsEnabledResponse
+CapabilityConfig = capabilities.CapabilityConfig
+
+class CapabilityServiceStub(apiproxy_stub.APIProxyStub):
+ """Python only capability service stub."""
+
+ def __init__(self, service_name='capability_service'):
+ """Constructor.
+
+ Args:
+ service_name: Service name expected for all calls.
+ """
+ super(CapabilityServiceStub, self).__init__(service_name)
+
+
+ def _Dynamic_IsEnabled(self, request, response):
+ """Implementation of CapabilityService::IsEnabled().
+
+ Args:
+ request: An IsEnabledRequest.
+ response: An IsEnabledResponse.
+ """
+ response.set_summary_status(IsEnabledResponse.ENABLED)
+
+ default_config = response.add_config()
+ default_config.set_package('')
+ default_config.set_capability('')
+ default_config.set_status(CapabilityConfig.ENABLED)
diff --git a/google_appengine/google/appengine/api/capabilities/capability_stub.pyc b/google_appengine/google/appengine/api/capabilities/capability_stub.pyc
new file mode 100644
index 0000000..6336e60
--- /dev/null
+++ b/google_appengine/google/appengine/api/capabilities/capability_stub.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/croninfo.py b/google_appengine/google/appengine/api/croninfo.py
new file mode 100755
index 0000000..0eab26e
--- /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.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):
+ """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))
+ schedule = groc.CreateParser(value)
+ try:
+ schedule.timespec()
+ 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):
+ """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 0000000..5540fb8
--- /dev/null
+++ b/google_appengine/google/appengine/api/croninfo.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/datastore.py b/google_appengine/google/appengine/api/datastore.py
new file mode 100755
index 0000000..6931db8
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore.py
@@ -0,0 +1,2170 @@
+#!/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 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_stub_map
+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:
+ from google.appengine.api.labs.taskqueue import taskqueue_service_pb
+except ImportError:
+ from google.appengine.api.taskqueue import taskqueue_service_pb
+
+MAX_ALLOWABLE_QUERIES = 30
+
+DEFAULT_TRANSACTION_RETRIES = 3
+
+_MAX_INDEXED_PROPERTIES = 5000
+
+_MAX_ID_BATCH_SIZE = 1000 * 1000 * 1000
+
+Key = datastore_types.Key
+typename = datastore_types.typename
+
+_txes = {}
+
+
+def NormalizeAndTypeCheck(arg, types):
+ """Normalizes and type checks the given argument.
+
+ Args:
+ arg: an instance, tuple, list, iterator, or generator 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:
+ try:
+ for val in arg:
+ if not isinstance(val, types):
+ raise datastore_errors.BadArgumentError(
+ 'Expected one of %s; received %s (a %s).' %
+ (types, val, typename(val)))
+ except TypeError:
+ raise datastore_errors.BadArgumentError(
+ 'Expected an instance or sequence of %s; received %s (a %s).' %
+ (types, arg, typename(arg)))
+
+ return (list(arg), 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 Put(entities):
+ """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
+
+ Returns:
+ Key or list of Keys
+
+ Raises:
+ TransactionFailedError, if the Put could not be committed.
+ """
+ entities, multiple = NormalizeAndTypeCheck(entities, Entity)
+
+ if multiple and not entities:
+ return []
+
+ for entity in entities:
+ if not entity.kind() or not entity.app_id_namespace():
+ 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)
+
+ resp = datastore_pb.PutResponse()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Put', req, resp)
+ 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):
+ """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
+
+ Returns:
+ Entity or list of Entity objects
+ """
+ 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)
+
+ resp = datastore_pb.GetResponse()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Get', req, resp)
+ 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):
+ """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
+
+ Raises:
+ TransactionFailedError, if the Delete could not be committed.
+ """
+ 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)
+
+ resp = datastore_pb.DeleteResponse()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Delete', req, resp)
+ 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):
+ """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
+ """
+ ref = entity_pb.Reference()
+ _app_namespace = datastore_types.ResolveAppIdNamespace(_app, _namespace)
+ ref.set_app(_app_namespace.to_encoded())
+
+ datastore_types.ValidateString(kind, 'kind',
+ datastore_errors.BadArgumentError)
+ if parent is not None:
+ parent = _GetCompleteKeyOrError(parent)
+ if _app_namespace != parent.app_id_namespace():
+ raise datastore_errors.BadArgumentError(
+ " %s doesn't match parent's app_namespace %s" %
+ (_app_namespace, parent.app_id_namespace()))
+ ref.CopyFrom(parent._Key__reference)
+
+ 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)
+
+ 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)
+
+ 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 app_id_namespace(self):
+ """Returns the AppIdNamespace of this entity or None if not set.
+ """
+ return self.__key.app_id_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 __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'<entity kind=%s' % saxutils.quoteattr(self.kind())
+ if self.__key.has_id_or_name():
+ xml += ' key=%s' % saxutils.quoteattr(str(self.__key))
+ xml += '>'
+ if self.__key.has_id_or_name():
+ xml += '\n <key>%s</key>' % self.__key.ToTagUri()
+
+
+ properties = self.keys()
+ if properties:
+ properties.sort()
+ xml += '\n ' + '\n '.join(self._PropertiesToXml(properties))
+
+ xml += '\n</entity>\n'
+ return xml
+
+ def _PropertiesToXml(self, properties):
+ """ Returns a list of the XML representations of each of the given
+ properties. Ignores properties that don't exist in this entity.
+
+ Arg:
+ properties: string or list of strings
+
+ Returns:
+ list of strings
+ """
+ xml_properties = []
+
+ for propname in properties:
+ if not self.has_key(propname):
+ continue
+
+ propname_xml = saxutils.quoteattr(propname)
+
+ values = self[propname]
+ if not isinstance(values, list):
+ values = [values]
+
+ proptype = datastore_types.PropertyTypeName(values[0])
+ proptype_xml = saxutils.quoteattr(proptype)
+
+ escaped_values = self._XmlEscapeValues(propname)
+ open_tag = u'<property name=%s type=%s>' % (propname_xml, proptype_xml)
+ close_tag = u'</property>'
+ 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]
+
+ sample = values
+ if isinstance(sample, list):
+ sample = values[0]
+
+ if (isinstance(sample, datastore_types._RAW_PROPERTY_TYPES) or
+ name in self.unindexed_properties()):
+ pb.raw_property_list().extend(properties)
+ else:
+ pb.property_list().extend(properties)
+
+ 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()]
+
+ e = Entity(unicode(last_path.type().decode('utf-8')),
+ unindexed_properties=unindexed_properties)
+ 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. %s: %s' %
+ (e.__class__, prop.name(), e))
+
+ 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:
+ 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 '<property name> <operator>'
+ 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(['<', '<=', '>', '>='])
+ FILTER_REGEX = re.compile(
+ '^\s*([^\s]+)(\s+(%s)\s*)?$' % '|'.join(OPERATORS.keys()),
+ re.IGNORECASE | re.UNICODE)
+
+ __kind = None
+ __app = None
+ __orderings = None
+ __cached_count = None
+ __hint = None
+ __ancestor = None
+
+ __filter_order = None
+ __filter_counter = 0
+
+ __inequality_prop = None
+ __inequality_count = 0
+
+ def __init__(self, kind=None, filters={}, _app=None, keys_only=False,
+ _namespace=None):
+ """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
+ """
+ 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.ResolveAppIdNamespace(_app,
+ _namespace).to_encoded()
+ self.__keys_only = keys_only
+
+ 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 Run(self):
+ """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.
+
+ Returns:
+ # an iterator that provides access to the query results
+ Iterator
+ """
+ return self._Run()
+
+ def _Run(self, limit=None, offset=None,
+ prefetch_count=None, next_count=None):
+ """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!
+ """
+ pb = self._ToPb(limit, offset, prefetch_count)
+ result = datastore_pb.QueryResult()
+
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'RunQuery', pb, result)
+ except apiproxy_errors.ApplicationError, err:
+ try:
+ _ToDatastoreError(err)
+ except datastore_errors.NeedIndexError, exc:
+ yaml = datastore_index.IndexYamlForQuery(
+ *datastore_index.CompositeIndexForQuery(pb)[1:-1])
+ raise datastore_errors.NeedIndexError(
+ str(exc) + '\nThis query needs this index:\n' + yaml)
+
+ return Iterator(result, batch_size=next_count)
+
+ def Get(self, limit, offset=0):
+ """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
+
+ Returns:
+ # a list of entities
+ [Entity, ...]
+ """
+ if not isinstance(limit, (int, long)) or limit <= 0:
+ raise datastore_errors.BadArgumentError(
+ 'Argument to Get named \'limit\' must be an int greater than 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)._Get(limit)
+
+ def Count(self, limit=None):
+ """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. If there are more results than this, stop short and
+ just return this number. Providing this argument makes the count
+ operation more efficient.
+ Returns:
+ The number of results.
+ """
+ if self.__cached_count:
+ return self.__cached_count
+
+ resp = api_base_pb.Integer64Proto()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Count',
+ self._ToPb(limit=limit), resp)
+ except apiproxy_errors.ApplicationError, err:
+ raise _ToDatastoreError(err)
+ else:
+ self.__cached_count = resp.value()
+
+ 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:
+ 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:
+ 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):
+ raise datastore_errors.BadFilterError(
+ 'Only %s filters are allowed on kindless queries.' %
+ datastore_types._KEY_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 _CurrentTransactionKey():
+ 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'))
+ 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)
+
+ return pb
+
+
+def AllocateIds(model_key, size):
+ """Allocates a range of IDs of size for the key defined by model_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.
+
+ Args:
+ model_key: Key or string to serve as a model specifying the ID sequence
+ in which to allocate IDs
+
+ Returns:
+ (start, end) of the allocated range, inclusive.
+ """
+ keys, multiple = NormalizeAndTypeCheckKeys(model_key)
+
+ if len(keys) > 1:
+ raise datastore_errors.BadArgumentError(
+ 'Cannot allocate IDs for more than one model key at a time')
+
+ if size > _MAX_ID_BATCH_SIZE:
+ raise datastore_errors.BadArgumentError(
+ 'Cannot allocate more than %s ids at a time' % _MAX_ID_BATCH_SIZE)
+
+ req = datastore_pb.AllocateIdsRequest()
+ req.mutable_model_key().CopyFrom(keys[0]._Key__reference)
+ req.set_size(size)
+
+ resp = datastore_pb.AllocateIdsResponse()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'AllocateIds', req, resp)
+ 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
+
+ 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):
+ """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
+
+ Returns:
+ A list of entities with at most "limit" entries (less if the query
+ completes before reading limit values).
+ """
+ count = 1
+ result = []
+
+ iterator = self.Run()
+
+ 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)
+ 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 isinstance(value, list):
+ if 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):
+ """Return an iterable output with all results in order."""
+ results = []
+ count = 1
+ log_level = logging.DEBUG - 1
+ for bound_query in self.__bound_queries:
+ logging.log(log_level, 'Running query #%i' % count)
+ results.append(bound_query.Run())
+ 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):
+ """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).
+ Returns:
+ count of the number of entries returned.
+ """
+ if limit is None:
+ count = 0
+ for i in self.Run():
+ count += 1
+ return count
+ else:
+ return len(self.Get(limit))
+
+ 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):
+ self.__cursor = query_result_pb.cursor()
+ self.__keys_only = query_result_pb.keys_only()
+ self.__batch_size = batch_size
+ 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:
+ next_results = self._Next(count - len(entity_list), self.__batch_size)
+ if not next_results:
+ break
+ entity_list += next_results
+ 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.
+
+ This method 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 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 count is greater than the number of remaining results or the
+ size of the remaining results exceeds the RPC buffer limit. Use _Get to
+ insure all possible entities are retrieved.
+
+ If the count is omitted, the datastore backend decides how many entities to
+ send.
+
+ There is an internal buffer for use with the next() method. If this buffer
+ is not empty, up to 'count' values are removed from this buffer and
+ returned. It's best not to mix _Next() and next().
+
+ 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 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 0; received %s (a %s)' %
+ (count, typename(count)))
+
+ if self.__buffer:
+ if count is None:
+ entity_list = self.__buffer
+ self.__buffer = []
+ return entity_list
+ elif count <= len(self.__buffer):
+ entity_list = self.__buffer[:count]
+ del self.__buffer[:count]
+ return entity_list
+ else:
+ entity_list = self.__buffer
+ self.__buffer = []
+ count -= len(entity_list)
+ else:
+ entity_list = []
+
+
+ if not self.__more_results:
+ return entity_list
+
+ req = datastore_pb.NextRequest()
+ if count is not None:
+ req.set_count(count)
+ req.mutable_cursor().CopyFrom(self.__cursor)
+ result = datastore_pb.QueryResult()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Next', req, result)
+ except apiproxy_errors.ApplicationError, err:
+ raise _ToDatastoreError(err)
+
+ return entity_list + self._ProcessQueryResult(result)
+
+ 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, ...]
+ """
+ self.__more_results = result.more_results()
+
+ 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 next(self):
+ if not self.__buffer:
+ self.__buffer = self._Next(self.__batch_size)
+ try:
+ return self.__buffer.pop(0)
+ except IndexError:
+ raise StopIteration
+
+ def __iter__(self): return self
+
+class _Transaction(object):
+ """Encapsulates a transaction currently in progress.
+
+ 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, handle):
+ """Initializes the transaction.
+
+ Args:
+ handle: a datastore_pb.Transaction returned by a BeginTransaction call
+ """
+ assert isinstance(handle, datastore_pb.Transaction)
+ explanation = []
+ assert handle.IsInitialized(explanation), explanation
+
+ self.handle = handle
+ 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()
+
+ for i in range(0, retries + 1):
+ handle = datastore_pb.Transaction()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'BeginTransaction',
+ api_base_pb.VoidProto(), handle)
+ except apiproxy_errors.ApplicationError, err:
+ raise _ToDatastoreError(err)
+
+ tx = _Transaction(handle)
+ _txes[tx_key] = tx
+
+ try:
+ result = function(*args, **kwargs)
+ except:
+ original_exception = sys.exc_info()
+
+ try:
+ resp = api_base_pb.VoidProto()
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Rollback',
+ tx.handle, resp)
+ except:
+ exc_info = sys.exc_info()
+ logging.info('Exception sending Rollback:\n' +
+ ''.join(traceback.format_exception(*exc_info)))
+
+ type, value, trace = original_exception
+ if type is datastore_errors.Rollback:
+ return
+ else:
+ raise type, value, trace
+
+ try:
+ resp = datastore_pb.CommitResponse()
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Commit',
+ tx.handle, resp)
+ 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):
+ """If we're in a transaction, validates 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, and 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
+
+ assert tx.handle.IsInitialized()
+ request.mutable_transaction().CopyFrom(tx.handle)
+
+ return tx
+
+ finally:
+ del tx_key
+
+
+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:
+ 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
+ """
+ errors = {
+ 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,
+ }
+
+ if err.application_error in errors:
+ raise errors[err.application_error](err.error_detail)
+ else:
+ raise datastore_errors.Error(err.error_detail)
diff --git a/google_appengine/google/appengine/api/datastore.pyc b/google_appengine/google/appengine/api/datastore.pyc
new file mode 100644
index 0000000..9056a21
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/datastore_admin.py b/google_appengine/google/appengine/api/datastore_admin.py
new file mode 100755
index 0000000..da2b6c7
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_admin.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.
+#
+
+"""The Python datastore admin API for managing indices and schemas.
+"""
+
+
+
+from google.appengine.api import api_base_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 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
+
+
+_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 GetSchema(_app=None, properties=True, start_kind=None, end_kind=None):
+ """Infers an app's schema from the entities in the datastore.
+
+ Note that the PropertyValue PBs in the returned EntityProtos are empty
+ placeholders, so they may cause problems if you try to convert them to
+ python values with e.g. datastore_types. In particular, user values will
+ throw UserNotFoundError because their email and auth domain fields will be
+ empty.
+
+ Args:
+ properties: boolean, whether to include property names and types
+ start_kind, end_kind: optional range endpoints for the kinds to return,
+ compared lexicographically
+
+ Returns:
+ list of entity_pb.EntityProto, with kind and property names and types
+ """
+ req = datastore_pb.GetSchemaRequest()
+ req.set_app(datastore_types.ResolveAppId(_app))
+ req.set_properties(properties)
+ if start_kind is not None:
+ req.set_start_kind(start_kind)
+ if end_kind is not None:
+ req.set_end_kind(end_kind)
+ resp = datastore_pb.Schema()
+
+ _Call('GetSchema', req, resp)
+ return resp.kind_list()
+
+
+def GetIndices(_app=None):
+ """Fetches all composite indices in the datastore for this app.
+
+ Returns:
+ list of entity_pb.CompositeIndex
+ """
+ req = api_base_pb.StringProto()
+ req.set_value(datastore_types.ResolveAppId(_app))
+ resp = datastore_pb.CompositeIndices()
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'GetIndices', req, resp)
+ except apiproxy_errors.ApplicationError, err:
+ raise datastore._ToDatastoreError(err)
+
+ return resp.index_list()
+
+
+def CreateIndex(index):
+ """Creates a new composite index in the datastore for this app.
+
+ Args:
+ index: entity_pb.CompositeIndex
+
+ Returns:
+ int, the id allocated to the index
+ """
+ resp = api_base_pb.Integer64Proto()
+ _Call('CreateIndex', index, resp)
+ return resp.value()
+
+
+def UpdateIndex(index):
+ """Updates an index's status. The entire index definition must be present.
+
+ Args:
+ index: entity_pb.CompositeIndex
+ """
+ _Call('UpdateIndex', index, api_base_pb.VoidProto())
+
+
+def DeleteIndex(index):
+ """Deletes an index. The entire index definition must be present.
+
+ Args:
+ index: entity_pb.CompositeIndex
+ """
+ _Call('DeleteIndex', index, api_base_pb.VoidProto())
+
+
+def _Call(call, req, resp):
+ """Generic method for making a datastore API call.
+
+ Args:
+ call: string, the name of the RPC call
+ req: the request PB. if the app_id field is not set, it defaults to the
+ local app.
+ resp: the response PB
+ """
+ if hasattr(req, 'app_id'):
+ req.set_app_id(datastore_types.ResolveAppId(req.app_id(), 'req.app_id()'))
+
+ try:
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', call, req, resp)
+ except apiproxy_errors.ApplicationError, err:
+ raise datastore._ToDatastoreError(err)
+
+
+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 = datastore_index.Property(name=prop_proto.name())
+ if prop_proto.direction() == entity_pb.Index_Property.DESCENDING:
+ prop_definition.direction = 'descending'
+ properties.append(prop_definition)
+
+ index = datastore_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/api/datastore_admin.pyc b/google_appengine/google/appengine/api/datastore_admin.pyc
new file mode 100644
index 0000000..302bf52
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_admin.pyc
Binary files differ
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 <key> tags.
+ """
+
+ HEADER = u"""<entry xmlns:gd='http://schemas.google.com/g/2005'>
+ <category scheme='http://schemas.google.com/g/2005#kind'
+ term='http://schemas.google.com/g/2005#%s' />"""
+ FOOTER = u"""
+</entry>"""
+
+ _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
+ <property> 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
+ <gd:who> 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</%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 += """
+ <gd:who rel="http://schemas.google.com/g/2005#%s.%s>
+ <gd:entryLink href="%s" />
+ </gd:who>""" % (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 <gd:who> 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 <gd:who> 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 += """
+ <author><name>%s</name></author>""" % self['author']
+
+ if 'eventStatus' in self:
+ xml += """
+ <gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
+ self['eventStatus'])
+
+ if 'where' in self:
+ lines = ['<gd:where valueString="%s" />' % val
+ for val in self._XmlEscapeValues('where')]
+ xml += '\n ' + '\n '.join(lines)
+
+ iso_format = '%Y-%m-%dT%H:%M:%S'
+ xml += '\n <gd:when'
+ for key in ['startTime', 'endTime']:
+ if key in self:
+ xml += ' %s="%s"' % (key, self[key].isoformat())
+ xml += ' />'
+
+ self._kind_properties.update(['author', 'where', 'startTime', 'endTime',
+ 'eventStatus'])
+ xml += self._ContactPropertiesToXml()
+ xml += self._LeftoverPropertiesToXml()
+ xml += GdKind.FOOTER
+ return xml
+
+
+class Contact(GdKind):
+ """A contact: a person, a venue such as a club or a restaurant, or an
+ organization.
+
+ This is the gd Contact kind. See:
+ http://code.google.com/apis/gdata/common-elements.html#gdContactKind
+
+ Most of the information about the contact is in the <gd:contactSection>
+ 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 = """
+ <gd:contactSection>"""
+ CONTACT_SECTION_FOOTER = """
+ </gd:contactSection>"""
+
+ 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..ff53ba2
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_errors.py
@@ -0,0 +1,105 @@
+#!/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. 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.
+ """
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 0000000..4f947c1
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_errors.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/datastore_file_stub.py b/google_appengine/google/appengine/api/datastore_file_stub.py
new file mode 100755
index 0000000..ebd47fe
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_file_stub.py
@@ -0,0 +1,1061 @@
+#!/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.
+#
+
+"""
+In-memory persistent stub for the Python datastore API. Gets, queries,
+and searches are implemented as in-memory scans over all entities.
+
+Stores entities across sessions as pickled proto bufs in a single file. On
+startup, all entities are read from the file and loaded into memory. On
+every Put(), the file is wiped and all entities are written from scratch.
+Clients can also manually Read() and Write() the file themselves.
+
+Transactions are serialized through __tx_lock. Each transaction acquires it
+when it begins and releases it when it commits or rolls back. This is
+important, since there are other member variables like __tx_snapshot that are
+per-transaction, so they should only be used by one tx at a time.
+"""
+
+
+
+
+
+
+import datetime
+import logging
+import md5
+import os
+import struct
+import sys
+import tempfile
+import threading
+import warnings
+
+import cPickle as pickle
+
+from google.appengine.api import api_base_pb
+from google.appengine.api import apiproxy_stub
+from google.appengine.api import datastore
+from google.appengine.api import datastore_admin
+from google.appengine.api import datastore_errors
+from google.appengine.api import datastore_types
+from google.appengine.api import users
+from google.appengine.datastore import datastore_pb
+from google.appengine.datastore import datastore_index
+from google.appengine.runtime import apiproxy_errors
+from google.net.proto import ProtocolBuffer
+from google.appengine.datastore import entity_pb
+
+warnings.filterwarnings('ignore', 'tempnam is a potential security risk')
+
+
+entity_pb.Reference.__hash__ = lambda self: hash(self.Encode())
+datastore_pb.Query.__hash__ = lambda self: hash(self.Encode())
+
+
+_MAXIMUM_RESULTS = 1000
+
+
+_MAX_QUERY_OFFSET = 1000
+
+
+_MAX_QUERY_COMPONENTS = 100
+
+_BATCH_SIZE = 20
+
+class _StoredEntity(object):
+ """Simple wrapper around an entity stored by the stub.
+
+ Public properties:
+ protobuf: Native protobuf Python object, entity_pb.EntityProto.
+ encoded_protobuf: Encoded binary representation of above protobuf.
+ native: datastore.Entity instance.
+ """
+
+ def __init__(self, entity):
+ """Create a _StoredEntity object and store an entity.
+
+ Args:
+ entity: entity_pb.EntityProto to store.
+ """
+ self.protobuf = entity
+
+ self.encoded_protobuf = entity.Encode()
+
+ self.native = datastore.Entity._FromPb(entity)
+
+
+class _Cursor(object):
+ """A query cursor.
+
+ Public properties:
+ cursor: the integer cursor
+ count: the original total number of results
+ keys_only: whether the query is keys_only
+
+ Class attributes:
+ _next_cursor: the next cursor to allocate
+ _next_cursor_lock: protects _next_cursor
+ """
+ _next_cursor = 1
+ _next_cursor_lock = threading.Lock()
+
+ def __init__(self, results, keys_only):
+ """Constructor.
+
+ Args:
+ # the query results, in order, such that pop(0) is the next result
+ results: list of entity_pb.EntityProto
+ keys_only: integer
+ """
+ self.__results = results
+ self.count = len(results)
+ self.keys_only = keys_only
+
+ self._next_cursor_lock.acquire()
+ try:
+ self.cursor = _Cursor._next_cursor
+ _Cursor._next_cursor += 1
+ finally:
+ self._next_cursor_lock.release()
+
+ def PopulateQueryResult(self, result, count):
+ """Populates a QueryResult with this cursor and the given number of results.
+
+ Args:
+ result: datastore_pb.QueryResult
+ count: integer
+ """
+ result.mutable_cursor().set_cursor(self.cursor)
+ result.set_keys_only(self.keys_only)
+
+ results_pbs = [r._ToPb() for r in self.__results[:count]]
+ result.result_list().extend(results_pbs)
+ del self.__results[:count]
+
+ result.set_more_results(len(self.__results) > 0)
+
+
+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,
+ 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: string, stores query history. Use None as with
+ datastore_file.
+ 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.__history_file = history_file
+ self.SetTrusted(trusted)
+
+ self.__entities = {}
+
+ self.__schema_cache = {}
+
+ self.__tx_snapshot = {}
+
+ self.__queries = {}
+
+ self.__transactions = {}
+
+ 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 = {}
+ 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.
+ """
+ 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 _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 key.app(), 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
+
+ self.__query_history = {}
+ for encoded_query, count in self.__ReadPickled(self.__history_file):
+ try:
+ query_pb = datastore_pb.Query(encoded_query)
+ except self.READ_PB_EXCEPTIONS, e:
+ raise datastore_errors.InternalError(self.READ_ERROR_MSG %
+ (self.__history_file, e))
+
+ if query_pb in self.__query_history:
+ self.__query_history[query_pb] += count
+ else:
+ self.__query_history[query_pb] = count
+
+ def Write(self):
+ """ Writes out the datastore and history files. Be careful! If the files
+ already exist, this method overwrites them!
+ """
+ self.__WriteDatastore()
+ self.__WriteHistory()
+
+ 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 __WriteHistory(self):
+ """ Writes out the history file. Be careful! If the file already exist,
+ this method overwrites it!
+ """
+ if self.__history_file and self.__history_file != '/dev/null':
+ encoded = [(query.Encode(), count)
+ for query, count in self.__query_history.items()]
+
+ self.__WritePickled(encoded, self.__history_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):
+ 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():
+ 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_Get(self, get_request, get_response):
+ if get_request.has_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):
+ 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 not self.__tx_lock.acquire(False):
+ 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
+ self.__tx_lock.release()
+
+ app_id_namespace = datastore_types.parse_app_id_namespace(query.app())
+ app_id = app_id_namespace.app_id()
+ self.__ValidateAppId(app_id)
+
+ if query.has_offset() and query.offset() > _MAX_QUERY_OFFSET:
+ raise apiproxy_errors.ApplicationError(
+ datastore_pb.Error.BAD_REQUEST, 'Too big query offset.')
+
+ 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_admin.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_namespace.to_encoded())
+ if query.has_kind():
+ results = entities[app_id_namespace.to_encoded(), query.kind()].values()
+ results = [entity.native for entity in results]
+ else:
+ results = []
+ for key in entities:
+ if key[0] == app_id_namespace.to_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)
+
+ offset = 0
+ limit = len(results)
+ if query.has_offset():
+ offset = query.offset()
+ if query.has_limit():
+ limit = query.limit()
+ if limit > _MAXIMUM_RESULTS:
+ limit = _MAXIMUM_RESULTS
+ results = results[offset:limit + offset]
+
+ clone = datastore_pb.Query()
+ clone.CopyFrom(query)
+ clone.clear_hint()
+ if clone in self.__query_history:
+ self.__query_history[clone] += 1
+ else:
+ self.__query_history[clone] = 1
+ self.__WriteHistory()
+
+ cursor = _Cursor(results, query.keys_only())
+ 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)
+
+ def _Dynamic_Next(self, next_request, query_result):
+ 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)
+
+ count = _BATCH_SIZE
+ if next_request.has_count():
+ count = next_request.count()
+ cursor.PopulateQueryResult(query_result, count)
+
+ def _Dynamic_Count(self, query, integer64proto):
+ self.__ValidateAppId(query.app())
+ query_result = datastore_pb.QueryResult()
+ self._Dynamic_RunQuery(query, query_result)
+ cursor = query_result.cursor().cursor()
+ integer64proto.set_value(self.__queries[cursor].count)
+ del self.__queries[cursor]
+
+ def _Dynamic_BeginTransaction(self, request, transaction):
+ self.__tx_handle_lock.acquire()
+ handle = self.__next_tx_handle
+ self.__next_tx_handle += 1
+ self.__tx_handle_lock.release()
+
+ self.__transactions[handle] = None
+ transaction.set_handle(handle)
+
+ self.__tx_lock.acquire()
+ snapshot = [(app_kind, dict(entities))
+ for app_kind, entities in self.__entities.items()]
+ self.__tx_snapshot = dict(snapshot)
+
+ def _Dynamic_Commit(self, transaction, transaction_response):
+ if not self.__transactions.has_key(transaction.handle()):
+ raise apiproxy_errors.ApplicationError(
+ datastore_pb.Error.BAD_REQUEST,
+ 'Transaction handle %d not found' % transaction.handle())
+
+ self.__tx_snapshot = {}
+ try:
+ self.__WriteDatastore()
+ finally:
+ self.__tx_lock.release()
+
+ def _Dynamic_Rollback(self, transaction, transaction_response):
+ if not self.__transactions.has_key(transaction.handle()):
+ raise apiproxy_errors.ApplicationError(
+ datastore_pb.Error.BAD_REQUEST,
+ 'Transaction handle %d not found' % transaction.handle())
+
+ self.__entities = self.__tx_snapshot
+ self.__tx_snapshot = {}
+ self.__tx_lock.release()
+
+ def _Dynamic_GetSchema(self, req, schema):
+ app_str = req.app()
+ self.__ValidateAppId(app_str)
+
+ kinds = []
+
+ for app, kind in self.__entities:
+ if (app != app_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, 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()
+ size = allocate_ids_request.size()
+
+ self.__ValidateAppId(model_key.app())
+
+ try:
+ self.__id_lock.acquire()
+ start = self.__next_id
+ self.__next_id += size
+ 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 0000000..2efca54
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_file_stub.pyc
Binary files differ
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..c2d1d5f
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_types.py
@@ -0,0 +1,1788 @@
+#!/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__'
+_SPECIAL_PROPERTIES = frozenset([_KEY_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.
+ 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.
+ 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):
+ 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, name='_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.
+ name: The argument name, for error messages.
+
+ 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
+
+
+class AppIdNamespace(object):
+ """Combined AppId and Namespace
+
+ An identifier that combines the application identifier and the
+ namespace.
+ """
+ __app_id = None
+ __namespace = None
+
+ def __init__(self, app_id, namespace):
+ """Constructor. Creates a AppIdNamespace from two strings.
+
+ Args:
+ app_id: application identifier string
+ namespace: namespace identifier string
+ Raises:
+ BadArgumentError if the values contain
+ the _NAMESPACE_SEPARATOR character (!) or
+ the app_id is empty.
+ """
+ self.__app_id = app_id
+ if namespace:
+ self.__namespace = namespace
+ else:
+ self.__namespace = None
+ ValidateString(self.__app_id, 'app_id', datastore_errors.BadArgumentError)
+ ValidateString(self.__namespace,
+ 'namespace', datastore_errors.BadArgumentError,
+ empty_ok=True)
+ if _NAMESPACE_SEPARATOR in self.__app_id:
+ raise datastore_errors.BadArgumentError(
+ 'app_id must not contain a "%s"' % _NAMESPACE_SEPARATOR)
+ if self.__namespace and _NAMESPACE_SEPARATOR in self.__namespace:
+ raise datastore_errors.BadArgumentError(
+ 'namespace must not contain a "%s"' % _NAMESPACE_SEPARATOR)
+
+ def __cmp__(self, other):
+ """Returns negative, zero, or positive when comparing two AppIdNamespace.
+
+ Args:
+ other: AppIdNamespace 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, AppIdNamespace):
+ return cmp(id(self), id(other))
+ return cmp((self.__app_id, self.__namespace),
+ (other.__app_id, other.__namespace))
+
+ def to_encoded(self):
+ """Returns this AppIdNamespace's string equivalent
+
+ i.e. "app!namespace"
+ """
+ if not self.__namespace:
+ return self.__app_id
+ else:
+ return self.__app_id + _NAMESPACE_SEPARATOR + self.__namespace
+
+ def app_id(self):
+ """Returns this AppId portion of this AppIdNamespace.
+ """
+ return self.__app_id;
+
+ def namespace(self):
+ """Returns this namespace portion of this AppIdNamespace.
+ """
+ return self.__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)])
+
+
+def parse_app_id_namespace(app_id_namespace):
+ """
+ An app_id_namespace string is valid if it's not empty, and contains
+ at most one namespace separator ('!'). Also, an app_id_namespace
+ with an empty namespace must not contain a namespace separator.
+
+ Args:
+ app_id_namespace: an encoded app_id_namespace.
+ Raises exception if format of app_id_namespace is invalid.
+ """
+ if not app_id_namespace:
+ raise datastore_errors.BadArgumentError(
+ 'app_id_namespace must be non empty')
+ parts = PartitionString(app_id_namespace, _NAMESPACE_SEPARATOR)
+ if parts[1] == _NAMESPACE_SEPARATOR:
+ if not parts[2]:
+ raise datastore_errors.BadArgumentError(
+ 'app_id_namespace must not contain a "%s" if the namespace is empty' %
+ _NAMESPACE_SEPARATOR)
+ if parts[2]:
+ return AppIdNamespace(parts[0], parts[2])
+ return AppIdNamespace(parts[0], None)
+
+def ResolveAppIdNamespace(
+ app_id=None, namespace=None, app_id_namespace=None):
+ """Validate an app id/namespace and substitute default values.
+
+ If the argument is None, $APPLICATION_ID!$NAMESPACE is substituted.
+
+ Args:
+ app_id: The app id argument value to be validated.
+ namespace: The namespace argument value to be validated.
+ app_id_namespace: An AppId/Namespace pair
+
+ Returns:
+ An AppIdNamespace object initialized with AppId and Namespace.
+
+ Raises:
+ BadArgumentError if the value is empty or not a string.
+ """
+ if app_id_namespace is None:
+ if app_id is None:
+ app_id = os.environ.get('APPLICATION_ID', '')
+ if namespace is None:
+ namespace = namespace_manager.get_request_namespace();
+ else:
+ if not app_id is None:
+ raise datastore_errors.BadArgumentError(
+ 'app_id is overspecified. Cannot define app_id_namespace and app_id')
+ if not namespace is None:
+ raise datastore_errors.BadArgumentError(
+ 'namespace is overspecified. ' +
+ 'Cannot define app_id_namespace and namespace')
+ return parse_app_id_namespace(app_id_namespace)
+
+ return AppIdNamespace(app_id, namespace)
+
+
+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
+ """
+ if encoded is not None:
+ if not isinstance(encoded, basestring):
+ try:
+ repr_encoded = repr(encoded)
+ except:
+ repr_encoded = "<couldn't encode>"
+ 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))
+
+ encoded_pb = base64.urlsafe_b64decode(str(encoded))
+ self.__reference = entity_pb.Reference(encoded_pb)
+ assert self.__reference.IsInitialized()
+
+ 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):
+ """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())
+ 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)
+
+ Additional positional arguments are allowed and should be
+ alternating kind and id/name.
+
+ Keyword args:
+ parent: optional parent Key; default None.
+
+ 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_namespace_obj = ResolveAppIdNamespace(
+ kwds.pop('_app', None),
+ kwds.pop('_namespace', None),
+ kwds.pop('_app_id_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 not parent.has_id_or_name():
+ raise datastore_errors.BadKeyError(
+ 'The parent Key is incomplete.')
+ if _app_id_namespace_obj != parent.app_id_namespace():
+ raise datastore_errors.BadArgumentError(
+ 'The app_id/namespace arguments (%r) should match ' +
+ 'parent.app_id_namespace().to_encoded() (%s)' %
+ (_app_id_namespace_obj, parent.app_id_namespace()))
+
+ key = Key()
+ ref = key.__reference
+ if parent is not None:
+ ref.CopyFrom(parent.__reference)
+ else:
+ ref.set_app(_app_id_namespace_obj.to_encoded())
+
+ 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.app_id_namespace().app_id().decode('utf-8')
+ else:
+ return None
+
+ def namespace(self):
+ """Returns this entity's app id, a string."""
+ if self.__reference.app():
+ return self.app_id_namespace().namespace().decode('utf-8')
+ else:
+ return None
+
+ def app_id_namespace(self):
+ """Returns this entity's app id/namespace, an appIdNamespace object."""
+ if self.__reference.app():
+ return parse_app_id_namespace(self.__reference.app())
+ else:
+ return None
+
+ 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 <kind>[<key>].
+
+ 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(self.app_id_namespace().to_encoded()),
+ 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
+ """
+ if (self.has_id_or_name()):
+ encoded = base64.urlsafe_b64encode(self.__reference.Encode())
+ return encoded.replace('=', '')
+ else:
+ raise datastore_errors.BadKeyError(
+ 'Cannot string encode an incomplete key!\n%s' % self.__reference)
+
+ 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_id_namespace=%r' % self.__reference.app().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 = []
+ other_args = []
+
+ self_args.append(self.__reference.app())
+ other_args.append(other.__reference.app())
+
+ for elem in self.__reference.path().element_list():
+ self_args.append(elem.type())
+ if elem.has_name():
+ self_args.append(elem.name())
+ else:
+ self_args.append(elem.id())
+
+ for elem in other.__reference.path().element_list():
+ other_args.append(elem.type())
+ if elem.has_name():
+ other_args.append(elem.name())
+ else:
+ other_args.append(elem.id())
+
+ 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
+ """
+ return hash(self.__str__())
+
+
+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="%s" label=%s />' % (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'<link href=%s />' % 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'<gd:email address=%s />' % 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 '<lat>,<lon>'. 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'<georss:point>%s %s</georss:point>' % (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 '<protocol> <address>'. 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(' ')
+ protocol, address = split
+ except (AttributeError, ValueError):
+ raise datastore_errors.BadValueError(
+ 'Expected string of format "protocol address"; received %s' %
+ str(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'<gd:im protocol=%s address=%s />' %
+ (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'<gd:phoneNumber>%s</gd:phoneNumber>' % 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'<gd:postalAddress>%s</gd:postalAddress>' % 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'<gd:rating value="%d" min="%d" max="%d" />' %
+ (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. The structure of the key is:
+
+ _<blob-key>
+
+ 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.
+ """
+ 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
+ """
+ s = type(self).__module__
+ return '%s.%s(%r)' % (type(self).__module__,
+ 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)
+
+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: ValidatePropertyString,
+}
+
+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'))
+
+
+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())
+ 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())
+
+ 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)
+ value = users.User(email=email,
+ _auth_domain=auth_domain,
+ _user_id=obfuscated_gaiaid)
+ 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 0000000..5c19ce2
--- /dev/null
+++ b/google_appengine/google/appengine/api/datastore_types.pyc
Binary files differ
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..757afcf
--- /dev/null
+++ b/google_appengine/google/appengine/api/images/__init__.py
@@ -0,0 +1,827 @@
+#!/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 Image(object):
+ """Image object to manipulate."""
+
+ def __init__(self, image_data):
+ """Constructor.
+
+ Args:
+ image_data: str, image data in string form.
+
+ Raises:
+ NotImageError if the given data is empty.
+ """
+ if not image_data:
+ raise NotImageError("Empty image data.")
+
+ self._image_data = image_data
+ 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.
+ """
+ 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("<HH", self._image_data[6:10])
+ else:
+ raise BadImageError("Corrupt GIF format")
+
+ def _update_png_dimensions(self):
+ """Updates the width and height fields of the png image.
+
+ Raises:
+ BadImageError if the image string is not a valid png image.
+ """
+ size = len(self._image_data)
+ if size >= 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("<I", self._image_data[14:18])[0]
+ if ((header_length == 40 or header_length == 108 or
+ header_length == 124 or header_length == 64) and size >= 26):
+ self._width, self._height = struct.unpack("<II",
+ self._image_data[18:26])
+ elif header_length == 12 and size >= 22:
+ self._width, self._height = struct.unpack("<HH",
+ self._image_data[18:22])
+ else:
+ raise BadImageError("Corrupt BMP format")
+ else:
+ raise BadImageError("Corrupt BMP format")
+
+ def _update_ico_dimensions(self):
+ """Updates the width and height fields of the ico image.
+
+ Raises:
+ BadImageError if the image string is not a valid ico image.
+ """
+ size = len(self._image_data)
+ if size >= 8:
+ self._width, self._height = struct.unpack("<BB", self._image_data[6:8])
+ if not self._width:
+ self._width = 256
+ if not self._height:
+ self._height = 256
+ else:
+ raise BadImageError("Corrupt ICO format")
+
+ def resize(self, width=0, height=0):
+ """Resize the image 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:
+ width: int, width (in pixels) to change the image width to.
+ height: int, height (in pixels) to change the image height to.
+
+ Raises:
+ TypeError when width or height is not either 'int' or 'long' types.
+ BadRequestError when there is something wrong with the given height or
+ width or if MAX_TRANSFORMS_PER_REQUEST transforms have already been
+ requested on this image.
+ """
+ if (not isinstance(width, (int, long)) or
+ not isinstance(height, (int, long))):
+ raise TypeError("Width and height must be integers.")
+ if width < 0 or height < 0:
+ raise BadRequestError("Width and height must be >= 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 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.
+ 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()
+
+ request.mutable_image().set_content(self._image_data)
+
+ 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.UNSPECIFIED_ERROR):
+ raise TransformationError()
+ else:
+ raise Error()
+
+ self._image_data = response.image().content()
+ 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()
+
+ request.mutable_image().set_content(self._image_data)
+ 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()
+ 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()
+ 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.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()
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 0000000..40ef57a
--- /dev/null
+++ b/google_appengine/google/appengine/api/images/__init__.pyc
Binary files differ
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_not_implemented_stub.pyc b/google_appengine/google/appengine/api/images/images_not_implemented_stub.pyc
new file mode 100644
index 0000000..6885635
--- /dev/null
+++ b/google_appengine/google/appengine/api/images/images_not_implemented_stub.pyc
Binary files differ
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 100644
index 0000000..927040c
--- /dev/null
+++ b/google_appengine/google/appengine/api/images/images_service_pb.py
@@ -0,0 +1,1988 @@
+#!/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
+
+ _ErrorCode_NAMES = {
+ 1: "UNSPECIFIED_ERROR",
+ 2: "BAD_TRANSFORM_DATA",
+ 3: "NOT_IMAGE",
+ 4: "BAD_IMAGE_DATA",
+ 5: "IMAGE_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 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_ = ""
+
+ 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 MergeFrom(self, x):
+ assert x is not self
+ if (x.has_content()): self.set_content(x.content())
+
+ 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
+ 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_))
+ return n + 1
+
+ def Clear(self):
+ self.clear_content()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(10)
+ out.putPrefixedString(self.content_)
+
+ def TryMerge(self, d):
+ while d.avail() > 0:
+ tt = d.getVarInt32()
+ if tt == 10:
+ self.set_content(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_))
+ return res
+
+
+ def _BuildTagLookupTable(sparse, maxtag, default=None):
+ return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+ kcontent = 1
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "content",
+ }, 1)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 1: ProtocolBuffer.Encoder.STRING,
+ }, 1, 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 = """"""
+
+__all__ = ['ImagesServiceError','ImagesServiceTransform','Transform','ImageData','OutputSettings','ImagesTransformRequest','ImagesTransformResponse','CompositeImageOptions','ImagesCanvas','ImagesCompositeRequest','ImagesCompositeResponse','ImagesHistogramRequest','ImagesHistogram','ImagesHistogramResponse']
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 0000000..6b98746
--- /dev/null
+++ b/google_appengine/google/appengine/api/images/images_service_pb.pyc
Binary files differ
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..d89f47e
--- /dev/null
+++ b/google_appengine/google/appengine/api/images/images_stub.py
@@ -0,0 +1,411 @@
+#!/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 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)
+
+
+class ImagesServiceStub(apiproxy_stub.APIProxyStub):
+ """Stub version of images API to be used with the dev_appserver."""
+
+ def __init__(self, service_name='images'):
+ """Preloads PIL to load all modules in the unhardened environment.
+
+ Args:
+ service_name: Service name expected for all calls.
+ """
+ super(ImagesServiceStub, self).__init__(service_name)
+ 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())
+ 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._OpenImage(image.content()))
+
+ 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._OpenImage(request.image().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)
+ 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._OpenImage(request.image().content())
+
+ 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 _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 _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:
+ image = Image.open(image)
+ except IOError:
+ raise apiproxy_errors.ApplicationError(
+ images_service_pb.ImagesServiceError.BAD_IMAGE_DATA)
+
+ 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 _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 0000000..a29f50c
--- /dev/null
+++ b/google_appengine/google/appengine/api/images/images_stub.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/labs/__init__.py b/google_appengine/google/appengine/api/labs/__init__.py
new file mode 100755
index 0000000..c33ae80
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/__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/labs/__init__.pyc b/google_appengine/google/appengine/api/labs/__init__.pyc
new file mode 100644
index 0000000..3557233
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/__init__.pyc
Binary files differ
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 0000000..a9ca241
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/taskqueue/__init__.pyc
Binary files differ
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..733df36
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue.py
@@ -0,0 +1,633 @@
+#!/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 datetime
+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 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."""
+
+
+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)
+
+
+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'])
+
+ 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.
+ 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', {})
+
+ 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 = Task.__determine_eta(
+ kwargs.get('eta'), kwargs.get('countdown'))
+ 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(eta=None, countdown=None, now=datetime.datetime.now):
+ """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
+ countdown: Count in seconds into the future from the present time that
+ the ETA should be assigned to.
+
+ Returns:
+ A datetime in the UTC timezone 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 countdown is not None:
+ try:
+ countdown = float(countdown)
+ except ValueError:
+ raise InvalidTaskError('Countdown must be a number')
+ else:
+ eta = now() + datetime.timedelta(seconds=countdown)
+ else:
+ eta = now()
+
+ if eta.tzinfo is None:
+ eta = eta.replace(tzinfo=_UTC)
+ return eta.astimezone(_UTC)
+
+ @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(self):
+ """Returns an datetime corresponding to when this Task will execute."""
+ 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=True):
+ """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=True):
+ """Adds a Task to this Queue.
+
+ Args:
+ task: The Task to add.
+ transactional: If false adds the task to a queue irrespectively to the
+ enclosing transaction success or failure. (optional)
+
+ Returns:
+ The Task that was supplied to this method.
+
+ Raises:
+ BadTaskStateError if the Task has already been added to a queue.
+ Error-subclass on application errors.
+ """
+ if task.was_enqueued:
+ raise BadTaskStateError('Task has already been enqueued')
+
+ request = taskqueue_service_pb.TaskQueueAddRequest()
+ response = taskqueue_service_pb.TaskQueueAddResponse()
+
+ adjusted_url = task.url
+ if task.on_queue_url:
+ adjusted_url = self.__url + task.url
+
+
+ request.set_queue_name(self.__name)
+ request.set_eta_usec(int(time.mktime(task.eta.utctimetuple())) * 10**6)
+ request.set_method(_METHOD_MAP.get(task.method))
+ request.set_url(adjusted_url)
+
+ if task.name:
+ request.set_task_name(task.name)
+ else:
+ request.set_task_name('')
+
+ if task.payload:
+ request.set_body(task.payload)
+ for key, value in _flatten_params(task.headers):
+ header = request.add_header()
+ header.set_key(key)
+ header.set_value(value)
+
+ if transactional:
+ from google.appengine.api import datastore
+ datastore._MaybeSetupTransaction(request, [])
+
+ call_tuple = ('taskqueue', 'Add', request, response)
+ apiproxy_stub_map.apiproxy.GetPreCallHooks().Call(*call_tuple)
+ try:
+ apiproxy_stub_map.MakeSyncCall(*call_tuple)
+ except apiproxy_errors.ApplicationError, e:
+ self.__TranslateError(e)
+ else:
+ apiproxy_stub_map.apiproxy.GetPostCallHooks().Call(*call_tuple)
+
+ if response.has_chosen_task_name():
+ task._Task__name = response.chosen_task_name()
+ task._Task__enqueued = True
+ return task
+
+ @property
+ def name(self):
+ """Returns the name of this queue."""
+ return self.__name
+
+ @staticmethod
+ def __TranslateError(error):
+ """Translates a TaskQueueServiceError into an exception.
+
+ Args:
+ error: Value from TaskQueueServiceError enum.
+
+ Raises:
+ The corresponding Exception sub-class for that error code.
+ """
+ if (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE):
+ raise UnknownQueueError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.TRANSIENT_ERROR):
+ raise TransientError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERROR):
+ raise InternalError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.TASK_TOO_LARGE):
+ raise TaskTooLargeError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.INVALID_TASK_NAME):
+ raise InvalidTaskNameError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.INVALID_QUEUE_NAME):
+ raise InvalidQueueNameError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.INVALID_URL):
+ raise InvalidUrlError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.INVALID_QUEUE_RATE):
+ raise InvalidQueueError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.PERMISSION_DENIED):
+ raise PermissionDeniedError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.TASK_ALREADY_EXISTS):
+ raise TaskAlreadyExistsError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.TOMBSTONED_TASK):
+ raise TombstonedTaskError(error.error_detail)
+ elif (error.application_error ==
+ taskqueue_service_pb.TaskQueueServiceError.INVALID_ETA):
+ raise InvalidTaskError(error.error_detail)
+ else:
+ raise Error('Application error %s: %s' %
+ (error.application_error, error.error_detail))
+
+
+def add(*args, **kwargs):
+ """Convenience method will create a Task and add it to the default queue.
+
+ Args:
+ *args, **kwargs: Passed to the Task constructor.
+
+ Returns:
+ The Task that was added to the queue.
+ """
+ return Task(*args, **kwargs).add()
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 0000000..ebb0b63
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue.pyc
Binary files differ
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 100644
index 0000000..1038974
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.py
@@ -0,0 +1,1645 @@
+#!/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 *
+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
+
+ _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",
+ }
+
+ 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(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
+
+ 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 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())
+
+ 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
+ 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.')
+ 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
+ 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.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_)
+ 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_body_): n += 1 + self.lengthString(len(self.body_))
+ if (self.has_transaction_): n += 1 + self.lengthString(self.transaction_.ByteSize())
+ return n + 4
+
+ 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()
+
+ 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_)
+ 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)
+
+ 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 == 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"
+ 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
+
+ _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",
+ }, 10)
+
+ _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,
+ }, 10, 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 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_ = ""
+
+ 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 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())
+
+ 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
+ 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.')
+ 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 + 19
+
+ def Clear(self):
+ 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(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_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "Queue",
+ 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.STARTGROUP,
+ 2: ProtocolBuffer.Encoder.STRING,
+ 3: ProtocolBuffer.Encoder.DOUBLE,
+ 4: ProtocolBuffer.Encoder.DOUBLE,
+ 5: ProtocolBuffer.Encoder.STRING,
+ }, 5, 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 TaskQueueFetchQueueStatsResponse_QueueStats(ProtocolBuffer.ProtocolMessage):
+ has_num_tasks_ = 0
+ num_tasks_ = 0
+ has_oldest_eta_usec_ = 0
+ oldest_eta_usec_ = 0
+
+ def __init__(self, contents=None):
+ 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 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())
+
+ 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
+ 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.')
+ return initialized
+
+ def ByteSize(self):
+ n = 0
+ n += self.lengthVarInt64(self.num_tasks_)
+ n += self.lengthVarInt64(self.oldest_eta_usec_)
+ return n + 2
+
+ def Clear(self):
+ self.clear_num_tasks()
+ self.clear_oldest_eta_usec()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(16)
+ out.putVarInt32(self.num_tasks_)
+ out.putVarInt32(24)
+ out.putVarInt64(self.oldest_eta_usec_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "QueueStats",
+ 2: "num_tasks",
+ 3: "oldest_eta_usec",
+ }, 3)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 1: ProtocolBuffer.Encoder.STARTGROUP,
+ 2: ProtocolBuffer.Encoder.NUMERIC,
+ 3: ProtocolBuffer.Encoder.NUMERIC,
+ }, 3, ProtocolBuffer.Encoder.MAX_TYPE)
+
+ _STYLE = """"""
+ _STYLE_CONTENT_TYPE = """"""
+
+__all__ = ['TaskQueueServiceError','TaskQueueAddRequest','TaskQueueAddRequest_Header','TaskQueueAddResponse','TaskQueueUpdateQueueRequest','TaskQueueUpdateQueueResponse','TaskQueueFetchQueuesRequest','TaskQueueFetchQueuesResponse','TaskQueueFetchQueuesResponse_Queue','TaskQueueFetchQueueStatsRequest','TaskQueueFetchQueueStatsResponse','TaskQueueFetchQueueStatsResponse_QueueStats']
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 0000000..e0c961a
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.pyc
Binary files differ
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..dfe5e16
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.py
@@ -0,0 +1,327 @@
+#!/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 only stores tasks; it doesn't actually run them. 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 base64
+import datetime
+import os
+
+import taskqueue_service_pb
+
+from google.appengine.api import apiproxy_stub
+from google.appengine.api import queueinfo
+from google.appengine.api import urlfetch
+from google.appengine.runtime import apiproxy_errors
+
+
+DEFAULT_RATE = '5.00/s'
+
+DEFAULT_BUCKET_SIZE = 5
+
+MAX_ETA_DELTA_DAYS = 30
+
+
+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
+ populaeted 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 does not attempt to automatically execute tasks. Instead, it
+ stores them for display on a console. The user may manually execute the
+ tasks from the console.
+ """
+
+ queue_yaml_parser = _ParseQueueYaml
+
+ def __init__(self, service_name='taskqueue', root_path=None):
+ """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.
+ """
+ super(TaskQueueServiceStub, self).__init__(service_name)
+ self._taskqueues = {}
+ self._next_task_id = 1
+ self._root_path = root_path
+
+ def _Dynamic_Add(self, request, response):
+ """Local implementation of the Add 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.TaskQueueAddRequest.
+ response: A taskqueue_service_pb.TaskQueueAddResponse.
+ """
+ if request.eta_usec() < 0:
+ raise apiproxy_errors.ApplicationError(
+ 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:
+ raise apiproxy_errors.ApplicationError(
+ taskqueue_service_pb.TaskQueueServiceError.INVALID_ETA)
+
+ if not self._IsValidQueue(request.queue_name()):
+ raise apiproxy_errors.ApplicationError(
+ taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE)
+
+ if not request.task_name():
+ request.set_task_name('task%d' % self._next_task_id)
+ response.set_chosen_task_name(request.task_name())
+ self._next_task_id += 1
+
+ tasks = self._taskqueues.setdefault(request.queue_name(), [])
+ for task in tasks:
+ if task.task_name() == request.task_name():
+ raise apiproxy_errors.ApplicationError(
+ taskqueue_service_pb.TaskQueueServiceError.TASK_ALREADY_EXISTS)
+ tasks.append(request)
+ 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 queue_name == 'default':
+ 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 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}, ...]
+ """
+ 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':
+ 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['max_rate'] = DEFAULT_RATE
+ queue['bucket_size'] = DEFAULT_BUCKET_SIZE
+
+ tasks = self._taskqueues.get('default', [])
+ 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': {'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-streamn'}, ...]
+
+ 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 = urlfetch._CaselessDict()
+ task['headers'] = headers
+ for req_header in task_request.header_list():
+ headers[req_header.key()] = req_header.value()
+
+ headers['X-AppEngine-QueueName'] = queue_name
+ headers['X-AppEngine-TaskName'] = task['name']
+ headers['X-AppEngine-TaskRetryCount'] = '0'
+ headers['X-AppEngine-Development-Payload'] = '1'
+ headers['Content-Length'] = len(task['body'])
+ headers['Content-Type'] = headers.get(
+ 'Content-Type', 'application/octet-stream')
+
+ 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] = []
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 0000000..d23fecb
--- /dev/null
+++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/mail.py b/google_appengine/google/appengine/api/mail.py
new file mode 100755
index 0000000..ca6df88
--- /dev/null
+++ b/google_appengine/google/appengine/api/mail.py
@@ -0,0 +1,1127 @@
+#!/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
+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 = {
+ 'asc': 'text/plain',
+ 'bmp': 'image/x-ms-bmp',
+ 'css': 'text/css',
+ 'csv': 'text/csv',
+ 'diff': 'text/plain',
+ 'gif': 'image/gif',
+ 'htm': 'text/html',
+ 'html': 'text/html',
+ 'ics': 'text/calendar',
+ 'jpe': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'jpg': 'image/jpeg',
+ 'pdf': 'application/pdf',
+ 'png': 'image/png',
+ 'pot': 'text/plain',
+ 'rss': 'text/rss+xml',
+ 'text': 'text/plain',
+ 'tif': 'image/tiff',
+ 'tiff': 'image/tiff',
+ 'txt': 'text/plain',
+ 'vcf': 'text/directory',
+ 'wbmp': 'image/vnd.wap.wbmp',
+ }
+
+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:]
+ 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).lower()
+ 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)).lower()
+ 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 = '<EncodedPayload payload=#%d' % hash(self.payload)
+ if self.charset:
+ result += ' charset=%s' % self.charset
+ if self.encoding:
+ result += ' encoding=%s' % self.encoding
+ return 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_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
diff --git a/google_appengine/google/appengine/api/mail.pyc b/google_appengine/google/appengine/api/mail.pyc
new file mode 100644
index 0000000..2a8a69c
--- /dev/null
+++ b/google_appengine/google/appengine/api/mail.pyc
Binary files differ
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 0000000..78f8b20
--- /dev/null
+++ b/google_appengine/google/appengine/api/mail_errors.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/mail_service_pb.py b/google_appengine/google/appengine/api/mail_service_pb.py
new file mode 100644
index 0000000..1b608ea
--- /dev/null
+++ b/google_appengine/google/appengine/api/mail_service_pb.py
@@ -0,0 +1,584 @@
+#!/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 *
+class MailServiceError(ProtocolBuffer.ProtocolMessage):
+
+ OK = 0
+ INTERNAL_ERROR = 1
+ BAD_REQUEST = 2
+ UNAUTHORIZED_SENDER = 3
+ INVALID_ATTACHMENT_TYPE = 4
+
+ _ErrorCode_NAMES = {
+ 0: "OK",
+ 1: "INTERNAL_ERROR",
+ 2: "BAD_REQUEST",
+ 3: "UNAUTHORIZED_SENDER",
+ 4: "INVALID_ATTACHMENT_TYPE",
+ }
+
+ 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 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 0000000..c60ce72
--- /dev/null
+++ b/google_appengine/google/appengine/api/mail_service_pb.pyc
Binary files differ
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 0000000..1f7e646
--- /dev/null
+++ b/google_appengine/google/appengine/api/mail_stub.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/memcache/__init__.py b/google_appengine/google/appengine/api/memcache/__init__.py
new file mode 100755
index 0000000..1f23cb6
--- /dev/null
+++ b/google_appengine/google/appengine/api/memcache/__init__.py
@@ -0,0 +1,931 @@
+#!/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.
+#
+
+"""Memcache API.
+
+Provides memcached-alike API to application developers to store
+data in memory when reliable storage via the DataStore API isn't
+required and higher performance is desired.
+"""
+
+
+
+import cStringIO
+import math
+import pickle
+import types
+import sha
+
+from google.appengine.api import api_base_pb
+from google.appengine.api import apiproxy_stub_map
+from google.appengine.api import namespace_manager
+from google.appengine.api.memcache import memcache_service_pb
+from google.appengine.runtime import apiproxy_errors
+
+MemcacheSetResponse = memcache_service_pb.MemcacheSetResponse
+MemcacheSetRequest = memcache_service_pb.MemcacheSetRequest
+
+MemcacheGetResponse = memcache_service_pb.MemcacheGetResponse
+MemcacheGetRequest = memcache_service_pb.MemcacheGetRequest
+
+MemcacheDeleteResponse = memcache_service_pb.MemcacheDeleteResponse
+MemcacheDeleteRequest = memcache_service_pb.MemcacheDeleteRequest
+
+MemcacheIncrementResponse = memcache_service_pb.MemcacheIncrementResponse
+MemcacheIncrementRequest = memcache_service_pb.MemcacheIncrementRequest
+
+MemcacheFlushResponse = memcache_service_pb.MemcacheFlushResponse
+MemcacheFlushRequest = memcache_service_pb.MemcacheFlushRequest
+
+MemcacheStatsRequest = memcache_service_pb.MemcacheStatsRequest
+MemcacheStatsResponse = memcache_service_pb.MemcacheStatsResponse
+
+DELETE_NETWORK_FAILURE = 0
+DELETE_ITEM_MISSING = 1
+DELETE_SUCCESSFUL = 2
+
+MAX_KEY_SIZE = 250
+MAX_VALUE_SIZE = 10 ** 6
+
+STAT_HITS = 'hits'
+STAT_MISSES = 'misses'
+STAT_BYTE_HITS = 'byte_hits'
+STAT_ITEMS = 'items'
+STAT_BYTES = 'bytes'
+STAT_OLDEST_ITEM_AGES = 'oldest_item_age'
+
+FLAG_TYPE_MASK = 7
+FLAG_COMPRESSED = 1 << 3
+
+TYPE_STR = 0
+TYPE_UNICODE = 1
+TYPE_PICKLED = 2
+TYPE_INT = 3
+TYPE_LONG = 4
+TYPE_BOOL = 5
+
+
+def _key_string(key, key_prefix='', server_to_user_dict=None):
+ """Utility function to handle different ways of requesting keys.
+
+ Args:
+ key: Either a string or tuple of (shard_number, string). In Google App
+ Engine the sharding is automatic so the shard number is ignored.
+ To memcache, the key is just bytes (no defined encoding).
+ key_prefix: Optional string prefix to prepend to key.
+ server_to_user_dict: Optional dictionary to populate with a mapping of
+ server-side key (which includes the key_prefix) to user-supplied key
+ (which does not have the prefix).
+
+ Returns:
+ The key as a non-unicode string prepended with key_prefix. This is
+ the key sent to and stored by the server. If the resulting key is
+ longer then MAX_KEY_SIZE, it will be hashed with sha1 and will be
+ replaced with the hex representation of the said hash.
+
+ Raises:
+ TypeError: If provided key isn't a string or tuple of (int, string)
+ or key_prefix or server_to_user_dict are of the wrong type.
+ """
+ if type(key) is types.TupleType:
+ key = key[1]
+ if not isinstance(key, basestring):
+ raise TypeError('Key must be a string instance, received %r' % key)
+ if not isinstance(key_prefix, basestring):
+ raise TypeError('key_prefix must be a string instance, received %r' %
+ key_prefix)
+
+ server_key = key_prefix + key
+ if isinstance(server_key, unicode):
+ server_key = server_key.encode('utf-8')
+
+ if len(server_key) > 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))
+ namespace_manager._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()
+ namespace_manager._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()
+ namespace_manager._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()
+ namespace_manager._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)))
+ namespace_manager._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()
+ 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)))
+ namespace_manager._add_name_space(request, namespace)
+
+ 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. 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:
+ 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.
+
+ 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. 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:
+ New long integer value, or None if key wasn't in cache and couldn't
+ be decremented, or a network/RPC/server error occurred.
+
+ 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.
+ 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.')
+
+ request = MemcacheIncrementRequest()
+ namespace_manager._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
+
+
+_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
+
+
+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 0000000..2e2cfef
--- /dev/null
+++ b/google_appengine/google/appengine/api/memcache/__init__.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/memcache/memcache_service_pb.py b/google_appengine/google/appengine/api/memcache/memcache_service_pb.py
new file mode 100644
index 0000000..8d499b2
--- /dev/null
+++ b/google_appengine/google/appengine/api/memcache/memcache_service_pb.py
@@ -0,0 +1,2002 @@
+#!/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 MemcacheServiceError(ProtocolBuffer.ProtocolMessage):
+
+ OK = 0
+ UNSPECIFIED_ERROR = 1
+
+ _ErrorCode_NAMES = {
+ 0: "OK",
+ 1: "UNSPECIFIED_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 MemcacheGetRequest(ProtocolBuffer.ProtocolMessage):
+ has_name_space_ = 0
+ name_space_ = ""
+
+ 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 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())
+
+ 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
+ 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_))
+ return n + 0
+
+ def Clear(self):
+ self.clear_key()
+ self.clear_name_space()
+
+ 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_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "key",
+ 2: "name_space",
+ }, 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 MemcacheGetResponse_Item(ProtocolBuffer.ProtocolMessage):
+ has_key_ = 0
+ key_ = ""
+ has_value_ = 0
+ value_ = ""
+ has_flags_ = 0
+ 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 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_key()): self.set_key(x.key())
+ 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_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
+ 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
+ return n + 2
+
+ def Clear(self):
+ self.clear_key()
+ self.clear_value()
+ self.clear_flags()
+
+ 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_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "Item",
+ 2: "key",
+ 3: "value",
+ 4: "flags",
+ }, 4)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 1: ProtocolBuffer.Encoder.STARTGROUP,
+ 2: ProtocolBuffer.Encoder.STRING,
+ 3: ProtocolBuffer.Encoder.STRING,
+ 4: ProtocolBuffer.Encoder.FLOAT,
+ }, 4, 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
+
+ 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 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())
+
+ 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
+ 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
+ return n + 2
+
+ def Clear(self):
+ self.clear_key()
+ self.clear_value()
+ self.clear_flags()
+ self.clear_set_policy()
+ self.clear_expiration_time()
+
+ 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_)
+
+ 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 == 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_))
+ return res
+
+class MemcacheSetRequest(ProtocolBuffer.ProtocolMessage):
+
+ SET = 1
+ ADD = 2
+ REPLACE = 3
+
+ _SetPolicy_NAMES = {
+ 1: "SET",
+ 2: "ADD",
+ 3: "REPLACE",
+ }
+
+ 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
+ kname_space = 7
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "Item",
+ 2: "key",
+ 3: "value",
+ 4: "flags",
+ 5: "set_policy",
+ 6: "expiration_time",
+ 7: "name_space",
+ }, 7)
+
+ _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,
+ }, 7, ProtocolBuffer.Encoder.MAX_TYPE)
+
+ _STYLE = """"""
+ _STYLE_CONTENT_TYPE = """"""
+class MemcacheSetResponse(ProtocolBuffer.ProtocolMessage):
+
+ STORED = 1
+ NOT_STORED = 2
+ ERROR = 3
+
+ _SetStatusCode_NAMES = {
+ 1: "STORED",
+ 2: "NOT_STORED",
+ 3: "ERROR",
+ }
+
+ 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
+
+ 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 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())
+
+ 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
+ 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_)
+ return n + 1
+
+ def Clear(self):
+ self.clear_key()
+ self.clear_name_space()
+ self.clear_delta()
+ self.clear_direction()
+ self.clear_initial_value()
+
+ 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_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "key",
+ 2: "delta",
+ 3: "direction",
+ 4: "name_space",
+ 5: "initial_value",
+ }, 5)
+
+ _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,
+ }, 5, ProtocolBuffer.Encoder.MAX_TYPE)
+
+ _STYLE = """"""
+ _STYLE_CONTENT_TYPE = """"""
+class MemcacheIncrementResponse(ProtocolBuffer.ProtocolMessage):
+ has_new_value_ = 0
+ new_value_ = 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 MergeFrom(self, x):
+ assert x is not self
+ if (x.has_new_value()): self.set_new_value(x.new_value())
+
+ 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
+ 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_)
+ return n + 0
+
+ def Clear(self):
+ self.clear_new_value()
+
+ def OutputUnchecked(self, out):
+ if (self.has_new_value_):
+ out.putVarInt32(8)
+ out.putVarUint64(self.new_value_)
+
+ def TryMerge(self, d):
+ while d.avail() > 0:
+ tt = d.getVarInt32()
+ if tt == 8:
+ self.set_new_value(d.getVarUint64())
+ 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_))
+ return res
+
+
+ def _BuildTagLookupTable(sparse, maxtag, default=None):
+ return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+ knew_value = 1
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "new_value",
+ }, 1)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 1: ProtocolBuffer.Encoder.NUMERIC,
+ }, 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 = """"""
+
+__all__ = ['MemcacheServiceError','MemcacheGetRequest','MemcacheGetResponse','MemcacheGetResponse_Item','MemcacheSetRequest','MemcacheSetRequest_Item','MemcacheSetResponse','MemcacheDeleteRequest','MemcacheDeleteRequest_Item','MemcacheDeleteResponse','MemcacheIncrementRequest','MemcacheIncrementResponse','MemcacheFlushRequest','MemcacheFlushResponse','MemcacheStatsRequest','MergedNamespaceStats','MemcacheStatsResponse']
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 0000000..e7f4872
--- /dev/null
+++ b/google_appengine/google/appengine/api/memcache/memcache_service_pb.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/memcache/memcache_stub.py b/google_appengine/google/appengine/api/memcache/memcache_stub.py
new file mode 100755
index 0000000..8d03bf2
--- /dev/null
+++ b/google_appengine/google/appengine/api/memcache/memcache_stub.py
@@ -0,0 +1,293 @@
+#!/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 memcache API, keeping all data in process memory."""
+
+
+
+import logging
+import time
+
+from google.appengine.api import apiproxy_stub
+from google.appengine.api import memcache
+from google.appengine.api.memcache import memcache_service_pb
+
+MemcacheSetResponse = memcache_service_pb.MemcacheSetResponse
+MemcacheSetRequest = memcache_service_pb.MemcacheSetRequest
+MemcacheIncrementRequest = memcache_service_pb.MemcacheIncrementRequest
+MemcacheDeleteResponse = memcache_service_pb.MemcacheDeleteResponse
+
+
+class CacheEntry(object):
+ """An entry in the cache."""
+
+ def __init__(self, value, expiration, flags, gettime):
+ """Initializer.
+
+ Args:
+ value: String containing the data for this entry.
+ expiration: Number containing the expiration time or offset in seconds
+ for this entry.
+ flags: Opaque flags used by the memcache implementation.
+ gettime: Used for testing. Function that works like time.time().
+ """
+ assert isinstance(value, basestring)
+ assert len(value) <= memcache.MAX_VALUE_SIZE
+ assert isinstance(expiration, (int, long))
+
+ self._gettime = gettime
+ self.value = value
+ self.flags = flags
+ self.created_time = self._gettime()
+ self.will_expire = expiration != 0
+ self.locked = False
+ self._SetExpiration(expiration)
+
+ def _SetExpiration(self, expiration):
+ """Sets the expiration for this entry.
+
+ Args:
+ expiration: Number containing the expiration time or offset in seconds
+ for this entry. If expiration is above one month, then it's considered
+ an absolute time since the UNIX epoch.
+ """
+ if expiration > (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 = 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 _Dynamic_Increment(self, request, response):
+ """Implementation of MemcacheService::Increment().
+
+ Args:
+ request: A MemcacheIncrementRequest.
+ response: A MemcacheIncrementResponse.
+ """
+ namespace = request.name_space()
+ key = request.key()
+ entry = self._GetKey(namespace, key)
+ if entry is None:
+ if not request.has_initial_value():
+ return
+ 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
+
+ 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)
+ response.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 0000000..d16bb1c
--- /dev/null
+++ b/google_appengine/google/appengine/api/memcache/memcache_stub.pyc
Binary files differ
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..43e68af
--- /dev/null
+++ b/google_appengine/google/appengine/api/namespace_manager/__init__.py
@@ -0,0 +1,75 @@
+#!/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.
+
+Each API call can specify an alternate namespace, but the functions
+here can be used to change the default namespace. The default is set
+before user code begins executing.
+"""
+
+
+
+import os
+
+ENV_DEFAULT_NAMESPACE = 'HTTP_X_APPENGINE_DEFAULT_NAMESPACE'
+ENV_CURRENT_NAMESPACE = '__INTERNAL_CURRENT_NAMESPACE'
+
+
+def set_request_namespace(namespace):
+ """Set the default namespace to use for future calls, for this request only.
+
+ Args:
+ namespace: A string naming the new namespace to use. The empty
+ string specifies the root namespace for this app.
+ """
+ os.environ[ENV_CURRENT_NAMESPACE] = namespace
+
+
+def get_request_namespace():
+ """Get the name of the current default namespace.
+
+ The empty string indicates that the root namespace is the default.
+ """
+ return os.getenv(ENV_CURRENT_NAMESPACE, '')
+
+
+def _enable_request_namespace():
+ """Automatically enable namespace to default for domain.
+
+ Calling this function will automatically default the namespace to the
+ chosen Google Apps domain for the current request.
+ """
+ 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]
+ else:
+ os.environ[ENV_CURRENT_NAMESPACE] = ''
+
+
+def _add_name_space(request, namespace=None):
+ """Add a name_space field to a request.
+
+ Args:
+ request: A protocol buffer supporting the set_name_space() operation.
+ namespace: The name of the namespace part. If None, use the
+ default namespace.
+ """
+ if namespace is None:
+ request.set_name_space(get_request_namespace())
+ else:
+ request.set_name_space(namespace)
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 0000000..5bb0673
--- /dev/null
+++ b/google_appengine/google/appengine/api/namespace_manager/__init__.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/queueinfo.py b/google_appengine/google/appengine/api/queueinfo.py
new file mode 100755
index 0000000..bdaa358
--- /dev/null
+++ b/google_appengine/google/appengine/api/queueinfo.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.
+#
+
+"""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.
+"""
+
+
+
+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])'
+
+QUEUE = 'queue'
+
+NAME = 'name'
+RATE = 'rate'
+BUCKET_SIZE = 'bucket_size'
+
+
+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 = {
+ 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)
diff --git a/google_appengine/google/appengine/api/queueinfo.pyc b/google_appengine/google/appengine/api/queueinfo.pyc
new file mode 100644
index 0000000..74dd348
--- /dev/null
+++ b/google_appengine/google/appengine/api/queueinfo.pyc
Binary files differ
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..8d9e836
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch.py
@@ -0,0 +1,361 @@
+#!/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()
+ 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 0000000..3b53f6e
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/urlfetch_errors.py b/google_appengine/google/appengine/api/urlfetch_errors.py
new file mode 100755
index 0000000..e71ca5d
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch_errors.py
@@ -0,0 +1,60 @@
+#!/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 urlfetch API
+developers.
+"""
+
+
+
+
+
+
+class Error(Exception):
+ """Base URL fetcher error type."""
+
+
+class InvalidURLError(Error):
+ """Raised when the URL given is empty or invalid.
+
+ Only http: and https: URLs are allowed. The maximum URL length
+ allowed is 2048 characters. The login/pass portion is not
+ allowed. In deployed applications, only ports 80 and 443 for http
+ and https respectively are allowed.
+ """
+
+
+class DownloadError(Error):
+ """Raised when the we could not fetch the URL for any reason.
+
+ Note that this exception is only raised when we could not contact the
+ server. HTTP errors (e.g., 404) are returned in as the status_code field
+ in the return value of Fetch, and no exception is raised.
+ """
+
+
+class ResponseTooLargeError(Error):
+ """Raised when the response was too large and was truncated."""
+ def __init__(self, response):
+ self.response = response
+
+
+class InvalidMethodError(Error):
+ """Raised when an invalid value for 'method' is provided"""
+
+
+class InvalidMethodError(Error):
+ """Raised when an invalid value for 'method' is provided"""
diff --git a/google_appengine/google/appengine/api/urlfetch_errors.pyc b/google_appengine/google/appengine/api/urlfetch_errors.pyc
new file mode 100644
index 0000000..1d41770
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch_errors.pyc
Binary files differ
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 100644
index 0000000..bf513a3
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch_service_pb.py
@@ -0,0 +1,823 @@
+#!/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
+
+ 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 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())
+
+ 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
+ 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_)
+ return n + 1
+
+ def Clear(self):
+ self.clear_content()
+ self.clear_statuscode()
+ self.clear_header()
+ self.clear_contentwastruncated()
+ self.clear_externalbytessent()
+ self.clear_externalbytesreceived()
+
+ 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_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "Content",
+ 2: "StatusCode",
+ 3: "Header",
+ 4: "Key",
+ 5: "Value",
+ 6: "ContentWasTruncated",
+ 7: "ExternalBytesSent",
+ 8: "ExternalBytesReceived",
+ }, 8)
+
+ _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,
+ }, 8, 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 0000000..0c0d0e1
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch_service_pb.pyc
Binary files differ
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..d317401
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch_stub.py
@@ -0,0 +1,270 @@
+#!/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,
+])
+
+PORTS_ALLOWED_IN_PRODUCTION = (
+ None, '80', '443', '4443', '8080', '8081', '8082', '8083', '8084', '8085',
+ '8086', '8087', '8088', '8089', '8188', '8444', '8990')
+
+_API_CALL_DEADLINE = 5.0
+
+
+_UNTRUSTED_REQUEST_HEADERS = frozenset([
+ 'content-length',
+ 'host',
+ 'vary',
+ 'via',
+ 'x-forwarded-for',
+])
+
+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(), response,
+ follow_redirects=request.followredirects(),
+ deadline=deadline)
+
+ def _RetrieveURL(self, url, payload, method, headers, 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.
+ response: Response object
+ 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 port not in PORTS_ALLOWED_IN_PRODUCTION:
+ 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)
+
+ 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 0000000..136ea22
--- /dev/null
+++ b/google_appengine/google/appengine/api/urlfetch_stub.pyc
Binary files differ
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 100644
index 0000000..1fe799b
--- /dev/null
+++ b/google_appengine/google/appengine/api/user_service_pb.py
@@ -0,0 +1,491 @@
+#!/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 *
+class UserServiceError(ProtocolBuffer.ProtocolMessage):
+
+ OK = 0
+ REDIRECT_URL_TOO_LONG = 1
+ NOT_ALLOWED = 2
+
+ _ErrorCode_NAMES = {
+ 0: "OK",
+ 1: "REDIRECT_URL_TOO_LONG",
+ 2: "NOT_ALLOWED",
+ }
+
+ 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_ = ""
+
+ 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 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 = """"""
+
+__all__ = ['UserServiceError','CreateLoginURLRequest','CreateLoginURLResponse','CreateLogoutURLRequest','CreateLogoutURLResponse']
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 0000000..2d478ee
--- /dev/null
+++ b/google_appengine/google/appengine/api/user_service_pb.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/user_service_stub.py b/google_appengine/google/appengine/api/user_service_stub.py
new file mode 100755
index 0000000..d1542e1
--- /dev/null
+++ b/google_appengine/google/appengine/api/user_service_stub.py
@@ -0,0 +1,106 @@
+#!/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.
+#
+
+"""Trivial implementation of the UserService."""
+
+
+import os
+import urllib
+import urlparse
+from google.appengine.api import apiproxy_stub
+from google.appengine.api import user_service_pb
+
+
+_DEFAULT_LOGIN_URL = 'https://www.google.com/accounts/Login?continue=%s'
+_DEFAULT_LOGOUT_URL = 'https://www.google.com/accounts/Logout?continue=%s'
+
+
+class UserServiceStub(apiproxy_stub.APIProxyStub):
+ """Trivial implementation of the UserService."""
+
+ def __init__(self,
+ login_url=_DEFAULT_LOGIN_URL,
+ logout_url=_DEFAULT_LOGOUT_URL,
+ service_name='user'):
+ """Initializer.
+
+ Args:
+ login_url: String containing the URL to use for logging in.
+ logout_url: String containing the URL to use for logging out.
+ service_name: Service name expected for all calls.
+
+ Note: Both the login_url and logout_url arguments must contain one format
+ parameter, which will be replaced with the continuation URL where the user
+ should be redirected after log-in or log-out has been completed.
+ """
+ super(UserServiceStub, self).__init__(service_name)
+ self.__num_requests = 0
+ self._login_url = login_url
+ self._logout_url = logout_url
+
+ os.environ['AUTH_DOMAIN'] = 'gmail.com'
+
+ def num_requests(self):
+ return self.__num_requests
+
+ def _Dynamic_CreateLoginURL(self, request, response):
+ """Trivial implementation of UserService.CreateLoginURL().
+
+ Args:
+ request: the URL to redirect to after login; a base.StringProto
+ response: the login URL; a base.StringProto
+ """
+ self.__num_requests += 1
+ response.set_login_url(
+ self._login_url %
+ urllib.quote(self._AddHostToContinueURL(request.destination_url())))
+
+ def _Dynamic_CreateLogoutURL(self, request, response):
+ """Trivial implementation of UserService.CreateLogoutURL().
+
+ Args:
+ request: the URL to redirect to after logout; a base.StringProto
+ response: the logout URL; a base.StringProto
+ """
+ self.__num_requests += 1
+ response.set_logout_url(
+ self._logout_url %
+ urllib.quote(self._AddHostToContinueURL(request.destination_url())))
+
+ def _AddHostToContinueURL(self, continue_url):
+ """Adds the request host to the continue url if no host is specified.
+
+ Args:
+ continue_url: the URL which may or may not have a host specified
+
+ Returns:
+ string
+ """
+ (protocol, host, path, parameters, query, fragment) = urlparse.urlparse(continue_url, 'http')
+
+ if host:
+ return continue_url
+
+ host = os.environ['SERVER_NAME']
+ if os.environ['SERVER_PORT'] != '80':
+ host = host + ":" + os.environ['SERVER_PORT']
+
+ if path == '':
+ path = '/'
+
+ return urlparse.urlunparse(
+ (protocol, host, path, parameters, query, fragment))
diff --git a/google_appengine/google/appengine/api/user_service_stub.pyc b/google_appengine/google/appengine/api/user_service_stub.pyc
new file mode 100644
index 0000000..e5083cd
--- /dev/null
+++ b/google_appengine/google/appengine/api/user_service_stub.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/users.py b/google_appengine/google/appengine/api/users.py
new file mode 100755
index 0000000..3577510
--- /dev/null
+++ b/google_appengine/google/appengine/api/users.py
@@ -0,0 +1,230 @@
+#!/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.
+ 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, auth domain, 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.
+ """
+
+
+ __user_id = None
+
+ def __init__(self, email=None, _auth_domain=None, _user_id=None):
+ """Constructor.
+
+ Args:
+ email: An optional string of the user's email address. It defaults to
+ the current user's email address.
+
+ Raises:
+ UserNotFoundError: Raised if the user is not logged in and the email
+ argument is empty.
+ """
+ if _auth_domain is None:
+ _auth_domain = os.environ.get('AUTH_DOMAIN')
+ else:
+ assert email is not None
+
+ assert _auth_domain
+
+ if email is None:
+ assert 'USER_EMAIL' in os.environ
+ email = os.environ['USER_EMAIL']
+ if _user_id is None and 'USER_ID' in os.environ:
+ _user_id = os.environ['USER_ID']
+
+ if not email:
+ raise UserNotFoundError
+
+ self.__email = email
+ 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, but not all.
+ """
+ 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]
+ 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."""
+ return self.__auth_domain
+
+ def __unicode__(self):
+ return unicode(self.nickname())
+
+ def __str__(self):
+ return str(self.nickname())
+
+ def __repr__(self):
+ if self.__user_id:
+ return "users.User(email='%s',_user_id='%s')" % (self.email(),
+ self.user_id())
+ else:
+ return "users.User(email='%s')" % self.email()
+
+ def __hash__(self):
+ return hash((self.__email, self.__auth_domain))
+
+ def __cmp__(self, other):
+ if not isinstance(other, User):
+ return NotImplemented
+ return cmp((self.__email, self.__auth_domain),
+ (other.__email, other.__auth_domain))
+
+
+def create_login_url(dest_url):
+ """Computes the login URL for this request and specified destination URL.
+
+ 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.
+
+ Returns:
+ string
+ """
+ req = user_service_pb.CreateLoginURLRequest()
+ resp = user_service_pb.CreateLoginURLResponse()
+ req.set_destination_url(dest_url)
+ 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):
+ """Computes the logout URL for this request and specified destination URL.
+
+ 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:
+ string
+ """
+ req = user_service_pb.CreateLogoutURLRequest()
+ resp = user_service_pb.CreateLogoutURLResponse()
+ req.set_destination_url(dest_url)
+ 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 0000000..365ddb8
--- /dev/null
+++ b/google_appengine/google/appengine/api/users.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/validation.py b/google_appengine/google/appengine/api/validation.py
new file mode 100755
index 0000000..00833e6
--- /dev/null
+++ b/google_appengine/google/appengine/api/validation.py
@@ -0,0 +1,928 @@
+#!/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.
+#
+
+"""Validation tools for generic object structures.
+
+This library is used for defining classes with constrained attributes.
+Attributes are defined on the class which contains them using validators.
+Although validators can be defined by any client of this library, a number
+of standard validators are provided here.
+
+Validators can be any callable that takes a single parameter which checks
+the new value before it is assigned to the attribute. Validators are
+permitted to modify a received value so that it is appropriate for the
+attribute definition. For example, using int as a validator will cast
+a correctly formatted string to a number, or raise an exception if it
+can not. This is not recommended, however. the correct way to use a
+validator that ensure the correct type is to use the Type validator.
+
+This validation library is mainly intended for use with the YAML object
+builder. See yaml_object.py.
+"""
+
+
+
+
+
+import re
+
+import google
+import yaml
+
+
+class Error(Exception):
+ """Base class for all package errors."""
+
+
+class AttributeDefinitionError(Error):
+ """An error occurred in the definition of class attributes."""
+
+
+class ValidationError(Error):
+ """Base class for raising exceptions during validation."""
+
+ def __init__(self, message, cause=None):
+ """Initialize exception."""
+ if hasattr(cause, 'args') and cause.args:
+ Error.__init__(self, message, *cause.args)
+ else:
+ Error.__init__(self, message)
+ self.message = message
+ self.cause = cause
+
+ def __str__(self):
+ return str(self.message)
+
+
+class MissingAttribute(ValidationError):
+ """Raised when a required attribute is missing from object."""
+
+
+def AsValidator(validator):
+ """Wrap various types as instances of a validator.
+
+ Used to allow shorthand for common validator types. It
+ converts the following types to the following Validators.
+
+ strings -> 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)
+ 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):
+ """Main interface to validator is call mechanism."""
+ return self.Validate(value)
+
+ def Validate(self, value):
+ """Override this method to customize sub-class behavior.
+
+ Args:
+ value: Value to validate.
+
+ 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 <type>(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):
+ """Validate that value is correct type.
+
+ Args:
+ value: Value to validate.
+
+ 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\'.'
+ % value,
+ e)
+ except TypeError, e:
+ raise ValidationError('Expected value of type %s, but got \'%s\'.'
+ % (self.expected_type, 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):
+ """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\' not in %s.'
+ % (value, 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):
+ """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.
+
+ Returns:
+ None if value is None, else results of contained validation.
+ """
+ if value is None:
+ return None
+ return self.validator(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):
+ """Does validation of a string against a regular expression.
+
+ Args:
+ value: String to match against regular expression.
+
+ 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\' does not match expression \'%s\''
+ % (value, 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):
+ """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.
+ """
+ self.__attribute = attribute
+ self.__value = value
+ self.__regex = None
+
+ 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\' does not compile: %s' % (regex, 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)
+ 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):
+ """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.
+
+ 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)
+ 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):
+ """Validate that value is within range.
+
+ Validates against range-type then checks the range.
+
+ Args:
+ value: Value to validate.
+
+ 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)
+ if cast_value < self.minimum or cast_value > self.maximum:
+ raise ValidationError('Value \'%s\' is out of range %s - %s'
+ % (str(value),
+ 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):
+ """Do validation of sequence.
+
+ Value must be a list and all elements must be of type 'constructor'.
+
+ Args:
+ value: Value to validate.
+
+ 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 must be sequence, '
+ 'but found \'%s\'.' % value)
+
+ for item in value:
+ if isinstance(self.constructor, Validator):
+ item = self.constructor.Validate(item)
+ elif not isinstance(item, self.constructor):
+ raise ValidationError('Repeated items must be %s, but found \'%s\'.'
+ % (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 0000000..ccfed3e
--- /dev/null
+++ b/google_appengine/google/appengine/api/validation.pyc
Binary files differ
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 <appid>@appspot.com. This is supported as a value. Custom JIDs can be
+ of the form <anything>@<appid>.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 <appid>@appspot.com. This is supported as a value. Custom JIDs can be
+ of the form <anything>@<appid>.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 <appid>@appspot.com. This is supported as a value. Custom JIDs can be
+ of the form <anything>@<appid>.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 0000000..fd06892
--- /dev/null
+++ b/google_appengine/google/appengine/api/xmpp/__init__.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/xmpp/xmpp_service_pb.py b/google_appengine/google/appengine/api/xmpp/xmpp_service_pb.py
new file mode 100644
index 0000000..f77e50b
--- /dev/null
+++ b/google_appengine/google/appengine/api/xmpp/xmpp_service_pb.py
@@ -0,0 +1,826 @@
+#!/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 XmppServiceError(ProtocolBuffer.ProtocolMessage):
+
+ UNSPECIFIED_ERROR = 1
+ INVALID_JID = 2
+ NO_BODY = 3
+ INVALID_XML = 4
+ INVALID_TYPE = 5
+
+ _ErrorCode_NAMES = {
+ 1: "UNSPECIFIED_ERROR",
+ 2: "INVALID_JID",
+ 3: "NO_BODY",
+ 4: "INVALID_XML",
+ 5: "INVALID_TYPE",
+ }
+
+ 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 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 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','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 0000000..6fc90d3
--- /dev/null
+++ b/google_appengine/google/appengine/api/xmpp/xmpp_service_pb.pyc
Binary files differ
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 0000000..8a26f65
--- /dev/null
+++ b/google_appengine/google/appengine/api/xmpp/xmpp_service_stub.pyc
Binary files differ
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 0000000..713f7b2
--- /dev/null
+++ b/google_appengine/google/appengine/api/yaml_builder.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/yaml_errors.py b/google_appengine/google/appengine/api/yaml_errors.py
new file mode 100755
index 0000000..6896e2c
--- /dev/null
+++ b/google_appengine/google/appengine/api/yaml_errors.py
@@ -0,0 +1,96 @@
+#!/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 YAML API, which is used by app developers."""
+
+
+
+class Error(Exception):
+ """Base datastore yaml error type."""
+
+class ProtocolBufferParseError(Error):
+ """Error in protocol buffer parsing"""
+
+
+class EmptyConfigurationFile(Error):
+ """Tried to load empty configuration file."""
+
+
+class MultipleConfigurationFile(Error):
+ """Tried to load configuration file with multiple objects."""
+
+
+class UnexpectedAttribute(Error):
+ """Raised when an unexpected attribute is encounted."""
+
+
+class DuplicateAttribute(Error):
+ """Generated when an attribute is assigned to twice."""
+
+
+class ListenerConfigurationError(Error):
+ """Generated when there is a parsing problem due to configuration."""
+
+
+class IllegalEvent(Error):
+ """Raised when an unexpected event type is received by listener."""
+
+
+class InternalError(Error):
+ """Raised when an internal implementation error is detected."""
+
+
+class EventListenerError(Error):
+ """Top level exception raised by YAML listener.
+
+ Any exception raised within the process of parsing a YAML file via an
+ EventListener is caught and wrapped in an EventListenerError. The causing
+ exception is maintained, but additional useful information is saved which
+ can be used for reporting useful information to users.
+
+ Attributes:
+ cause: The original exception which caused the EventListenerError.
+ """
+
+ def __init__(self, cause):
+ """Initialize event-listener error."""
+ if hasattr(cause, 'args') and cause.args:
+ Error.__init__(self, *cause.args)
+ else:
+ Error.__init__(self, str(cause))
+ self.cause = cause
+
+
+class EventListenerYAMLError(EventListenerError):
+ """Generated specifically for yaml.error.YAMLError."""
+
+
+class EventError(EventListenerError):
+ """Generated specifically when an error occurs in event handler.
+
+ Attributes:
+ cause: The original exception which caused the EventListenerError.
+ event: Event being handled when exception occured.
+ """
+
+ def __init__(self, cause, event):
+ """Initialize event-listener error."""
+ EventListenerError.__init__(self, cause)
+ self.event = event
+
+ def __str__(self):
+ return '%s\n%s' % (self.cause, self.event.start_mark)
diff --git a/google_appengine/google/appengine/api/yaml_errors.pyc b/google_appengine/google/appengine/api/yaml_errors.pyc
new file mode 100644
index 0000000..a89c146
--- /dev/null
+++ b/google_appengine/google/appengine/api/yaml_errors.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/api/yaml_listener.py b/google_appengine/google/appengine/api/yaml_listener.py
new file mode 100755
index 0000000..e7d978f
--- /dev/null
+++ b/google_appengine/google/appengine/api/yaml_listener.py
@@ -0,0 +1,218 @@
+#!/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 listener
+
+Contains class which interprets YAML events and forwards them to
+a handler object.
+"""
+
+
+from google.appengine.api import yaml_errors
+import yaml
+
+
+_EVENT_METHOD_MAP = {
+ yaml.events.StreamStartEvent: 'StreamStart',
+ yaml.events.StreamEndEvent: 'StreamEnd',
+ yaml.events.DocumentStartEvent: 'DocumentStart',
+ yaml.events.DocumentEndEvent: 'DocumentEnd',
+ yaml.events.AliasEvent: 'Alias',
+ yaml.events.ScalarEvent: 'Scalar',
+ yaml.events.SequenceStartEvent: 'SequenceStart',
+ yaml.events.SequenceEndEvent: 'SequenceEnd',
+ yaml.events.MappingStartEvent: 'MappingStart',
+ yaml.events.MappingEndEvent: 'MappingEnd',
+}
+
+
+class EventHandler(object):
+ """Handler interface for parsing YAML files.
+
+ Implement this interface to define specific YAML event handling class.
+ Implementing classes instances are passed to the constructor of
+ EventListener to act as a receiver of YAML parse events.
+ """
+ def StreamStart(self, event, loader):
+ """Handle start of stream event"""
+
+ def StreamEnd(self, event, loader):
+ """Handle end of stream event"""
+
+ def DocumentStart(self, event, loader):
+ """Handle start of document event"""
+
+ def DocumentEnd(self, event, loader):
+ """Handle end of document event"""
+
+ def Alias(self, event, loader):
+ """Handle alias event"""
+
+ def Scalar(self, event, loader):
+ """Handle scalar event"""
+
+ def SequenceStart(self, event, loader):
+ """Handle start of sequence event"""
+
+ def SequenceEnd(self, event, loader):
+ """Handle end of sequence event"""
+
+ def MappingStart(self, event, loader):
+ """Handle start of mappping event"""
+
+ def MappingEnd(self, event, loader):
+ """Handle end of mapping event"""
+
+
+class EventListener(object):
+ """Helper class to re-map PyYAML events to method calls.
+
+ By default, PyYAML generates its events via a Python generator. This class
+ is a helper that iterates over the events from the PyYAML parser and forwards
+ them to a handle class in the form of method calls. For simplicity, the
+ underlying event is forwarded to the handler as a parameter to the call.
+
+ This object does not itself produce iterable objects, but is really a mapping
+ to a given handler instance.
+
+ Example use:
+
+ class PrintDocumentHandler(object):
+ def DocumentStart(event):
+ print "A new document has been started"
+
+ EventListener(PrintDocumentHandler()).Parse('''
+ key1: value1
+ ---
+ key2: value2
+ '''
+
+ >>> 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 0000000..5e0a8e3
--- /dev/null
+++ b/google_appengine/google/appengine/api/yaml_listener.pyc
Binary files differ
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 = '<unknown>'
+
+ 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 = '<unknown>'
+
+ try:
+ value_str = str(value)
+ except Exception:
+ value_str = '<unknown>'
+
+ 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 = '<unknown>'
+
+ try:
+ value_str = str(value)
+ except Exception:
+ value_str = '<unknown>'
+
+ 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 0000000..7ec78cd
--- /dev/null
+++ b/google_appengine/google/appengine/api/yaml_object.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/base/__init__.py b/google_appengine/google/appengine/base/__init__.py
new file mode 100755
index 0000000..c33ae80
--- /dev/null
+++ b/google_appengine/google/appengine/base/__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/base/__init__.pyc b/google_appengine/google/appengine/base/__init__.pyc
new file mode 100644
index 0000000..ea4e353
--- /dev/null
+++ b/google_appengine/google/appengine/base/__init__.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/base/capabilities_pb.py b/google_appengine/google/appengine/base/capabilities_pb.py
new file mode 100644
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 0000000..c6a3645
--- /dev/null
+++ b/google_appengine/google/appengine/base/capabilities_pb.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/cron/GrocLexer.py b/google_appengine/google/appengine/cron/GrocLexer.py
new file mode 100755
index 0000000..7224334
--- /dev/null
+++ b/google_appengine/google/appengine/cron/GrocLexer.py
@@ -0,0 +1,1669 @@
+#!/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=12
+SEPTEMBER=35
+FOURTH=13
+SECOND=11
+WEDNESDAY=21
+NOVEMBER=37
+SATURDAY=24
+JULY=33
+APRIL=30
+DIGITS=8
+OCTOBER=36
+MAY=31
+EVERY=6
+FEBRUARY=28
+MONDAY=19
+SUNDAY=25
+DAY=18
+JUNE=32
+OF=4
+MARCH=29
+EOF=-1
+JANUARY=27
+MONTH=26
+FRIDAY=23
+MINUTES=17
+FIFTH=14
+TIME=5
+WS=40
+QUARTER=39
+THURSDAY=22
+COMMA=9
+DECEMBER=38
+AUGUST=34
+DIGIT=7
+TUESDAY=20
+HOURS=16
+FOURTH_OR_FIFTH=15
+FIRST=10
+
+
+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 (LA1_1 == 58) :
+ alt1 = 1
+ elif ((48 <= LA1_1 <= 57)) :
+ alt1 = 2
+ 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 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 = 37
+ alt25 = self.dfa25.predict(self.input)
+ if alt25 == 1:
+ pass
+ self.mTIME()
+
+
+ elif alt25 == 2:
+ pass
+ self.mFIRST()
+
+
+ elif alt25 == 3:
+ pass
+ self.mSECOND()
+
+
+ elif alt25 == 4:
+ pass
+ self.mTHIRD()
+
+
+ elif alt25 == 5:
+ pass
+ self.mFOURTH()
+
+
+ elif alt25 == 6:
+ pass
+ self.mFIFTH()
+
+
+ elif alt25 == 7:
+ pass
+ self.mFOURTH_OR_FIFTH()
+
+
+ elif alt25 == 8:
+ pass
+ self.mDAY()
+
+
+ elif alt25 == 9:
+ pass
+ self.mMONDAY()
+
+
+ elif alt25 == 10:
+ pass
+ self.mTUESDAY()
+
+
+ elif alt25 == 11:
+ pass
+ self.mWEDNESDAY()
+
+
+ elif alt25 == 12:
+ pass
+ self.mTHURSDAY()
+
+
+ elif alt25 == 13:
+ pass
+ self.mFRIDAY()
+
+
+ elif alt25 == 14:
+ pass
+ self.mSATURDAY()
+
+
+ elif alt25 == 15:
+ pass
+ self.mSUNDAY()
+
+
+ elif alt25 == 16:
+ pass
+ self.mJANUARY()
+
+
+ elif alt25 == 17:
+ pass
+ self.mFEBRUARY()
+
+
+ elif alt25 == 18:
+ pass
+ self.mMARCH()
+
+
+ elif alt25 == 19:
+ pass
+ self.mAPRIL()
+
+
+ elif alt25 == 20:
+ pass
+ self.mMAY()
+
+
+ elif alt25 == 21:
+ pass
+ self.mJUNE()
+
+
+ elif alt25 == 22:
+ pass
+ self.mJULY()
+
+
+ elif alt25 == 23:
+ pass
+ self.mAUGUST()
+
+
+ elif alt25 == 24:
+ pass
+ self.mSEPTEMBER()
+
+
+ elif alt25 == 25:
+ pass
+ self.mOCTOBER()
+
+
+ elif alt25 == 26:
+ pass
+ self.mNOVEMBER()
+
+
+ elif alt25 == 27:
+ pass
+ self.mDECEMBER()
+
+
+ elif alt25 == 28:
+ pass
+ self.mMONTH()
+
+
+ elif alt25 == 29:
+ pass
+ self.mQUARTER()
+
+
+ elif alt25 == 30:
+ pass
+ self.mEVERY()
+
+
+ elif alt25 == 31:
+ pass
+ self.mHOURS()
+
+
+ elif alt25 == 32:
+ pass
+ self.mMINUTES()
+
+
+ elif alt25 == 33:
+ pass
+ self.mCOMMA()
+
+
+ elif alt25 == 34:
+ pass
+ self.mOF()
+
+
+ elif alt25 == 35:
+ pass
+ self.mWS()
+
+
+ elif alt25 == 36:
+ pass
+ self.mDIGIT()
+
+
+ elif alt25 == 37:
+ pass
+ self.mDIGITS()
+
+
+
+
+
+
+
+
+ DFA25_eot = DFA.unpack(
+ u"\1\uffff\4\30\2\uffff\1\30\1\uffff\2\30\14\uffff\1\36\3\uffff\2"
+ u"\36\33\uffff\1\76\6\uffff"
+ )
+
+ DFA25_eof = DFA.unpack(
+ u"\77\uffff"
+ )
+
+ DFA25_min = DFA.unpack(
+ u"\1\11\4\60\1\145\1\141\1\60\1\150\2\60\2\141\1\uffff\1\141\1\160"
+ u"\1\143\6\uffff\1\72\3\uffff\2\72\3\uffff\1\146\3\uffff\1\143\3"
+ 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\2\162\1\165\1\164\1\165\1\164\1\72\1\145"
+ u"\1\157\1\uffff\2\165\1\146\6\uffff\1\72\3\uffff\2\72\3\uffff\1"
+ u"\162\3\uffff\1\160\3\uffff\1\165\4\uffff\1\156\1\171\2\uffff\1"
+ u"\156\6\uffff\1\164\6\uffff"
+ )
+
+ DFA25_accept = DFA.unpack(
+ u"\15\uffff\1\13\3\uffff\1\32\1\35\1\36\1\37\1\41\1\43\1\uffff\1"
+ u"\44\1\1\1\2\2\uffff\1\3\1\45\1\4\1\uffff\1\7\1\15\1\21\1\uffff"
+ u"\1\16\1\17\1\5\1\uffff\1\12\1\6\1\10\1\33\2\uffff\1\40\1\20\1\uffff"
+ u"\1\23\1\27\1\31\1\42\1\30\1\14\1\uffff\1\22\1\24\1\25\1\26\1\34"
+ u"\1\11"
+ )
+
+ DFA25_special = DFA.unpack(
+ u"\77\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\5\1\uffff\1\24\1\uffff\1\16\2\uffff\1\14\1\21\1\20\1\uffff\1"
+ u"\22\1\uffff\1\6\1\10\2\uffff\1\15"),
+ DFA.unpack(u"\12\27\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\43\3\uffff\1\40\5\uffff\1\41\2\uffff\1\42"),
+ DFA.unpack(u"\1\45\3\uffff\1\44\17\uffff\1\46"),
+ DFA.unpack(u"\12\36\1\31\71\uffff\1\47"),
+ DFA.unpack(u"\1\50\14\uffff\1\51"),
+ DFA.unpack(u"\12\36\1\31\71\uffff\1\52"),
+ DFA.unpack(u"\12\36\1\31"),
+ DFA.unpack(u"\1\53\3\uffff\1\54"),
+ DFA.unpack(u"\1\56\7\uffff\1\57\5\uffff\1\55"),
+ DFA.unpack(u""),
+ DFA.unpack(u"\1\60\23\uffff\1\61"),
+ DFA.unpack(u"\1\62\4\uffff\1\63"),
+ DFA.unpack(u"\1\64\2\uffff\1\65"),
+ 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""),
+ DFA.unpack(u"\1\31"),
+ DFA.unpack(u"\1\31"),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u"\1\41\13\uffff\1\32"),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u"\1\35\14\uffff\1\66"),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u"\1\37\13\uffff\1\67"),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u"\1\70"),
+ DFA.unpack(u"\1\71\6\uffff\1\72"),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u"\1\74\1\uffff\1\73"),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u"\1\75"),
+ 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 0000000..c0bd09f
--- /dev/null
+++ b/google_appengine/google/appengine/cron/GrocLexer.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/cron/GrocParser.py b/google_appengine/google/appengine/cron/GrocParser.py
new file mode 100755
index 0000000..b86cb4e
--- /dev/null
+++ b/google_appengine/google/appengine/cron/GrocParser.py
@@ -0,0 +1,1008 @@
+#!/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=12
+SEPTEMBER=35
+FOURTH=13
+SECOND=11
+WEDNESDAY=21
+NOVEMBER=37
+SATURDAY=24
+JULY=33
+APRIL=30
+DIGITS=8
+OCTOBER=36
+MAY=31
+EVERY=6
+FEBRUARY=28
+MONDAY=19
+SUNDAY=25
+JUNE=32
+DAY=18
+MARCH=29
+OF=4
+EOF=-1
+JANUARY=27
+MONTH=26
+FRIDAY=23
+FIFTH=14
+MINUTES=17
+TIME=5
+WS=40
+QUARTER=39
+THURSDAY=22
+COMMA=9
+DECEMBER=38
+AUGUST=34
+DIGIT=7
+TUESDAY=20
+HOURS=16
+FIRST=10
+FOURTH_OR_FIFTH=15
+
+tokenNames = [
+ "<invalid>", "<EOR>", "<DOWN>", "<UP>",
+ "OF", "TIME", "EVERY", "DIGIT", "DIGITS", "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.dfa3 = self.DFA3(
+ self, 3,
+ eot = self.DFA3_eot,
+ eof = self.DFA3_eof,
+ min = self.DFA3_min,
+ max = self.DFA3_max,
+ accept = self.DFA3_accept,
+ special = self.DFA3_special,
+ transition = self.DFA3_transition
+ )
+
+
+
+
+ self.ordinal_set = set()
+ self.weekday_set = set()
+ self.month_set = set()
+ self.time_string = '';
+ self.interval_mins = 0;
+ self.period_string = '';
+
+
+
+
+
+
+
+
+
+
+ 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 ((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
+ alt3 = 2
+ alt3 = self.dfa3.predict(self.input)
+ if alt3 == 1:
+ pass
+ pass
+ pass
+ self._state.following.append(self.FOLLOW_ordinals_in_specifictime69)
+ self.ordinals()
+
+ self._state.following.pop()
+ self._state.following.append(self.FOLLOW_weekdays_in_specifictime71)
+ self.weekdays()
+
+ self._state.following.pop()
+
+
+
+ self.match(self.input, OF, self.FOLLOW_OF_in_specifictime74)
+ alt2 = 2
+ LA2_0 = self.input.LA(1)
+
+ if ((MONTH <= LA2_0 <= DECEMBER)) :
+ alt2 = 1
+ elif ((FIRST <= LA2_0 <= THIRD) or LA2_0 == QUARTER) :
+ alt2 = 2
+ else:
+ nvae = NoViableAltException("", 2, 0, self.input)
+
+ raise nvae
+
+ if alt2 == 1:
+ pass
+ self._state.following.append(self.FOLLOW_monthspec_in_specifictime77)
+ self.monthspec()
+
+ self._state.following.pop()
+
+
+ elif alt2 == 2:
+ pass
+ self._state.following.append(self.FOLLOW_quarterspec_in_specifictime79)
+ self.quarterspec()
+
+ self._state.following.pop()
+
+
+
+
+
+
+
+
+ elif alt3 == 2:
+ pass
+ pass
+ self._state.following.append(self.FOLLOW_ordinals_in_specifictime96)
+ self.ordinals()
+
+ self._state.following.pop()
+ self._state.following.append(self.FOLLOW_weekdays_in_specifictime98)
+ self.weekdays()
+
+ self._state.following.pop()
+ self.month_set = set(range(1,13))
+
+
+
+
+
+
+ TIME1=self.match(self.input, TIME, self.FOLLOW_TIME_in_specifictime112)
+ 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_interval131)
+ 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_interval157)
+ 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"
+
+
+
+
+
+
+
+
+ except RecognitionException, re:
+ self.reportError(re)
+ self.recover(self.input, re)
+ finally:
+
+ pass
+
+ return
+
+
+
+ def ordinals(self, ):
+
+ try:
+ try:
+ pass
+ alt5 = 2
+ LA5_0 = self.input.LA(1)
+
+ if (LA5_0 == EVERY) :
+ alt5 = 1
+ elif ((FIRST <= LA5_0 <= FOURTH_OR_FIFTH)) :
+ alt5 = 2
+ else:
+ nvae = NoViableAltException("", 5, 0, self.input)
+
+ raise nvae
+
+ if alt5 == 1:
+ pass
+ self.match(self.input, EVERY, self.FOLLOW_EVERY_in_ordinals176)
+ self.ordinal_set = self.ordinal_set.union(allOrdinals)
+
+
+ elif alt5 == 2:
+ pass
+ pass
+ self._state.following.append(self.FOLLOW_ordinal_in_ordinals192)
+ self.ordinal()
+
+ self._state.following.pop()
+ while True:
+ alt4 = 2
+ LA4_0 = self.input.LA(1)
+
+ if (LA4_0 == COMMA) :
+ alt4 = 1
+
+
+ if alt4 == 1:
+ pass
+ self.match(self.input, COMMA, self.FOLLOW_COMMA_in_ordinals195)
+ self._state.following.append(self.FOLLOW_ordinal_in_ordinals197)
+ 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 weekdays(self, ):
+
+ try:
+ try:
+ pass
+ alt7 = 2
+ LA7_0 = self.input.LA(1)
+
+ if (LA7_0 == DAY) :
+ alt7 = 1
+ elif ((MONDAY <= LA7_0 <= SUNDAY)) :
+ alt7 = 2
+ else:
+ nvae = NoViableAltException("", 7, 0, self.input)
+
+ raise nvae
+
+ if alt7 == 1:
+ pass
+ self.match(self.input, DAY, self.FOLLOW_DAY_in_weekdays280)
+
+ 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 alt7 == 2:
+ pass
+ pass
+ self._state.following.append(self.FOLLOW_weekday_in_weekdays288)
+ self.weekday()
+
+ 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_weekdays291)
+ self._state.following.append(self.FOLLOW_weekday_in_weekdays293)
+ 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
+ alt8 = 2
+ LA8_0 = self.input.LA(1)
+
+ if (LA8_0 == MONTH) :
+ alt8 = 1
+ elif ((JANUARY <= LA8_0 <= DECEMBER)) :
+ alt8 = 2
+ else:
+ nvae = NoViableAltException("", 8, 0, self.input)
+
+ raise nvae
+
+ if alt8 == 1:
+ pass
+ self.match(self.input, MONTH, self.FOLLOW_MONTH_in_monthspec373)
+
+ 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 alt8 == 2:
+ pass
+ self._state.following.append(self.FOLLOW_months_in_monthspec383)
+ 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_months400)
+ self.month()
+
+ 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_months403)
+ self._state.following.append(self.FOLLOW_month_in_months405)
+ 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
+ alt10 = 2
+ LA10_0 = self.input.LA(1)
+
+ if (LA10_0 == QUARTER) :
+ alt10 = 1
+ elif ((FIRST <= LA10_0 <= THIRD)) :
+ alt10 = 2
+ else:
+ nvae = NoViableAltException("", 10, 0, self.input)
+
+ raise nvae
+
+ if alt10 == 1:
+ pass
+ self.match(self.input, QUARTER, self.FOLLOW_QUARTER_in_quarterspec497)
+
+ self.month_set = self.month_set.union(set([
+ self.ValueOf(JANUARY), self.ValueOf(APRIL), self.ValueOf(JULY),
+ self.ValueOf(OCTOBER)]))
+
+
+ elif alt10 == 2:
+ pass
+ pass
+ self._state.following.append(self.FOLLOW_quarter_ordinals_in_quarterspec509)
+ self.quarter_ordinals()
+
+ self._state.following.pop()
+ self.match(self.input, MONTH, self.FOLLOW_MONTH_in_quarterspec511)
+ self.match(self.input, OF, self.FOLLOW_OF_in_quarterspec513)
+ self.match(self.input, QUARTER, self.FOLLOW_QUARTER_in_quarterspec515)
+
+
+
+
+
+
+
+
+
+
+ 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_ordinals534)
+ self.month_of_quarter_ordinal()
+
+ self._state.following.pop()
+ while True:
+ alt11 = 2
+ LA11_0 = self.input.LA(1)
+
+ if (LA11_0 == COMMA) :
+ alt11 = 1
+
+
+ if alt11 == 1:
+ pass
+ self.match(self.input, COMMA, self.FOLLOW_COMMA_in_quarter_ordinals537)
+ self._state.following.append(self.FOLLOW_month_of_quarter_ordinal_in_quarter_ordinals539)
+ 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
+
+
+
+
+
+
+ DFA3_eot = DFA.unpack(
+ u"\13\uffff"
+ )
+
+ DFA3_eof = DFA.unpack(
+ u"\13\uffff"
+ )
+
+ DFA3_min = DFA.unpack(
+ u"\1\6\1\22\1\11\2\4\1\12\2\uffff\1\23\1\11\1\4"
+ )
+
+ DFA3_max = DFA.unpack(
+ u"\1\17\2\31\1\5\1\11\1\17\2\uffff\2\31\1\11"
+ )
+
+ DFA3_accept = DFA.unpack(
+ u"\6\uffff\1\1\1\2\3\uffff"
+ )
+
+ DFA3_special = DFA.unpack(
+ u"\13\uffff"
+ )
+
+
+ DFA3_transition = [
+ DFA.unpack(u"\1\1\3\uffff\6\2"),
+ DFA.unpack(u"\1\3\7\4"),
+ DFA.unpack(u"\1\5\10\uffff\1\3\7\4"),
+ DFA.unpack(u"\1\6\1\7"),
+ DFA.unpack(u"\1\6\1\7\3\uffff\1\10"),
+ DFA.unpack(u"\6\11"),
+ DFA.unpack(u""),
+ DFA.unpack(u""),
+ DFA.unpack(u"\7\12"),
+ DFA.unpack(u"\1\5\10\uffff\1\3\7\4"),
+ DFA.unpack(u"\1\6\1\7\3\uffff\1\10")
+ ]
+
+
+ DFA3 = DFA
+
+
+ FOLLOW_specifictime_in_timespec44 = frozenset([1])
+ FOLLOW_interval_in_timespec48 = frozenset([1])
+ FOLLOW_ordinals_in_specifictime69 = frozenset([18, 19, 20, 21, 22, 23, 24, 25])
+ FOLLOW_weekdays_in_specifictime71 = frozenset([4])
+ FOLLOW_OF_in_specifictime74 = frozenset([10, 11, 12, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39])
+ FOLLOW_monthspec_in_specifictime77 = frozenset([5])
+ FOLLOW_quarterspec_in_specifictime79 = frozenset([5])
+ FOLLOW_ordinals_in_specifictime96 = frozenset([18, 19, 20, 21, 22, 23, 24, 25])
+ FOLLOW_weekdays_in_specifictime98 = frozenset([5])
+ FOLLOW_TIME_in_specifictime112 = frozenset([1])
+ FOLLOW_EVERY_in_interval131 = frozenset([7, 8])
+ FOLLOW_set_in_interval141 = frozenset([16, 17])
+ FOLLOW_period_in_interval157 = frozenset([1])
+ FOLLOW_EVERY_in_ordinals176 = frozenset([1])
+ FOLLOW_ordinal_in_ordinals192 = frozenset([1, 9])
+ FOLLOW_COMMA_in_ordinals195 = frozenset([10, 11, 12, 13, 14, 15])
+ FOLLOW_ordinal_in_ordinals197 = frozenset([1, 9])
+ FOLLOW_set_in_ordinal218 = frozenset([1])
+ FOLLOW_set_in_period257 = frozenset([1])
+ FOLLOW_DAY_in_weekdays280 = frozenset([1])
+ FOLLOW_weekday_in_weekdays288 = frozenset([1, 9])
+ FOLLOW_COMMA_in_weekdays291 = frozenset([18, 19, 20, 21, 22, 23, 24, 25])
+ FOLLOW_weekday_in_weekdays293 = frozenset([1, 9])
+ FOLLOW_set_in_weekday314 = frozenset([1])
+ FOLLOW_MONTH_in_monthspec373 = frozenset([1])
+ FOLLOW_months_in_monthspec383 = frozenset([1])
+ FOLLOW_month_in_months400 = frozenset([1, 9])
+ FOLLOW_COMMA_in_months403 = frozenset([26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38])
+ FOLLOW_month_in_months405 = frozenset([1, 9])
+ FOLLOW_set_in_month424 = frozenset([1])
+ FOLLOW_QUARTER_in_quarterspec497 = frozenset([1])
+ FOLLOW_quarter_ordinals_in_quarterspec509 = frozenset([26])
+ FOLLOW_MONTH_in_quarterspec511 = frozenset([4])
+ FOLLOW_OF_in_quarterspec513 = frozenset([39])
+ FOLLOW_QUARTER_in_quarterspec515 = frozenset([1])
+ FOLLOW_month_of_quarter_ordinal_in_quarter_ordinals534 = frozenset([1, 9])
+ FOLLOW_COMMA_in_quarter_ordinals537 = frozenset([10, 11, 12, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39])
+ FOLLOW_month_of_quarter_ordinal_in_quarter_ordinals539 = frozenset([1, 9])
+ FOLLOW_set_in_month_of_quarter_ordinal558 = 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 0000000..094d1b9
--- /dev/null
+++ b/google_appengine/google/appengine/cron/GrocParser.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/cron/__init__.py b/google_appengine/google/appengine/cron/__init__.py
new file mode 100755
index 0000000..d5eed70
--- /dev/null
+++ b/google_appengine/google/appengine/cron/__init__.py
@@ -0,0 +1,17 @@
+#!/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 file is needed to make this a package"
diff --git a/google_appengine/google/appengine/cron/__init__.pyc b/google_appengine/google/appengine/cron/__init__.pyc
new file mode 100644
index 0000000..1d7c118
--- /dev/null
+++ b/google_appengine/google/appengine/cron/__init__.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/cron/groc.py b/google_appengine/google/appengine/cron/groc.py
new file mode 100755
index 0000000..373d56a
--- /dev/null
+++ b/google_appengine/google/appengine/cron/groc.py
@@ -0,0 +1,74 @@
+#!/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 wrapper around the generated Groc parser and lexer."""
+
+
+import google
+
+import antlr3
+
+import GrocLexer
+import GrocParser
+
+
+class GrocException(Exception):
+ """An error occurred while parsing the groc input string."""
+
+
+class GrocLexerWithErrors(GrocLexer.GrocLexer):
+ """An overridden Lexer that raises exceptions."""
+
+ def emitErrorMessage(self, msg):
+ """Raise an exception if the input fails to parse correctly.
+
+ Overriding the default, which normally just prints a message to
+ stderr.
+
+ Arguments:
+ msg: the error message
+ Raises:
+ GrocException: always.
+ """
+ raise GrocException(msg)
+
+
+class GrocParserWithErrors(GrocParser.GrocParser):
+ """An overridden Parser that raises exceptions."""
+
+ def emitErrorMessage(self, msg):
+ """Raise an exception if the input fails to parse correctly.
+
+ Overriding the default, which normally just prints a message to
+ stderr.
+
+ Arguments:
+ msg: the error message
+ Raises:
+ GrocException: always.
+ """
+ raise GrocException(msg)
+
+
+def CreateParser(parse_string):
+ """Creates a Groc Parser."""
+ input_string = antlr3.ANTLRStringStream(parse_string)
+ lexer = GrocLexerWithErrors(input_string)
+ tokens = antlr3.CommonTokenStream(lexer)
+ parser = GrocParserWithErrors(tokens)
+ return parser
diff --git a/google_appengine/google/appengine/cron/groc.pyc b/google_appengine/google/appengine/cron/groc.pyc
new file mode 100644
index 0000000..a6770cf
--- /dev/null
+++ b/google_appengine/google/appengine/cron/groc.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/cron/groctimespecification.py b/google_appengine/google/appengine/cron/groctimespecification.py
new file mode 100755
index 0000000..9acb7bf
--- /dev/null
+++ b/google_appengine/google/appengine/cron/groctimespecification.py
@@ -0,0 +1,304 @@
+#!/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):
+ """Factory function.
+
+ Turns a schedule specification into a TimeSpecification.
+
+ Arguments:
+ schedule: the schedule specification, as a string
+
+ Returns:
+ a TimeSpecification instance
+ """
+ parser = groc.CreateParser(schedule)
+ parser.timespec()
+
+ if parser.interval_mins:
+ return IntervalTimeSpecification(parser.interval_mins,
+ parser.period_string)
+ else:
+ return SpecificTimeSpecification(parser.ordinal_set, parser.weekday_set,
+ parser.month_set,
+ None,
+ parser.time_string)
+
+
+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 two
+ attributes:
+ period - the type of interval, either "hours" or "minutes"
+ interval - the number of units of type period.
+ """
+
+ def __init__(self, interval, period):
+ super(IntervalTimeSpecification, self).__init__()
+ self.interval = interval
+ self.period = period
+
+ 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 self.period == HOURS:
+ return t + datetime.timedelta(hours=self.interval)
+ else:
+ return t + datetime.timedelta(minutes=self.interval)
+
+
+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__(self)
+ if weekdays is not None and monthdays is not None:
+ raise ValueError("can't 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 monthdays is None:
+ self.monthdays = set()
+ else:
+ 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 the ordinals and weekdays.
+
+ 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
+ """
+ out_days = []
+ start_day, last_day = calendar.monthrange(year, month)
+ 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)
+
+ if self.monthdays:
+ _, last_day = calendar.monthrange(candidate_month.year,
+ candidate_month.month)
+ day_matches = sorted(x for x in self.monthdays if x <= last_day)
+ else:
+ 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.replace(minute=1) + 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 0000000..f030218
--- /dev/null
+++ b/google_appengine/google/appengine/cron/groctimespecification.pyc
Binary files differ
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 0000000..c9d9f68
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/__init__.pyc
Binary files differ
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 0000000..8fc8717
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/action_pb.pyc
Binary files differ
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..3e60947
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/datastore_index.py
@@ -0,0 +1,438 @@
+#!/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
+
+
+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,
+ ))
+
+
+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
+ """
+
+ 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);
+
+ eq_properties = set([f.property(0).name() for f in filters if f.op() == datastore_pb.Query_Filter.EQUAL]);
+
+ 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:
+ ineq_property = ineq_filters[0].property(0).name()
+ for filter in ineq_filters:
+ 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
+
+ unique_names = set(name for name, dir in props)
+ if len(props) > 1 and len(unique_names) == 1:
+ required = False
+
+ 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)
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 0000000..e91e7be
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/datastore_index.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/datastore/datastore_pb.py b/google_appengine/google/appengine/datastore/datastore_pb.py
new file mode 100644
index 0000000..50ff5e7
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/datastore_pb.py
@@ -0,0 +1,4673 @@
+#!/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
+
+ 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 MergeFrom(self, x):
+ assert x is not self
+ if (x.has_handle()): self.set_handle(x.handle())
+
+ 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
+ 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
+ return n + 9
+
+ def Clear(self):
+ self.clear_handle()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(9)
+ out.put64(self.handle_)
+
+ def TryMerge(self, d):
+ while d.avail() > 0:
+ tt = d.getVarInt32()
+ if tt == 9:
+ self.set_handle(d.get64())
+ 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_))
+ return res
+
+
+ def _BuildTagLookupTable(sparse, maxtag, default=None):
+ return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+ khandle = 1
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "handle",
+ }, 1)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 1: ProtocolBuffer.Encoder.DOUBLE,
+ }, 1, 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_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_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
+
+ 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 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 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 MergeFrom(self, x):
+ assert x is not self
+ if (x.has_app()): self.set_app(x.app())
+ 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())
+ 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())
+
+ 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_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 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
+ 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
+ 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_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_)
+ 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
+ return n + 1
+
+ def Clear(self):
+ self.clear_app()
+ 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_composite_index()
+ self.clear_require_perfect_plan()
+ self.clear_keys_only()
+ self.clear_transaction()
+ self.clear_distinct()
+ self.clear_compile()
+
+ 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_)
+
+ 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 == 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_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_))
+ 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_))
+ return res
+
+
+ def _BuildTagLookupTable(sparse, maxtag, default=None):
+ return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+ kapp = 1
+ 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
+ kcomposite_index = 19
+ krequire_perfect_plan = 20
+ kkeys_only = 21
+ ktransaction = 22
+ kdistinct = 24
+ kcompile = 25
+
+ _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",
+ }, 25)
+
+ _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,
+ }, 25, 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
+
+ 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 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())
+
+ 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
+ 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
+ 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()
+
+ 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_)
+
+ 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 == 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_))
+ 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_offset_ = 0
+ offset_ = 0
+ has_limit_ = 0
+ limit_ = 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 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 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_offset()): self.set_offset(x.offset())
+ if (x.has_limit()): self.set_limit(x.limit())
+ 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_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_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_offset_): n += 1 + self.lengthVarInt64(self.offset_)
+ if (self.has_limit_): n += 2 + self.lengthVarInt64(self.limit_)
+ 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_offset()
+ self.clear_limit()
+ self.clear_kind()
+ self.clear_ancestor()
+
+ def OutputUnchecked(self, out):
+ if (self.has_distinct_):
+ out.putVarInt32(112)
+ out.putBoolean(self.distinct_)
+ if (self.has_offset_):
+ out.putVarInt32(120)
+ out.putVarInt32(self.offset_)
+ if (self.has_limit_):
+ out.putVarInt32(128)
+ out.putVarInt32(self.limit_)
+ 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 == 120:
+ self.set_offset(d.getVarInt32())
+ continue
+ if tt == 128:
+ self.set_limit(d.getVarInt32())
+ 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_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_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
+ kMergeJoinScanGroup = 7
+ kMergeJoinScanindex_name = 8
+ kMergeJoinScanprefix_value = 9
+ koffset = 10
+ klimit = 11
+ kkeys_only = 12
+ kEntityFilterGroup = 13
+ kEntityFilterdistinct = 14
+ kEntityFilteroffset = 15
+ kEntityFilterlimit = 16
+ 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",
+ 15: "offset",
+ 16: "limit",
+ 17: "kind",
+ 18: "ancestor",
+ }, 18)
+
+ _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,
+ 15: ProtocolBuffer.Encoder.NUMERIC,
+ 16: ProtocolBuffer.Encoder.NUMERIC,
+ 17: ProtocolBuffer.Encoder.STRING,
+ 18: ProtocolBuffer.Encoder.STRING,
+ }, 18, ProtocolBuffer.Encoder.MAX_TYPE)
+
+ _STYLE = """"""
+ _STYLE_CONTENT_TYPE = """"""
+class RunCompiledQueryRequest(ProtocolBuffer.ProtocolMessage):
+ has_compiled_query_ = 0
+ has_original_query_ = 0
+ original_query_ = None
+ has_count_ = 0
+ count_ = 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 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 MergeFrom(self, x):
+ assert x is not self
+ 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())
+
+ def Equals(self, x):
+ if x is self: return 1
+ 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
+ return 1
+
+ def IsInitialized(self, debug_strs=None):
+ initialized = 1
+ 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(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_)
+ return n + 1
+
+ def Clear(self):
+ self.clear_compiled_query()
+ self.clear_original_query()
+ self.clear_count()
+
+ 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_)
+
+ 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 == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+ d.skipData(tt)
+
+
+ def __str__(self, prefix="", printElemNumber=0):
+ res=""
+ 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_))
+ return res
+
+
+ def _BuildTagLookupTable(sparse, maxtag, default=None):
+ return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+ kcompiled_query = 1
+ koriginal_query = 2
+ kcount = 3
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "compiled_query",
+ 2: "original_query",
+ 3: "count",
+ }, 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 QueryExplanation(ProtocolBuffer.ProtocolMessage):
+ has_native_ancestor_ = 0
+ native_ancestor_ = 0
+ has_native_offset_ = 0
+ native_offset_ = 0
+ has_native_limit_ = 0
+ native_limit_ = 0
+
+ def __init__(self, contents=None):
+ self.native_index_ = []
+ if contents is not None: self.MergeFromString(contents)
+
+ def native_ancestor(self): return self.native_ancestor_
+
+ def set_native_ancestor(self, x):
+ self.has_native_ancestor_ = 1
+ self.native_ancestor_ = x
+
+ def clear_native_ancestor(self):
+ if self.has_native_ancestor_:
+ self.has_native_ancestor_ = 0
+ self.native_ancestor_ = 0
+
+ def has_native_ancestor(self): return self.has_native_ancestor_
+
+ def native_index_size(self): return len(self.native_index_)
+ def native_index_list(self): return self.native_index_
+
+ def native_index(self, i):
+ return self.native_index_[i]
+
+ def mutable_native_index(self, i):
+ return self.native_index_[i]
+
+ def add_native_index(self):
+ x = Index()
+ self.native_index_.append(x)
+ return x
+
+ def clear_native_index(self):
+ self.native_index_ = []
+ def native_offset(self): return self.native_offset_
+
+ def set_native_offset(self, x):
+ self.has_native_offset_ = 1
+ self.native_offset_ = x
+
+ def clear_native_offset(self):
+ if self.has_native_offset_:
+ self.has_native_offset_ = 0
+ self.native_offset_ = 0
+
+ def has_native_offset(self): return self.has_native_offset_
+
+ def native_limit(self): return self.native_limit_
+
+ def set_native_limit(self, x):
+ self.has_native_limit_ = 1
+ self.native_limit_ = x
+
+ def clear_native_limit(self):
+ if self.has_native_limit_:
+ self.has_native_limit_ = 0
+ self.native_limit_ = 0
+
+ def has_native_limit(self): return self.has_native_limit_
+
+
+ def MergeFrom(self, x):
+ assert x is not self
+ if (x.has_native_ancestor()): self.set_native_ancestor(x.native_ancestor())
+ for i in xrange(x.native_index_size()): self.add_native_index().CopyFrom(x.native_index(i))
+ if (x.has_native_offset()): self.set_native_offset(x.native_offset())
+ if (x.has_native_limit()): self.set_native_limit(x.native_limit())
+
+ def Equals(self, x):
+ if x is self: return 1
+ if self.has_native_ancestor_ != x.has_native_ancestor_: return 0
+ if self.has_native_ancestor_ and self.native_ancestor_ != x.native_ancestor_: return 0
+ if len(self.native_index_) != len(x.native_index_): return 0
+ for e1, e2 in zip(self.native_index_, x.native_index_):
+ if e1 != e2: return 0
+ if self.has_native_offset_ != x.has_native_offset_: return 0
+ if self.has_native_offset_ and self.native_offset_ != x.native_offset_: return 0
+ if self.has_native_limit_ != x.has_native_limit_: return 0
+ if self.has_native_limit_ and self.native_limit_ != x.native_limit_: return 0
+ return 1
+
+ def IsInitialized(self, debug_strs=None):
+ initialized = 1
+ for p in self.native_index_:
+ if not p.IsInitialized(debug_strs): initialized=0
+ return initialized
+
+ def ByteSize(self):
+ n = 0
+ if (self.has_native_ancestor_): n += 2
+ n += 1 * len(self.native_index_)
+ for i in xrange(len(self.native_index_)): n += self.lengthString(self.native_index_[i].ByteSize())
+ if (self.has_native_offset_): n += 1 + self.lengthVarInt64(self.native_offset_)
+ if (self.has_native_limit_): n += 1 + self.lengthVarInt64(self.native_limit_)
+ return n + 0
+
+ def Clear(self):
+ self.clear_native_ancestor()
+ self.clear_native_index()
+ self.clear_native_offset()
+ self.clear_native_limit()
+
+ def OutputUnchecked(self, out):
+ if (self.has_native_ancestor_):
+ out.putVarInt32(8)
+ out.putBoolean(self.native_ancestor_)
+ for i in xrange(len(self.native_index_)):
+ out.putVarInt32(18)
+ out.putVarInt32(self.native_index_[i].ByteSize())
+ self.native_index_[i].OutputUnchecked(out)
+ if (self.has_native_offset_):
+ out.putVarInt32(24)
+ out.putVarInt32(self.native_offset_)
+ if (self.has_native_limit_):
+ out.putVarInt32(32)
+ out.putVarInt32(self.native_limit_)
+
+ def TryMerge(self, d):
+ while d.avail() > 0:
+ tt = d.getVarInt32()
+ if tt == 8:
+ self.set_native_ancestor(d.getBoolean())
+ continue
+ if tt == 18:
+ length = d.getVarInt32()
+ tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+ d.skip(length)
+ self.add_native_index().TryMerge(tmp)
+ continue
+ if tt == 24:
+ self.set_native_offset(d.getVarInt32())
+ continue
+ if tt == 32:
+ self.set_native_limit(d.getVarInt32())
+ continue
+ if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+ d.skipData(tt)
+
+
+ def __str__(self, prefix="", printElemNumber=0):
+ res=""
+ if self.has_native_ancestor_: res+=prefix+("native_ancestor: %s\n" % self.DebugFormatBool(self.native_ancestor_))
+ cnt=0
+ for e in self.native_index_:
+ elm=""
+ if printElemNumber: elm="(%d)" % cnt
+ res+=prefix+("native_index%s <\n" % elm)
+ res+=e.__str__(prefix + " ", printElemNumber)
+ res+=prefix+">\n"
+ cnt+=1
+ if self.has_native_offset_: res+=prefix+("native_offset: %s\n" % self.DebugFormatInt32(self.native_offset_))
+ if self.has_native_limit_: res+=prefix+("native_limit: %s\n" % self.DebugFormatInt32(self.native_limit_))
+ return res
+
+
+ def _BuildTagLookupTable(sparse, maxtag, default=None):
+ return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+ knative_ancestor = 1
+ knative_index = 2
+ knative_offset = 3
+ knative_limit = 4
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "native_ancestor",
+ 2: "native_index",
+ 3: "native_offset",
+ 4: "native_limit",
+ }, 4)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 1: ProtocolBuffer.Encoder.NUMERIC,
+ 2: ProtocolBuffer.Encoder.STRING,
+ 3: ProtocolBuffer.Encoder.NUMERIC,
+ 4: ProtocolBuffer.Encoder.NUMERIC,
+ }, 4, ProtocolBuffer.Encoder.MAX_TYPE)
+
+ _STYLE = """"""
+ _STYLE_CONTENT_TYPE = """"""
+class Cursor(ProtocolBuffer.ProtocolMessage):
+ has_cursor_ = 0
+ cursor_ = 0
+
+ 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 MergeFrom(self, x):
+ assert x is not self
+ if (x.has_cursor()): self.set_cursor(x.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
+ 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
+ return n + 9
+
+ def Clear(self):
+ self.clear_cursor()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(9)
+ out.put64(self.cursor_)
+
+ def TryMerge(self, d):
+ while d.avail() > 0:
+ tt = d.getVarInt32()
+ if tt == 9:
+ self.set_cursor(d.get64())
+ 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_))
+ return res
+
+
+ def _BuildTagLookupTable(sparse, maxtag, default=None):
+ return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+ kcursor = 1
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "cursor",
+ }, 1)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 1: ProtocolBuffer.Encoder.DOUBLE,
+ }, 1, 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
+
+ _ErrorCode_NAMES = {
+ 1: "BAD_REQUEST",
+ 2: "CONCURRENT_TRANSACTION",
+ 3: "INTERNAL_ERROR",
+ 4: "NEED_INDEX",
+ 5: "TIMEOUT",
+ 6: "PERMISSION_DENIED",
+ }
+
+ 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
+
+ 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 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())
+
+ 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
+ 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())
+ return n + 0
+
+ def Clear(self):
+ self.clear_key()
+ self.clear_transaction()
+
+ 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)
+
+ 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 == 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"
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "key",
+ 2: "transaction",
+ }, 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 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 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_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 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_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_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_compile_): n += 2
+ return n + 1
+
+ def Clear(self):
+ self.clear_cursor()
+ self.clear_count()
+ 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_)
+
+ 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 == 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_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
+ kcompile = 3
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "cursor",
+ 2: "count",
+ 3: "compile",
+ }, 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 QueryResult(ProtocolBuffer.ProtocolMessage):
+ has_cursor_ = 0
+ cursor_ = None
+ has_more_results_ = 0
+ more_results_ = 0
+ has_keys_only_ = 0
+ keys_only_ = 0
+ has_compiled_query_ = 0
+ compiled_query_ = 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 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 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_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())
+
+ 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_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
+ 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
+ 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_keys_only_): n += 2
+ if (self.has_compiled_query_): n += 1 + self.lengthString(self.compiled_query_.ByteSize())
+ return n + 2
+
+ def Clear(self):
+ self.clear_cursor()
+ self.clear_result()
+ self.clear_more_results()
+ self.clear_keys_only()
+ self.clear_compiled_query()
+
+ 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)
+
+ 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 == 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_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"
+ 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
+ kmore_results = 3
+ kkeys_only = 4
+ kcompiled_query = 5
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "cursor",
+ 2: "result",
+ 3: "more_results",
+ 4: "keys_only",
+ 5: "compiled_query",
+ }, 5)
+
+ _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,
+ }, 5, ProtocolBuffer.Encoder.MAX_TYPE)
+
+ _STYLE = """"""
+ _STYLE_CONTENT_TYPE = """"""
+class GetSchemaRequest(ProtocolBuffer.ProtocolMessage):
+ has_app_ = 0
+ app_ = ""
+ 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 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_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_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_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_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_)
+
+ 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 == 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_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
+ kstart_kind = 2
+ kend_kind = 3
+ kproperties = 4
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "app",
+ 2: "start_kind",
+ 3: "end_kind",
+ 4: "properties",
+ }, 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 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 AllocateIdsRequest(ProtocolBuffer.ProtocolMessage):
+ has_model_key_ = 0
+ has_size_ = 0
+ size_ = 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 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())
+
+ 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
+ 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
+ if (not self.has_size_):
+ initialized = 0
+ if debug_strs is not None:
+ debug_strs.append('Required field: size not set.')
+ return initialized
+
+ def ByteSize(self):
+ n = 0
+ n += self.lengthString(self.model_key_.ByteSize())
+ n += self.lengthVarInt64(self.size_)
+ return n + 2
+
+ def Clear(self):
+ self.clear_model_key()
+ self.clear_size()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(10)
+ out.putVarInt32(self.model_key_.ByteSize())
+ self.model_key_.OutputUnchecked(out)
+ out.putVarInt32(16)
+ out.putVarInt64(self.size_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "model_key",
+ 2: "size",
+ }, 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 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 ActionRequest(ProtocolBuffer.ProtocolMessage):
+ has_transaction_ = 0
+ has_action_ = 0
+
+ def __init__(self, contents=None):
+ self.transaction_ = Transaction()
+ self.action_ = 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(self): return self.action_
+
+ def mutable_action(self): self.has_action_ = 1; return self.action_
+
+ def clear_action(self):self.has_action_ = 0; self.action_.Clear()
+
+ def has_action(self): return self.has_action_
+
+
+ def MergeFrom(self, x):
+ assert x is not self
+ if (x.has_transaction()): self.mutable_transaction().MergeFrom(x.transaction())
+ if (x.has_action()): self.mutable_action().MergeFrom(x.action())
+
+ 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 self.has_action_ != x.has_action_: return 0
+ if self.has_action_ and self.action_ != x.action_: 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
+ if (not self.has_action_):
+ initialized = 0
+ if debug_strs is not None:
+ debug_strs.append('Required field: action not set.')
+ elif not self.action_.IsInitialized(debug_strs): initialized = 0
+ return initialized
+
+ def ByteSize(self):
+ n = 0
+ n += self.lengthString(self.transaction_.ByteSize())
+ n += self.lengthString(self.action_.ByteSize())
+ return n + 2
+
+ 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)
+ out.putVarInt32(18)
+ out.putVarInt32(self.action_.ByteSize())
+ self.action_.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.mutable_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"
+ if self.has_action_:
+ res+=prefix+"action <\n"
+ res+=self.action_.__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)])
+
+ 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 ActionResponse(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 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','RunCompiledQueryRequest','QueryExplanation','Cursor','Error','Cost','GetRequest','GetResponse','GetResponse_Entity','PutRequest','PutResponse','DeleteRequest','DeleteResponse','NextRequest','QueryResult','GetSchemaRequest','Schema','AllocateIdsRequest','AllocateIdsResponse','CompositeIndices','ActionRequest','ActionResponse','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 0000000..7cd9465
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/datastore_pb.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/datastore/datastore_v3_pb.py b/google_appengine/google/appengine/datastore/datastore_v3_pb.py
new file mode 100755
index 0000000..627c7bf
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/datastore_v3_pb.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.
+#
+
+"""The Python datastore protocol buffer definition.
+
+Proto2 compiler expects generated file names to follow specific pattern,
+which is not the case for the datastore_pb.py (should be datastore_v3_pb.py).
+This file with the expected name redirects to the real legacy file.
+"""
+
+
+from google.appengine.datastore.datastore_pb import *
diff --git a/google_appengine/google/appengine/datastore/datastore_v3_pb.pyc b/google_appengine/google/appengine/datastore/datastore_v3_pb.pyc
new file mode 100644
index 0000000..40ced82
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/datastore_v3_pb.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/datastore/entity_pb.py b/google_appengine/google/appengine/datastore/entity_pb.py
new file mode 100644
index 0000000..c6817bc
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/entity_pb.py
@@ -0,0 +1,2599 @@
+#!/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 PropertyValue_ReferenceValuePathElement(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 += 2 + self.lengthVarInt64(self.id_)
+ if (self.has_name_): n += 2 + 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(122)
+ out.putPrefixedString(self.type_)
+ if (self.has_id_):
+ out.putVarInt32(128)
+ out.putVarInt64(self.id_)
+ if (self.has_name_):
+ out.putVarInt32(138)
+ out.putPrefixedString(self.name_)
+
+ def TryMerge(self, d):
+ while 1:
+ tt = d.getVarInt32()
+ if tt == 116: break
+ if tt == 122:
+ self.set_type(d.getPrefixedString())
+ continue
+ if tt == 128:
+ self.set_id(d.getVarInt64())
+ continue
+ if tt == 138:
+ 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 PropertyValue_PointValue(ProtocolBuffer.ProtocolMessage):
+ has_x_ = 0
+ x_ = 0.0
+ has_y_ = 0
+ y_ = 0.0
+
+ def __init__(self, contents=None):
+ if contents is not None: self.MergeFromString(contents)
+
+ def x(self): return self.x_
+
+ def set_x(self, x):
+ self.has_x_ = 1
+ self.x_ = x
+
+ def clear_x(self):
+ if self.has_x_:
+ self.has_x_ = 0
+ self.x_ = 0.0
+
+ def has_x(self): return self.has_x_
+
+ def y(self): return self.y_
+
+ def set_y(self, x):
+ self.has_y_ = 1
+ self.y_ = x
+
+ def clear_y(self):
+ if self.has_y_:
+ self.has_y_ = 0
+ self.y_ = 0.0
+
+ def has_y(self): return self.has_y_
+
+
+ def MergeFrom(self, x):
+ assert x is not self
+ if (x.has_x()): self.set_x(x.x())
+ if (x.has_y()): self.set_y(x.y())
+
+ def Equals(self, x):
+ if x is self: return 1
+ if self.has_x_ != x.has_x_: return 0
+ if self.has_x_ and self.x_ != x.x_: return 0
+ if self.has_y_ != x.has_y_: return 0
+ if self.has_y_ and self.y_ != x.y_: return 0
+ return 1
+
+ def IsInitialized(self, debug_strs=None):
+ initialized = 1
+ if (not self.has_x_):
+ initialized = 0
+ if debug_strs is not None:
+ debug_strs.append('Required field: x not set.')
+ if (not self.has_y_):
+ initialized = 0
+ if debug_strs is not None:
+ debug_strs.append('Required field: y not set.')
+ return initialized
+
+ def ByteSize(self):
+ n = 0
+ return n + 18
+
+ def Clear(self):
+ self.clear_x()
+ self.clear_y()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(49)
+ out.putDouble(self.x_)
+ out.putVarInt32(57)
+ out.putDouble(self.y_)
+
+ def TryMerge(self, d):
+ while 1:
+ tt = d.getVarInt32()
+ if tt == 44: break
+ if tt == 49:
+ self.set_x(d.getDouble())
+ continue
+ if tt == 57:
+ self.set_y(d.getDouble())
+ continue
+ if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+ d.skipData(tt)
+
+
+ def __str__(self, prefix="", printElemNumber=0):
+ res=""
+ if self.has_x_: res+=prefix+("x: %s\n" % self.DebugFormat(self.x_))
+ if self.has_y_: res+=prefix+("y: %s\n" % self.DebugFormat(self.y_))
+ return res
+
+class PropertyValue_UserValue(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_ = ""
+
+ 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 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())
+
+ 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
+ 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 += 2 + self.lengthString(len(self.obfuscated_gaiaid_))
+ return n + 4
+
+ def Clear(self):
+ self.clear_email()
+ self.clear_auth_domain()
+ self.clear_nickname()
+ self.clear_gaiaid()
+ self.clear_obfuscated_gaiaid()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(74)
+ out.putPrefixedString(self.email_)
+ out.putVarInt32(82)
+ out.putPrefixedString(self.auth_domain_)
+ if (self.has_nickname_):
+ out.putVarInt32(90)
+ out.putPrefixedString(self.nickname_)
+ out.putVarInt32(144)
+ out.putVarInt64(self.gaiaid_)
+ if (self.has_obfuscated_gaiaid_):
+ out.putVarInt32(154)
+ out.putPrefixedString(self.obfuscated_gaiaid_)
+
+ def TryMerge(self, d):
+ while 1:
+ tt = d.getVarInt32()
+ if tt == 68: break
+ if tt == 74:
+ self.set_email(d.getPrefixedString())
+ continue
+ if tt == 82:
+ self.set_auth_domain(d.getPrefixedString())
+ continue
+ if tt == 90:
+ self.set_nickname(d.getPrefixedString())
+ continue
+ if tt == 144:
+ self.set_gaiaid(d.getVarInt64())
+ continue
+ if tt == 154:
+ self.set_obfuscated_gaiaid(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_))
+ return res
+
+class PropertyValue_ReferenceValue(ProtocolBuffer.ProtocolMessage):
+ has_app_ = 0
+ app_ = ""
+
+ def __init__(self, contents=None):
+ self.pathelement_ = []
+ 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 pathelement_size(self): return len(self.pathelement_)
+ def pathelement_list(self): return self.pathelement_
+
+ def pathelement(self, i):
+ return self.pathelement_[i]
+
+ def mutable_pathelement(self, i):
+ return self.pathelement_[i]
+
+ def add_pathelement(self):
+ x = PropertyValue_ReferenceValuePathElement()
+ self.pathelement_.append(x)
+ return x
+
+ def clear_pathelement(self):
+ self.pathelement_ = []
+
+ def MergeFrom(self, x):
+ assert x is not self
+ if (x.has_app()): self.set_app(x.app())
+ for i in xrange(x.pathelement_size()): self.add_pathelement().CopyFrom(x.pathelement(i))
+
+ 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 len(self.pathelement_) != len(x.pathelement_): return 0
+ for e1, e2 in zip(self.pathelement_, x.pathelement_):
+ if e1 != e2: 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.')
+ for p in self.pathelement_:
+ if not p.IsInitialized(debug_strs): initialized=0
+ return initialized
+
+ def ByteSize(self):
+ n = 0
+ n += self.lengthString(len(self.app_))
+ n += 2 * len(self.pathelement_)
+ for i in xrange(len(self.pathelement_)): n += self.pathelement_[i].ByteSize()
+ return n + 1
+
+ def Clear(self):
+ self.clear_app()
+ self.clear_pathelement()
+
+ def OutputUnchecked(self, out):
+ out.putVarInt32(106)
+ out.putPrefixedString(self.app_)
+ for i in xrange(len(self.pathelement_)):
+ out.putVarInt32(115)
+ self.pathelement_[i].OutputUnchecked(out)
+ out.putVarInt32(116)
+
+ def TryMerge(self, d):
+ while 1:
+ tt = d.getVarInt32()
+ if tt == 100: break
+ if tt == 106:
+ self.set_app(d.getPrefixedString())
+ continue
+ if tt == 115:
+ self.add_pathelement().TryMerge(d)
+ 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_))
+ cnt=0
+ for e in self.pathelement_:
+ elm=""
+ if printElemNumber: elm="(%d)" % cnt
+ res+=prefix+("PathElement%s {\n" % elm)
+ res+=e.__str__(prefix + " ", printElemNumber)
+ res+=prefix+"}\n"
+ cnt+=1
+ return res
+
+class PropertyValue(ProtocolBuffer.ProtocolMessage):
+ has_int64value_ = 0
+ int64value_ = 0
+ has_booleanvalue_ = 0
+ booleanvalue_ = 0
+ has_stringvalue_ = 0
+ stringvalue_ = ""
+ has_doublevalue_ = 0
+ doublevalue_ = 0.0
+ has_pointvalue_ = 0
+ pointvalue_ = None
+ has_uservalue_ = 0
+ uservalue_ = None
+ has_referencevalue_ = 0
+ referencevalue_ = None
+
+ def __init__(self, contents=None):
+ self.lazy_init_lock_ = thread.allocate_lock()
+ if contents is not None: self.MergeFromString(contents)
+
+ def int64value(self): return self.int64value_
+
+ def set_int64value(self, x):
+ self.has_int64value_ = 1
+ self.int64value_ = x
+
+ def clear_int64value(self):
+ if self.has_int64value_:
+ self.has_int64value_ = 0
+ self.int64value_ = 0
+
+ def has_int64value(self): return self.has_int64value_
+
+ def booleanvalue(self): return self.booleanvalue_
+
+ def set_booleanvalue(self, x):
+ self.has_booleanvalue_ = 1
+ self.booleanvalue_ = x
+
+ def clear_booleanvalue(self):
+ if self.has_booleanvalue_:
+ self.has_booleanvalue_ = 0
+ self.booleanvalue_ = 0
+
+ def has_booleanvalue(self): return self.has_booleanvalue_
+
+ def stringvalue(self): return self.stringvalue_
+
+ def set_stringvalue(self, x):
+ self.has_stringvalue_ = 1
+ self.stringvalue_ = x
+
+ def clear_stringvalue(self):
+ if self.has_stringvalue_:
+ self.has_stringvalue_ = 0
+ self.stringvalue_ = ""
+
+ def has_stringvalue(self): return self.has_stringvalue_
+
+ def doublevalue(self): return self.doublevalue_
+
+ def set_doublevalue(self, x):
+ self.has_doublevalue_ = 1
+ self.doublevalue_ = x
+
+ def clear_doublevalue(self):
+ if self.has_doublevalue_:
+ self.has_doublevalue_ = 0
+ self.doublevalue_ = 0.0
+
+ def has_doublevalue(self): return self.has_doublevalue_
+
+ def pointvalue(self):
+ if self.pointvalue_ is None:
+ self.lazy_init_lock_.acquire()
+ try:
+ if self.pointvalue_ is None: self.pointvalue_ = PropertyValue_PointValue()
+ finally:
+ self.lazy_init_lock_.release()
+ return self.pointvalue_
+
+ def mutable_pointvalue(self): self.has_pointvalue_ = 1; return self.pointvalue()
+
+ def clear_pointvalue(self):
+ if self.has_pointvalue_:
+ self.has_pointvalue_ = 0;
+ if self.pointvalue_ is not None: self.pointvalue_.Clear()
+
+ def has_pointvalue(self): return self.has_pointvalue_
+
+ def uservalue(self):
+ if self.uservalue_ is None:
+ self.lazy_init_lock_.acquire()
+ try:
+ if self.uservalue_ is None: self.uservalue_ = PropertyValue_UserValue()
+ finally:
+ self.lazy_init_lock_.release()
+ return self.uservalue_
+
+ def mutable_uservalue(self): self.has_uservalue_ = 1; return self.uservalue()
+
+ def clear_uservalue(self):
+ if self.has_uservalue_:
+ self.has_uservalue_ = 0;
+ if self.uservalue_ is not None: self.uservalue_.Clear()
+
+ def has_uservalue(self): return self.has_uservalue_
+
+ def referencevalue(self):
+ if self.referencevalue_ is None:
+ self.lazy_init_lock_.acquire()
+ try:
+ if self.referencevalue_ is None: self.referencevalue_ = PropertyValue_ReferenceValue()
+ finally:
+ self.lazy_init_lock_.release()
+ return self.referencevalue_
+
+ def mutable_referencevalue(self): self.has_referencevalue_ = 1; return self.referencevalue()
+
+ def clear_referencevalue(self):
+ if self.has_referencevalue_:
+ self.has_referencevalue_ = 0;
+ if self.referencevalue_ is not None: self.referencevalue_.Clear()
+
+ def has_referencevalue(self): return self.has_referencevalue_
+
+
+ def MergeFrom(self, x):
+ assert x is not self
+ if (x.has_int64value()): self.set_int64value(x.int64value())
+ if (x.has_booleanvalue()): self.set_booleanvalue(x.booleanvalue())
+ if (x.has_stringvalue()): self.set_stringvalue(x.stringvalue())
+ if (x.has_doublevalue()): self.set_doublevalue(x.doublevalue())
+ if (x.has_pointvalue()): self.mutable_pointvalue().MergeFrom(x.pointvalue())
+ if (x.has_uservalue()): self.mutable_uservalue().MergeFrom(x.uservalue())
+ if (x.has_referencevalue()): self.mutable_referencevalue().MergeFrom(x.referencevalue())
+
+ def Equals(self, x):
+ if x is self: return 1
+ if self.has_int64value_ != x.has_int64value_: return 0
+ if self.has_int64value_ and self.int64value_ != x.int64value_: return 0
+ if self.has_booleanvalue_ != x.has_booleanvalue_: return 0
+ if self.has_booleanvalue_ and self.booleanvalue_ != x.booleanvalue_: return 0
+ if self.has_stringvalue_ != x.has_stringvalue_: return 0
+ if self.has_stringvalue_ and self.stringvalue_ != x.stringvalue_: return 0
+ if self.has_doublevalue_ != x.has_doublevalue_: return 0
+ if self.has_doublevalue_ and self.doublevalue_ != x.doublevalue_: return 0
+ if self.has_pointvalue_ != x.has_pointvalue_: return 0
+ if self.has_pointvalue_ and self.pointvalue_ != x.pointvalue_: return 0
+ if self.has_uservalue_ != x.has_uservalue_: return 0
+ if self.has_uservalue_ and self.uservalue_ != x.uservalue_: return 0
+ if self.has_referencevalue_ != x.has_referencevalue_: return 0
+ if self.has_referencevalue_ and self.referencevalue_ != x.referencevalue_: return 0
+ return 1
+
+ def IsInitialized(self, debug_strs=None):
+ initialized = 1
+ if (self.has_pointvalue_ and not self.pointvalue_.IsInitialized(debug_strs)): initialized = 0
+ if (self.has_uservalue_ and not self.uservalue_.IsInitialized(debug_strs)): initialized = 0
+ if (self.has_referencevalue_ and not self.referencevalue_.IsInitialized(debug_strs)): initialized = 0
+ return initialized
+
+ def ByteSize(self):
+ n = 0
+ if (self.has_int64value_): n += 1 + self.lengthVarInt64(self.int64value_)
+ if (self.has_booleanvalue_): n += 2
+ if (self.has_stringvalue_): n += 1 + self.lengthString(len(self.stringvalue_))
+ if (self.has_doublevalue_): n += 9
+ if (self.has_pointvalue_): n += 2 + self.pointvalue_.ByteSize()
+ if (self.has_uservalue_): n += 2 + self.uservalue_.ByteSize()
+ if (self.has_referencevalue_): n += 2 + self.referencevalue_.ByteSize()
+ return n + 0
+
+ def Clear(self):
+ self.clear_int64value()
+ self.clear_booleanvalue()
+ self.clear_stringvalue()
+ self.clear_doublevalue()
+ self.clear_pointvalue()
+ self.clear_uservalue()
+ self.clear_referencevalue()
+
+ def OutputUnchecked(self, out):
+ if (self.has_int64value_):
+ out.putVarInt32(8)
+ out.putVarInt64(self.int64value_)
+ if (self.has_booleanvalue_):
+ out.putVarInt32(16)
+ out.putBoolean(self.booleanvalue_)
+ if (self.has_stringvalue_):
+ out.putVarInt32(26)
+ out.putPrefixedString(self.stringvalue_)
+ if (self.has_doublevalue_):
+ out.putVarInt32(33)
+ out.putDouble(self.doublevalue_)
+ if (self.has_pointvalue_):
+ out.putVarInt32(43)
+ self.pointvalue_.OutputUnchecked(out)
+ out.putVarInt32(44)
+ if (self.has_uservalue_):
+ out.putVarInt32(67)
+ self.uservalue_.OutputUnchecked(out)
+ out.putVarInt32(68)
+ if (self.has_referencevalue_):
+ out.putVarInt32(99)
+ self.referencevalue_.OutputUnchecked(out)
+ out.putVarInt32(100)
+
+ def TryMerge(self, d):
+ while d.avail() > 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
+ kReferenceValueGroup = 12
+ kReferenceValueapp = 13
+ 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",
+ }, 19)
+
+ _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,
+ }, 19, 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_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 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_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_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_))
+ n += self.lengthString(self.path_.ByteSize())
+ return n + 2
+
+ def Clear(self):
+ self.clear_app()
+ 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)
+
+ 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 == 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_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
+ kpath = 14
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 13: "app",
+ 14: "path",
+ }, 14)
+
+ _TYPES = _BuildTagLookupTable({
+ 0: ProtocolBuffer.Encoder.NUMERIC,
+ 13: ProtocolBuffer.Encoder.STRING,
+ 14: ProtocolBuffer.Encoder.STRING,
+ }, 14, 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_ = ""
+
+ 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 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())
+
+ 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
+ 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_))
+ return n + 3
+
+ def Clear(self):
+ self.clear_email()
+ self.clear_auth_domain()
+ self.clear_nickname()
+ self.clear_gaiaid()
+ self.clear_obfuscated_gaiaid()
+
+ 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_)
+
+ 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 == 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_))
+ 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
+
+ _TEXT = _BuildTagLookupTable({
+ 0: "ErrorCode",
+ 1: "email",
+ 2: "auth_domain",
+ 3: "nickname",
+ 4: "gaiaid",
+ 5: "obfuscated_gaiaid",
+ }, 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 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 0000000..1e463c9
--- /dev/null
+++ b/google_appengine/google/appengine/datastore/entity_pb.pyc
Binary files differ
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 0000000..aa8ac0d
--- /dev/null
+++ b/google_appengine/google/appengine/dist/__init__.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/dist/_library.py b/google_appengine/google/appengine/dist/_library.py
new file mode 100755
index 0000000..a0148e5
--- /dev/null
+++ b/google_appengine/google/appengine/dist/_library.py
@@ -0,0 +1,284 @@
+#!/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']
+
+
+import distutils.version
+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 0000000..457aed8
--- /dev/null
+++ b/google_appengine/google/appengine/dist/_library.pyc
Binary files differ
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/httplib.pyc b/google_appengine/google/appengine/dist/httplib.pyc
new file mode 100644
index 0000000..5e42f94
--- /dev/null
+++ b/google_appengine/google/appengine/dist/httplib.pyc
Binary files differ
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..a6a0f38
--- /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 '\0\0\0\0'
+
+
+_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_imp.pyc b/google_appengine/google/appengine/dist/py_imp.pyc
new file mode 100644
index 0000000..38fafcb
--- /dev/null
+++ b/google_appengine/google/appengine/dist/py_imp.pyc
Binary files differ
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 '<zipimporter object "%s">' % 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 0000000..b8cbca8
--- /dev/null
+++ b/google_appengine/google/appengine/dist/py_zipimport.pyc
Binary files differ
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/socket.pyc b/google_appengine/google/appengine/dist/socket.pyc
new file mode 100644
index 0000000..4be2c06
--- /dev/null
+++ b/google_appengine/google/appengine/dist/socket.pyc
Binary files differ
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/dist/tempfile.pyc b/google_appengine/google/appengine/dist/tempfile.pyc
new file mode 100644
index 0000000..a32e559
--- /dev/null
+++ b/google_appengine/google/appengine/dist/tempfile.pyc
Binary files differ
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 0000000..de39486
--- /dev/null
+++ b/google_appengine/google/appengine/ext/__init__.pyc
Binary files differ
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..8062c9a
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/__init__.py
@@ -0,0 +1,1297 @@
+#!/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
+
+
+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, '<string>', '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')
+ if not kind:
+ return ([], 0)
+ query = datastore.Query(kind)
+
+ 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):
+ """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.
+ """
+ schema = datastore_admin.GetSchema()
+ 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]
+ value = DataType.get(raw_value).format(raw_value)
+ short_value = DataType.get(raw_value).short_format(raw_value)
+ else:
+ value = ''
+ short_value = ''
+ attributes.append({
+ 'name': key,
+ 'value': value,
+ 'short_value': short_value,
+ })
+ 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()
+
+ 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,
+ 'num': num,
+ 'next_start': -1,
+ 'prev_start': -1,
+ 'start': start,
+ 'total': total,
+ 'start_base_url': self.filter_url(['kind', 'order', 'order_type',
+ 'num']),
+ 'order_base_url': self.filter_url(['kind', '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):
+ kind = self.request.get('kind')
+ sample_entities = self.execute_query()[0]
+ if len(sample_entities) < 1:
+ next_uri = self.request.get('next')
+ 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
+
+ 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()
+ parent_key = key_instance.parent()
+ entity = datastore.Get(key_instance)
+ else:
+ key_instance = None
+ entity_key_name = None
+ entity_key_id = None
+ parent_key = None
+ entity = None
+
+ if parent_key:
+ parent_kind = parent_key.kind()
+ else:
+ parent_kind = 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'),
+ 'next': self.request.get('next'),
+ 'parent_key': parent_key,
+ 'parent_kind': parent_kind,
+ })
+
+ 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:
+ entity = datastore.Entity(kind)
+
+ 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 == 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 <input> 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 str(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 '<input class="%s" name="%s" type="text" size="%d" value="%s"/>' % (cgi.escape(self.name()), cgi.escape(name), self.input_field_size(),
+ cgi.escape(string_value, True))
+
+ def input_field_size(self):
+ return 30
+
+
+class StringType(DataType):
+ def format(self, value):
+ return value
+
+ def input_field(self, name, value, 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 '<textarea name="%s" rows="5" cols="50">%s</textarea>' % (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 '<textarea name="%s" rows="5" cols="50">%s</textarea>' % (cgi.escape(name), cgi.escape(str(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 '&lt;binary&gt;'
+
+ def format(self, value):
+ return '<binary>'
+
+ 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(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(value)
+ return value_file.getvalue()
+ finally:
+ value_file.close()
+
+ def name(self):
+ return 'list'
+
+ def parse(self, value):
+ value_file = cStringIO.StringIO(value)
+ try:
+ reader = csv.reader(value_file)
+ return reader.next()
+ 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 """<select class="%s" name="%s">
+ <option %s value=''></option>
+ <option %s value='0'>False</option>
+ <option %s value='1'>True</option></select>""" % (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_size(self):
+ return 85
+
+
+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'
+
+_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(),
+}
+
+_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 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..beef28e
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/templates/base.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+ <title>{% block title %}{% endblock %}</title>
+ <style type="text/css">{% include "css/base.css" %}</style>
+ <style type="text/css">{% include "css/ae.css" %}</style>
+ <style type="text/css">{% include "css/nav.css" %}</style>
+ {% block head %}{% endblock %}
+ </head>
+ <body {% block bodyattributes %}{% endblock %}>
+ <div class="g-doc">
+
+ <div id="hd" class="g-section">
+
+ <div class="g-section">
+ <img id="ae-logo" src="./images/google.gif" width="153" height="47"
+ alt="Google App Engine"/>
+ </div>
+
+ <div id="ae-appbar-lrg" class="g-section">
+ <h1>{{ application_name }} Development Console</h1>
+ </div>
+
+ </div>
+
+
+ <div id="bd" class="g-section">
+
+ <div class="g-section g-tpl-160">
+
+ <div id="ae-lhs-nav" class="g-unit g-first">
+
+ <div id="ae-nav" class="g-c">
+
+ <ul id="menu">
+ <li><a href="{{ datastore_path }}">Datastore Viewer</a></li>
+ <li><a href="{{ interactive_path }}">Interactive Console</a></li>
+ <li><a href="{{ memcache_path }}">Memcache Viewer</a></li>
+ <li><a href="{{ queues_path }}">Task Queues</a></li>
+ {% if cron_path %}
+ <li><a href="{{ cron_path }}">Cron Jobs</a></li>
+ {% endif %}
+ <li><a href="{{ xmpp_path }}">XMPP</a></li>
+ {% comment %}
+ <li><a href="{{ inboundmail_path }}">Inbound Mail</a></li>
+ {% endcomment %}
+ </ul>
+
+ </div>
+
+ </div>
+
+ <div id="ae-content" class="g-unit">
+ {% block body %}{% endblock %}
+ </div>
+
+ </div>
+
+ <div id="ft">
+ <p>
+ &copy;2009 Google
+ </p>
+ </div>
+ {% block final %}{% endblock %}
+ </div>
+ <script type="text/javascript">
+ //<![CDATA[
+
+ function walk(element, condition, operation) {
+ if (!element) return;
+ if (condition(element)) {
+ operation(element);
+ return;
+ }
+ for (var e = element.firstChild; e != null; e = e.nextSibling) {
+ walk(e, condition, operation);
+ }
+ }
+
+ function isCurrentLink(e) {
+ if (e.tagName != "A") return false;
+ re = new RegExp("^" + e.href + ".*(\\?.*)?$");
+ return re.test(window.location.href);
+ }
+
+ function makeSelected(e) {
+ e.className = "ae-nav-selected";
+ }
+
+ walk(document.getElementById("menu"), isCurrentLink, makeSelected);
+
+ //]]>
+ </script>
+ </body>
+</html>
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 %}
+ <style type="text/css">{% include "css/cron.css" %}</style>
+{% endblock %}
+
+{% block breadcrumbs %}
+ <span class="item"><a href="">Cron Viewer</a></span>
+{% endblock %}
+
+{% block body %}
+<h3>Cron Jobs</h3>
+
+{% if message %}
+<div class="ah-cron-message">
+{{ message|escape }}
+</div>
+{% endif %}
+
+{% if cronjobs %}
+ <table id="ah-cron-jobs" class="ae-table ae-table-striped">
+ <colgroup>
+ <col style="width:60%">
+ <col>
+ </colgroup>
+ <thead>
+ <tr>
+ <th>Cron Job</th>
+ <th>Schedule</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for job in cronjobs %}
+ <tr class="{% cycle ae-odd,ae-even %}">
+ <td valign="top">
+ <h3>{{ job.url|escape }}</h3>
+ <p>
+ {{ job.description|escape }}
+ </p>
+ </td>
+ <td valign="top">
+ <table class="ae-table">
+ <tr>
+ <td>
+ <strong>{{ job.schedule|escape }}</strong>
+ </td>
+ <td class="ah-cron-test">
+ <a href="{{ job.url }}">Test this job</a>
+ </td>
+ </tr>
+ </table>
+
+ {% if job.timezone %}
+ <strong>Timezone: {{ job.timezone }}</strong>
+ <div class="ah-cron-message">
+ 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.
+ </div>
+ {% endif %}
+ <div class="ah-cron-times">
+ In production, this would run at these times:
+ <ol>
+ {% for run in job.times %}
+ <li>
+ {{ run.runtime }} <span class="ae-unimportant">{{ run.difference }} from now</span>
+ </li>
+ {% endfor %}
+ </ol>
+ </div>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+{% 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..4ef5ef2
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/templates/datastore.html
@@ -0,0 +1,183 @@
+{% extends "base.html" %}
+
+{% block title %}{{ application_name }} Development Console - Datastore Viewer{% endblock %}
+
+{% block head %}
+ <style type="text/css">{% include "css/datastore.css" %}</style>
+ <style type="text/css">{% include "css/pager.css" %}</style>
+ <script type="text/javascript">
+ //<![CDATA[
+
+ {% if in_production %}
+ function manageCreateButton() {
+ var input = document.getElementById("kind_input");
+ var button = document.getElementById("create_button");
+ if (input && button) {
+ if (input.value.length == 0) {
+ button.disabled = true;
+ } else {
+ button.disabled = false;
+ }
+ }
+ }
+ {% endif %}
+
+ {% if entities %}
+ function checkAllEntities() {
+ var allCheckBox = document.getElementById("allkeys");
+ var check = allCheckBox.checked;
+ for (var i = 1; i <= {{ entities|length }}; i++) {
+ var box = document.getElementById("key" + i);
+ if (box)
+ box.checked = check;
+ }
+ updateDeleteButtonAndCheckbox();
+ }
+
+ function updateDeleteButtonAndCheckbox() {
+ var button = document.getElementById("delete_button");
+ var uncheck = false;
+ var disable = true;
+ for (var i = 1; i <= {{ entities|length }}; i++) {
+ var box = document.getElementById("key" + i);
+ if (box) {
+ if (box.checked) {
+ disable = false;
+ } else {
+ uncheck = true;
+ }
+ }
+ }
+ button.disabled = disable;
+ if (uncheck)
+ document.getElementById("allkeys").checked = false;
+ }
+ {% endif %}
+
+ //]]>
+ </script>
+{% endblock %}
+
+{% block body %}
+ <h3>Datastore Viewer</h3>
+
+ {% if in_production %}
+ <div id="hint">
+ The <a href="http://appengine.google.com/datastore/explorer?&app_id={{ application_name }}">Admin Console Data Viewer</a>
+ allows you to run GQL queries and much more!
+ </div>
+ {% endif %}
+
+ {% if message %}
+ <div id="message">
+ {{ message }}
+ </div>
+ {% endif %}
+
+ {% if entities %}
+ <div id="pagetotal">
+ Results <span class="count">{{ start|add:1 }}</span> - <span class="count">{{ entities|length|add:start }}</span> of <span class="count">{{ total }}</span>
+ </div>
+ {% endif %}
+
+ {% if kinds or in_production %}
+ <form action="{{ request.path }}" method="get">
+ <div id="datastore_search">
+ <span class="field">
+ <span class="name">Entity Kind:</span>
+ <span class="value">
+ {% if in_production %}
+ <input id="kind_input" name="kind" type="text" size="8" value="{{ kind|escape }}" onkeyup="manageCreateButton()" onkeydown="manageCreateButton()"/>
+ {% else %}
+ <select name="kind" id="kind_input">
+ {% for a_kind in kinds %}
+ <option value="{{ a_kind|escape }}"{% ifequal a_kind kind %} selected="selected"{% endifequal %}>{{ a_kind|escape }}</option>
+ {% endfor %}
+ </select>
+ {% endif %}
+ </span>
+ </span>
+ <span class="buttons">
+ <input type="submit" value="List Entities"/>
+ <input type="button" id="create_button" onclick="location.href='{{ datastore_edit_path }}?kind=' + encodeURIComponent(document.getElementById('kind_input').value) + '&amp;next={{ request.uri|urlencode }}'" value="Create New Entity"/>
+ </span>
+ </div>
+ </form>
+ {% else %}
+ <div id="datastore_empty">
+ The datastore is empty. You need to add data programatically before you can use this tool to view and edit it.
+ </div>
+ {% endif %}
+
+ {% if entities %}
+ <form action="{{ datastore_batch_edit_path }}" method="post">
+ <input type="hidden" name="kind" value="{{ kind|escape }}"/>
+ <input type="hidden" name="numkeys" value="{{ entities|length }}"/>
+ <input type="hidden" name="next" value="{{ start_base_url }}"/>
+ <input type="hidden" name="action" value="Delete"/>
+ <table class="entities">
+ <thead>
+ <tr>
+ <th><input id="allkeys" type="checkbox" onclick="checkAllEntities();"/></th>
+ <th>Key</th>
+ <th>ID</th>
+ <th>Key Name</th>
+ {% for header in headers %}
+ <th style="cursor: pointer" onclick="document.location.href='{{ order_base_url }}&amp;order={% ifequal order header.name %}-{% endifequal %}{{ header.name|urlencode }}&amp;order_type={{ header.type|urlencode }}'"><a href="{{ order_base_url }}&amp;order={% ifequal order header.name %}-{% endifequal %}{{ header.name|urlencode }}&amp;order_type={{ header.type|urlencode }}" onclick="return false">{{ header.name }}</a></th>
+ {% endfor %}
+ </tr>
+ {% for entity in entities %}
+ <tr class="{% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}">
+ <td><input id="key{{ forloop.counter }}" type="checkbox" name="key{{ forloop.counter }}" value="{{ entity.key|escape }}" onclick="updateDeleteButtonAndCheckbox();"/></td>
+ <td onclick="location.href='{{ entity.edit_uri|escape }}'"><a href="{{ entity.edit_uri|escape }}" title="Edit entity #{{ entity.key|escape }}" onclick="return false">{{ entity.shortened_key|escape }}</a></td>
+ <td>
+ {% if entity.key_id %}
+ {{entity.key_id}}
+ {% endif %}
+ </td>
+ <td>
+ {% if entity.key_name %}
+ {{entity.key_name}}
+ {% endif %}
+ </td>
+ {% for attribute in entity.attributes %}
+ <td onclick="location.href='{{ entity.edit_uri|escape }}&amp;focus={{ attribute.name|urlencode }}'">{{ attribute.short_value|truncatewords:20|escape }}</td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </table>
+ <div class="entities g-section g-tpl-50-50">
+ <div class="g-unit g-first">
+ <div id="entities-control"><input id="delete_button" type="submit" value="Delete" onclick="return confirm('Are you sure you wish to delete these entities?')" /></div>
+ </div>
+ <div class="g-unit">
+ <div id="entities-pager">
+ {% if pages %}
+ {% include "pager.html" %}
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ </form>
+ {% else %}
+ {% if kind %}
+ <p style="font-size: medium">Sorry, there are no entities of kind &quot;{{ kind|escape }}&quot; in your datastore.</p>
+ {% endif %}
+ {% endif %}
+{% endblock %}
+
+{% block final %}
+ <script type="text/javascript">
+ //<![CDATA[
+
+ {% if in_production %}
+ manageCreateButton();
+ {% endif %}
+ updateDeleteButtonAndCheckbox();
+ document.getElementById("kind_input").focus();
+
+ //]]>
+ </script>
+{% 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..a441218
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/templates/datastore_edit.html
@@ -0,0 +1,162 @@
+{% extends "base.html" %}
+
+{% block title %}{{ application_name }} Development Console - Datastore Viewer - {% if key %}Edit Entity{% else %}New Entity{% endif %}{% endblock %}
+
+{% block head %}
+ <style type="text/css">{% include "css/form.css" %}</style>
+ <style type="text/css">
+
+ .field_type {
+ color: gray;
+ font-weight: normal;
+ }
+
+ </style>
+ <script type="text/javascript">
+
+ function load() {
+ var elements = document.getElementsByTagName("input");
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ var hint = null;
+ if (element.className == "time") {
+ hint = "e.g., 2006-30-05 23:56:04";
+ }
+ if (hint) registerHint(element, hint);
+ }
+ }
+
+ function registerHint(element, hint) {
+ function showDefault() {
+ if (element.value.length == 0 || element.value == hint) {
+ element.style.color = "gray";
+ element.value = hint;
+ }
+ }
+ function clearDefault() {
+ if (element.style.color == "gray" || element.value == hint) {
+ element.value = "";
+ element.style.color = "black";
+ }
+ }
+ element.onblur = showDefault;
+ element.onfocus = clearDefault;
+ showDefault();
+ }
+
+ function clearHints(form) {
+ var elements = form.getElementsByTagName("input");
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ if (element.type == "text" && element.style.color == "gray") {
+ element.onblur = null;
+ element.onfocus = null;
+ element.value = "";
+ }
+ }
+ return true;
+ }
+
+ </script>
+{% endblock %}
+
+{% block bodyattributes %}onload="load()"{% endblock %}
+
+{% block body %}
+ <h3>{% if key %}Edit Entity{% else %}New Entity{% endif %}</h3>
+
+ <form action="{{ request.path }}" method="post" onsubmit="return clearHints(this)">
+ <div><input type="hidden" name="next" value="{{ next }}"/></div>
+ <table class="form">
+ <tr>
+ <td class="name">Entity Kind</td>
+ <td class="value text">
+ {{ kind }}
+ <input type="hidden" name="kind" value="{{ kind }}"/>
+ </td>
+ </tr>
+ {% if key %}
+ <tr>
+ <td class="name">Entity Key</td>
+ <td class="value text">
+ {{ key }}
+ <input type="hidden" name="key" value="{{ key }}"/>
+ </td>
+ </tr>
+ {% endif %}
+ {% if key_name %}
+ <tr>
+ <td class="name">Key Name</td>
+ <td class="value text">
+ {{ key_name }}
+ </td>
+ </tr>
+ {% endif %}
+ {% if key_id %}
+ <tr>
+ <td class="name">ID</td>
+ <td class="value text">
+ {{ key_id }}
+ </td>
+ </tr>
+ {% endif %}
+ {% if parent_key %}
+ <tr>
+ <td class="name">Parent</td>
+ <td class="value text">
+ <a href="?key={{parent_key}}&kind={{parent_kind}}">{{ parent_key }}</a>
+ </td>
+ </tr>
+ {% endif %}
+ {% for field in fields %}
+ <tr>
+ <td class="name">
+ <span class="field_name">{{ field.0|escape }}</span>
+ <span class="field_type">({{ field.1|escape }})</span>
+ </td>
+ <td class="value"><div style="position: relative">{{ field.2|safe }}</div></td>
+ </tr>
+ {% endfor %}
+ <tr>
+ <td></td>
+ <td class="buttons">
+ <input type="submit" value="Save Changes"/>
+ {% if key %}
+ <input type="submit" name="action" value="Delete" onclick="return confirm('Are you sure you want to permanently delete this entity?');"/>
+ {% endif %}
+ </td>
+ </tr>
+ </table>
+ </form>
+
+ <div id="datepicker"></div>
+{% endblock %}
+
+{% block final %}
+<script type="text/javascript">
+//<![CDATA[
+
+// Sets the focus on the field with the given name in the given array (if any)
+function setFocus(fields, fieldName) {
+ for (var i = 0; i < fields.length; i++) {
+ var field = fields[i];
+ var name = field.name;
+ if (field.focus && name.length > focus.length &&
+ name.substring(name.length - focus.length - 1) == '|' + focus) {
+ field.focus();
+ break;
+ }
+ }
+}
+
+// Focus on the appropriate field in the form based on the "focus" argument
+// in the URL
+var focus = "{{ focus|addslashes }}";
+if (focus) {
+ setFocus(document.getElementsByTagName("input"), focus);
+ setFocus(document.getElementsByTagName("textarea"), focus);
+}
+
+//]]>
+</script>
+{% 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 0000000..5e9d2f3
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/templates/images/google.gif
Binary files differ
diff --git a/google_appengine/google/appengine/ext/admin/templates/inboundmail.html b/google_appengine/google/appengine/ext/admin/templates/inboundmail.html
new file mode 100644
index 0000000..44cb389
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/templates/inboundmail.html
@@ -0,0 +1,158 @@
+{% extends "base.html" %}
+
+{% block title %}{{ application_name }} Development Console - Inbound Mail{% endblock %}
+
+{% block breadcrumbs %}
+ <span class="item"><a href="">Email</a></span>
+{% endblock %}
+
+{% block head %}
+
+ <style type="text/css">{% include "css/inboundmail.css" %}</style>
+ <script type="text/javascript">
+ {% include "js/webhook.js" %}
+ {% include "js/multipart_form_data.js" %}
+ {% include "js/rfc822_date.js" %}
+
+ var feedbackEl;
+ var formEl;
+ var payloadEl;
+ var fromEl;
+ var toEl;
+ var ccEl;
+ var subjectEl;
+ var bodyEl;
+ var contentLengthEl;
+ //var contentTypeEl;
+
+ var sendInboundMailWebhook = function() {
+
+ if (!feedbackEl) {
+ feedbackEl = document.getElementById('feedback');
+ formEl = document.getElementById('inboundmail-form');
+ fromEl = document.getElementById('from');
+ toEl = document.getElementById('to');
+ ccEl = document.getElementById('cc');
+ subjectEl = document.getElementById('subject');
+ bodyEl = document.getElementById('body');
+ payloadEl = document.getElementById('payload');
+ contentLengthEl = document.getElementById('content-length');
+ }
+
+ var from = fromEl.value;
+ var to = toEl.value;
+ var cc = ccEl.value;
+ var subject = subjectEl.value;
+ var body = bodyEl.value;
+
+ if (!to || !from || !body) {
+ feedbackEl.className = 'ae-errorbox';
+ feedbackEl.innerHTML = 'From, To and Message body are required.';
+ return;
+ }
+
+ feedbackEl.className = 'ae-message';
+ feedbackEl.innerHTML = 'Sending mail message...';
+
+ var mpfd = new MultipartFormData();
+ mpfd.addHeader('MIME-Version', '1.0');
+ mpfd.addHeader('Date', RFC822Date.format(new Date()));
+ mpfd.addHeader('From', from);
+ mpfd.addHeader('To', to);
+ if (cc) {
+ mpfd.addHeader('Cc', cc);
+ }
+ mpfd.addHeader('Subject', subject);
+ mpfd.addHeader('Content-Type', 'multipart/alternative; ' +
+ 'boundary=' + mpfd.boundary);
+ mpfd.addPart(null, body, 'text/plain; charset=UTF-8');
+ mpfd.addPart(null, body, 'text/html; charset=UTF-8');
+
+ payloadEl.value = mpfd.toString();
+
+ contentLengthEl = payloadEl.value.length;
+
+ formEl.action = '/_ah/mail/' + escape(to);
+
+ (new Webhook('inboundmail-form')).run(handleInboundMailResult);
+
+ // Prevents actual form posts.
+ return false;
+ };
+
+ var handleInboundMailResult = function(hook, req, error) {
+ if (error != null || req == null || req.status != 200) {
+ feedbackEl.className = 'ae-errorbox';
+ feedbackEl.innerHTML = 'Message send failure<br>' +
+ req.responseText;
+ } else {
+ var timestamp;
+ var dateString = new Date().toString();
+ var match = dateString.match(/(\d\d:\d\d:\d\d).+\((.+)\)/);
+ if (!match || !match[0] || !match[2]) {
+ timestamp = dateString;
+ } else {
+ timestamp = match[1] + ' ' + match[2];
+ }
+
+ feedbackEl.className = 'ae-message';
+ feedbackEl.innerHTML = 'Message has been sent at ' + timestamp;
+ }
+ };
+
+ </script>
+{% endblock %}
+
+{% block body %}
+<div id="inboundmail">
+ <h3>Email</h3>
+ {% if inboundmail_configured %}{% else %}
+ <div class="ae-errorbox">
+ Inbound mail is not yet configured properly in your app.yaml in the services section.
+ </div>
+ {% endif %}
+ <div id="feedback"></div>
+ <form id="inboundmail-form"
+ action="/_ah/mail/" method="post"
+ onsubmit="sendInboundMailWebhook(); return false">
+
+ <input type="hidden" name="payload" id="payload">
+ <input type="hidden" id="content-type" name="header:Content-Type" value="message/rfc822">
+ <input type="hidden" id="content-length" name="header:Content-Length">
+
+ <div class="fieldset">
+ <label for="from">From:</label>
+ <input type="text" id="from" name="from" size="40">
+ </div>
+
+ <div class="fieldset">
+ <label for="to">To:</label>
+ <input type="text" id="to" name="to" size="40">
+ </div>
+
+ <div class="fieldset">
+ <label for="cc">Cc:</label>
+ <input type="text" id="cc" name="cc" size="40">
+ </div>
+
+ <div class="fieldset">
+ <label for="subject">Subject:</label>
+ <input type="text" id="subject" name="subject" size="40">
+ </div>
+
+ <div id="body-c" class="fieldset">
+ <label for="body">Message body (plain text):</label>
+ <textarea id="body" name="body" rows="10" cols="50"></textarea>
+ </div>
+
+ <div id="inboundmail-submit">
+ <input type="submit" value="Send Email">
+ </div>
+
+ </form>
+</div>
+
+{% 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+<title>Results</title>
+<style type="text/css">
+body {
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: #f5f5f5;
+}
+#output {
+ font-family: monospace;
+ font-size: 10pt;
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+ white-space: -moz-pre-wrap;
+ white-space: -pre-wrap;
+ white-space: -o-pre-wrap;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+</style>
+</head>
+<body>
+<pre id="output">{{ output|escape }}</pre>
+</body>
+</html>
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 %}
+ <span class="item"><a href="">Interactive Console</a></span>
+{% endblock %}
+
+{% block head %}
+ <style type="text/css">
+
+ #console {
+ width: 100%;
+ border-collapse: collapse;
+ }
+
+ #console td {
+ width: 50%;
+ padding: 0;
+ border: 0;
+ vertical-align: top;
+ padding-right: 25px;
+ }
+
+ #code {
+ overflow: auto;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ }
+
+ #output {
+ border: 1px solid silver;
+ background-color: #f5f5f5;
+ overflow: auto;
+ }
+
+ #code, #output {
+ font-family: monospace;
+ font-size: 10pt;
+ height: 25em;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ }
+
+ #submitbutton {
+ text-align: center;
+ margin-top: 1em;
+ }
+ </style>
+{% endblock %}
+
+{% block body %}
+<h3>Interactive Console</h3>
+<form action="{{ interactive_execute_path }}" target="output" method="post">
+ <table id="console">
+ <tr>
+ <td>
+ <textarea id="code" name="code" wrap="off" rows="20" cols="80">from google.appengine.api import users
+
+# Say hello to the current user
+user = users.get_current_user()
+if user:
+ nickname = user.nickname()
+else:
+ nickname = "guest"
+print "Hello, " + nickname
+
+</textarea>
+ </td>
+ <td>
+ <iframe name="output" id="output"></iframe>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div id="submitbutton"><input type="submit" value="Run Program"/></div>
+ </td>
+ </tr>
+ </table>
+</form>
+{% endblock %}
+
+{% block final %}
+<script type="text/javascript">
+//<![CDATA[
+var iframe = document.getElementById('output');
+var idoc = null;
+if (iframe.contentDocument) {
+ // DOM
+ idoc = iframe.contentDocument;
+} else if (iframe.contentWindow) {
+ // IE
+ idoc = iframe.contentWindow.document;
+}
+if (idoc) {
+ idoc.open();
+ idoc.write('<html><body style="background-color:#f5f5f5;margin:0;padding:0"><pre style="margin:0;padding:0;color:#888">Press "Run Program" to see the<br/>output of your code in this frame!</pre></body></html>');
+ idoc.close();
+}
+document.getElementById('code').focus();
+//]]>
+</script>
+{% 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..7eb348e
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/templates/js/webhook.js
@@ -0,0 +1,87 @@
+// 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);
+ this.headers[header] = 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) {
+ req.setRequestHeader(key, this.headers[key]);
+ };
+ req.send(this.payload);
+ } catch (e) {
+ callback(this, req, e);
+ return;
+ }
+
+ // If the responseText matches our <form action="/_ah/login then the
+ // user is not logged in as an Administrator so we'll fake the request.
+ if (req.responseText.match(/<form[^>]+_ah\/login/)) {
+ var fakeReq = {
+ 'status': 403,
+ 'responseText': 'Current logged in user is not authorized ' +
+ 'to view this page'
+ }
+ fakeReq.getAllResponseHeaders = function(){};
+ callback(this, fakeReq, null);
+ } else {
+ callback(this, req, null);
+ }
+};
+
+Webhook.prototype.run = function(callback) {
+ var error = this.parse();
+ if (error != '') {
+ callback(this, null, error);
+ } else {
+ this.send(callback);
+ }
+};
diff --git a/google_appengine/google/appengine/ext/admin/templates/memcache.html b/google_appengine/google/appengine/ext/admin/templates/memcache.html
new file mode 100644
index 0000000..55f869a
--- /dev/null
+++ b/google_appengine/google/appengine/ext/admin/templates/memcache.html
@@ -0,0 +1,119 @@
+{% extends "base.html" %}
+
+{% block title %}{{ application_name }} Development Console - Memcache Viewer{% endblock %}
+
+{% block head %}
+ <style type="text/css">{% include "css/memcache.css" %}</style>
+{% endblock %}
+
+{% block breadcrumbs %}
+ <span class="item"><a href="">Memcache Viewer</a></span>
+{% endblock %}
+
+{% block body %}
+<h3>Memcache Viewer</h3>
+
+{% if message %}
+<div class="message">
+{{ message|escape }}
+</div>
+{% endif %}
+
+{% if show_stats %}
+<div id="stats">
+ <ul>
+ <li>Hit ratio: {{ hitratio }}% ({{ stats.hits }} hit{{ stats.hits|pluralize }} and {{ stats.misses }} miss{{ stats.misses|pluralize:"es" }})</li>
+ <li>Size of cache: {{ stats.items }} item{{ stats.items|pluralize }}, {{ stats.bytes|filesizeformat }}
+ <form id="flush_form" action="{{ request.path }}" method="post">
+ <input type="submit" name="action:flush" value="Flush Cache" onclick="return confirm('Are you sure you want to flush all keys from the cache?');"/>
+ </form>
+ </li>
+ <li>Cache contains items up to {{ oldest_item_age|timesince }} old.</li>
+ </ul>
+</div>
+
+<div id="memcache_search">
+ <form action="{{ request.path }}" method="post">
+ <span class="field">
+ <span class="name">Key:</span>
+ <span class="value"><input id="key_input" name="key" type="text" size="40" value="{{ key|escape }}"/></span>
+ </span>
+ <span class="buttons">
+ <input type="submit" name="action:display" value="Display"/>
+ <input type="submit" name="action:edit" value="Edit/Create"/>
+ <input type="submit" name="action:delete" value="Delete" onclick="return confirm('Are you sure you want to permanently delete this key?');"/>
+ </span>
+ </form>
+</div>
+{% endif %}
+
+{% if show_value %}
+{% if key_exists %}
+{% ifequal type "error" %}
+<div class="message">Error fetching {{ key|escape }}: {{ value|escape }}</div>
+{% else %}
+<div id="value_display">
+ <div id="value_display_key">"<b>{{ key|escape }}</b>" is a <b>{{ type|escape }}</b>:</div>
+ <pre id="value_display_value">{{ value|escape }}</pre>
+</div>
+{% endifequal %}
+{% else %}
+<div class="message">No such key: {{ key|escape }}</div>
+{% endif %}
+{% endif %}
+
+{% if show_valueform %}
+<div id="memcache_edit">
+ <form action="{{ request.path }}" method="post">
+ <table>
+ <tr>
+ <th>Key</th>
+ <td>
+ <input name="key" type="hidden" value="{{ key|escape }}"/>
+ {{ key|escape }}
+ </td>
+ </tr>
+ <tr>
+ <th>Type</th>
+ <td>
+ {% if key_exists %}
+ <input name="type" type="hidden" value="{{ type|escape }}"/>
+ {{ type|escape }}
+ {% else %}
+ <select name="type" size="1">
+ {% for typeopt in types %}
+ <option>{{ typeopt }}</option>
+ {% endfor %}
+ </select>
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <th id="value_key"><div id="value_key_text">Value</div></th>
+ <td>
+ <textarea id="value_input" name="value" cols="80" rows="20"{% if not writable %} readonly{% endif %}>{{ value|default_if_none:""|escape }}</textarea>
+ </td>
+ </tr>
+ <tr>
+ <th>&nbsp;</th>
+ <td>
+ {% if writable %}
+ <input type="submit" name="action:save" value="Save"/>
+ {% endif %}
+ <input type="submit" name="action:cancel" value="Cancel"/>
+ </td>
+ </tr>
+ </table>
+ </form>
+</div>
+{% endif %}
+
+{% endblock %}
+
+{% block final %}
+<script type="text/javascript">
+//<![CDATA[
+document.getElementById('key_input').focus();
+//]]>
+</script>
+{% 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 %}<a href="{{ start_base_url }}&amp;start={{ prev_start }}">&lsaquo; Previous</a>{% endifnotequal %}
+&nbsp;
+{% for page in pages %}
+ <a {% ifequal page.number current_page %} class="ae-page-selected ae-page-number" {% endifequal %}
+ href="{{ start_base_url }}&amp;start={{ page.start }}">{{ page.number }}</a>
+{% endfor %}
+&nbsp;
+{% ifnotequal next_start -1 %}<a href="{{ start_base_url }}&amp;start={{ next_start }}">Next &rsaquo;</a>{% 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..a3d46f5
--- /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 %}
+ <style type="text/css">{% include "css/queues.css" %}</style>
+{% endblock %}
+
+{% block breadcrumbs %}
+ <span class="item"><a href="">Queue Viewer</a></span>
+{% endblock %}
+
+{% block body %}
+<h3>Task Queues</h3>
+
+{% if queues %}
+ <p>
+ Tasks will not run automatically. Select a queue to run tasks manually.
+ </p>
+
+ <table id="ah-queues" class="ae-table ae-table-striped">
+ <thead>
+ <tr>
+ <th>Queue Name</th>
+ <th>Maximum Rate</th>
+ <th>Bucket Size</th>
+ <th>Oldest Task (UTC)</th>
+ <th>Tasks in Queue</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for queue in queues %}
+ <tr class="{% cycle ae-odd,ae-even %}">
+ <td valign="top">
+ <a href="/_ah/admin/tasks?queue={{ queue.name|escape }}">
+ {{ queue.name|escape }}</a>
+ </td>
+ <td valign="top">
+ {{ queue.max_rate|escape }}
+ </td>
+ <td valign="top">
+ {{ queue.bucket_size|escape }}
+ </td>
+ <td valign="top">
+ {% if queue.oldest_task %}
+ {{ queue.oldest_task|escape }}<br/>
+ ({{ queue.eta_delta|escape }})
+ {% else %}
+ None
+ {% endif %}
+ </td>
+ <td valign="top">
+ {{ queue.tasks_in_queue|escape }}
+ </td>
+ <td valign="top">
+ <form id="flushform" action="/_ah/admin/queues" method="post">
+ <input type="hidden" name="queue" value="{{ queue.name|escape }}"/>
+ <input type="submit" name="action:flushqueue" value="Flush Queue"
+ onclick="return confirm('Are you sure you want to flush all ' +
+ 'tasks from {{ queue.name|escape }}?');"/>
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+{% 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..c4749a0
--- /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 %}
+ <style type="text/css">{% include "css/pager.css" %}</style>
+ <style type="text/css">{% include "css/tasks.css" %}</style>
+ <script type="text/javascript">
+ {% include "js/webhook.js" %}
+
+ var handleTaskResult = function(hook, req, error) {
+ if (error != null) {
+ return;
+ };
+ if (req == null) {
+ return;
+ };
+ if (req.status != 200) {
+ return;
+ };
+ var parts = hook.formId.split('.');// + [''];
+ var deleteForm = document.getElementById('deleteform.' + parts[1]);
+ if (deleteForm != null) {
+ deleteForm.submit();
+ };
+ };
+ </script>
+{% endblock %}
+
+{% block breadcrumbs %}
+ <span class="item"><a href="">Tasks Viewer</a></span>
+{% endblock %}
+
+{% block body %}
+<h3>Tasks for Queue: {{ queue_name|escape }}</h3>
+
+{% if tasks %}
+ <p>
+ Tasks will not run automatically. Push the 'Run' button to execute each task.
+ </p>
+
+ <table id="ah-tasks" class="ae-table ae-table-striped">
+ <thead>
+ <tr>
+ <th>Task Name</th>
+ <th>ETA (UTC)</th>
+ <th>Method</th>
+ <th>URL</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for task in tasks %}
+ <tr class="{% cycle ae-odd,ae-even %}">
+ <td valign="top">
+ {{ task.name|escape }}
+ </td>
+ <td valign="top">
+ {{ task.eta|escape }} ({{ task.eta_delta|escape }})
+ </td>
+ <td valign="top">
+ {{ task.method|escape }}
+ </td>
+ <td valign="top">
+ {{ task.url|escape }}
+ </td>
+ <td valign="top">
+ <form id="runform.{{ task.name|escape }}" action="{{ task.url|escape }}" method="{{ task.method|escape }}" onsubmit="(new Webhook('runform.{{ task.name|escape }}')).run(handleTaskResult); return false">
+ <input type="hidden" name="payload" value="{{ task.body|escape }}">
+ {% for header in task.headers.items %}
+ <input type="hidden" name="header:{{ header.0|escape }}"
+ value="{{ header.1|escape }}"/>
+ {% endfor %}
+ <input type="submit" value="Run"/>
+ </form>
+ </td>
+ <td valign="top">
+ <form id="deleteform.{{ task.name|escape }}" action="/_ah/admin/tasks" method="post">
+ <input type="hidden" name="queue" value="{{ queue_name|escape }}"/>
+ <input type="hidden" name="task" value="{{ task.name|escape }}"/>
+ <input type="hidden" name="action:deletetask" value="true"/>
+ <input type="submit" value="Delete"/>
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ <tr>
+ <td colspan="6" class="ae-pager" align="right">
+ {% include "pager.html" %}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+{% 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 %}
+ <span class="item"><a href="">XMPP</a></span>
+{% endblock %}
+
+{% block head %}
+ <style type="text/css">{% include "css/xmpp.css" %}</style>
+ <script type="text/javascript">
+ {% include "js/webhook.js" %}
+ {% include "js/multipart_form_data.js" %}
+
+ var xmppFeedbackEl;
+ var xmppForm;
+ var payloadEl;
+ var fromEl;
+ var toEl;
+ var chatEl;
+ var contentLengthEl;
+ var contentTypeEl;
+
+ var sendXmppWebhook = function() {
+
+ if (!xmppFeedbackEl) {
+ xmppFeedbackEl = document.getElementById('xmpp-feedback');
+ xmppForm = document.getElementById('xmpp-form');
+ fromEl = document.getElementById('from');
+ toEl = document.getElementById('to');
+ chatEl = document.getElementById('chat');
+ payloadEl = document.getElementById('payload');
+ contentTypeEl = document.getElementById('content-type');
+ }
+
+ var to = toEl.value;
+ var from = fromEl.value;
+ var body = chatEl.value;
+
+ if (!to || !from) {
+ xmppFeedbackEl.className = 'ae-errorbox';
+ xmppFeedbackEl.innerHTML = 'From and To are required.';
+ return;
+ }
+
+ xmppFeedbackEl.className = 'ae-message';
+ xmppFeedbackEl.innerHTML = 'Sending XMPP message...';
+
+ var formData = new MultipartFormData();
+ formData.addPart('to', to, null, 'form-data');
+ formData.addPart('from', from, null, 'form-data');
+ formData.addPart('body', body, null, 'form-data');
+ formData.addPart('stanza', buildXmlStanza(from, to, body), 'text/xml', 'form-data');
+
+ payloadEl.value = formData.toString();
+ contentTypeEl.value = 'multipart/form-data; boundary=' +
+ formData.boundary;
+
+ (new Webhook('xmpp-form')).run(handleXmppResult);
+
+ // Prevents actual form posts.
+ return false;
+ };
+
+ var handleXmppResult = function(hook, req, error) {
+ if (error != null || req == null || req.status != 200) {
+ xmppFeedbackEl.className = 'ae-errorbox';
+ xmppFeedbackEl.innerHTML = 'Message send failure<br>' +
+ req.responseText;
+ } else {
+ var timestamp;
+ var dateString = new Date().toString();
+ var match = dateString.match(/(\d\d:\d\d:\d\d).+\((.+)\)/);
+ if (!match || !match[0] || !match[2]) {
+ timestamp = dateString;
+ } else {
+ timestamp = match[1] + ' ' + match[2];
+ }
+
+ xmppFeedbackEl.className = 'ae-message';
+ xmppFeedbackEl.innerHTML = 'Message has been sent at ' + timestamp;
+ }
+ };
+
+ var buildXmlStanza = function(from, to, body) {
+ var xml = '<message from="' + from + '" '+
+ 'to="' + to + '">' +
+ '<body>' + body + '</body>' +
+ '</message>';
+ return xml;
+ };
+ </script>
+{% endblock %}
+
+{% block body %}
+<div id="xmpp">
+ <h3>XMPP</h3>
+ {% if xmpp_configured %}{% else %}
+ <div class="ae-errorbox">
+ XMPP is not yet configured properly in your app.yaml, in the services section.
+ </div>
+ {% endif %}
+ <div id="xmpp-feedback"></div>
+ <form id="xmpp-form"
+ action="/_ah/xmpp/message/chat/" method="post"
+ onsubmit="sendXmppWebhook(); return false">
+
+ <input type="hidden" name="payload" id="payload">
+ <input type="hidden" id="content-type" name="header:Content-Type">
+
+ <fieldset>
+ <input type="hidden" name="message_type" id="message-type-chat" value="chat">
+ <!--
+ <legend>Message Type:</legend>
+ <div class="radio">
+ <input type="radio" name="message_type" id="message-type-chat" value="chat">
+ <label for="message-type-chat">Chat message</label>
+ </div>
+
+ <div class="radio">
+ <input type="radio" name="message_type" id="message-type-xml" value="xml">
+ <label for="message-type-xml">XML stanza</label>
+ </div>
+
+ <div class="radio">
+ <input type="radio" name="message_type" id="message-type-presence" value="presence">
+ <label for="message-type-presence">Presence</label>
+ </div>
+ -->
+ </fieldset>
+
+ <div class="fieldset">
+ <label for="from">From:</label>
+ <input type="text" id="from" name="from" size="40">
+ </div>
+
+
+ <div class="fieldset">
+ <label for="to">To:</label>
+ <input type="text" id="to" name="to" size="40">
+ </div>
+
+
+ <div id="chat-c" class="fieldset">
+ <label for="chat">Chat (plain text):</label>
+ <textarea id="chat" name="chat" rows="10" cols="50"></textarea>
+ </div>
+
+ <!--
+ <div id="xml-c" class="fieldset">
+ <label for="xml">XML Stanza:</label>
+ <textarea id="xml" name="xml" rows="10" cols="50"></textarea>
+ </div>
+
+
+ <fieldset id="presence-c">
+ <legend>Presence:</legend>
+
+ <div class="radio">
+ <input type="radio" id="presence-online" name="presence" value="online">
+ <label for="presence-online">Online</label>
+ </div>
+
+ <div class="radio">
+ <input type="radio" id="presence-offline" name="presence" value="offline">
+ <label for="presence-offline">Offline</label>
+ </div>
+ </div>
+ -->
+
+ <div id="xmpp-submit">
+ <input type="submit" value="Send Message">
+ </div>
+
+ </form>
+</div>
+<!--
+<script type="text/javascript">
+ var messageTypes = ['chat', 'xml', 'presence'];
+
+ var messageTypeEls = [];
+ for (var i = 0, messageType; messageType = messageTypes[i]; i++) {
+ var messageTypeEl = document.getElementById('message-type-' +
+ messageType);
+ messageTypeEls.push(messageTypeEl);
+ }
+
+ // Initializes the chosen type to be the first radio.
+ var chosenMessageTypeId = messageTypeEls[0].id;
+
+ var messageTypeDict = {};
+ for (var i = 0, messageTypeEl; messageTypeEl = messageTypeEls[i]; i++) {
+ var type = messageTypeEl.id.replace('message-type-', '');
+ var formEl = document.getElementById(type + '-c');
+ messageTypeDict[messageTypeEl.id] = formEl;
+ // Initially hides all of the conditional form elements.
+ formEl.style.display = 'none';
+ }
+
+ var setChosenMessageType = function(messageTypeId) {
+ document.getElementById(messageTypeId).checked = true;
+
+ // Hides previously chosen message type
+ messageTypeDict[chosenMessageTypeId].style.display = 'none';
+
+ // Sets the new chosen type and shows its field.
+ chosenMessageTypeId = messageTypeId;
+ messageTypeDict[chosenMessageTypeId].style.display = '';
+ }
+
+ var messageTypeClickHandler = function(e) {
+ for (var i = 0, messageTypeEl; messageTypeEl = messageTypeEls[i]; i++) {
+ if (messageTypeEl.checked) {
+ setChosenMessageType(messageTypeEl.id);
+ break;
+ }
+ }
+ };
+
+ // set up event listeners
+ for (var i = 0, messageTypeEl; messageTypeEl = messageTypeEls[i]; i++) {
+ messageTypeEl.onclick = messageTypeClickHandler;
+ }
+
+ // Init
+ setChosenMessageType(chosenMessageTypeId);
+
+</script>
+-->
+
+{% endblock %}
+
+{% block final %}
+{% endblock %}
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..75a899a
--- /dev/null
+++ b/google_appengine/google/appengine/ext/bulkload/__init__.py
@@ -0,0 +1,435 @@
+#!/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 mix-in handler for bulk loading data into an application.
+
+For complete documentation, see the Tools and Libraries section of the
+documentation.
+
+To use this in your app, first write a script, e.g. bulkload.py, that
+instantiates a Loader for each entity kind you want to import and call
+bulkload.main(instance). For example:
+
+person = bulkload.Loader(
+ 'Person',
+ [('name', str),
+ ('email', datastore_types.Email),
+ ('cool', bool), # ('0', 'False', 'No', '')=False, otherwise bool(value)
+ ('birthdate', lambda x: datetime.datetime.fromtimestamp(float(x))),
+ ])
+
+if __name__ == '__main__':
+ bulkload.main(person)
+
+See the Loader class for more information. Then, add a handler for it in your
+app.yaml, e.g.:
+
+ handlers:
+ - url: /load
+ script: bulkload.py
+ login: admin
+
+Finally, deploy your app and run bulkloader.py. For example, to load the
+file people.csv into a dev_appserver running on your local machine:
+
+./bulkloader.py --filename people.csv --kind Person --cookie ... \
+ --url http://localhost:8080/load
+
+The kind parameter is used to look up the Loader instance that will be used.
+The bulkload handler should usually be admin_only, so that non-admins can't use
+the shell to modify your app's data. The bulkload client uses the cookie
+parameter to piggyback its HTTP requests on your login session. A GET request
+to the URL specified for your bulkload script will give you a cookie parameter
+you can use (/load in the example above). If your bulkload handler is not
+admin_only, you may omit the cookie parameter.
+
+If you want to do extra processing before the entities are stored, you can
+subclass Loader and override HandleEntity. HandleEntity is called once with
+each entity that is imported from the CSV data. You can return one or more
+entities from HandleEntity to be stored in its place, or None if nothing
+should be stored.
+
+For example, this loads calendar events and stores them as
+datastore_entities.Event entities. It also populates their author field with a
+reference to the corresponding datastore_entites.Contact entity. If no Contact
+entity exists yet for the given author, it creates one and stores it first.
+
+class EventLoader(bulkload.Loader):
+ def __init__(self):
+ EventLoader.__init__(self, 'Event',
+ [('title', str),
+ ('creator', str),
+ ('where', str),
+ ('startTime', lambda x:
+ datetime.datetime.fromtimestamp(float(x))),
+ ])
+
+ def HandleEntity(self, entity):
+ event = datastore_entities.Event(entity.title)
+ event.update(entity)
+
+ creator = event['creator']
+ if creator:
+ contact = datastore.Query('Contact', {'title': creator}).Get(1)
+ if not contact:
+ contact = [datastore_entities.Contact(creator)]
+ datastore.Put(contact[0])
+ event['author'] = contact[0].key()
+
+ return event
+
+if __name__ == '__main__':
+ bulkload.main(EventLoader())
+"""
+
+
+
+
+
+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 = """
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html><head>
+<title>Bulk Loader</title>
+</head><body>"""
+
+ page += ('The bulk load endpoint is: <a href="%s">%s</a><br />\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 += '</body></html>'
+ 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/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/db/__init__.py b/google_appengine/google/appengine/ext/db/__init__.py
new file mode 100755
index 0000000..365c4fd
--- /dev/null
+++ b/google_appengine/google/appengine/ext/db/__init__.py
@@ -0,0 +1,2959 @@
+#!/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 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 users
+
+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
+
+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
+
+_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(
+ name for name, prop in model_class._properties.items() if not prop.indexed)
+
+
+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,
+ key=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.
+ key: Key instance for this instance, overrides parent and key_name
+ _from_entity: Intentionally undocumented.
+ args: Keyword arguments mapping to properties of model.
+ """
+ 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 key_name is not None:
+ raise BadArgumentError('Cannot use key and key_name at the same time')
+ if parent is not None:
+ raise BadArgumentError('Cannot use key and parent at the same time')
+ 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
+
+ self._entity = None
+ self._app = _app
+
+ 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)
+ 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
+
+ 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):
+ """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.
+ """
+ self._populate_internal_entity()
+ return datastore.Put(self._entity)
+
+ 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,
+ '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):
+ """Deletes this entity from the datastore.
+
+ Raises:
+ TransactionFailedError if the data could not be committed.
+ """
+ datastore.Delete(self.key())
+ 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):
+ """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'.
+ """
+ results = get(keys)
+ 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):
+ """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.
+ """
+ if isinstance(parent, Model):
+ parent = parent.key()
+ 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)
+ else:
+ return get(*keys)
+
+ @classmethod
+ def get_by_id(cls, ids, parent=None):
+ """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.
+ """
+ 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)
+ else:
+ return get(*keys)
+
+ @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 <entity>' 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)
+ instance = cls(None, _from_entity=True, **entity_values)
+ if entity.is_saved():
+ instance._entity = entity
+ del instance._key_name
+ del instance._key
+ elif entity.key().has_id_or_name():
+ instance._key = entity.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 get(keys):
+ """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.
+ """
+ keys, multiple = datastore.NormalizeAndTypeCheckKeys(keys)
+ try:
+ entities = datastore.Get(keys)
+ 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):
+ """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.
+ """
+ models, multiple = datastore.NormalizeAndTypeCheck(models, Model)
+ entities = [model._populate_internal_entity() for model in models]
+ keys = datastore.Put(entities)
+ if multiple:
+ return keys
+ assert len(keys) == 1
+ return keys[0]
+
+save = put
+
+
+def delete(models):
+ """Delete one or more Model instances.
+
+ Args:
+ models_or_keys: Model instance or list of Model instances.
+
+ Raises:
+ TransactionFailedError if the data could not be committed.
+ """
+ models_or_keys, multiple = datastore.NormalizeAndTypeCheck(
+ models, (Model, Key, basestring))
+ keys = []
+ for model_or_key in models_or_keys:
+ if isinstance(model_or_key, Model):
+ key = model_or_key = model_or_key.key()
+ elif isinstance(model_or_key, basestring):
+ key = model_or_key = Key(model_or_key)
+ else:
+ key = model_or_key
+ keys.append(key)
+ datastore.Delete(keys)
+
+def allocate_ids(model, size):
+ """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, Key or string to serve as a model specifying the ID sequence
+ in which to allocate IDs
+
+ Returns:
+ (start, end) of the allocated range, inclusive.
+ """
+ models_or_keys, multiple = datastore.NormalizeAndTypeCheck(
+ model, (Model, Key, basestring))
+ keys = []
+ for model_or_key in models_or_keys:
+ if isinstance(model_or_key, Model):
+ key = model_or_key = model_or_key.key()
+ elif isinstance(model_or_key, basestring):
+ key = model_or_key = Key(model_or_key)
+ else:
+ key = model_or_key
+ keys.append(key)
+ return datastore.AllocateIds(keys, size)
+
+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 = {}
+ for prop, value in kwds.iteritems():
+ if prop not in self.properties() and value is not None:
+ setattr(self, prop, value)
+
+ 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 key not in self.properties():
+ 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 __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.
+ """
+ if self._dynamic_properties and key in self._dynamic_properties:
+ return self._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(self.properties().iterkeys())
+ 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
+
+
+class _BaseQuery(object):
+ """Base class for both Query and GqlQuery."""
+
+ def __init__(self, model_class=None, keys_only=False):
+ """Constructor.
+
+ Args:
+ model_class: Model class from which entities are constructed.
+ keys_only: Whether the query should return full entities or only keys.
+ """
+ self._model_class = model_class
+ self._keys_only = keys_only
+
+ 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):
+ """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.
+
+ Returns:
+ Iterator for this query.
+ """
+ iterator = self._get_query().Run()
+ 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):
+ """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.
+ """
+ results = self.fetch(1)
+ try:
+ return results[0]
+ except IndexError:
+ return None
+
+ def count(self, limit=None):
+ """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.
+ """
+ return self._get_query().Count(limit=limit)
+
+ def fetch(self, limit, offset=0):
+ """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.
+
+ Returns:
+ A list of db.Model instances. There may be fewer than 'limit'
+ results if there aren't enough results to satisfy the request.
+ """
+ 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')
+ if limit == 0:
+ return []
+ raw = self._get_query().Get(limit, offset)
+
+ 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 __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):
+ """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.
+ """
+ super(Query, self).__init__(model_class, keys_only)
+ 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)
+ 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.copy(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)
+
+ self._proto_query = gql.GQL(query_string, _app=app)
+ 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):
+ """Override _BaseQuery.run() so the LIMIT clause is handled properly."""
+ query_run = self._proto_query.Run(*self._args, **self._kwds)
+ if self._keys_only:
+ return query_run
+ else:
+ return _QueryIterator(self._model_class, iter(query_run))
+
+ def _get_query(self):
+ return self._proto_query.Bind(self._args, self._kwds)
+
+
+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 <reference>_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')
+
+
+run_in_transaction = datastore.RunInTransaction
+run_in_transaction_custom_retries = datastore.RunInTransactionCustomRetries
+
+RunInTransaction = run_in_transaction
+RunInTransactionCustomRetries = run_in_transaction_custom_retries
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 0000000..8e9880f
--- /dev/null
+++ b/google_appengine/google/appengine/ext/db/__init__.pyc
Binary files differ
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..98f347f
--- /dev/null
+++ b/google_appengine/google/appengine/ext/db/djangoforms.py
@@ -0,0 +1,886 @@
+#!/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 | <iterable> | 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.Property):
+ """This class exists solely to log a warning when it is used."""
+
+ def __init__(self, *args, **kwds):
+ logging.warn("Please don't use modelforms.UserProperty; "
+ "use db.UserProperty instead.")
+ super(UserProperty, self).__init__(*args, **kwds)
+
+
+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 <entity>' 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 0000000..3c39e4f
--- /dev/null
+++ b/google_appengine/google/appengine/ext/db/polymodel.pyc
Binary files differ
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..55fc5ee
--- /dev/null
+++ b/google_appengine/google/appengine/ext/deferred/__init__.py
@@ -0,0 +1,22 @@
+#!/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 *
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..4851a6d
--- /dev/null
+++ b/google_appengine/google/appengine/ext/deferred/deferred.py
@@ -0,0 +1,267 @@
+#!/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/__init__.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 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, _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.
+ """
+ taskargs = dict((x, kwargs.pop(("_%s" % x), None))
+ for x in ("countdown", "eta", "name"))
+ taskargs["url"] = kwargs.pop("_url", _DEFAULT_URL)
+ taskargs["headers"] = _TASKQUEUE_HEADERS
+ queue = kwargs.pop("_queue", _DEFAULT_QUEUE)
+ pickled = serialize(obj, *args, **kwargs)
+ try:
+ task = taskqueue.Task(payload=pickled, **taskargs)
+ task.add(queue)
+ except taskqueue.TaskTooLargeError:
+ key = _DeferredTaskEntity(data=pickled).put()
+ pickled = serialize(run_from_datastore, str(key))
+ task = taskqueue.Task(payload=pickled, **taskargs)
+ 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():
+ run_wsgi_app(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 @@
+<!-- Unusual layout is to ensure template is useful with tags stripped, too -->
+<html><head><title>Daily exception report for app "{{app_id}}", major version "{{major_version}}".</title></head>
+<body><p>At least {{occurrence_count}} occurrences of {{exception_count}} exceptions across {{version_count}} versions.</p>
+{% for version in versions %}
+<h1>Minor version {{version.0}}</h1>
+{% for exception in version.1 %}
+<h2>{{exception.signature}} (at least {{exception.count}} occurrences)</h2>
+ <table><tr><th>Handler:</th> <td>{{exception.handler}}</td></tr>
+ <tr><th>URL:</th> <td>{{exception.method|escape}} {{exception.url|escape}}</td></tr>
+ <tr><th>Stacktrace:</th>
+
+<td><pre>{{exception.stacktrace|escape}}</pre></td></tr></table>
+
+
+{% endfor %}{% endfor %}</body> \ 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..8648587
--- /dev/null
+++ b/google_appengine/google/appengine/ext/gql/__init__.py
@@ -0,0 +1,1151 @@
+#!/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 <entity>]
+ [WHERE <condition> [AND <condition> ...]]
+ [ORDER BY <property> [ASC | DESC] [, <property> [ASC | DESC] ...]]
+ [LIMIT [<offset>,]<count>]
+ [OFFSET <offset>]
+ [HINT (ORDER_FIRST | HINT FILTER_FIRST | HINT ANCESTOR_FIRST)]
+
+ <condition> := <property> {< | <= | > | >= | = | != | IN} <value>
+ <condition> := <property> {< | <= | > | >= | = | != | IN} CAST(<value>)
+ <condition> := <property> IN (<value>, ...)
+ <condition> := ANCESTOR IS <entity or key>
+
+ 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, and strings)
+ 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):
+ """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.
+
+ 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.__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):
+ """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))
+
+ 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, *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(values, 'user', 'requires one and only one value')
+ 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, err)
+
+ 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 = 0
+ if self.__offset != -1:
+ 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 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 <Entity Name>
+ 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 condition.lower() != 'in' and operator == 'list':
+ 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)
+ 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/gql/__init__.pyc b/google_appengine/google/appengine/ext/gql/__init__.pyc
new file mode 100644
index 0000000..f73e391
--- /dev/null
+++ b/google_appengine/google/appengine/ext/gql/__init__.pyc
Binary files differ
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..4c6b632
--- /dev/null
+++ b/google_appengine/google/appengine/ext/key_range/__init__.py
@@ -0,0 +1,570 @@
+#!/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 EmptyDbQuery(db.Query):
+ """A query that returns no results."""
+
+ def get(self):
+ return None
+
+ def fetch(self, limit=1000, offset=0):
+ return []
+
+ def count(self, limit=1000):
+ return 0
+
+
+class EmptyDatastoreQuery(datastore.Query):
+ """A query that returns no results."""
+
+ def __init__(self, kind):
+ datastore.Query.__init__(self, kind)
+
+ def _Run(self, *unused_args, **unused_kwargs):
+ empty_result_pb = datastore_pb.QueryResult()
+ empty_result_pb.set_cursor(0)
+ empty_result_pb.set_more_results(False)
+ return datastore.Iterator(empty_result_pb)
+
+ def Count(self, *unused_args, **unused_kwargs):
+ return 0
+
+ def Get(self, *unused_args, **unused_kwargs):
+ return []
+
+ def Next(self, *unused_args, **unused_kwargs):
+ return []
+
+
+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):
+ """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
+
+ 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-%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 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 or an empty query if
+ this key range is empty.
+ """
+ assert isinstance(query, db.Query)
+ if self.key_start == self.key_end and not (
+ self.include_start or self.include_end):
+ return EmptyDbQuery()
+ 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 or an empty query if
+ this key range is empty.
+ """
+ assert isinstance(query, datastore.Query)
+ if self.key_start == self.key_end and not (
+ self.include_start or self.include_end):
+ return EmptyDatastoreQuery(query.kind)
+ 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):
+ """Construct a query for this key range, including the scan direction.
+
+ Args:
+ kind_class: A kind implementation class.
+
+ 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)
+ query.order('%s__key__' % direction)
+
+ query = self.filter_query(query)
+ return query
+
+ def make_directed_datastore_query(self, kind):
+ """Construct a query for this key range, including the scan direction.
+
+ Args:
+ kind: A string.
+
+ 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)
+ query.Order(('__key__', direction))
+
+ query = self.filter_datastore_query(query)
+ return query
+
+ def make_ascending_query(self, kind_class):
+ """Construct a query for this key range without setting the scan direction.
+
+ Args:
+ kind_class: A kind implementation class.
+
+ Returns:
+ A db.Query instance.
+ """
+ query = db.Query(kind_class)
+ query.order('__key__')
+
+ query = self.filter_query(query)
+ return query
+
+ def make_ascending_datastore_query(self, kind):
+ """Construct a query for this key range without setting the scan direction.
+
+ Args:
+ kind: A string.
+
+ Returns:
+ A datastore.Query instance.
+ """
+ query = datastore.Query(kind)
+ 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)
+ 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)
+
+ @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)
+
+ 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
+
+ return simplejson.dumps({
+ "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,
+ }, 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"])
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 0000000..dd44920
--- /dev/null
+++ b/google_appengine/google/appengine/ext/key_range/__init__.pyc
Binary files differ
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 0000000..ab8361b
--- /dev/null
+++ b/google_appengine/google/appengine/ext/remote_api/__init__.pyc
Binary files differ
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..a5cceb4
--- /dev/null
+++ b/google_appengine/google/appengine/ext/remote_api/handler.py
@@ -0,0 +1,319 @@
+#!/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 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 datastore_errors
+from google.appengine.api import mail_service_pb
+from google.appengine.api import urlfetch_service_pb
+from google.appengine.api import users
+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
+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.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 _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.
+ """
+ runquery_response = datastore_pb.QueryResult()
+ apiproxy_stub_map.MakeSyncCall('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())
+ apiproxy_stub_map.MakeSyncCall('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.
+ """
+ tx = datastore_pb.Transaction()
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'BeginTransaction',
+ api_base_pb.VoidProto(), 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()
+ apiproxy_stub_map.MakeSyncCall('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)
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Put',
+ put_request, response)
+
+ if request.has_deletes():
+ delete_request = request.deletes()
+ delete_request.mutable_transaction().CopyFrom(tx)
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Delete',
+ delete_request, api_base_pb.VoidProto())
+
+ apiproxy_stub_map.MakeSyncCall('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()
+
+ tx = datastore_pb.Transaction()
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'BeginTransaction',
+ api_base_pb.VoidProto(), tx)
+
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Put', request, response)
+
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Rollback', tx,
+ api_base_pb.VoidProto())
+
+
+SERVICE_PB_MAP = {
+ '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),
+ },
+ '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),
+ },
+ 'urlfetch': {
+ 'Fetch': (urlfetch_service_pb.URLFetchRequest,
+ urlfetch_service_pb.URLFetchResponse),
+ },
+}
+
+
+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, datastore_errors.Error):
+ application_error = response.mutable_application_error()
+ application_error.setCode(e.application_error)
+ application_error.setDetail(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 """
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html><head>
+<title>App Engine API endpoint.</title>
+</head><body>
+<h1>App Engine API endpoint.</h1>
+<p>This is an endpoint for the App Engine remote API interface.
+Point your stubs (google.appengine.ext.remote_api.remote_api_stub) here.</p>
+</body>
+</html>"""
+
+
+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 100644
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 0000000..57b1989
--- /dev/null
+++ b/google_appengine/google/appengine/ext/remote_api/remote_api_pb.pyc
Binary files differ
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..eeeea66
--- /dev/null
+++ b/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py
@@ -0,0 +1,499 @@
+#!/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 directly. Fetching as many
+ results as you will need is faster and more efficient.
+- If you need to iterate, consider instead fetching items in batches with a sort
+ order and constructing a new query starting from where the previous one left
+ off. The __key__ pseudo-property can be used as a sort key for this purpose,
+ and does not even require a custom index if you are iterating over all
+ entities of a given type.
+- 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.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):
+ """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
+
+ 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)
+ 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)
+
+ try:
+ if response_pb.has_application_error():
+ error_pb = response_pb.application_error()
+ raise datastore._ToDatastoreError(
+ 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())
+ finally:
+ self._PostHookHandler(service, call, request, response)
+
+ 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):
+ super(RemoteDatastoreStub, self).__init__(server, path)
+ 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):
+ self.__local_cursor_lock.acquire()
+ try:
+ cursor_id = self.__next_local_cursor
+ self.__next_local_cursor += 1
+ finally:
+ self.__local_cursor_lock.release()
+ query.clear_count()
+ self.__queries[cursor_id] = query
+
+ query_result.mutable_cursor().set_cursor(cursor_id)
+ query_result.set_more_results(True)
+ query_result.set_keys_only(query.keys_only())
+
+ def _Dynamic_Next(self, next_request, query_result):
+ cursor = next_request.cursor().cursor()
+ if cursor not in self.__queries:
+ raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST,
+ 'Cursor %d not found' % cursor)
+ query = self.__queries[cursor]
+
+ if query is None:
+ query_result.set_more_results(False)
+ return
+
+ request = datastore_pb.Query()
+ request.CopyFrom(query)
+ if request.has_limit():
+ request.set_limit(min(request.limit(), next_request.count()))
+ else:
+ request.set_limit(next_request.count())
+ request.set_count(request.limit())
+
+ super(RemoteDatastoreStub, self).MakeSyncCall(
+ 'remote_datastore', 'RunQuery', request, query_result)
+
+ query.set_offset(query.offset() + query_result.result_size())
+ if query.has_limit():
+ query.set_limit(query.limit() - query_result.result_size())
+ if not query_result.more_results():
+ self.__queries[cursor] = None
+
+ 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,))
+
+ 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.')
+
+
+def ConfigureRemoteApi(app_id,
+ path,
+ auth_func,
+ servername=None,
+ rpc_server_factory=appengine_rpc.HttpRpcServer,
+ rtok=None,
+ secure=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
+ <app_id>.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.
+
+ 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(), 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']
+
+ os.environ['APPLICATION_ID'] = app_id
+ apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()
+ datastore_stub = RemoteDatastoreStub(server, path)
+ apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore_stub)
+ stub = RemoteStub(server, path)
+ for service in ['capability_service', 'images', 'mail', 'memcache',
+ 'urlfetch']:
+ 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 0000000..0f773c2
--- /dev/null
+++ b/google_appengine/google/appengine/ext/remote_api/remote_api_stub.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/ext/remote_api/throttle.py b/google_appengine/google/appengine/ext/remote_api/throttle.py
new file mode 100755
index 0000000..f9454bf
--- /dev/null
+++ b/google_appengine/google/appengine/ext/remote_api/throttle.py
@@ -0,0 +1,637 @@
+#!/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.
+#
+
+"""Client-side transfer throttling for use with remote_api_stub.
+
+This module is used to configure rate limiting for programs accessing
+AppEngine services through remote_api.
+
+See the Throttle class for more information.
+
+An example with throttling:
+---
+from google.appengine.ext import db
+from google.appengine.ext.remote_api import remote_api_stub
+from google.appengine.ext.remote_api import throttle
+from myapp import models
+import getpass
+import threading
+
+def auth_func():
+ return (raw_input('Username:'), getpass.getpass('Password:'))
+
+remote_api_stub.ConfigureRemoteDatastore('my-app', '/remote_api', auth_func)
+full_throttle = throttle.DefaultThrottle(multiplier=1.0)
+throttle.ThrottleRemoteDatastore(full_throttle)
+
+# Register any threads that will be using the datastore with the throttler
+full_throttle.Register(threading.currentThread())
+
+# Now you can access the remote datastore just as if your code was running on
+# App Engine, and you don't need to worry about exceeding quota limits!
+
+houses = models.House.all().fetch(100)
+for a_house in houses:
+ a_house.doors += 1
+db.put(houses)
+---
+
+This example limits usage to the default free quota levels. The multiplier
+kwarg to throttle.DefaultThrottle can be used to scale the throttle levels
+higher or lower.
+
+Throttles can also be constructed directly for more control over the limits
+for different operations. See the Throttle class and the constants following
+it for details.
+"""
+
+
+import logging
+import threading
+import time
+import urllib2
+import urlparse
+
+from google.appengine.api import apiproxy_stub_map
+from google.appengine.ext.remote_api import remote_api_stub
+from google.appengine.tools import appengine_rpc
+
+logger = logging.getLogger('google.appengine.ext.remote_api.throttle')
+
+MINIMUM_THROTTLE_SLEEP_DURATION = 0.001
+
+
+class Error(Exception):
+ """Base class for errors in this module."""
+
+
+class ThreadNotRegisteredError(Error):
+ """An unregistered thread has accessed the throttled datastore stub."""
+
+
+class UnknownThrottleNameError(Error):
+ """A transfer was added for an unknown throttle name."""
+
+
+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 hasattr(thread, 'exit_flag') and thread.exit_flag:
+ return
+
+
+class Throttle(object):
+ """A base class for upload rate throttling.
+
+ Transferring large number of entities, too quickly, could trigger
+ quota limits and cause the transfer process to halt. In order to
+ stay within the application's quota, we throttle the data transfer
+ to a specified limit (across all transfer threads).
+
+ This class tracks a moving average of some aspect of the transfer
+ rate (bandwidth, records per second, http connections per
+ second). It keeps two windows of counts of bytes transferred, on a
+ per-thread basis. One block is the "current" block, and the other is
+ the "prior" block. It will rotate the counts from current to prior
+ when ROTATE_PERIOD has passed. Thus, the current block will
+ represent from 0 seconds to ROTATE_PERIOD seconds of activity
+ (determined by: time.time() - self.last_rotate). The prior block
+ will always represent a full ROTATE_PERIOD.
+
+ Sleeping is performed just before a transfer of another block, and is
+ based on the counts transferred *before* the next transfer. It really
+ does not matter how much will be transferred, but only that for all the
+ data transferred SO FAR that we have interspersed enough pauses to
+ ensure the aggregate transfer rate is within the specified limit.
+
+ These counts are maintained on a per-thread basis, so we do not require
+ any interlocks around incrementing the counts. There IS an interlock on
+ the rotation of the counts because we do not want multiple threads to
+ multiply-rotate the counts.
+
+ There are various race conditions in the computation and collection
+ of these counts. We do not require precise values, but simply to
+ keep the overall transfer within the bandwidth limits. If a given
+ pause is a little short, or a little long, then the aggregate delays
+ will be correct.
+ """
+
+ ROTATE_PERIOD = 600
+
+ def __init__(self,
+ get_time=time.time,
+ thread_sleep=InterruptibleSleep,
+ layout=None):
+ self.get_time = get_time
+ self.thread_sleep = thread_sleep
+
+ self.start_time = get_time()
+ self.transferred = {}
+ self.prior_block = {}
+ self.totals = {}
+ self.throttles = {}
+
+ self.last_rotate = {}
+ self.rotate_mutex = {}
+ if layout:
+ self.AddThrottles(layout)
+
+ def AddThrottle(self, name, limit):
+ self.throttles[name] = limit
+ self.transferred[name] = {}
+ self.prior_block[name] = {}
+ self.totals[name] = {}
+ self.last_rotate[name] = self.get_time()
+ self.rotate_mutex[name] = threading.Lock()
+
+ def AddThrottles(self, layout):
+ for key, value in layout.iteritems():
+ self.AddThrottle(key, value)
+
+ def Register(self, thread):
+ """Register this thread with the throttler."""
+ thread_id = id(thread)
+ for throttle_name in self.throttles.iterkeys():
+ self.transferred[throttle_name][thread_id] = 0
+ self.prior_block[throttle_name][thread_id] = 0
+ self.totals[throttle_name][thread_id] = 0
+
+ def VerifyThrottleName(self, throttle_name):
+ if throttle_name not in self.throttles:
+ raise UnknownThrottleNameError('%s is not a registered throttle' %
+ throttle_name)
+
+ def AddTransfer(self, throttle_name, token_count):
+ """Add a count to the amount this thread has transferred.
+
+ Each time a thread transfers some data, it should call this method to
+ note the amount sent. The counts may be rotated if sufficient time
+ has passed since the last rotation.
+
+ Args:
+ throttle_name: The name of the throttle to add to.
+ token_count: The number to add to the throttle counter.
+ """
+ self.VerifyThrottleName(throttle_name)
+ transferred = self.transferred[throttle_name]
+ try:
+ transferred[id(threading.currentThread())] += token_count
+ except KeyError:
+ thread = threading.currentThread()
+ raise ThreadNotRegisteredError(
+ 'Unregistered thread accessing throttled datastore stub: id = %s\n'
+ 'name = %s' % (id(thread), thread.getName()))
+
+ if self.last_rotate[throttle_name] + self.ROTATE_PERIOD < self.get_time():
+ self._RotateCounts(throttle_name)
+
+ def Sleep(self, throttle_name=None):
+ """Possibly sleep in order to limit the transfer rate.
+
+ Note that we sleep based on *prior* transfers rather than what we
+ may be about to transfer. The next transfer could put us under/over
+ and that will be rectified *after* that transfer. Net result is that
+ the average transfer rate will remain within bounds. Spiky behavior
+ or uneven rates among the threads could possibly bring the transfer
+ rate above the requested limit for short durations.
+
+ Args:
+ throttle_name: The name of the throttle to sleep on. If None or
+ omitted, then sleep on all throttles.
+ """
+ if throttle_name is None:
+ for throttle_name in self.throttles:
+ self.Sleep(throttle_name=throttle_name)
+ return
+
+ self.VerifyThrottleName(throttle_name)
+
+ thread = threading.currentThread()
+
+ while True:
+ duration = self.get_time() - self.last_rotate[throttle_name]
+
+ total = 0
+ for count in self.prior_block[throttle_name].values():
+ total += count
+
+ if total:
+ duration += self.ROTATE_PERIOD
+
+ for count in self.transferred[throttle_name].values():
+ total += count
+
+ sleep_time = self._SleepTime(total, self.throttles[throttle_name],
+ duration)
+
+ if sleep_time < MINIMUM_THROTTLE_SLEEP_DURATION:
+ break
+
+ logger.debug('[%s] Throttling on %s. Sleeping for %.1f ms '
+ '(duration=%.1f ms, total=%d)',
+ thread.getName(), throttle_name,
+ sleep_time * 1000, duration * 1000, total)
+ self.thread_sleep(sleep_time)
+ if thread.exit_flag:
+ break
+ self._RotateCounts(throttle_name)
+
+ def _SleepTime(self, total, limit, duration):
+ """Calculate the time to sleep on a throttle.
+
+ Args:
+ total: The total amount transferred.
+ limit: The amount per second that is allowed to be sent.
+ duration: The amount of time taken to send the total.
+
+ Returns:
+ A float for the amount of time to sleep.
+ """
+ if not limit:
+ return 0.0
+ return max(0.0, (total / limit) - duration)
+
+ def _RotateCounts(self, throttle_name):
+ """Rotate the transfer counters.
+
+ If sufficient time has passed, then rotate the counters from active to
+ the prior-block of counts.
+
+ This rotation is interlocked to ensure that multiple threads do not
+ over-rotate the counts.
+
+ Args:
+ throttle_name: The name of the throttle to rotate.
+ """
+ self.VerifyThrottleName(throttle_name)
+ self.rotate_mutex[throttle_name].acquire()
+ try:
+ next_rotate_time = self.last_rotate[throttle_name] + self.ROTATE_PERIOD
+ if next_rotate_time >= 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):
+ for throttle_name in throttle_names:
+ self._DatastoreThrottler__throttle.Sleep(throttle_name)
+ 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())
+
+
+ _Prehook_Put = SleepHandler(ENTITIES_MODIFIED, INDEX_MODIFICATIONS)
+
+ 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 0000000..648c646
--- /dev/null
+++ b/google_appengine/google/appengine/ext/remote_api/throttle.pyc
Binary files differ
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/webapp/__init__.py b/google_appengine/google/appengine/ext/webapp/__init__.py
new file mode 100755
index 0000000..446475a
--- /dev/null
+++ b/google_appengine/google/appengine/ext/webapp/__init__.py
@@ -0,0 +1,580 @@
+#!/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 extremely simple WSGI web application framework.
+
+This module exports three primary classes: Request, Response, and
+RequestHandler. You implement a web application by subclassing RequestHandler.
+As WSGI requests come in, they are passed to instances of your RequestHandlers.
+The RequestHandler class provides access to the easy-to-use Request and
+Response objects so you can interpret the request and write the response with
+no knowledge of the esoteric WSGI semantics. Here is a simple example:
+
+ from google.appengine.ext import webapp
+ import wsgiref.simple_server
+
+ class MainPage(webapp.RequestHandler):
+ def get(self):
+ self.response.out.write(
+ '<html><body><form action="/hello" method="post">'
+ 'Name: <input name="name" type="text" size="20"> '
+ '<input type="submit" value="Say Hello"></form></body></html>')
+
+ 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.headers
+import wsgiref.util
+
+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('<pre>%s</pre>' % (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, 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:
+
+ handler_map[handler.__name__] = handler
+
+ if not regexp.startswith('^'):
+ regexp = '^' + regexp
+ if not regexp.endswith('$'):
+ regexp += '$'
+
+ 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 0000000..e3033aa
--- /dev/null
+++ b/google_appengine/google/appengine/ext/webapp/__init__.pyc
Binary files differ
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:
+
+ <a href="{% url MyPageHandler "overview" %}">
+ {% 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/template.pyc b/google_appengine/google/appengine/ext/webapp/template.pyc
new file mode 100644
index 0000000..22fd997
--- /dev/null
+++ b/google_appengine/google/appengine/ext/webapp/template.pyc
Binary files differ
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..80adf75
--- /dev/null
+++ b/google_appengine/google/appengine/ext/webapp/util.py
@@ -0,0 +1,90 @@
+#!/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"]
+
+import os
+import sys
+import wsgiref.util
+
+from google.appengine.api import users
+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
+
+
+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.
+ """
+ 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/util.pyc b/google_appengine/google/appengine/ext/webapp/util.pyc
new file mode 100644
index 0000000..8e617dc
--- /dev/null
+++ b/google_appengine/google/appengine/ext/webapp/util.pyc
Binary files differ
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 0000000..21281a6
--- /dev/null
+++ b/google_appengine/google/appengine/runtime/__init__.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/runtime/apiproxy.py b/google_appengine/google/appengine/runtime/apiproxy.py
new file mode 100755
index 0000000..385318b
--- /dev/null
+++ b/google_appengine/google/appengine/runtime/apiproxy.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.
+#
+
+"""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
+
+_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)
+ assert isinstance(self.response, ProtocolBuffer.ProtocolMessage)
+
+ 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'] 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 0000000..c408167
--- /dev/null
+++ b/google_appengine/google/appengine/runtime/apiproxy.pyc
Binary files differ
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..bad4fc1
--- /dev/null
+++ b/google_appengine/google/appengine/runtime/apiproxy_errors.py
@@ -0,0 +1,84 @@
+#!/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 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 0000000..48a4e27
--- /dev/null
+++ b/google_appengine/google/appengine/runtime/apiproxy_errors.pyc
Binary files differ
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 0000000..2b87356
--- /dev/null
+++ b/google_appengine/google/appengine/tools/__init__.pyc
Binary files differ
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..8458289
--- /dev/null
+++ b/google_appengine/google/appengine/tools/adaptive_thread_pool.py
@@ -0,0 +1,460 @@
+#!/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('Backing off due to errors: %.1f seconds',
+ 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 0000000..25b62c4
--- /dev/null
+++ b/google_appengine/google/appengine/tools/adaptive_thread_pool.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/tools/appcfg.py b/google_appengine/google/appengine/tools/appcfg.py
new file mode 100755
index 0000000..f9abe25
--- /dev/null
+++ b/google_appengine/google/appengine/tools/appcfg.py
@@ -0,0 +1,2525 @@
+#!/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 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 BuildClonePostBody(file_tuples):
+ """Build the post body for the /api/clone{files,blobs} 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_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_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
+ while not callable_func() and max_tries > 0:
+ StatusUpdate('Will check again in %s seconds.' % delay)
+ time.sleep(delay)
+ delay *= backoff_factor
+ max_tries -= 1
+ return max_tries > 0
+
+
+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/datastore/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 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, now, vhost, include_vhost):
+ """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.
+ now: POSIX timestamp used for calculating valid dates for num_days.
+ 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.
+ """
+ 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.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.valid_dates = None
+ if self.num_days:
+ patterns = []
+ now = PacificTime(now)
+ for i in xrange(self.num_days):
+ then = time.gmtime(now - 24*3600 * i)
+ patterns.append(re.escape(time.strftime('%d/%m/%Y', then)))
+ patterns.append(re.escape(time.strftime('%d/%b/%Y', then)))
+ self.valid_dates = re.compile(r'[^[]+\[(' + '|'.join(patterns) + r'):')
+
+ 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()
+ offset = None
+ try:
+ while True:
+ try:
+ offset = self.RequestLogLines(tf, offset)
+ if not offset:
+ break
+ 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': 100,
+ }
+ 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)
+ 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
+ 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')) or
+ (valid_dates and not valid_dates.match(line))):
+ return None
+ tf.write(line + '\n')
+ if not lines:
+ return None
+ return offset
+
+
+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' 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'), 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)
+
+ 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 = []
+ for path, content_hash in self.files.iteritems():
+ mime_type = GetMimeTypeIfStaticFile(self.config, path)
+ if mime_type is not None:
+ blobs_to_clone.append((path, content_hash, mime_type))
+ else:
+ 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)
+
+ 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]
+ mime_type = GetMimeTypeIfStaticFile(self.config, path)
+ payload = file_handle.read()
+ if mime_type is None:
+ self.file_batcher.AddToBatch(path, payload, mime_type)
+ else:
+ self.blob_batcher.AddToBatch(path, payload, mime_type)
+
+ 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, 8, 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:
+ if self.config.skip_files.match(path):
+ logging.info('Ignoring file \'%s\': File matches ignore regex.',
+ path)
+ else:
+ 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()
+ StatusUpdate('Uploaded %d files and blobs' % num_files)
+
+ 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, separator=os.path.sep):
+ """Walks a directory tree, returning all the files. Follows symlinks.
+
+ Args:
+ base: The base path to search for files under.
+ 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 os.path.isfile(fullname):
+ if separator == '\\':
+ name = name.replace('\\', '/')
+ yield name
+ elif os.path.isdir(fullname):
+ 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 <action>\' for a detailed description.') % desc
+
+ parser = self.parser_class(usage='%prog [options] <action>',
+ 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=False,
+ help='Use SSL 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.')
+ 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)
+
+ 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)
+ 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()
+ 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 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 <directory> 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), 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()
+
+ 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 <directory> 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 <directory> 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 <directory> 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 <directory> 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 Rollback(self):
+ """Does a rollback of any existing transaction for this app version."""
+ if len(self.args) != 1:
+ self.parser.error('Expected a single <directory> argument.')
+
+ basepath = self.args[0]
+ appyaml = self._ParseAppYaml(basepath)
+
+ appversion = AppVersionUpload(self._GetRpcServer(), appyaml)
+ appversion.in_transaction = True
+ appversion.Rollback()
+
+ def RequestLogs(self):
+ """Write request logs to a file."""
+ if len(self.args) != 2:
+ self.parser.error(
+ 'Expected a <directory> argument and an <output_file> 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 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)
+ logs_requester.DownloadLogs()
+
+ def _ParseEndDate(self, date, time_func=time.time):
+ """Translates a user-readable end date to a POSIX timestamp.
+
+ Args:
+ date: A utc date string as YYYY-MM-DD.
+ time_func: time.time() function for testing.
+
+ Returns:
+ A POSIX timestamp representing the last moment of that day.
+ If no date is given, returns a timestamp representing now.
+ """
+ if not date:
+ return time_func()
+ struct_time = time.strptime('%s' % date, '%Y-%m-%d')
+ return calendar.timegm(struct_time) + 86400
+
+ 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 UTC. '
+ '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('--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 <directory> 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 = '<no 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', 'kind', 'config_file']:
+ 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:
+ self.parser.error('Expected <directory> argument.')
+
+ 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):
+ return dict([(arg_name, getattr(self.options, arg_name, None)) for
+ arg_name in (
+ 'app_id',
+ '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',
+ )])
+
+ 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'] = True
+ args['has_header'] = False
+ args['map'] = False
+ args['dump'] = False
+ args['restore'] = 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'] = False
+
+ 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('--config_file', type='string', dest='config_file',
+ action='store',
+ help='Name of the configuration file. (Required)')
+ parser.add_option('--kind', type='string', dest='kind',
+ action='store',
+ help='The kind of the entities to store. (Required)')
+ 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')
+
+ 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.')
+
+ 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.')
+
+ 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 <action>',
+ short_desc='Print help for a specific action.'),
+
+ 'update': Action(
+ function='Update',
+ usage='%prog [options] update <directory>',
+ 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 <directory>',
+ 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 <directory>',
+ 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 <directory>',
+ 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."""),
+
+ 'vacuum_indexes': Action(
+ function='VacuumIndexes',
+ usage='%prog [options] vacuum_indexes <directory>',
+ 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 <directory>',
+ 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 <directory> <output_file>',
+ 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 <directory>',
+ 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 <directory>',
+ 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 <directory>',
+ 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."""),
+
+
+
+ }
+
+
+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 0000000..4ee2247
--- /dev/null
+++ b/google_appengine/google/appengine/tools/appcfg.pyc
Binary files differ
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..2f82e3c
--- /dev/null
+++ b/google_appengine/google/appengine/tools/appengine_rpc.py
@@ -0,0 +1,435 @@
+#!/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 cookielib
+import logging
+import os
+import re
+import socket
+import sys
+import urllib
+import urllib2
+
+
+https_handler = urllib2.HTTPSHandler
+uses_cert_verification = False
+certpath = os.path.join(os.path.dirname(__file__), "cacerts.txt")
+cert_file_available = os.path.exists(certpath)
+try:
+ import https_wrapper
+ if cert_file_available:
+ https_handler = lambda: https_wrapper.CertValidatingHTTPSHandler(
+ ca_certs=certpath)
+ uses_cert_verification = True
+except ImportError:
+ pass
+
+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=False):
+ """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 = urllib2.Request(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()
+ self.extra_headers["Cookie"] = ('dev_appserver_login="%s:True"; Path=/;' %
+ (credentials[0],))
+
+ 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 = "HOSTED"
+ self._Authenticate()
+ elif loc.startswith("http://%s/_ah/login" % (self.host,)):
+ self._DevAppServerAuthenticate()
+ 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 _Authenticate(self):
+ """Save the cookie jar after authentication."""
+ if cert_file_available and not uses_cert_verification:
+ logger.warn("ssl module not found. Without this the identity of the "
+ "remote host cannot be verified, and connections are NOT "
+ "secure. To fix this, please install the ssl module from "
+ "http://pypi.python.org/pypi/ssl")
+ 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(urllib2.ProxyHandler())
+ opener.add_handler(urllib2.UnknownHandler())
+ opener.add_handler(urllib2.HTTPHandler())
+ opener.add_handler(urllib2.HTTPDefaultErrorHandler())
+ opener.add_handler(https_handler())
+ 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 0000000..f63df5b
--- /dev/null
+++ b/google_appengine/google/appengine/tools/appengine_rpc.pyc
Binary files differ
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=<string> Whole Cookie header to supply to the server, including
+ the parameter name (e.g., "ACSID=..."). (Optional)
+ --url=<string> URL endpoint to post to for importing data. (Required)
+ --batch_size=<int> 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> Path to the CSV file to import. (Required)
+ --kind=<string> 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..e288b00
--- /dev/null
+++ b/google_appengine/google/appengine/tools/bulkloader.py
@@ -0,0 +1,3827 @@
+#!/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)
+ --app_id=<string> Application ID of endpoint (Optional for
+ *.appspot.com)
+ --auth_domain=<domain> The auth domain to use for logging in and for
+ UserProperties. (Default: gmail.com)
+ --bandwidth_limit=<int> 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=<int> 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=<path> File containing Model and Loader definitions.
+ (Required unless --dump or --restore are used)
+ --db_filename=<path> 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=<string> The username to use. Will prompt if omitted.
+ --exporter_opts=<string>
+ A string to pass to the Exporter.initialize method.
+ --filename=<path> Path to the file to import. (Required)
+ --has_header Skip the first row of the input.
+ --http_limit=<int> The maximum numer of HTTP requests per second to
+ send to the server. (Default: 8)
+ --kind=<string> Name of the Entity object kind to put in the
+ datastore. (Required)
+ --loader_opts=<string> A string to pass to the Loader.initialize method.
+ --log_file=<path> 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=<string> A string to pass to the Mapper.Initialize method.
+ --num_threads=<int> 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=<path>
+ Result database to write to for downloads.
+ --rps_limit=<int> The maximum number of records per second to
+ transfer to the server. (Default: 20)
+ --url=<string> URL endpoint to post to for importing data.
+ (Required)
+
+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.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.db import polymodel
+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 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):
+ """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.
+
+ 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)
+ else:
+ if self.column_count != len(row):
+ raise ResumeError('Column count mismatch, %d: %s' %
+ (self.column_count, str(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, 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'):
+ limit = e.args[1]
+ raise FieldSizeLimitError(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, kind, progress_queue, progress_generator,
+ key_range_item_factory):
+ """Initialize the KeyRangeItemGenerator.
+
+ Args:
+ request_manager: A RequestManager instance.
+ kind: The kind of entities being transferred.
+ 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
+ self.kind = kind
+ 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, 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,
+ self.kind,
+ key_range,
+ progress_key=progress_key,
+ state=STATE_READ)
+ yield result
+ else:
+ key_range = KeyRange()
+
+ yield self.key_range_item_factory(self.request_manager,
+ self.progress_queue,
+ self.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
+
+ 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 == 403 or (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):
+ """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.
+ """
+ _WorkItem.__init__(self, progress_queue, key_range.key_start,
+ key_range.key_end, ExportStateName, state=state,
+ progress_key=progress_key)
+ 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
+
+ def __str__(self):
+ return str(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)
+ 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()
+ download_result = self.request_manager.GetEntities(self)
+ transfer_time = get_time() - t
+ mapper = self.request_manager.GetMapper()
+ 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))
+
+ def CookieHttpRpcServer(*args, **kwargs):
+ kwargs['save_cookies'] = True
+ kwargs['account_type'] = 'HOSTED_OR_GOOGLE'
+ return appengine_rpc.HttpRpcServer(*args, **kwargs)
+
+ remote_api_stub.ConfigureRemoteDatastore(
+ app_id,
+ url_path,
+ self.AuthFunction,
+ servername=host_port,
+ rpc_server_factory=CookieHttpRpcServer,
+ 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 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 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)
+ result_pb = datastore_pb.QueryResult()
+ apiproxy_stub_map.MakeSyncCall('datastore_v3', 'RunQuery', query_pb,
+ result_pb)
+ 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):
+ """Gets Entity records from a remote endpoint over HTTP.
+
+ Args:
+ key_range_item: Range of keys to get.
+ key_factory: Used for dependency injection.
+
+ Returns:
+ A DownloadResult instance.
+
+ Raises:
+ ConfigurationError: if no Exporter is defined for self.kind
+ """
+ keys = []
+ entities = []
+
+ if self.parallel_download:
+ query = key_range_item.key_range.make_directed_datastore_query(self.kind)
+ try:
+ results = self._QueryForPbs(query)
+ except datastore_errors.NeedIndexError:
+ logger.info('%s: No descending index on __key__, '
+ 'performing serial download', self.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(self.kind)
+ 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):
+ """Returns a mapper for the registered kind.
+
+ Returns:
+ A Mapper instance.
+
+ Raises:
+ ConfigurationError: if no Mapper is defined for self.kind
+ """
+ if not self.mapper:
+ try:
+ self.mapper = Mapper.RegisteredMapper(self.kind)
+ except KeyError:
+ logger.error('No Mapper defined for kind %s.' % self.kind)
+ raise ConfigurationError('No Mapper defined for kind %s.' % self.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,
+ thread_pool,
+ progress_queue,
+ workitem_generator_factory,
+ progress_generator_factory):
+ """Initialize the DataSourceThread instance.
+
+ Args:
+ request_manager: A RequestManager instance.
+ 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.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.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):
+ """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.
+ """
+ self.complete = False
+ create_table = ('create table result (\n'
+ 'id BLOB primary key,\n'
+ 'value BLOB not null)')
+
+ _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
+
+ 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
+ value = entity.Encode()
+ self.insert_cursor.execute(
+ 'insert into result (id, value) values (?, ?)',
+ (key_str, buffer(value)))
+ 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 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'
+ '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, 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:
+ 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, key_start, key_end) values (?, ?, ?)',
+ (STATE_READ, 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 4-tuples that specify
+ progress information about a prior run of the uploader. The 4-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.
+ 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 4-tuple will be generated:
+
+ None
+ DATA_CONSUMED_TO_HERE: A unique string value indicating this record
+ is being provided.
+ 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, 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, state, key_start, key_end = row
+
+ yield progress_key, state, key_start, key_end
+
+ yield None, DATA_CONSUMED_TO_HERE, 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_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
+
+
+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.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, kind, progress_queue, progress_db, result_db):
+ """Initialize the ExportProgressThread instance.
+
+ Args:
+ kind: The kind of entities being stored in the database.
+ 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.kind = kind
+ 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."""
+ exporter = Exporter.RegisteredExporter(self.kind)
+ 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, kind, progress_queue, progress_db):
+ """Initialize the MapperProgressThread instance.
+
+ Args:
+ kind: The kind of entities being stored in the database.
+ 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.kind = kind
+ self.mapper = Mapper.RegisteredMapper(self.kind)
+
+ 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 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([entity_id, 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):
+ self.kind = kind
+
+ def initialize(self, filename, loader_opts):
+ CheckFile(filename)
+ self.queue = Queue.Queue(1000)
+ restore_thread = RestoreThread(self.queue, filename)
+ restore_thread.start()
+
+ def generate_records(self, filename):
+ while True:
+ record = self.queue.get(block=True)
+ if id(record) == id(RestoreThread._ENTITIES_DONE):
+ break
+ yield record
+
+ def create_entity(self, values, key_name=None, parent=None):
+ key = StrKey(unicode(values[0], 'utf-8'))
+ entity_proto = entity_pb.EntityProto(contents=str(values[1]))
+ entity_proto.mutable_key().CopyFrom(key._Key__reference)
+ return datastore.Entity._FromPb(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
+
+ 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 AttributeError:
+ if 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, lineterminator='')
+ 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) + '\n'
+ 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
+
+ @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)
+
+ @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['app_id']
+ 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 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)
+
+ self.throttle.Register(threading.currentThread())
+ threading.currentThread().exit_flag = False
+
+ progress_queue = self.progress_queue_factory(self.max_queue_size)
+ 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:
+ 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 (request_manager.auth_called and
+ not request_manager.authenticated):
+ self.error = True
+ raise AuthenticationError('Authentication failed')
+
+ 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(request_manager,
+ thread_pool,
+ progress_queue,
+ self.input_generator_factory,
+ progress_generator_factory))
+
+ 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 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 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()
+
+
+FLAG_SPEC = ['debug',
+ 'help',
+ 'url=',
+ 'filename=',
+ 'batch_size=',
+ 'kind=',
+ 'num_threads=',
+ 'bandwidth_limit=',
+ 'rps_limit=',
+ 'http_limit=',
+ 'db_filename=',
+ 'app_id=',
+ 'config_file=',
+ 'has_header',
+ 'csv_has_header',
+ 'auth_domain=',
+ 'result_db_filename=',
+ 'download',
+ 'loader_opts=',
+ 'exporter_opts=',
+ 'log_file=',
+ 'mapper_opts=',
+ 'email=',
+ 'passin',
+ 'map',
+ 'dry_run',
+ 'dump',
+ 'restore',
+ ]
+
+
+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['db_filename'] = None
+ arg_dict['app_id'] = ''
+ arg_dict['auth_domain'] = 'gmail.com'
+ arg_dict['has_header'] = False
+ arg_dict['result_db_filename'] = None
+ arg_dict['download'] = False
+ arg_dict['loader_opts'] = None
+ arg_dict['exporter_opts'] = None
+ arg_dict['debug'] = False
+ arg_dict['log_file'] = None
+ arg_dict['email'] = None
+ arg_dict['passin'] = False
+ arg_dict['mapper_opts'] = None
+ arg_dict['map'] = False
+ arg_dict['dry_run'] = False
+ arg_dict['dump'] = False
+ arg_dict['restore'] = False
+
+ 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 == '--debug':
+ arg_dict['debug'] = True
+ elif option in ('-h', '--help'):
+ PrintUsageExit(0)
+ elif option == '--url':
+ arg_dict['url'] = value
+ elif option == '--filename':
+ arg_dict['filename'] = ExpandFilename(value)
+ elif option == '--batch_size':
+ arg_dict['batch_size'] = int(value)
+ elif option == '--kind':
+ arg_dict['kind'] = value
+ elif option == '--num_threads':
+ arg_dict['num_threads'] = int(value)
+ elif option == '--bandwidth_limit':
+ arg_dict['bandwidth_limit'] = int(value)
+ elif option == '--rps_limit':
+ arg_dict['rps_limit'] = int(value)
+ elif option == '--http_limit':
+ arg_dict['http_limit'] = int(value)
+ elif option == '--db_filename':
+ arg_dict['db_filename'] = ExpandFilename(value)
+ elif option == '--app_id':
+ arg_dict['app_id'] = value
+ elif option == '--config_file':
+ arg_dict['config_file'] = ExpandFilename(value)
+ elif option == '--auth_domain':
+ arg_dict['auth_domain'] = value
+ elif option == '--has_header':
+ arg_dict['has_header'] = True
+ elif option == '--csv_has_header':
+ print >>sys.stderr, ('--csv_has_header is deprecated, please use '
+ '--has_header.')
+ arg_dict['has_header'] = True
+ elif option == '--result_db_filename':
+ arg_dict['result_db_filename'] = ExpandFilename(value)
+ elif option == '--download':
+ arg_dict['download'] = True
+ elif option == '--loader_opts':
+ arg_dict['loader_opts'] = value
+ elif option == '--exporter_opts':
+ arg_dict['exporter_opts'] = value
+ elif option == '--log_file':
+ arg_dict['log_file'] = ExpandFilename(value)
+ elif option == '--email':
+ arg_dict['email'] = value
+ elif option == '--passin':
+ arg_dict['passin'] = True
+ elif option == '--map':
+ arg_dict['map'] = True
+ elif option == '--mapper_opts':
+ arg_dict['mapper_opts'] = value
+ elif option == '--dry_run':
+ arg_dict['dry_run'] = True
+ elif option == '--dump':
+ arg_dict['dump'] = True
+ elif option == '--restore':
+ arg_dict['restore'] = True
+
+ 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 LoadConfig(config_file_name, exit_fn=sys.exit):
+ """Loads a config file and registers any Loader classes present.
+
+ 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.
+ """
+ app_id = GetArgument(arg_dict, 'app_id', die_fn)
+ url = GetArgument(arg_dict, 'url', die_fn)
+ dump = GetArgument(arg_dict, 'dump', die_fn)
+ restore = GetArgument(arg_dict, 'restore', 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)
+
+ errors = []
+
+ if batch_size is None:
+ if download or perform_map:
+ 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:
+ errors.append('One of --config_file, --dump, or --restore is required')
+
+ if url is REQUIRED_OPTION:
+ errors.append(required % 'url')
+
+ if not filename and not perform_map:
+ errors.append(required % 'filename')
+
+ if kind is None:
+ if download or map:
+ errors.append('kind argument required for this operation')
+ elif not dump and not restore:
+ errors.append(
+ 'kind argument required unless --dump or --restore is specified')
+
+ if not app_id:
+ 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['app_id'] = host_port[:suffix_idx]
+ elif host_port.split(':')[0].endswith('google.com'):
+ arg_dict['app_id'] = host_port.split('.')[0]
+ else:
+ errors.append('app_id argument required for non appspot.com domains')
+
+ 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['app_id']
+ 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']
+
+ os.environ['AUTH_DOMAIN'] = auth_domain
+
+ kind = ParseKind(kind)
+
+ if not dump and not restore:
+ check_file(config_file)
+
+ if download and perform_map:
+ logger.error('--download and --map are mutually exclusive.')
+
+ if download or dump:
+ check_output_file(filename)
+ elif not perform_map:
+ check_file(filename)
+
+ if dump:
+ Exporter.RegisterExporter(DumpExporter(kind, result_db_filename))
+ elif restore:
+ Loader.RegisterLoader(RestoreLoader(kind))
+ else:
+ LoadConfig(config_file)
+
+ os.environ['APPLICATION_ID'] = app_id
+
+ 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)
+
+ throttle = remote_api_throttle.Throttle(layout=throttle_layout)
+ 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)
+
+ if db_filename == 'skip':
+ progress_db = StubProgressDatabase()
+ elif not download and not perform_map and not dump:
+ progress_db = ProgressDatabase(db_filename, signature)
+ else:
+ progress_db = ExportProgressDatabase(db_filename, signature)
+
+ return_code = 1
+
+ if not download and not perform_map and not dump:
+ 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 not perform_map:
+ result_db = ResultDatabase(result_db_filename, signature)
+ exporter = Exporter.RegisteredExporter(kind)
+ try:
+ exporter.initialize(filename, exporter_opts)
+
+ def KeyRangeGeneratorFactory(request_manager, progress_queue,
+ progress_gen):
+ return KeyRangeItemGenerator(request_manager, kind, progress_queue,
+ progress_gen, DownloadItem)
+
+ def ExportProgressThreadFactory(progress_queue, progress_db):
+ return ExportProgressThread(kind,
+ 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')
+ finally:
+ exporter.finalize()
+ elif not download:
+ mapper = Mapper.RegisteredMapper(kind)
+ try:
+ mapper.initialize(mapper_opts)
+ def KeyRangeGeneratorFactory(request_manager, progress_queue,
+ progress_gen):
+ return KeyRangeItemGenerator(request_manager, kind, progress_queue,
+ progress_gen, MapperItem)
+
+ def MapperProgressThreadFactory(progress_queue, progress_db):
+ return MapperProgressThread(kind,
+ 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 0000000..119fff9
--- /dev/null
+++ b/google_appengine/google/appengine/tools/bulkloader.pyc
Binary files differ
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..b7e5f82
--- /dev/null
+++ b/google_appengine/google/appengine/tools/dev_appserver.py
@@ -0,0 +1,3542 @@
+#!/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 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 croninfo
+from google.appengine.api import datastore_admin
+from google.appengine.api import datastore_file_stub
+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.capabilities import capability_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 import dist
+
+from google.appengine.tools import dev_appserver_index
+from google.appengine.tools import dev_appserver_login
+
+
+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',
+ 'TZ': 'UTC',
+}
+
+DEFAULT_SELECT_DELAY = 30.0
+
+for ext, mime_type in (('.asc', 'text/plain'),
+ ('.diff', 'text/plain'),
+ ('.csv', 'text/comma-separated-values'),
+ ('.rss', 'application/rss+xml'),
+ ('.text', 'text/plain'),
+ ('.wbmp', 'image/vnd.wap.wbmp')):
+ mimetypes.add_type(mime_type, ext)
+
+MAX_RUNTIME_RESPONSE_SIZE = 10 << 20
+
+MAX_REQUEST_SIZE = 10 * 1024 * 1024
+
+API_VERSION = '1'
+
+SITE_PACKAGES = os.path.normcase(os.path.join(os.path.dirname(os.__file__),
+ 'site-packages'))
+
+
+
+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)
+
+
+
+class URLDispatcher(object):
+ """Base-class for handling HTTP requests."""
+
+ def Dispatch(self,
+ relative_url,
+ path,
+ headers,
+ infile,
+ 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:
+ 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.
+ 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.
+ Tuple (path, headers, input_file) for an internal redirect:
+ path: Path of URL to redirect to.
+ headers: Headers to send to other dispatcher.
+ input_file: New input to send to new dispatcher.
+ """
+ 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):
+ """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.
+
+ 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)
+ 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), 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 = 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
+
+ return 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,
+ relative_url,
+ path,
+ headers,
+ infile,
+ 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.
+ """
+ cookies = ', '.join(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 = matcher.Match(
+ relative_url)
+ if dispatcher is None:
+ continue
+
+ logging.debug('Matched "%s" to %s with path %s',
+ relative_url, dispatcher, matched_path)
+
+ if (requires_login or admin_only) and not email_addr:
+ logging.debug('Login required, redirecting user')
+ self._login_redirect(self._login_url,
+ base_env_dict['SERVER_NAME'],
+ base_env_dict['SERVER_PORT'],
+ relative_url,
+ outfile)
+ elif admin_only and not 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:
+ forward = dispatcher.Dispatch(relative_url,
+ matched_path,
+ headers,
+ infile,
+ outfile,
+ base_env_dict=base_env_dict)
+
+ if forward:
+ new_path, new_headers, new_input = forward
+ logging.info('Internal redirection to %s', new_path)
+ new_outfile = cStringIO.StringIO()
+ self.Dispatch(new_path,
+ None,
+ new_headers,
+ new_input,
+ 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, 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('<span class="_ah_logline_%s">\n' % level_name)
+ outfile.write('<span class="_ah_logline_%s_prefix">%2.5f %s &gt;</span>\n'
+ % (level_name, time_diff, level_letter))
+ outfile.write('%s\n' % message)
+ outfile.write('</span>\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'
+
+ for key in headers:
+ if key in _IGNORE_REQUEST_HEADERS:
+ continue
+ adjusted_name = key.replace('-', '_').upper()
+ env['HTTP_' + adjusted_name] = ', '.join(headers.getheaders(key))
+
+ PAYLOAD_HEADER = 'HTTP_X_APPENGINE_DEVELOPMENT_PAYLOAD'
+ if PAYLOAD_HEADER in env:
+ del env[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',
+
+
+
+
+ '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__))),
+ ])
+
+ 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 and
+ FakeFile._skip_files.match(relative_filename)):
+ logging.warning('Blocking access to skipped file "%s"',
+ logical_filename)
+ return False
+
+ 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 = [
+ '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',
+ '_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 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)
+ 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)
+ before_path = sys.path[:]
+ 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,
+ relative_url,
+ path,
+ headers,
+ infile,
+ outfile,
+ base_env_dict=None):
+ """Dispatches the Python CGI."""
+ 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(path)
+ env.update(self._setup_env(cgi_path, relative_url, headers, infile))
+ self._exec_cgi(self._root_path,
+ path,
+ cgi_path,
+ env,
+ infile,
+ outfile,
+ self._module_dict)
+ handler.AddDebuggingConsole(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,
+ relative_url,
+ path,
+ headers,
+ infile,
+ outfile,
+ base_env_dict=None):
+ """Reads the file and returns the response status and data."""
+ full_path = self._path_adjuster.AdjustPath(path)
+ status, data = self._read_data_file(full_path)
+ content_type = self._static_file_config_matcher.GetMimeType(path)
+ expiration = self._static_file_config_matcher.GetExpiration(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',
+ ])
+
+
+def IgnoreHeadersRewriter(status_code, status_message, headers, body):
+ """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 headers:
+ del headers[h]
+
+ return status_code, status_message, headers, body
+
+
+def ParseStatusRewriter(status_code, status_message, headers, body):
+ """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 = headers.getheader('location')
+ status_value = headers.getheader('status')
+ if status_value:
+ response_status = status_value
+ del headers['status']
+ elif location_value:
+ response_status = '%d Redirecting' % httplib.FOUND
+ else:
+ return status_code, status_message, headers, body
+
+ status_parts = response_status.split(' ', 1)
+ status_code, status_message = (status_parts + [''])[:2]
+ try:
+ status_code = int(status_code)
+ except ValueError:
+ status_code = 500
+ body = cStringIO.StringIO('Error: Invalid "status" header value returned.')
+
+ return status_code, status_message, headers, body
+
+
+def CacheRewriter(status_code, status_message, headers, body):
+ """Update the cache header."""
+ if not 'Cache-Control' in headers:
+ headers['Cache-Control'] = 'no-cache'
+ if not 'Expires' in headers:
+ headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
+ return status_code, status_message, headers, body
+
+
+def ContentLengthRewriter(status_code, status_message, headers, body):
+ """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 = body.tell()
+ body.seek(0, 2)
+
+ headers['Content-Length'] = str(body.tell() - current_position)
+ body.seek(current_position)
+ return status_code, status_message, headers, body
+
+
+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:
+ status_code: Rewritten status code or original.
+ status_message: Rewritter message or original.
+ headers: Rewritten/modified headers or original.
+ body: Rewritten/modified body or original.
+
+ Returns:
+ List of response rewriters.
+ """
+ return [IgnoreHeadersRewriter,
+ ParseStatusRewriter,
+ CacheRewriter,
+ ContentLengthRewriter,
+ ]
+
+
+def RewriteResponse(response_file, response_rewriters=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.
+
+ Returns:
+ Tuple (status_code, status_message, header, body) where:
+ 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.
+ header: String containing the HTTP headers of the response, without
+ a trailing new-line (CRLF).
+ body: String containing the body of the response.
+ """
+ if response_rewriters is None:
+ response_rewriters = CreateResponseRewritersChain()
+
+ status_code = 200
+ status_message = 'Good to go'
+ headers = mimetools.Message(response_file)
+
+ for response_rewriter in response_rewriters:
+ status_code, status_message, headers, response_file = response_rewriter(
+ status_code,
+ status_message,
+ headers,
+ response_file)
+
+ header_list = []
+ for header in headers.headers:
+ header = header.rstrip('\n')
+ header = header.rstrip('\r')
+ header_list.append(header)
+
+ header_data = '\r\n'.join(header_list) + '\r\n'
+ return status_code, status_message, header_data, response_file.read()
+
+
+
+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
+
+
+
+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 _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)
+
+ infile = cStringIO.StringIO()
+ infile.write(self.rfile.read(
+ int(self.headers.get('content-length', 0))))
+ infile.seek(0)
+
+ request_size = len(infile.getvalue())
+ if request_size > MAX_REQUEST_SIZE:
+ msg = ('HTTP request was too large: %d. The limit is: %d.'
+ % (request_size, MAX_REQUEST_SIZE))
+ logging.error(msg)
+ self.send_response(httplib.REQUEST_ENTITY_TOO_LARGE, msg)
+ return
+
+ outfile = cStringIO.StringIO()
+ try:
+ dispatcher.Dispatch(self.path,
+ None,
+ self.headers,
+ infile,
+ outfile,
+ base_env_dict=env_dict)
+ finally:
+ self.module_manager.UpdateModuleFileModificationTimes()
+
+ outfile.flush()
+ outfile.seek(0)
+
+ status_code, status_message, header_data, body = (
+ RewriteResponse(outfile, self.rewriter_chain))
+
+ runtime_response_size = len(outfile.getvalue())
+ if runtime_response_size > MAX_RUNTIME_RESPONSE_SIZE:
+ status_code = 403
+ status_message = 'Forbidden'
+ new_headers = []
+ for header in header_data.split('\n'):
+ if not header.lower().startswith('content-length'):
+ new_headers.append(header)
+ header_data = '\n'.join(new_headers)
+ body = ('HTTP response was too large: %d. The limit is: %d.'
+ % (runtime_response_size, MAX_RUNTIME_RESPONSE_SIZE))
+
+ 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\n\n')
+ self.wfile.write('<pre>%s</pre>' % cgi.escape(msg))
+ except:
+ msg = 'Exception encountered handling request'
+ logging.exception(msg)
+ self.send_response(httplib.INTERNAL_SERVER_ERROR, msg)
+ tbhandler()
+ else:
+ try:
+ self.send_response(status_code, status_message)
+ self.wfile.write(header_data)
+ self.wfile.write('\r\n')
+ if self.command != 'HEAD':
+ self.wfile.write(body)
+ elif 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
+
+ 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)
+
+ 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.
+ datastore_path: Path to the file to store Datastore file stub data in.
+ history_path: Path to the file to store Datastore history in.
+ clear_datastore: If the datastore and history 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.
+ 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.
+ """
+ root_path = config.get('root_path', None)
+ login_url = config['login_url']
+ datastore_path = config['datastore_path']
+ history_path = config['history_path']
+ clear_datastore = config['clear_datastore']
+ 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)
+ trusted = config.get('trusted', False)
+
+ os.environ['APPLICATION_ID'] = app_id
+
+ if clear_datastore:
+ for path in (datastore_path, history_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()
+
+ datastore = datastore_file_stub.DatastoreFileStub(
+ app_id, datastore_path, history_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))
+
+ apiproxy_stub_map.apiproxy.RegisterStub(
+ 'xmpp',
+ xmpp_service_stub.XmppServiceStub())
+
+
+
+ try:
+ from google.appengine.api.images import images_stub
+ apiproxy_stub_map.apiproxy.RegisterStub(
+ 'images',
+ images_stub.ImagesServiceStub())
+ 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())
+
+
+def CreateImplicitMatcher(module_dict,
+ root_path,
+ login_url,
+ create_path_adjuster=PathAdjuster,
+ create_local_dispatcher=LocalCGIDispatcher,
+ create_cgi_dispatcher=CGIDispatcher):
+ """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.
+
+ 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)
+
+ admin_dispatcher = create_cgi_dispatcher(module_dict, root_path,
+ path_adjuster)
+ url_matcher.AddURL('/_ah/admin(?:/.*)?',
+ admin_dispatcher,
+ DEVEL_CONSOLE_PATH,
+ False,
+ False)
+
+ 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)
+ return HTTPServerWithScheduler((serve_address, port), handler_class)
+
+
+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 = []
+
+ 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)
+ runnable()
+
+ 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 0000000..37585ff
--- /dev/null
+++ b/google_appengine/google/appengine/tools/dev_appserver.pyc
Binary files differ
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..d69f656
--- /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.
+ """
+
+ 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
+
+ res = []
+ for (kind, ancestor, props), count in sorted(indexes.iteritems()):
+ res.append('')
+ if count == 0:
+ message = '# Unused in query history -- copied from input.'
+ elif count == 1:
+ message = '# Used once in query history.'
+ else:
+ message = '# Used %d times in query history.' % count
+ res.append(message)
+ 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 = 0
+ 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)
+
+ 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_admin.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 0000000..62d6d2c
--- /dev/null
+++ b/google_appengine/google/appengine/tools/dev_appserver_index.pyc
Binary files differ
diff --git a/google_appengine/google/appengine/tools/dev_appserver_info.py b/google_appengine/google/appengine/tools/dev_appserver_info.py
new file mode 100755
index 0000000..40f7406
--- /dev/null
+++ b/google_appengine/google/appengine/tools/dev_appserver_info.py
@@ -0,0 +1,160 @@
+#!/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.
+#
+
+"""CGI for displaying info about the currently running app in dev_appserver.
+
+This serves pages under /_ah/info/ that display information about the app
+currently running in the dev_appserver. It currently serves on these URLs:
+
+ /_ah/info/queries:
+ A list of datastore queries run so far, grouped by kind. Used to suggest
+ composite indices that should be built.
+
+ /_ah/info/index.yaml:
+ Produces an index.yaml file that can be uploaded to the real app
+ server by appcfg.py. This information is derived from the query
+ history above, by removing queries that don't need any indexes to
+ be built and by combining queries that can use the same index.
+"""
+
+
+
+import cgi
+import wsgiref.handlers
+
+from google.appengine.api import apiproxy_stub_map
+from google.appengine.datastore import datastore_pb
+from google.appengine.ext import webapp
+from google.appengine.tools import dev_appserver_index
+
+
+class QueriesHandler(webapp.RequestHandler):
+ """A handler that displays a list of the datastore queries run so far.
+ """
+
+ HEADER = """<html>
+<head><title>Query History</title></head>
+
+<body>
+<h3>Query History</h3>
+
+<p>This is a list of datastore queries your app has run. You have to
+make composite indices for these queries before deploying your app.
+This is normally done automatically by running dev_appserver, which
+will write the file index.yaml into your app's root directory, and
+then deploying your app with appcfg, which will upload that
+index.yaml.</p>
+
+<p>You can also view a 'clean' <a href="index.yaml">index.yaml</a>
+file and save that to your app's root directory.</p>
+
+<table>
+<tr><th>Times run</th><th>Query</th></tr>
+"""
+
+ ROW = """<tr><td>%(count)s</td><td>%(query)s</td></tr>"""
+
+ FOOTER = """
+</table>
+</body>
+</html>"""
+
+ def Render(self):
+ """Renders and returns the query history page HTML.
+
+ Returns:
+ A string, formatted as an HTML page.
+ """
+ history = apiproxy_stub_map.apiproxy.GetStub('datastore_v3').QueryHistory()
+ history_items = [(count, query) for query, count in history.items()]
+ history_items.sort(reverse=True)
+ rows = [self.ROW % {'query': _FormatQuery(query),
+ 'count': count}
+ for count, query in history_items]
+ return self.HEADER + '\n'.join(rows) + self.FOOTER
+
+ def get(self):
+ """Handle a GET. Just calls Render()."""
+ self.response.out.write(self.Render())
+
+
+class IndexYamlHandler(webapp.RequestHandler):
+ """A handler that renders an index.yaml file suitable for upload."""
+
+ def Render(self):
+ """Renders and returns the index.yaml file.
+
+ Returns:
+ A string, formatted as an index.yaml file.
+ """
+ datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3')
+ query_history = datastore_stub.QueryHistory()
+ body = dev_appserver_index.GenerateIndexFromHistory(query_history)
+ return 'indexes:\n' + body
+
+ def get(self):
+ """Handle a GET. Just calls Render()."""
+ self.response.headers['Content-Type'] = 'text/plain'
+ self.response.out.write(self.Render())
+
+
+def _FormatQuery(query):
+ """Format a Query protobuf as (very simple) HTML.
+
+ Args:
+ query: A datastore_pb.Query instance.
+
+ Returns:
+ A string containing formatted HTML. This is mostly the output of
+ str(query) with '<' etc. escaped, and '<br>' inserted in front of
+ Order and Filter parts.
+ """
+ res = cgi.escape(str(query))
+ res = res.replace('Order', '<br>Order')
+ res = res.replace('Filter', '<br>Filter')
+ return res
+
+
+def _DirectionToString(direction):
+ """Turn a direction enum into a string.
+
+ Args:
+ direction: ASCENDING or DESCENDING
+
+ Returns:
+ Either 'asc' or 'descending'.
+ """
+ if direction == datastore_pb.Query_Order.DESCENDING:
+ return 'descending'
+ else:
+ return 'asc'
+
+
+URL_MAP = {
+ '/_ah/info/queries': QueriesHandler,
+ '/_ah/info/index.yaml': IndexYamlHandler,
+
+}
+
+
+def main():
+ application = webapp.WSGIApplication(URL_MAP.items())
+ wsgiref.handlers.CGIHandler().run(application)
+
+
+if __name__ == '__main__':
+ main()
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 = """<html>
+<head>
+ <title>Login</title>
+</head>
+<body>
+
+<form method='get' action='%(login_url)s'
+ style='text-align:center; font: 13px sans-serif'>
+ <div style='width: 20em; margin: 1em auto;
+ text-align:left;
+ padding: 0 2em 1.25em 2em;
+ background-color: #d6e9f8;
+ border: 2px solid #67a7e3'>
+ <h3>%(login_message)s</h3>
+ <p style='padding: 0; margin: 0'>
+ <label for='email' style="width: 3em">Email:</label>
+ <input name='email' type='text' value='%(email)s' id='email'/>
+ </p>
+ <p style='margin: .5em 0 0 3em; font-size:12px'>
+ <input name='admin' type='checkbox' value='True'
+ %(admin_checked)s id='admin'/>
+ <label for='admin'>Sign in as Administrator</label>
+ </p>
+ <p style='margin-left: 3em'>
+ <input name='action' value='Login' type='submit'
+ id='submit-login' />
+ <input name='action' value='Logout' type='submit'
+ id='submit-logout' />
+ </p>
+ </div>
+ <input name='continue' type='hidden' value='%(continue_url)s'/>
+</form>
+
+</body>
+</html>
+"""
+
+
+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 0000000..c4575c0
--- /dev/null
+++ b/google_appengine/google/appengine/tools/dev_appserver_login.pyc
Binary files differ
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..232804a
--- /dev/null
+++ b/google_appengine/google/appengine/tools/dev_appserver_main.py
@@ -0,0 +1,498 @@
+#!/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>
+
+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)
+ --datastore_path=PATH Path to use for storing Datastore file stub data.
+ (Default %(datastore_path)s)
+ --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)
+"""
+
+
+
+from google.appengine.tools import os_compat
+
+import getopt
+import logging
+import os
+import re
+import signal
+import sys
+import traceback
+import tempfile
+
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
+
+
+def SetGlobals():
+ """Set various global variables involving the 'google' package.
+
+ This function should not be called until sys.path has been properly set.
+ """
+ global yaml_errors, appcfg, appengine_rpc, dev_appserver, os_compat
+ 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
+ from google.appengine.tools import os_compat
+
+
+
+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_DATASTORE_PATH = 'datastore_path'
+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_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_DATASTORE_PATH: os.path.join(tempfile.gettempdir(),
+ 'dev_appserver.datastore'),
+ 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_TRUSTED: False,
+}
+
+API_PATHS = {'1':
+ {'google': (),
+ 'antlr3': ('lib', 'antlr3'),
+ 'django': ('lib', 'django'),
+ 'webob': ('lib', 'webob'),
+ 'yaml': ('lib', 'yaml', 'lib'),
+ }
+ }
+
+DEFAULT_API_VERSION = '1'
+
+API_PATHS['test'] = API_PATHS[DEFAULT_API_VERSION].copy()
+API_PATHS['test']['_test'] = ('nonexistent', 'test', 'path')
+
+
+def SetPaths(app_config_path):
+ """Set the interpreter to use the specified API version.
+
+ The app.yaml file is scanned for the api_version field and the value is
+ extracted. With that information, the paths in API_PATHS are added to the
+ front of sys.paths to make sure that they take precedent over any other paths
+ to older versions of a package. All modules for each package set are cleared
+ out of sys.modules to make sure only the newest version is used.
+
+ Args:
+ - app_config_path: Path to the app.yaml file.
+ """
+ api_version_re = re.compile(r'api_version:\s*(?P<api_version>[\w.]{1,32})')
+ api_version = None
+ app_config_file = open(app_config_path, 'r')
+ try:
+ for line in app_config_file:
+ re_match = api_version_re.match(line)
+ if re_match:
+ api_version = re_match.group('api_version')
+ break
+ finally:
+ app_config_file.close()
+
+ if api_version is None:
+ logging.error("Application configuration file missing an 'api_version' "
+ "value:\n%s" % app_config_path)
+ sys.exit(1)
+ if api_version not in API_PATHS:
+ logging.error("Value of %r for 'api_version' from the application "
+ "configuration file is not valid:\n%s" %
+ (api_version, app_config_path))
+ sys.exit(1)
+
+ if api_version == DEFAULT_API_VERSION:
+ return DEFAULT_API_VERSION
+
+ sdk_path = os.path.dirname(
+ os.path.dirname(
+ os.path.dirname(
+ os.path.dirname(os_compat.__file__)
+ )
+ )
+ )
+ for pkg_name, path_parts in API_PATHS[api_version].iteritems():
+ for name in sys.modules.keys():
+ if name == pkg_name or name.startswith('%s.' % pkg_name):
+ del sys.modules[name]
+ pkg_path = os.path.join(sdk_path, *path_parts)
+ sys.path.insert(0, pkg_path)
+
+ return api_version
+
+
+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',
+ 'datastore_path=',
+ '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=',
+ '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 == '--datastore_path':
+ option_dict[ARG_DATASTORE_PATH] = os.path.abspath(value)
+
+ 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 == '--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]
+ for suffix in ('yaml', 'yml'):
+ path = os.path.join(root_path, 'app.%s' % suffix)
+ if os.path.exists(path):
+ api_version = SetPaths(path)
+ break
+ else:
+ logging.error("Application configuration file not found in %s" % root_path)
+ return 1
+
+ SetGlobals()
+ dev_appserver.API_VERSION = api_version
+
+ 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]
+ 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))
+else:
+ SetGlobals()
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 0000000..fe0ff3e
--- /dev/null
+++ b/google_appengine/google/appengine/tools/os_compat.pyc
Binary files differ
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..8705803
--- /dev/null
+++ b/google_appengine/google/appengine/tools/remote_api_shell.py
@@ -0,0 +1,94 @@
+#!/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]
+"""
+
+
+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 <app_id>.appspot.com.')
+ (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)
+ 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 0000000..fc51eb2
--- /dev/null
+++ b/google_appengine/google/appengine/tools/requeue.pyc
Binary files differ
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 0000000..f06c1e3
--- /dev/null
+++ b/google_appengine/google/net/__init__.pyc
Binary files differ
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("<f", v))
+ self.buf.extend(a)
+ return
+
+ def putDouble(self, v):
+ a = array.array('B')
+ a.fromstring(struct.pack("<d", v))
+ self.buf.extend(a)
+ return
+
+ def putBoolean(self, v):
+ if v:
+ self.buf.append(1)
+ else:
+ self.buf.append(0)
+ return
+
+ def putPrefixedString(self, v):
+ v = str(v)
+ self.putVarInt32(len(v))
+ self.buf.fromstring(v)
+ return
+
+ def putRawString(self, v):
+ self.buf.fromstring(v)
+
+
+class Decoder:
+ def __init__(self, buf, idx, limit):
+ self.buf = buf
+ self.idx = idx
+ self.limit = limit
+ return
+
+ def avail(self):
+ return self.limit - self.idx
+
+ def buffer(self):
+ return self.buf
+
+ def pos(self):
+ return self.idx
+
+ def skip(self, n):
+ if self.idx + n > 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("<f", a)[0]
+
+ def getDouble(self):
+ if self.idx + 8 > self.limit: raise ProtocolBufferDecodeError, "truncated"
+ a = self.buf[self.idx:self.idx+8]
+ self.idx += 8
+ return struct.unpack("<d", a)[0]
+
+ def getBoolean(self):
+ b = self.get8()
+ if b != 0 and b != 1: raise ProtocolBufferDecodeError, "corrupted"
+ return b
+
+ def getPrefixedString(self):
+ length = self.getVarInt32()
+ if self.idx + length > 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 0000000..ddfca06
--- /dev/null
+++ b/google_appengine/google/net/proto/ProtocolBuffer.pyc
Binary files differ
diff --git a/google_appengine/google/net/proto/RawMessage.py b/google_appengine/google/net/proto/RawMessage.py
new file mode 100755
index 0000000..2d4d5f5
--- /dev/null
+++ b/google_appengine/google/net/proto/RawMessage.py
@@ -0,0 +1,83 @@
+#!/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 is the Python counterpart to the RawMessage class defined in rawmessage.h.
+
+To use this, put the following line in your .proto file:
+
+python from google.net.proto.RawMessage import RawMessage
+
+"""
+
+__pychecker__ = 'no-callinit no-argsused'
+
+from google.net.proto import ProtocolBuffer
+
+class RawMessage(ProtocolBuffer.ProtocolMessage):
+ """
+ This is a special subclass of ProtocolMessage that doesn't interpret its data
+ in any way. Instead, it just stores it in a string.
+
+ See rawmessage.h for more details.
+ """
+
+ def __init__(self, initial=None):
+ self.__contents = ''
+ if initial is not None:
+ self.MergeFromString(initial)
+
+ def contents(self):
+ return self.__contents
+
+ def set_contents(self, contents):
+ self.__contents = contents
+
+ def Clear(self):
+ self.__contents = ''
+
+ def IsInitialized(self, debug_strs=None):
+ return 1
+
+ def __str__(self, prefix="", printElemNumber=0):
+ return prefix + self.DebugFormatString(self.__contents)
+
+ def OutputUnchecked(self, e):
+ e.putRawString(self.__contents)
+
+ def TryMerge(self, d):
+ self.__contents = d.getRawString()
+
+ def MergeFrom(self, pb):
+ assert pb is not self
+ if pb.__class__ != self.__class__:
+ return 0
+ self.__contents = pb.__contents
+ return 1
+
+ def Equals(self, pb):
+ return self.__contents == pb.__contents
+
+ 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 ByteSize(self):
+ return len(self.__contents)
+
diff --git a/google_appengine/google/net/proto/RawMessage.pyc b/google_appengine/google/net/proto/RawMessage.pyc
new file mode 100644
index 0000000..23f5cd0
--- /dev/null
+++ b/google_appengine/google/net/proto/RawMessage.pyc
Binary files differ
diff --git a/google_appengine/google/net/proto/__init__.py b/google_appengine/google/net/proto/__init__.py
new file mode 100755
index 0000000..c33ae80
--- /dev/null
+++ b/google_appengine/google/net/proto/__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/proto/__init__.pyc b/google_appengine/google/net/proto/__init__.pyc
new file mode 100644
index 0000000..b1aa8cc
--- /dev/null
+++ b/google_appengine/google/net/proto/__init__.pyc
Binary files differ
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/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 0000000..1bef95f
--- /dev/null
+++ b/google_appengine/google/pyglib/__init__.pyc
Binary files differ
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 0000000..45764fc
--- /dev/null
+++ b/google_appengine/google/pyglib/gexcept.pyc
Binary files differ
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 <pink at odahoda dot de>: Main developer of Python target.
+Clinton Roy <clinton.roy at gmail dot com>: 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 <http://www.antlr.org/> 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 <http://www.antlr.org/download.html>.
+
+If you are interested in the latest, most bleeding edge version, have a look at
+the perforce depot at <http://fisheye2.cenqua.com/browse/antlr>. 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 <http://docs.python.org/inst/> for more information.
+
+
+5) DOCUMENTATION
+================
+
+Documentation (as far as it exists) can be found in the wiki
+<http://www.antlr.org/wiki/display/ANTLR3/Antlr3PythonTarget>
+
+
+6) REPORTING BUGS
+=================
+
+Please send bug reports to the ANTLR mailing list
+<http://www.antlr.org:8080/mailman/listinfo/antlr-interest> or
+<pink@odahoda.de>.
+
+Existing bugs may appear someday in the bugtracker:
+<http://www.antlr.org:8888/browse/ANTLR>
+
+
+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 <pink@odahoda.de>. For larger code contributions you'll
+have to sign the "Developer's Certificate of Origin", which can be found on
+<http://www.antlr.org/license.html> or use the feedback form at
+<http://www.antlr.org/misc/feedback>.
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 0000000..75b8628
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/__init__.pyc
Binary files differ
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 0000000..6298a61
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/compat.pyc
Binary files differ
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 0000000..261b4c3
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/constants.pyc
Binary files differ
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 0000000..aa089f7
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/dfa.pyc
Binary files differ
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 "<unique-name> [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 0000000..243a2c0
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/exceptions.pyc
Binary files differ
diff --git a/google_appengine/lib/antlr3/antlr3/extras.py b/google_appengine/lib/antlr3/antlr3/extras.py
new file mode 100755
index 0000000..9155cda
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/extras.py
@@ -0,0 +1,47 @@
+""" @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 treewizard import TreeWizard
+
+try:
+ from antlr3.dottreegen import toDOT
+except ImportError, exc:
+ def toDOT(*args, **kwargs):
+ raise exc
diff --git a/google_appengine/lib/antlr3/antlr3/main.py b/google_appengine/lib/antlr3/antlr3/main.py
new file mode 100755
index 0000000..c793370
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/main.py
@@ -0,0 +1,289 @@
+"""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 optparse
+
+import antlr3
+
+
+class _Main(object):
+ def __init__(self):
+ self.stdin = sys.stdin
+ self.stdout = sys.stdout
+ self.stderr = sys.stderr
+
+
+ def parseOptions(self, argv):
+ optParser = optparse.OptionParser()
+ optParser.add_option(
+ "--encoding",
+ action="store",
+ type="string",
+ dest="encoding"
+ )
+ optParser.add_option(
+ "--input",
+ action="store",
+ type="string",
+ dest="input"
+ )
+ optParser.add_option(
+ "--interactive", "-i",
+ action="store_true",
+ dest="interactive"
+ )
+ optParser.add_option(
+ "--no-output",
+ action="store_true",
+ dest="no_output"
+ )
+ optParser.add_option(
+ "--profile",
+ action="store_true",
+ dest="profile"
+ )
+ optParser.add_option(
+ "--hotshot",
+ action="store_true",
+ dest="hotshot"
+ )
+
+ self.setupOptions(optParser)
+
+ return optParser.parse_args(argv[1:])
+
+
+ def setupOptions(self, optParser):
+ pass
+
+
+ def execute(self, argv):
+ options, args = self.parseOptions(argv)
+
+ self.setUp(options)
+
+ if options.interactive:
+ while True:
+ try:
+ input = raw_input(">>> ")
+ 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<Integer,Integer> 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 = "<unknown>"
+ 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 = "<unknown>"
+ 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 = "<unknown>"
+ 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 = "<unknown>"
+ 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 = "<EOF>"
+ 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 \<EOF> 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<String> 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(<class>.__module__)
+
+ where <class> 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', '<module>'):
+ 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<Token> to List<String>
+ """
+
+ 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 = '<EOF>'
+ 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 = "<missing EOF>"
+ else:
+ tokenText = "<missing " + self.tokenNames[expectedTokenType] + ">"
+ 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 0000000..61db8f2
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/recognizers.pyc
Binary files differ
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<tokentype, channel> to override some Tokens' channel numbers
+ self.channelOverrideMap = {}
+
+ # Set<tokentype>; 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 '<ReplaceOp@%d..%d:"%s">' % (
+ 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 '<DeleteOp@%d..%d>' % (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 0000000..6a8c60b
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/streams.pyc
Binary files differ
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 "<EOF>"
+
+ 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 = "<no text>"
+
+ 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 = "<no text>"
+
+ 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 0000000..3fc443a
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/tokens.pyc
Binary files differ
diff --git a/google_appengine/lib/antlr3/antlr3/tree.py b/google_appengine/lib/antlr3/antlr3/tree.py
new file mode 100755
index 0000000..67197a5
--- /dev/null
+++ b/google_appengine/lib/antlr3/antlr3/tree.py
@@ -0,0 +1,2448 @@
+""" @package antlr3.tree
+@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.constants import UP, DOWN, EOF, INVALID_TOKEN_TYPE
+from antlr3.recognizers import BaseRecognizer, RuleReturnScope
+from antlr3.streams import IntStream
+from antlr3.tokens import CommonToken, Token, INVALID_TOKEN
+from antlr3.exceptions import MismatchedTreeNodeException, \
+ MissingTokenException, UnwantedTokenException, MismatchedTokenException, \
+ NoViableAltException
+
+
+############################################################################
+#
+# tree related exceptions
+#
+############################################################################
+
+
+class RewriteCardinalityException(RuntimeError):
+ """
+ @brief Base class for all exceptions thrown during AST rewrite construction.
+
+ This signifies a case where the cardinality of two or more elements
+ in a subrule are different: (ID INT)+ where |ID|!=|INT|
+ """
+
+ def __init__(self, elementDescription):
+ RuntimeError.__init__(self, elementDescription)
+
+ self.elementDescription = elementDescription
+
+
+ def getMessage(self):
+ return self.elementDescription
+
+
+class RewriteEarlyExitException(RewriteCardinalityException):
+ """@brief No elements within a (...)+ in a rewrite rule"""
+
+ def __init__(self, elementDescription=None):
+ RewriteCardinalityException.__init__(self, elementDescription)
+
+
+class RewriteEmptyStreamException(RewriteCardinalityException):
+ """
+ @brief Ref to ID or expr but no tokens in ID stream or subtrees in expr stream
+ """
+
+ pass
+
+
+############################################################################
+#
+# basic Tree and TreeAdaptor interfaces
+#
+############################################################################
+
+class Tree(object):
+ """
+ @brief Abstract baseclass for tree nodes.
+
+ What does a tree look like? ANTLR has a number of support classes
+ such as CommonTreeNodeStream that work on these kinds of trees. You
+ don't have to make your trees implement this interface, but if you do,
+ you'll be able to use more support code.
+
+ NOTE: When constructing trees, ANTLR can build any kind of tree; it can
+ even use Token objects as trees if you add a child list to your tokens.
+
+ This is a tree node without any payload; just navigation and factory stuff.
+ """
+
+
+ def getChild(self, i):
+ raise NotImplementedError
+
+
+ def getChildCount(self):
+ raise NotImplementedError
+
+
+ def getParent(self):
+ """Tree tracks parent and child index now > 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 "<errornode>"
+
+ 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 = "<unknown>"
+
+ return badText
+
+
+ def toString(self):
+ if isinstance(self.trappedException, MissingTokenException):
+ return ("<missing type: "
+ + str(self.trappedException.getMissingType())
+ + ">")
+
+ elif isinstance(self.trappedException, UnwantedTokenException):
+ return ("<extraneous: "
+ + str(self.trappedException.getUnexpectedToken())
+ + ", resync=" + self.getText() + ">")
+
+ elif isinstance(self.trappedException, MismatchedTokenException):
+ return ("<mismatched token: "
+ + str(self.trappedException.token)
+ + ", resync=" + self.getText() + ">")
+
+ elif isinstance(self.trappedException, NoViableAltException):
+ return ("<unexpected: "
+ + str(self.trappedException.token)
+ + ", resync=" + self.getText() + ">")
+
+ return "<error: "+self.getText()+">"
+
+
+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 = "<missing " + self.tokenNames[expectedTokenType] + ">"
+ 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 <http://www.antlr.org/wiki/display/~admin/2007/07/02/Exploring+Concept+of+TreeWizard> 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/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 <http://www.holovaty.com/>, who originally created Django with
+Simon and currently oversees things with Jacob.
+
+Simon Willison <http://simon.incutio.com/>, who originally created Django with
+Adrian during his year-long internship/placement at World Online and currently
+helps from the sidelines.
+
+Jacob Kaplan-Moss <http://www.jacobian.org/>, who joined the team shortly
+before Simon departed and currently oversees things with Adrian.
+
+Wilson Miner <http://www.wilsonminer.com/>, 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 <http://www.pointy-stick.com/blog/>, who has made
+significant contributions to all levels of the framework, from its database
+layer to template system and documentation.
+
+Georg "Hugo" Bauer <http://hugo.muensterland.org/>, who added
+internationalization support, manages i18n contributions and has made a ton
+of excellent tweaks, feature additions and bug fixes.
+
+Luke Plant <http://lukeplant.me.uk/>, who has contributed many excellent
+improvements, including database-level improvements, the CSRF middleware and
+unit tests.
+
+Russell Keith-Magee <freakboy@iinet.net.au>, who has contributed many excellent
+improvements, including refactoring of the Django ORM code and unit tests.
+
+Robert Wittams <http://robert.wittams.com/>, 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 <http://ascher.ca/>
+ Arthur <avandorp@gmail.com>
+ Jiri Barton
+ Ned Batchelder <http://www.nedbatchelder.com/>
+ Shannon -jj Behrens <http://jjinux.blogspot.com/>
+ Esdras Beleza <linux@esdrasbeleza.com>
+ James Bennett
+ Ben <afternoon@uk2.net>
+ Paul Bissex <http://e-scribe.com/>
+ Simon Blanchard
+ Andrew Brehaut <http://brehaut.net/blog>
+ brut.alll@gmail.com
+ Jonathan Buchanan <jonathan.buchanan@gmail.com>
+ Antonio Cavedoni <http://cavedoni.com/>
+ C8E
+ Chris Chamberlin <dja@cdc.msbx.net>
+ Amit Chakradeo <http://amit.chakradeo.net/>
+ ChaosKCW
+ Ian Clelland <clelland@gmail.com>
+ crankycoder@gmail.com
+ Matt Croydon <http://www.postneo.com/>
+ Jure Cuhalev <gandalf@owca.info>
+ dackze+django@gmail.com
+ Dirk Datzert <dummy@habmalnefrage.de>
+ Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
+ dave@thebarproject.com
+ Jason Davies (Esaj) <http://www.jasondavies.com/>
+ Alex Dedul
+ deric@monowerks.com
+ dne@mayonnaise.net
+ Maximillian Dornseif <md@hudora.de>
+ Jeremy Dunck <http://dunck.us/>
+ Andy Dustman <farcepest@gmail.com>
+ Clint Ecker
+ Enrico <rico.bl@gmail.com>
+ Ludvig Ericson <ludvig.ericson@gmail.com>
+ Dirk Eschler <dirk.eschler@gmx.net>
+ Marc Fargas <telenieko@telenieko.com>
+ favo@exoweb.net
+ Eric Floehr <eric@intellovations.com>
+ Jorge Gajon <gajon@gajon.org>
+ gandalf@owca.info
+ Baishampayan Ghose
+ martin.glueck@gmail.com
+ Simon Greenhill <dev@simon.net.nz>
+ Owen Griffiths
+ Espen Grindhaug <http://grindhaug.org/>
+ Brian Harring <ferringb@gmail.com>
+ Brant Harris
+ Hawkeye
+ Joe Heck <http://www.rhonabwy.com/wp/>
+ Joel Heenan <joelh-django@planetjoel.com>
+ hipertracker@gmail.com
+ Ian Holsman <http://feh.holsman.net/>
+ Kieran Holland <http://www.kieranholland.com>
+ Robert Rock Howard <http://djangomojo.com/>
+ Jason Huggins <http://www.jrandolph.com/blog/>
+ Tom Insam
+ Baurzhan Ismagulov <ibr@radix50.net>
+ jcrasta@gmail.com
+ Michael Josephson <http://www.sdjournal.com/>
+ jpellerin@gmail.com
+ junzhang.jn@gmail.com
+ Antti Kaihola <http://akaihola.blogspot.com/>
+ Ben Dean Kawamura <ben.dean.kawamura@gmail.com>
+ Garth Kidd <http://www.deadlybloodyserious.com/>
+ kilian <kilian.cavalotti@lip6.fr>
+ Sune Kirkeby <http://ibofobi.dk/>
+ Bastian Kleineidam <calvin@debian.org>
+ Cameron Knight (ckknight)
+ Meir Kriheli <http://mksoft.co.il/>
+ Bruce Kroeze <http://coderseye.com/>
+ Joseph Kocherhans
+ konrad@gwu.edu
+ lakin.wecker@gmail.com
+ Stuart Langridge <http://www.kryogenix.org/>
+ Nicola Larosa <nico@teknico.net>
+ Eugene Lazutkin <http://lazutkin.com/blog/>
+ Jeong-Min Lee <falsetru@gmail.com>
+ Christopher Lenz <http://www.cmlenz.net/>
+ lerouxb@gmail.com
+ Waylan Limberg <waylan@gmail.com>
+ limodou
+ mattmcc
+ Martin Maney <http://www.chipy.org/Martin_Maney>
+ masonsimon+django@gmail.com
+ Manuzhai
+ Petar Marić <http://www.petarmaric.com/>
+ Nuno Mariz <nmariz@gmail.com>
+ mark@junklight.com
+ Yasushi Masuda <whosaysni@gmail.com>
+ mattycakes@gmail.com
+ Jason McBrayer <http://www.carcosa.net/jason/>
+ mccutchen@gmail.com
+ michael.mcewan@gmail.com
+ mikko@sorl.net
+ mitakummaa@gmail.com
+ mmarshall
+ Eric Moritz <http://eric.themoritzfamily.com/>
+ Robin Munn <http://www.geekforgod.com/>
+ Robert Myers <myer0052@gmail.com>
+ Nebojša Dorđević
+ Fraser Nevett <mail@nevett.org>
+ Sam Newman <http://www.magpiebrain.com/>
+ Neal Norwitz <nnorwitz@google.com>
+ oggie rob <oz.robharvey@gmail.com>
+ Jay Parlar <parlar@gmail.com>
+ pavithran s <pavithran.s@gmail.com>
+ pgross@thoughtworks.com
+ phaedo <http://phaedo.cx/>
+ phil@produxion.net
+ phil.h.smith@gmail.com
+ Gustavo Picon
+ Luke Plant <http://lukeplant.me.uk/>
+ plisk
+ Daniel Poelzleithner <http://poelzi.org/>
+ J. Rademaker
+ Michael Radziej <mir@noris.de>
+ ramiro
+ Brian Ray <http://brianray.chipy.org/>
+ remco@diji.biz
+ rhettg@gmail.com
+ Oliver Rutherfurd <http://rutherfurd.net/>
+ Ivan Sagalaev (Maniac) <http://www.softwaremaniacs.org/>
+ David Schein
+ scott@staplefish.com
+ serbaut@gmail.com
+ Pete Shinners <pete@shinners.org>
+ SmileyChris <smileychris@gmail.com>
+ smurf@smurf.noris.de
+ sopel
+ Georgi Stanojevski <glisha@gmail.com>
+ Thomas Steinacher <http://www.eggdrop.ch/>
+ nowell strite
+ Radek Å varz <http://www.svarz.cz/translate/>
+ Swaroop C H <http://www.swaroopch.info>
+ Aaron Swartz <http://www.aaronsw.com/>
+ Tyson Tate <tyson@fallingbullets.com>
+ Tom Tobin
+ Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
+ torne-django@wolfpuppy.org.uk
+ Karen Tracey <graybark@bellsouth.net>
+ Makoto Tsuyuki <mtsuyuki@gmail.com>
+ Amit Upadhyay
+ Geert Vanderkelen
+ viestards.lists@gmail.com
+ Milton Waddams
+ wam-djangobug@wamber.net
+ Dan Watson <http://theidioteque.net/>
+ Chris Wesseling <Chris.Wesseling@cwi.nl>
+ Rachel Willmer <http://www.willmer.com/kb/>
+ Gary Wilson <gary.wilson@gmail.com>
+ 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/__init__.pyc b/google_appengine/lib/django/django/__init__.pyc
new file mode 100644
index 0000000..e35da17
--- /dev/null
+++ b/google_appengine/lib/django/django/__init__.pyc
Binary files differ
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
--- /dev/null
+++ b/google_appengine/lib/django/django/bin/__init__.py
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 <language>"
+ 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
--- /dev/null
+++ b/google_appengine/lib/django/django/bin/profiling/__init__.py
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/__init__.pyc b/google_appengine/lib/django/django/conf/__init__.pyc
new file mode 100644
index 0000000..d07147b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/__init__.pyc
Binary files differ
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
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/app_template/__init__.py
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/global_settings.pyc b/google_appengine/lib/django/django/conf/global_settings.pyc
new file mode 100644
index 0000000..ca6a199
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/global_settings.pyc
Binary files differ
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 0000000..323e533
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.mo
Binary files differ
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 <trans@ahmadh.com>, 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 <ahmad@ahmadh.com>\n"
+"Language-Team: Ahmad Alhashemi <trans@ahmadh.com>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>بواسطة %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "خطأ ÙÙŠ المزود <em>(500)</em>"
+
+#: .\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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+
+#: .\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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">لتركيب أوامر المÙضلة، قم بسحب الوصلة إلى\n"
+"شريط أدات المÙضلات ÙÙŠ متصÙحك، أو قم بالضغط عليها بالزر الأيمن وأضÙها إلى Ù…Ùضلاتك.\n"
+"سيمكنك الآن أن اختيار أوامر المÙضلة من أي صÙحة ÙÙŠ الموقع، لاحظ بأن بعض\n"
+"أوامر المÙضلة هذه معدة لتعمل على أجهزة كمبيوتر تعتبر على أنها \"داخلية\"\n"
+"(تحدث إلى مسؤول النظم إذا لم تكن متأكدا ما إذا كان كمبيوتر يعتبر \"داخليا\").</p>\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 0000000..02c1d67
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..29b16ae
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.po
@@ -0,0 +1,110 @@
+# 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 <EMAIL@ADDRESS>, 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 <ahmad@ahmadh.com>\n"
+"Language-Team: Ahmad Alhashemi <trans@ahmadh.com>\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 0000000..672e73e
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.po
new file mode 100644
index 0000000..5171baf
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.po
@@ -0,0 +1,1993 @@
+# django.po -- Bengali (bn) translation of Django core
+# Copyright (C) 2005 Django Authors
+# This file is distributed under the same license as the Django package.
+# Baishampayan Ghose <b.ghose@gnu.org.in>, 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 <b.ghose@gnu.org.in>\n"
+"Language-Team: Ankur Bangla <core@bengalinux.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "সারà§à¦­à¦¾à¦° তà§à¦°à§à¦Ÿà¦¿<em>(৫০০)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "আপনি কি <a href=\"/password_reset/\">আপনার পাসওয়ারà§à¦¡ ভà§à¦²à§‡ গেছেন</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\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 0000000..00cc135
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.po
new file mode 100644
index 0000000..212ecda
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.po
@@ -0,0 +1,2383 @@
+# 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.
+#
+# Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>, 2005.
+# Ricardo Javier Cardenes Medina <ricardo.cardenes@gmail.com>, 2005.
+# Marc Fargas <marc@fargas.com>, 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 <marc@fargas.com>\n"
+"Language-Team: <es@li.org>\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 <a href=\"password/\">change "
+"password form</a>."
+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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Per %s:</h3>\n"
+"<ul>\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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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).</p>\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 <em>(500)</em>"
+msgstr "Error del servidor <em>(500)</em>"
+
+#: 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 <strong>%(username)s</strong>."
+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 <a href=\"/password_reset/\">forgotten your password</a>?"
+#~ msgstr "Ha <a href=\"/password_reset/\">oblidat la seva clau</a>?"
+
+#~ 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 0000000..412c2eb
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.mo
Binary files differ
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 <gajon@gajon.org>, 2005.
+# Marc Fargas <marc@fargas.com>, 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 <marc@fargas.com>\n"
+"Language-Team: <es@li.org>\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 0000000..c2f04ed
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.mo
Binary files differ
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 <translate@svarz.cz>, 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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>%s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Chyba serveru <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "<a href=\"/password_reset/\">Zapomněl(a) jste své heslo?</a>"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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í\").</p>\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 0000000..097bd19
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.mo
Binary files differ
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 <translate@svarz.cz>, 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 0000000..2c2f075
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.mo
Binary files differ
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 <jason@jasondavies.com>, 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 <jason@jasondavies.com>\n"
+"Language-Team: Cymraeg <cy@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Gwall Gweinyddwr <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Ydych wedi <a href=\"/password_reset/\">anghofio eich cyfrinair</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\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 0000000..c35e11b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..991c2f7
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.po
@@ -0,0 +1,112 @@
+# Translation of Django admin JS to Welsh.
+# Copyright (C) 2005 Django project
+# This file is distributed under the same license as the Django package.
+# Jason Davies <jason@jasondavies.com>, 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 <jason@jasondavies.com>\n"
+"Language-Team: Cymraeg <cy@li.org>\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 0000000..9f86e56
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.mo
Binary files differ
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 <runerl@skjoldhoej.dk>, 2006.
+# Copyright (C) 2005 and beyond
+# This file is distributed under the same license as the PACKAGE package.
+# Morten Bagai <m@bagai.com>, Nov 2005.
+# Rune Rønde Laursen <runerl@skjoldhoej.dk>, 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 <runerl@skjoldhoej.dk>\n"
+"Language-Team: Dansk <dansk@klid.dk>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Af %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Serverfejl <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Har du <a href=\"/password_reset/\">glemt din adgangskode</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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\").</p>\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 0000000..b5c518c
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.mo
Binary files differ
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 <EMAIL@ADDRESS>, 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 <dirk.eschler@gmx.net>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Nach %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Serverfehler <em>(500)</em>"
+
+#: .\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 <strong>%(username)s</strong>."
+msgstr "Bitte geben Sie ein neues Passwort für den Benutzer <strong>%(username)s</strong> 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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.</p>\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 <a href=\"password/\">change password form</a>."
+msgstr "Die Form '[algo]$[salt]$[hexdigest]' verwenden, oder das <a href=\"password/\">Passwort ändern Formular</a> 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 0000000..9f39c16
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..3c0852e
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.po
@@ -0,0 +1,119 @@
+# 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 <EMAIL@ADDRESS>, 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 <dirk.eschler@gmx.net>\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 0000000..4a7d8e4
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.mo
Binary files differ
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 <panos.laganakos@gmail.com>, 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 <panos.laganakos@gmail.com>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Από %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Σφάλμα Διακομιστή <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "<a href=\"/password_reset/\">Ξεχάσατε τον κωδικό σας;</a> "
+
+#: 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"
+"<p class=\"help\">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\").</p>\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 "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ÎÏα:"
+
+#: 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 0000000..7d43b31
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..545f9f8
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.po
@@ -0,0 +1,109 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2006 and beyond.
+# This file is distributed under the same license as the Django package.
+# Orestis Markou <orestis@orestis.gr>, 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 <orestis@orestis.gr>\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 0000000..6c4dbe4
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 0000000..feba39f
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,2097 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+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 <a href=\"/password_reset/\">forgotten your password</a>?"
+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"
+"<p class=\"help\">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\").</p>\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 0000000..b51ec1c
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..802e0db
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.po
@@ -0,0 +1,108 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 0000000..e9105aa
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000..d8166e6
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,2371 @@
+# translation of django.po to Castellano
+# This file is distributed under the same license as the PACKAGE package.
+# Copyright (C) 2007 THE PACKAGE'S COPYRIGHT HOLDER.
+#
+# Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>, 2005.
+# Ricardo Javier Cardenes Medina <ricardo.cardenes@gmail.com>, 2005.
+# AgarFu <heaven@croasanaso.sytes.net>, 2007.
+# Mario Gonzalez <gonzalemario @t gmail.com>, 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 <gonzalemario @t gmail.com>\n"
+"Language-Team: Castellano <Django-I18N@googlegroups.com>\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 <a href=\"password/\">change "
+"password form</a>."
+msgstr ""
+"Use'[algo]$[sal]$[hash hexadecimal]' o use <a href=\"password/\">el "
+"formulario para cambiar la contraseña</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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Por %s:</h3>\n"
+"<ul>\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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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).</"
+"p>\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 <em>(500)</em>"
+msgstr "Error de servidor <em>(500)</em>"
+
+#: 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 <strong>%(username)s</strong>."
+msgstr ""
+"Introduzca una nueva contraseña para el usuario <strong>%(username)s</"
+"strong>."
+
+#: 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 0000000..701443c
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.mo
Binary files differ
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 <gajon@gajon.org>, 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 <gajon@gajon.org>\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 0000000..b7f7776
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.po
new file mode 100644
index 0000000..c299f2c
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.po
@@ -0,0 +1,2425 @@
+# Translation of django.po to Argentinean spanish, based on Spanish
+# translation work by Ricardo Javier Cárdenes Medina.
+# This file is distributed under the same license as the Django package.
+# Copyright (C) 2006,2007 Ramiro Morales <rm0@gmx.net>
+#
+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 <rm0@gmx.net>\n"
+"Language-Team: Spanish <es@li.org>\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 <a href=\"password/\">change "
+"password form</a>."
+msgstr ""
+"Use '[algo]$[salt]$[hexdigest]' o use el <a href=\"password/\">formulario de "
+"cambio de contraseña</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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Por %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Error de servidor <em>(500)</em>"
+
+#: 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 <strong>%(username)s</strong>."
+msgstr ""
+"Introduzca una nueva contraseªna para el usuario <strong>%(username)s</"
+"strong>."
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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).</"
+"p>\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 <a href=\"/password_reset/\">forgotten your password</a>?"
+#~ msgstr "¿Ha <a href=\"/password_reset/\">olvidado su contraseña</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 0000000..d8e3cc6
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..91ecb09
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po
@@ -0,0 +1,118 @@
+# Argentinean spanish translation for the django-admin JS files, based on
+# Spanish translation work by Jorge Gajon.
+# This file is distributed under the same license as the Django package.
+# Copyright (C) Ramiro Morales <rm0@gmx.net>, 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 <rm0@gmx.net>\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 0000000..024d2ad
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.mo
Binary files differ
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 <EMAIL@ADDRESS>, 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 <antti.kaihola@ambitone.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Yksi %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Palvelinvirhe <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Oletko <a href=\"/password_reset\">unohtanut salasanasi</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr "\n<p class=\"help\">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ä).</p>\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 0000000..34b397e
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..35aa82e
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.po
@@ -0,0 +1,110 @@
+# 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 <EMAIL@ADDRESS>, 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 <akaihola@ambitone.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 0000000..14b8a93
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.mo
Binary files differ
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 <laurent.rahuel@gmail.com>, 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 <baptiste.goupil_at_google_email.com>\n"
+"Language-Team: français <fr@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Par %s :</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Erreur du serveur <em>(500)</em>"
+
+#: 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 <strong>%(username)s</strong>."
+msgstr ""
+"Entrez un nouveau mot de passe pour l'utilisateur <strong>%(username)s</"
+"strong>."
+
+#: 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"
+"<p class=\"help\">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\").</p>\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 <a href=\"password/\">change "
+"password form</a>."
+msgstr ""
+"Utilisez [algo]$[salt]$[hexdigest]' ou le <a href=\"password/\">formulaire "
+"de changement de mot de passe</a>."
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+#~ msgstr ""
+#~ "Avez vous <a href=\"/password_reset/\">perdu votre mot de passe</a>?"
+
+#~ 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 0000000..12a53b3
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..8cf4671
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.po
@@ -0,0 +1,110 @@
+# French translation for js.
+# Copyright (C) 2005 Mikaël Barbero
+# This file is distributed under the same license as the PACKAGE package.
+# Mikaël Barbero <mikael.barbero nospam at nospam free.fr>, 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 <mikael.barbero nospam at nospam free.fr>\n"
+"Language-Team: French <fr@li.org>\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 0000000..00beabe
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.mo
Binary files differ
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 <fonzzo.django@gmail.com>, 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 <fonzzo.django@gmail.com>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Por %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Erro do servidor <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "<a href=\"/password_reset/\">Esqueceu o contrasinal</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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\" .</p>\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 0000000..140f9a2
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..2a8f284
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.po
@@ -0,0 +1,110 @@
+# Translation of djangojs.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 <fonzzo.django@gmail.com>, 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 <fonzzo.django@gmail.com>\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 0000000..0f8b07e
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.mo
Binary files differ
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 <meir@mksoft.co.il>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>×¢\"×™ %s:</h3>\n"
+"<ul>\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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">כדי להתקין ייסומניות, יש לגרור ×ת הקישור לסרגל הסימניות\n"
+"שלך, ×ו קליק ימני והוספה לסימניות. כעת ניתן\n"
+"לבחור ×ת הייסומניה מכל עמוד ב×תר. יש ×œ×©×™× ×œ×‘ ×›×™ חלק מהייסומניות\n"
+"ניתנות לצפיה רק ממחשב שמסווג\n"
+"×›\"פנימי\" (יש לדבר ×¢× ×ž× ×”×œ המערכת שלך ×× ×ינך בטוח/×”\n"
+"שהמחשב מסווג ככזה).</p>\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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "×”×× <a href=\"/password_reset/\">שכחת ×ת הסיסמה שלך</a>?"
+
+#: 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 <em>(500)</em>"
+msgstr "שגי×ת שרת <em>(500)</em>"
+
+#: 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 0000000..2539598
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.mo
Binary files differ
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 <meir@mksoft.co.il>, 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 <meir@mksoft.co.il>\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 0000000..797b87b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.mo
Binary files differ
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 <charlie@rendszergazda.com>, 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 <charlie@rendszergazda.com>\n"
+"Language-Team: <hu@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Szerző %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Szerver hiba <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Csak nem <a href=\"/password_reset/\">elfelejtetted a jelszavadat</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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.).</p>\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 0000000..5b8214f
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..901a90b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.po
@@ -0,0 +1,110 @@
+# translation of djangojs.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 <charlie@rendszergazda.com>, 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 <charlie@rendszergazda.com>\n"
+"Language-Team: <hu@li.org>\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 0000000..6efae42
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.mo
Binary files differ
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 <dagurp@gmail.com>, 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 <dagurp@gmail.com>\n"
+"Language-Team: <dagurp@gmail.com>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Frá %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Kerfisvilla <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "<a href=\"/password_reset/\">Gleymdir þú lykilorðinu</a> þí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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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\").</p>\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 0000000..55a333b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..dec49d6
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.po
@@ -0,0 +1,109 @@
+# 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 <EMAIL@ADDRESS>, 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 <dagurp@gmail.com>\n"
+"Language-Team: Icelandic <dagurp@gmail.com>\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 0000000..8218283
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.mo
Binary files differ
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 <carlo.miron AT gmail.com>, 2006.
+# Nicola 'tekNico' Larosa <nico AT tekNico.net>, 2007.
+# Nicola Larosa <nico@tekNico.net>, 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 <nico@tekNico.net>\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 <a href=\"password/\">change "
+"password form</a>."
+msgstr "Usare '[algo]$[salt]$[hexdigest]' oppure la maschera di <a href=\"password/\">cambio password</a>."
+
+#: 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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Da %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Errore del server <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Hai <a href=\"/password_reset/\">dimenticato la password</a>?"
+
+#: 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 <strong>%(username)s</strong>."
+msgstr "Inserire una nuova password per l'utente <strong>%(username)s</strong>."
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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\").</p>\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 0000000..c34009f
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.mo
Binary files differ
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 <carlo.miron AT gmail.com>, 2006.
+# Nicola 'tekNico' Larosa <nico@tekNico.net>, 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 <nico@tekNico.net>\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 0000000..97b8f41
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.po
new file mode 100644
index 0000000..4670050
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.po
@@ -0,0 +1,2327 @@
+# Translation of django.po to japanese.
+# Copyright (C) 2005,2006 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# makoto tsuyuki <mtsuyuki@gmail.com>, 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 <mtsuyuki@gmail.com>\n"
+"Language-Team: Japanese <django-ja@googlegroups.com>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>%s ã§çµžã‚Šè¾¼ã‚€</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "サーãƒã‚¨ãƒ©ãƒ¼ <em>(500)</em>"
+
+#: 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 <strong>%(username)s</strong>."
+msgstr "<strong>%(username)s</strong>ã•ã‚“ã®æ–°ã—ã„パスワードを入力ã—ã¦ãã ã•ã„。"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">ブックマークレットをインストールã™ã‚‹ã«ã¯ã€ãƒªãƒ³ã‚¯ã‚’ブック"
+"マークツールãƒãƒ¼ã«ãƒ‰ãƒ©ãƒƒã‚°ã™ã‚‹ã‹ã€\n"
+"リンクをå³ã‚¯ãƒªãƒƒã‚¯ã—ã¦ãƒ–ックマークã«è¿½åŠ ã—ã¦ãã ã•ã„。ã“ã‚Œã§\n"
+"サイトã®ã©ã®ãƒšãƒ¼ã‚¸ã‹ã‚‰ã§ã‚‚ブックマークレットをé¸æŠžå¯èƒ½ã«ãªã‚Šã¾ã—ãŸã€‚\n"
+"ブックマークレットã«ã‚ˆã£ã¦ã¯ã€å†…部ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã«ã‚るコンピュータã‹ã‚‰ã“ã®ã‚µã‚¤"
+"トを\n"
+"å‚ç…§ã—ã¦ã„ãªã‘ã‚Œã°ãªã‚‰ãªã„ã“ã¨ãŒã‚ã‚Šã¾ã™ã€‚内部ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã«ã‚ã‚‹ã‹ã©ã†ã‹ä¸æ˜Ž"
+"ãªå ´åˆã¯ã€ã‚·ã‚¹ãƒ†ãƒ ç®¡ç†è€…ã«ç¢ºèªã—ã¦ãã ã•ã„。</p>\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 <a href=\"password/\">change "
+"password form</a>."
+msgstr ""
+"'[algo]$[salt]$[hexdigest]'å½¢å¼ã‹ã€"
+"<a href=\"password/\">パスワード変更フォーム</a>を使ã£ã¦ãã ã•ã„。"
+
+#: 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 0000000..bddecac
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..0ec1cad
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.po
@@ -0,0 +1,118 @@
+# 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 <EMAIL@ADDRESS>, 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 <mtsuyuki@gmail.com>\n"
+"Language-Team: Japanese <django-ja@googlegroups.com>\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 0000000..51e9ab5
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.mo
Binary files differ
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 <translation@sampada.info>
+# This file is distributed under the same license as the Django package.
+# Kannada Localization Team <translation@sampada.info>, 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 <translation@sampada.info>\n"
+"Language-Team: Kannada <translation@sampada.info>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>%s ಇಂದ :</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "ಸರà³à²µà²°à³ ದೋಷ<em>(೫೦೦)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr ""
+"ನೀವೠ<a "
+"href=\"/password_reset/\">ಪà³à²°à²µà³‡à²¶à²ªà²¦à²µà²¨à³à²¨à³ "
+"ಮರೆತಿದà³à²¦à³€à²°à²¾</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p "
+"class=\"help\">ಸಣà³à²£à³à²ªà³à²Ÿà²—à³à²°à³à²¤à³à²—ಳನà³à²¨à³ "
+"ಅನà³à²¸à³à²¥à²¾à²ªà²¿à²¸à²²à³ ಕೊಂಡಿಯನà³à²¨à³ "
+"ಪà³à²Ÿà²—à³à²°à³à²¤à³ ಉಪಕರಣಪಟà³à²Ÿà²¿(ಟೂಲೠ"
+"ಬಾರà³)ಕಡೆಗೆ ಎಳೆದೊಯà³à²¯à²¿à²°à²¿,\n"
+" ಅಥವಾ ಕೊಂಡಿಯ ಮೇಲೆ "
+"ಮೂಷಿಕ(ಮೌಸà³)ದ ಬಲಬಟನà³à²¨à²¨à³à²¨à³ "
+"ಒತà³à²¤à²¿ ಪà³à²Ÿà²—à³à²°à³à²¤à³à²—ಳಿಗೆ "
+"ಸೇರಿಸಿಕೊಳà³à²³à²¿. ಈಗ \n"
+" ನೀವೠಸಣà³à²£à²ªà³à²Ÿà²—à³à²°à³à²¤à³à²—ಳನà³à²¨à³ "
+"ತಾಣದ ಯಾವ ಪà³à²Ÿà²¦à²¿à²‚ದ ಬೇಕಾದರೂ "
+"ಆಯà³à²•à³†à²®à²¾à²¡à²¿à²•à³Šà²³à³à²³à²¬à²¹à³à²¦à³.\n"
+"ನೆನಪಿರಲಿ, ಕೆಲವೠ"
+"ಸಣà³à²£à²ªà³à²Ÿà²—à³à²°à³à²¤à³à²—ಳಿಗೆ ನೀವೠಈ "
+"ತಾಣವನà³à²¨à³ ಆಂತರಿಕ (\"internal\") ಎಂದೠ"
+"ಸೂಚಿತವಾಗಿರà³à²µ ಗಣಕದಿಂದ "
+"ವೀಕà³à²·à²¿à²¸à²¬à³‡à²•à²¾à²—à³à²¤à³à²¤à²¦à³†.\n"
+"( ನಿಮà³à²® ಗಣಕವೠ\"internal\" ಹೌದೇ "
+"ಅಲà³à²²à²µà³‡ ಎಂದೠ"
+"ಗೊತà³à²¤à²¿à²²à³à²²à²¦à²¿à²¦à³à²¦à²°à³† , ಗಣಕದ "
+"ಆಡಳಿತಗಾರರನà³à²¨à³ ಕೇಳಿರಿ).</p>\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 0000000..cbb9fb5
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..38b4a6d
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.po
@@ -0,0 +1,116 @@
+# 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 <EMAIL@ADDRESS>, 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 <translation@sampada.info>\n"
+"Language-Team: Kannada <translation@sampada.info>\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 0000000..21f9e8a
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.po
new file mode 100644
index 0000000..72e2316
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.po
@@ -0,0 +1,2326 @@
+# Django Latvian translation.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# ARVIS BICKOVSKIS <viestards.lists@gmail.com>, 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 <viestards.lists@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 <a href=\"password/\">change "
+"password form</a>."
+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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\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"
+"<p class=\"help\">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\").</p>\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 <em>(500)</em>"
+msgstr "Servera kļūda <em>(500)</em>"
+
+#: 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 <strong>%(username)s</strong>."
+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 <a href=\"/password_reset/\">forgotten your password</a>?"
+#~ msgstr "Vai esat <a href=\"/password_reset/\">aizmirsis paroli</a>?"
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 0000000..b81550a
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..534cffd
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.po
@@ -0,0 +1,118 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 0000000..5ab5aa6
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.mo
Binary files differ
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 <glisha@gmail.com>, 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 <glisha@gmail.com>\n"
+"Language-Team: Macedonian <ossm-members@hedona.on.net.mk>\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 <a href=\"password/\">change "
+"password form</a>."
+msgstr "КориÑтете '[algo]$[salt]$[hexdigest]' или кориÑтете ја <a href=\"password/\">формата за промена на лозинката</a>."
+
+#: 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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Од %s:</h3>\n"
+"<ul>\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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">За да инÑталирате обележувачи, влечете ја врÑката до "
+"вашата\n"
+"лента Ñо алатки, или кликнете Ñо деÑното копче и додадете го во вашите \n"
+"обележувачи. Сега може да го изберете обележувачот од било која Ñтраница "
+"на \n"
+"Ñајтот. Имајте на ум дека за некои од овие обележувачи е потребно да го "
+"гледате \n"
+"Ñајтот од компјутер кој е означен како „внатрешен“ (разговарајте Ñо вашиот \n"
+"админиÑтратор ако не Ñте Ñигурни).</p>\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 <em>(500)</em>"
+msgstr "Грешка Ñо Ñерверот <em>(500)</em>"
+
+#: 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 <strong>%(username)s</strong>."
+msgstr "ВнеÑете нова лозинка за кориÑникот <strong>%(username)s</strong>."
+
+#: 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 0000000..64971dc
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.mo
Binary files differ
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 <glisha@gmail.com>, 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 <glisha@gmail.com>\n"
+"Language-Team: Macedonian <ossm-members@hedona.on.net.mk>\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 0000000..f4be11f
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.mo
Binary files differ
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 <johan@nilling.nl>, 2005.
+# Rudolph Froger <rfroger@estrate.nl>, 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 <johan@nilling.nl>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Door %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Server Fout <em>(500)</em>"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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).<p>\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 0000000..6b3dfaf
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..0a4ccd5
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.po
@@ -0,0 +1,110 @@
+# Dutch Javascript translations.
+# Copyright (C) 2006
+# This file is distributed under the same license as the Django package.
+# Rudolph Froger <rfroger@estrate.nl>, 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 <rfroger@estrate.nl>\n"
+"Language-Team: nl <LL@li.org>\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 0000000..ee2152b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.mo
Binary files differ
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 <espen@grindhaug.org>, 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 <espen@grindhaug.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Av %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Tjener feil <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Har du <a href=\"/password_reset/\">glemt passordet ditt</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\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 0000000..4b23aba
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..c608764
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.po
@@ -0,0 +1,118 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Espen Grindhaug <espen.grindhaug@mail.com>, 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 <espen.grindhaug@gmail.com>\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 0000000..d132fc1
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.po
new file mode 100644
index 0000000..61fc4d0
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.po
@@ -0,0 +1,1961 @@
+# Polish .po file.
+# Copyright (C) 2006 Krzysztof Kajkowski
+# This file is distributed under the same license as the django package.
+# Krzysztof Kajkowski <krzysztof.kajkowski@gmail.com>, 2006.
+# cayco <cayco@cayco.pl>, 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 <admin@rk.edu.pl>\n"
+"Language-Team: Polish <pl@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Przez %s:</h3>\n"
+"</ul>\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 <em>(500)</em>"
+msgstr "BÅ‚Ä…d Serwera <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr ""
+"Czy <a href=\"/password_reset/\">zapomniałeś/łaś</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"
+"<p class=\"help\">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\").</p>\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 0000000..752211a
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.mo
Binary files differ
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 <troll@pld-linux.org>, 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 <troll@pld-linux.org>\n"
+"Language-Team: Polish <pl@li.org>\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 0000000..7adc41b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.po
new file mode 100644
index 0000000..d99f51d
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.po
@@ -0,0 +1,2125 @@
+# 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 <nmariz@gmail.com>, 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 <nmariz@gmail.com>\n"
+"Language-Team: pt_PT <nmariz@gmail.com>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Por %s:</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Erro do servidor <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "<a href=\"/password_reset/\">Esqueceu-se a sua palavra-passe?</a>"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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\".</p>\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 0000000..669dd9d
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.mo
Binary files differ
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 <nmariz@gmail.com>, 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 <nmariz@gmail.com>\n"
+"Language-Team: pt_PT <nmariz@gmail.com>\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 0000000..7f506f9
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.mo
Binary files differ
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 <joaoma@gmail.com>, 2006.
+# Carlos Eduardo de Paula <carlosedp@gmail.com>, 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 <carlosedp@gmail.com>\n"
+"Language-Team: Português do Brasil <pt-br@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Por %s</h3>\n"
+"<ul>\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 <em>(500)</em>"
+msgstr "Erro no Servidor <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Você <a href=\"/password_reset/\">esqueceu sua senha</a>?"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">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\").</p>\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 0000000..31a2b1b
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.mo
Binary files differ
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 <carlosedp@gmail.com>, 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 <carlosedp@gmail.com>\n"
+"Language-Team: Português do Brasil <pt-br@li.org>\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 0000000..ffac5d5
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.mo
Binary files differ
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 <tibimicu@gmx.net>, 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 <tibimicu@gmx.net>\n"
+"Language-Team: Romanian <ro@li.org>\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\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 "Luni"
+
+#: 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 "Noi."
+
+#: contrib/admin/filterspecs.py:150
+msgid "Unknown"
+msgstr ""
+
+#: contrib/admin/models.py:16
+msgid "action time"
+msgstr "timp acţiune"
+
+#: contrib/admin/models.py:19
+msgid "object id"
+msgstr "id obiect"
+
+#: contrib/admin/models.py:20
+msgid "object repr"
+msgstr "repr obiect"
+
+#: contrib/admin/models.py:21
+msgid "action flag"
+msgstr "steguleţ acţiune"
+
+#: contrib/admin/models.py:22
+msgid "change message"
+msgstr "schimbă mesaj"
+
+#: contrib/admin/models.py:25
+msgid "log entry"
+msgstr "intrare log"
+
+#: contrib/admin/models.py:26
+msgid "log entries"
+msgstr "intrări log"
+
+#: 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 "Login"
+
+#: 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 "Administrare Django"
+
+#: 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 "Adaugă"
+
+#: 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 "Schimbă"
+
+#: 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 "Schimbă"
+
+#: 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 "Schimbă parola"
+
+#: 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 "timp acţiune"
+
+#: contrib/admin/views/doc.py:282
+#, fuzzy
+msgid "Date (with time)"
+msgstr "Dată/oră"
+
+#: contrib/admin/views/doc.py:283
+#, fuzzy
+msgid "E-mail address"
+msgstr "Adresa email:"
+
+#: 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 "Decembrie"
+
+#: 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 "Introduceţi un număr întreg."
+
+#: 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 "URL"
+
+#: 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 "Schimbă parola"
+
+#: 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 "Acasă"
+
+#: contrib/admin/templates/admin/object_history.html:5
+#: contrib/admin/templates/admin/change_form.html:20
+msgid "History"
+msgstr "Istoric"
+
+#: contrib/admin/templates/admin/object_history.html:18
+msgid "Date/time"
+msgstr "Dată/oră"
+
+#: contrib/admin/templates/admin/object_history.html:19
+msgid "User"
+msgstr "Utilizator"
+
+#: contrib/admin/templates/admin/object_history.html:20
+msgid "Action"
+msgstr "Acţiune"
+
+#: 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 ""
+"Acest obiect nu are un istoric al schimbărilor. Probabil nu a fost adăugat "
+"prinintermediul acestui sit de administrare."
+
+#: contrib/admin/templates/admin/base_site.html:4
+msgid "Django site admin"
+msgstr "Administrare sit Django"
+
+#: contrib/admin/templates/admin/base_site.html:7
+msgid "Django administration"
+msgstr "Administrare Django"
+
+#: contrib/admin/templates/admin/500.html:4
+msgid "Server error"
+msgstr "Eroare de server"
+
+#: contrib/admin/templates/admin/500.html:6
+msgid "Server error (500)"
+msgstr "Eroare de server (500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "Eroare server <em>(500)</em>"
+
+#: 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 ""
+"A apărut o eroare. Este raportată către administrator via emailşi va fi "
+"fixată în scurt timp. Mulţumim pentru înţelegere."
+
+#: contrib/admin/templates/admin/404.html:4
+#: contrib/admin/templates/admin/404.html:8
+msgid "Page not found"
+msgstr "Pagină inexistentă"
+
+#: contrib/admin/templates/admin/404.html:10
+msgid "We're sorry, but the requested page could not be found."
+msgstr "Ne pare rău, dar pagina cerută nu există."
+
+#: 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 "Adaugă"
+
+#: contrib/admin/templates/admin/index.html:34
+msgid "Change"
+msgstr "Schimbă"
+
+#: contrib/admin/templates/admin/index.html:44
+msgid "You don't have permission to edit anything."
+msgstr "Nu ai drepturi de editare."
+
+#: contrib/admin/templates/admin/index.html:52
+msgid "Recent Actions"
+msgstr "Acţiuni Recente"
+
+#: contrib/admin/templates/admin/index.html:53
+msgid "My Actions"
+msgstr "Acţiunile Mele"
+
+#: contrib/admin/templates/admin/index.html:57
+msgid "None available"
+msgstr "Indisponibil"
+
+#: contrib/admin/templates/admin/change_list.html:11
+#, fuzzy, python-format
+msgid "Add %(name)s"
+msgstr "Adaugă"
+
+#: contrib/admin/templates/admin/login.html:22
+msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Ai <a href=\"/password_reset/\">uitat parola</a>?"
+
+#: contrib/admin/templates/admin/base.html:23
+msgid "Welcome,"
+msgstr "Bine ai venit,"
+
+#: 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 ""
+"Ştergînd %(object_name)s '%(object)s' va avea ca rezultat ştergerea şi a "
+"obiectelor ce au legătură, dar contul tău nu are permisiunea de a şterge "
+"următoarele tipuri de obiecte:"
+
+#: 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 ""
+"Eşti sigur că vrei să ştergi %(object_name)s \"%(object)s\"?Următoarele "
+"componente vor fi ÅŸterse:"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:26
+msgid "Yes, I'm sure"
+msgstr "Da, sînt sigur"
+
+#: 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 "activ"
+
+#: 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 "Schimbă parola"
+
+#: contrib/admin/templates/registration/password_change_done.html:6
+#: contrib/admin/templates/registration/password_change_done.html:10
+msgid "Password change successful"
+msgstr "Schimbare reuşită a parolei"
+
+#: contrib/admin/templates/registration/password_change_done.html:12
+msgid "Your password was changed."
+msgstr "Parola a fost schimbată."
+
+#: 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 "Resetează parola"
+
+#: 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 ""
+"Ai uitat parola? Introdu adresa de email mai jos, iar noi vom reseta parola "
+"şi-ţi vom trimite una nouă prin email."
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "E-mail address:"
+msgstr "Adresa email:"
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "Reset my password"
+msgstr "Resetează-mi parola"
+
+#: contrib/admin/templates/registration/logged_out.html:8
+msgid "Thanks for spending some quality time with the Web site today."
+msgstr "Mulţumesc pentru petrecerea folositoare a timpului cu saitul astăzi."
+
+#: contrib/admin/templates/registration/logged_out.html:10
+msgid "Log in again"
+msgstr "Relogare"
+
+#: contrib/admin/templates/registration/password_reset_done.html:6
+#: contrib/admin/templates/registration/password_reset_done.html:10
+msgid "Password reset successful"
+msgstr "Parola resetată cu succes"
+
+#: 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 ""
+"Am trimis o nouă parolă prin email la adresa furnizată. Ar trebuisă o "
+"primeşti în scurt timp."
+
+#: 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 ""
+"Introdu te rog vechea parolă, pentru motive de siguranţă, şi apoi tastează "
+"noua parolă de două ori aşa încît putem verifica dacă ai tastat corect."
+
+#: contrib/admin/templates/registration/password_change_form.html:17
+msgid "Old password:"
+msgstr "Vechea parolă:"
+
+#: contrib/admin/templates/registration/password_change_form.html:19
+msgid "New password:"
+msgstr "Noua parolă:"
+
+#: contrib/admin/templates/registration/password_change_form.html:21
+msgid "Confirm password:"
+msgstr "Confirmă parola:"
+
+#: contrib/admin/templates/registration/password_change_form.html:23
+msgid "Change my password"
+msgstr "Schimbă-mi parola"
+
+#: contrib/admin/templates/registration/password_reset_email.html:2
+msgid "You're receiving this e-mail because you requested a password reset"
+msgstr "Ai primit acest email pentru că ai cerut o resetare a parolei"
+
+#: contrib/admin/templates/registration/password_reset_email.html:3
+#, python-format
+msgid "for your user account at %(site_name)s"
+msgstr "pentru contul tău la %(site_name)s"
+
+#: contrib/admin/templates/registration/password_reset_email.html:5
+#, python-format
+msgid "Your new password is: %(new_password)s"
+msgstr "Noua ta parolă este: %(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 "Poţi schmiba această parolă vizitînd această pagină:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:11
+msgid "Your username, in case you've forgotten:"
+msgstr "Numele tău utilizator, în caz că ai uitat:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:13
+msgid "Thanks for using our site!"
+msgstr "Mulţumesc pentru folosirea saitului nostru!"
+
+#: contrib/admin/templates/registration/password_reset_email.html:15
+#, python-format
+msgid "The %(site_name)s team"
+msgstr "Echipa %(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"
+"<p class=\"help\">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\").</p>\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 "id obiect"
+
+#: 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 "Schimbă"
+
+#: contrib/redirects/models.py:7
+msgid "redirect from"
+msgstr "redirectat de la "
+
+#: contrib/redirects/models.py:8
+msgid ""
+"This should be an absolute path, excluding the domain name. Example: '/"
+"events/search/'."
+msgstr ""
+"Aceasta ar trebui să fie o cale absolută, excluzînd numele de domeniu. "
+"Exemplu: '/evenimente/cautare/'."
+
+#: contrib/redirects/models.py:9
+msgid "redirect to"
+msgstr "redirectat la"
+
+#: contrib/redirects/models.py:10
+msgid ""
+"This can be either an absolute path (as above) or a full URL starting with "
+"'http://'."
+msgstr ""
+"Aceasta poate fi o cale absolută (ca mai sus) sau un URL începînd cu "
+"'http://'."
+
+#: contrib/redirects/models.py:12
+msgid "redirect"
+msgstr "redirectare"
+
+#: contrib/redirects/models.py:13
+msgid "redirects"
+msgstr "redictări"
+
+#: contrib/flatpages/models.py:8
+msgid ""
+"Example: '/about/contact/'. Make sure to have leading and trailing slashes."
+msgstr ""
+"Exemplu: '/about/contact'. Asiguraţi-vă că aveţi slash-uri la început şi la "
+"sfîrşit."
+
+#: contrib/flatpages/models.py:9
+msgid "title"
+msgstr "titlu"
+
+#: contrib/flatpages/models.py:10
+msgid "content"
+msgstr "conţinut"
+
+#: contrib/flatpages/models.py:11
+msgid "enable comments"
+msgstr "permite comentarii"
+
+#: contrib/flatpages/models.py:12
+msgid "template name"
+msgstr "nume ÅŸablon"
+
+#: contrib/flatpages/models.py:13
+#, fuzzy
+msgid ""
+"Example: 'flatpages/contact_page'. If this isn't provided, the system will "
+"use 'flatpages/default'."
+msgstr ""
+"Exemplu: 'flatfiles/pagina_contact'. Dacă aceasta nu există, sistemul va "
+"folosi 'flatfiles/default'."
+
+#: contrib/flatpages/models.py:14
+msgid "registration required"
+msgstr "necesită înregistrare"
+
+#: contrib/flatpages/models.py:14
+msgid "If this is checked, only logged-in users will be able to view the page."
+msgstr ""
+"Dacă aceasta este bifată, numai utilizatorii logaţi vor putea vedea pagina."
+
+#: contrib/flatpages/models.py:18
+msgid "flat page"
+msgstr "pagina plată"
+
+#: contrib/flatpages/models.py:19
+msgid "flat pages"
+msgstr "pagini plate"
+
+#: contrib/auth/models.py:13 contrib/auth/models.py:26
+msgid "name"
+msgstr "nume"
+
+#: contrib/auth/models.py:15
+msgid "codename"
+msgstr "nume cod"
+
+#: contrib/auth/models.py:17
+#, fuzzy
+msgid "permission"
+msgstr "Permisiune"
+
+#: contrib/auth/models.py:18 contrib/auth/models.py:27
+#, fuzzy
+msgid "permissions"
+msgstr "Permisiuni"
+
+#: contrib/auth/models.py:29
+#, fuzzy
+msgid "group"
+msgstr "Grup"
+
+#: contrib/auth/models.py:30 contrib/auth/models.py:65
+#, fuzzy
+msgid "groups"
+msgstr "Grupuri"
+
+#: contrib/auth/models.py:55
+msgid "username"
+msgstr "nume utilizator"
+
+#: contrib/auth/models.py:56
+msgid "first name"
+msgstr "Prenume"
+
+#: contrib/auth/models.py:57
+msgid "last name"
+msgstr "Nume"
+
+#: contrib/auth/models.py:58
+msgid "e-mail address"
+msgstr "adresa email"
+
+#: contrib/auth/models.py:59
+msgid "password"
+msgstr "parola"
+
+#: contrib/auth/models.py:59
+msgid "Use '[algo]$[salt]$[hexdigest]'"
+msgstr ""
+
+#: contrib/auth/models.py:60
+msgid "staff status"
+msgstr "stare staff"
+
+#: contrib/auth/models.py:60
+msgid "Designates whether the user can log into this admin site."
+msgstr "Decide cînd utilizatorul se poate loga în acest sit de adminstrare."
+
+#: contrib/auth/models.py:61
+msgid "active"
+msgstr "activ"
+
+#: contrib/auth/models.py:62
+msgid "superuser status"
+msgstr "stare superutilizator"
+
+#: contrib/auth/models.py:63
+msgid "last login"
+msgstr "ultima logare"
+
+#: contrib/auth/models.py:64
+msgid "date joined"
+msgstr "data aderării"
+
+#: 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 ""
+"Suplimentar permisiunilor manual alocate, acest utilizator va primi toate "
+"permisiunile alocate fiecărui grup din care el/ea face parte."
+
+#: contrib/auth/models.py:67
+#, fuzzy
+msgid "user permissions"
+msgstr "Permisiuni"
+
+#: contrib/auth/models.py:70
+#, fuzzy
+msgid "user"
+msgstr "Utilizator"
+
+#: contrib/auth/models.py:71
+#, fuzzy
+msgid "users"
+msgstr "Utilizatori"
+
+#: contrib/auth/models.py:76
+msgid "Personal info"
+msgstr "Informaţii personale"
+
+#: contrib/auth/models.py:77
+msgid "Permissions"
+msgstr "Permisiuni"
+
+#: contrib/auth/models.py:78
+msgid "Important dates"
+msgstr "Date importante"
+
+#: contrib/auth/models.py:79
+msgid "Groups"
+msgstr "Grupuri"
+
+#: contrib/auth/models.py:219
+#, fuzzy
+msgid "message"
+msgstr "Mesaj"
+
+#: 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 "nume modul python"
+
+#: contrib/contenttypes/models.py:28
+msgid "content type"
+msgstr "tip conţinut"
+
+#: contrib/contenttypes/models.py:29
+msgid "content types"
+msgstr "tipuri conţinute"
+
+#: contrib/sessions/models.py:35
+msgid "session key"
+msgstr "cheie sesiune"
+
+#: contrib/sessions/models.py:36
+msgid "session data"
+msgstr "date sesiune"
+
+#: contrib/sessions/models.py:37
+msgid "expire date"
+msgstr "data expirare"
+
+#: contrib/sessions/models.py:41
+msgid "session"
+msgstr "seiune"
+
+#: contrib/sessions/models.py:42
+msgid "sessions"
+msgstr "sesiuni"
+
+#: contrib/sites/models.py:10
+msgid "domain name"
+msgstr "nume domeniu"
+
+#: contrib/sites/models.py:11
+msgid "display name"
+msgstr "nume afiÅŸat"
+
+#: contrib/sites/models.py:15
+msgid "site"
+msgstr "sit"
+
+#: contrib/sites/models.py:16
+msgid "sites"
+msgstr "sit"
+
+#: 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 "Luni"
+
+#: utils/dates.py:6
+msgid "Tuesday"
+msgstr "Marţi"
+
+#: utils/dates.py:6
+msgid "Wednesday"
+msgstr "Miercuri"
+
+#: utils/dates.py:6
+msgid "Thursday"
+msgstr "Joi"
+
+#: utils/dates.py:6
+msgid "Friday"
+msgstr "Vineri"
+
+#: utils/dates.py:7
+msgid "Saturday"
+msgstr "Sîmbătă"
+
+#: utils/dates.py:7
+msgid "Sunday"
+msgstr "Duminică"
+
+#: utils/dates.py:14
+msgid "January"
+msgstr "Ianuarie"
+
+#: utils/dates.py:14
+msgid "February"
+msgstr "Februarie"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "March"
+msgstr "Martie"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "April"
+msgstr "Aprilie"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "May"
+msgstr "Mai"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "June"
+msgstr "Iunie"
+
+#: utils/dates.py:15 utils/dates.py:27
+msgid "July"
+msgstr "Iulie"
+
+#: utils/dates.py:15
+msgid "August"
+msgstr "August"
+
+#: utils/dates.py:15
+msgid "September"
+msgstr "Septembrie"
+
+#: utils/dates.py:15
+msgid "October"
+msgstr "Octombrie"
+
+#: utils/dates.py:15
+msgid "November"
+msgstr "Noiembrie"
+
+#: utils/dates.py:16
+msgid "December"
+msgstr "Decembrie"
+
+#: 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 "Mai"
+
+#: 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 "Ian."
+
+#: 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 "Oct"
+
+#: utils/dates.py:28
+msgid "Nov."
+msgstr "Noi."
+
+#: utils/dates.py:28
+msgid "Dec."
+msgstr "Dec."
+
+#: 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] "Mai"
+msgstr[1] "Mai"
+
+#: 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] "sit"
+msgstr[1] "sit"
+
+#: conf/global_settings.py:37
+msgid "Bengali"
+msgstr ""
+
+#: conf/global_settings.py:38
+msgid "Czech"
+msgstr "Cehă"
+
+#: conf/global_settings.py:39
+msgid "Welsh"
+msgstr ""
+
+#: conf/global_settings.py:40
+#, fuzzy
+msgid "Danish"
+msgstr "Spaniolă"
+
+#: conf/global_settings.py:41
+msgid "German"
+msgstr "Germană"
+
+#: conf/global_settings.py:42
+msgid "Greek"
+msgstr ""
+
+#: conf/global_settings.py:43
+msgid "English"
+msgstr "Engleză"
+
+#: conf/global_settings.py:44
+msgid "Spanish"
+msgstr "Spaniolă"
+
+#: conf/global_settings.py:45
+msgid "French"
+msgstr "Franceză"
+
+#: conf/global_settings.py:46
+msgid "Galician"
+msgstr "Galiciană"
+
+#: 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 "Italiană"
+
+#: conf/global_settings.py:51
+msgid "Japanese"
+msgstr ""
+
+#: conf/global_settings.py:52
+msgid "Dutch"
+msgstr ""
+
+#: conf/global_settings.py:53
+msgid "Norwegian"
+msgstr "Norvegiană"
+
+#: conf/global_settings.py:54
+msgid "Brazilian"
+msgstr "Braziliană"
+
+#: conf/global_settings.py:55
+msgid "Romanian"
+msgstr ""
+
+#: conf/global_settings.py:56
+msgid "Russian"
+msgstr "Rusă"
+
+#: conf/global_settings.py:57
+msgid "Slovak"
+msgstr ""
+
+#: conf/global_settings.py:58
+#, fuzzy
+msgid "Slovenian"
+msgstr "Sîrbă"
+
+#: conf/global_settings.py:59
+msgid "Serbian"
+msgstr "Sîrbă"
+
+#: conf/global_settings.py:60
+msgid "Swedish"
+msgstr ""
+
+#: conf/global_settings.py:61
+#, fuzzy
+msgid "Ukrainian"
+msgstr "Braziliană"
+
+#: conf/global_settings.py:62
+msgid "Simplified Chinese"
+msgstr "Chineză simplificată"
+
+#: conf/global_settings.py:63
+msgid "Traditional Chinese"
+msgstr ""
+
+#: core/validators.py:60
+msgid "This value must contain only letters, numbers and underscores."
+msgstr ""
+"Această valoare trebuie să conţină numai litere, numere şi liniuţe de "
+"subliniere."
+
+#: core/validators.py:64
+#, fuzzy
+msgid ""
+"This value must contain only letters, numbers, underscores, dashes or "
+"slashes."
+msgstr ""
+"Această valoare trebuie să conţină numai litere, numere, liniuţe de "
+"subliniere ÅŸi slash-uri."
+
+#: core/validators.py:72
+msgid "Uppercase letters are not allowed here."
+msgstr "Literele mari nu sînt permise aici."
+
+#: core/validators.py:76
+msgid "Lowercase letters are not allowed here."
+msgstr "Literele mici nu sînt permise aici."
+
+#: core/validators.py:83
+msgid "Enter only digits separated by commas."
+msgstr "Introduceţi numai numere separate de virgule."
+
+#: core/validators.py:95
+msgid "Enter valid e-mail addresses separated by commas."
+msgstr "Introduceţi adrese de email valide separate de virgule."
+
+#: core/validators.py:99
+msgid "Please enter a valid IP address."
+msgstr "Introduceţi vă rog o adresă IP validă."
+
+#: core/validators.py:103
+msgid "Empty values are not allowed here."
+msgstr "Valorile vide nu sînt permise aici."
+
+#: core/validators.py:107
+msgid "Non-numeric characters aren't allowed here."
+msgstr "Caracterele ne-numerice nu sînt permise aici."
+
+#: core/validators.py:111
+msgid "This value can't be comprised solely of digits."
+msgstr "Această valoare nu poate conţîne numai cifre."
+
+#: core/validators.py:116
+msgid "Enter a whole number."
+msgstr "Introduceţi un număr întreg."
+
+#: core/validators.py:120
+msgid "Only alphabetical characters are allowed here."
+msgstr "Numai caractere alfabetice sînt permise aici."
+
+#: core/validators.py:124
+msgid "Enter a valid date in YYYY-MM-DD format."
+msgstr "Introduceţi o dată validă in format: AAAA-LL-ZZ."
+
+#: core/validators.py:128
+msgid "Enter a valid time in HH:MM format."
+msgstr "Introduceţi o oră în format OO: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 "Introduceţi o dată/oră validă în format AAAA-LL-ZZ OO:MM."
+
+#: core/validators.py:136
+msgid "Enter a valid e-mail address."
+msgstr "Introduceţi o adresă de email validă."
+
+#: core/validators.py:148
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+"Încărcaţi o imagine validă. Fişierul încărcat nu era o imagine sau era o "
+"imagine coruptă."
+
+#: core/validators.py:155
+#, python-format
+msgid "The URL %s does not point to a valid image."
+msgstr "URL-ul %s nu pointează către o imagine validă."
+
+#: core/validators.py:159
+#, python-format
+msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
+msgstr ""
+"Numerele de telefon trebuie să fie in format XXX-XXX-XXXX. \"%s\" e invalid."
+
+#: core/validators.py:167
+#, python-format
+msgid "The URL %s does not point to a valid QuickTime video."
+msgstr "URL-ul %s nu pointează către o imagine video QuickTime validă."
+
+#: core/validators.py:171
+msgid "A valid URL is required."
+msgstr "E necesar un URL valid."
+
+#: core/validators.py:185
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"E necesar cod HTML valid. Erorile specifice sînt:\n"
+"%s"
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Format XML invalid: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "URL invalid: %s"
+
+#: core/validators.py:206 core/validators.py:208
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "URL-ul %s e invalid."
+
+#: core/validators.py:214
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "Introduceţi o abreviere validă în U.S."
+
+#: 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] "Îngrijiţi-vă limbajul! Cuvîntul %s nu este permis aici."
+msgstr[1] "Îngrijiţi-vă limbajul! Cuvintele %s nu sînt permise aici."
+
+#: core/validators.py:236
+#, python-format
+msgid "This field must match the '%s' field."
+msgstr ""
+
+#: core/validators.py:255
+#, fuzzy
+msgid "Please enter something for at least one field."
+msgstr "Vă rog comletaţi ambele cîmpuri sau lăsaţi-le goale pe ambele."
+
+#: core/validators.py:264 core/validators.py:275
+msgid "Please enter both fields or leave them both empty."
+msgstr "Vă rog comletaţi ambele cîmpuri sau lăsaţi-le goale pe ambele."
+
+#: core/validators.py:282
+#, python-format
+msgid "This field must be given if %(field)s is %(value)s"
+msgstr "Acest cîmp e necesar dacă %(field)s este %(value)s"
+
+#: core/validators.py:294
+#, python-format
+msgid "This field must be given if %(field)s is not %(value)s"
+msgstr "Acest cîmp e necesar dacă %(field)s nu este %(value)s"
+
+#: core/validators.py:313
+msgid "Duplicate values are not allowed."
+msgstr "Valorile duplicate nu sînt permise."
+
+#: core/validators.py:336
+#, python-format
+msgid "This value must be a power of %s."
+msgstr "Această valoare trebuie să fie o putere a lui %s."
+
+#: core/validators.py:347
+msgid "Please enter a valid decimal number."
+msgstr "Vă rog introduceţi un număr zecimal valid."
+
+#: 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] "Vă rog introduceţi un număr zecimal valid cu cel mult %s cifră."
+msgstr[1] "Vă rog introduceţi un număr zecimal valid cu cel mult %s cifre."
+
+#: 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] "Vă rog introduceţi un număr zecimal valid cu cel mult %s zecimală."
+msgstr[1] "Vă rog introduceţi un număr zecimal valid cu cel mult %s zecimale."
+
+#: core/validators.py:362
+#, python-format
+msgid "Make sure your uploaded file is at least %s bytes big."
+msgstr "Asigură-te că fişierul încărcact are cel puţin %s octeţi."
+
+#: core/validators.py:363
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr "Asigură-te că fişierul încărcact are cel mult %s octeţi."
+
+#: core/validators.py:376
+msgid "The format for this field is wrong."
+msgstr "Formatul acestui cîmp este invalid."
+
+#: core/validators.py:391
+msgid "This field is invalid."
+msgstr "Cîmpul este invalid."
+
+#: core/validators.py:426
+#, python-format
+msgid "Could not retrieve anything from %s."
+msgstr "Nu pot prelua nimic de la %s."
+
+#: core/validators.py:429
+#, python-format
+msgid ""
+"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
+msgstr ""
+"URL-ul %(url)s a returnat un header Content-Type invalid '%(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 ""
+"Te rog închide tagurile %(tag)s din linia %(line)s. ( Linia începe cu \"%"
+"(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 ""
+"Textul începînd cu linia %(line)s nu e permis în acest context. (Linia "
+"începînd cu \"%(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\" în linia %(line)s e un atribut invalid. (Linia începînd cu \"%"
+"(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>\" în linia %(line)s este un tag invalid. (Linia începe cu \"%"
+"(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 ""
+"Unui tag din linia %(line)s îi lipseşte unul sau mai multe atribute. (Linia "
+"începe cu \"%(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 ""
+"Atributul \"%(attr)s\" din linia %(line)s are o valoare invalidă. ( Linia "
+"începe cu \"%(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 "Cîmpul este invalid."
+
+#: db/models/fields/__init__.py:337
+#, fuzzy
+msgid "This value must be an integer."
+msgstr "Această valoare trebuie să fie o putere a lui %s."
+
+#: db/models/fields/__init__.py:369
+#, fuzzy
+msgid "This value must be either True or False."
+msgstr "Această valoare trebuie să fie o putere a lui %s."
+
+#: db/models/fields/__init__.py:385
+#, fuzzy
+msgid "This field cannot be null."
+msgstr "Cîmpul este invalid."
+
+#: db/models/fields/__init__.py:562
+#, fuzzy
+msgid "Enter a valid filename."
+msgstr "Introduceţi o adresă de email validă."
+
+#: db/models/fields/related.py:43
+#, fuzzy, python-format
+msgid "Please enter a valid %s."
+msgstr "Introduceţi vă rog o adresă IP validă."
+
+#: db/models/fields/related.py:579
+#, fuzzy
+msgid "Separate multiple IDs with commas."
+msgstr "Separă ID-urile multiple cu virgulă."
+
+#: db/models/fields/related.py:581
+#, fuzzy
+msgid ""
+"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr ""
+" Ţine apăsat \"Control\", sau \"Command\" pe un Mac, pentru a selecta mai "
+"multe."
+
+#: 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 "Literele mici nu sînt permise aici."
+
+#: 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 "Introduceţi un număr întreg."
+
+#: forms/__init__.py:708
+#, fuzzy
+msgid "Enter a positive number."
+msgstr "Introduceţi un număr întreg."
+
+#: forms/__init__.py:717
+#, fuzzy
+msgid "Enter a whole number between 0 and 32,767."
+msgstr "Introduceţi un număr întreg."
+
+#: template/defaultfilters.py:379
+msgid "yes,no,maybe"
+msgstr ""
+
+#, fuzzy
+#~ msgid "Comments"
+#~ msgstr "permite comentarii"
+
+#~ msgid "label"
+#~ msgstr "etichetă"
+
+#~ msgid "package"
+#~ msgstr "pachet"
+
+#~ msgid "packages"
+#~ msgstr "pachete"
+
+#, fuzzy
+#~ msgid "count"
+#~ msgstr "conţinut"
diff --git a/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..12e2405
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 0000000..f329efe
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,1906 @@
+# Translation of django.po to russian.
+# Copyright (C) 2005 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Dmitry Sorokin <ds@dial.com.ru>, 2005.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django 0.95\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-16 10:11+0200\n"
+"PO-Revision-Date: 2006-09-07 15:28+0300\n"
+"Last-Translator: Alexander Yakovlev <AYakovlev@rambler.ru>\n"
+"Language-Team: Dialcom Services <greg@dial.com.ru>\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: Russian\n"
+"X-Poedit-Country: RUSSIAN FEDERATION\n"
+"X-Poedit-SourceCharset: utf-8\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
+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/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/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"
+msgstr[2] ""
+"Этот комментарий Ñделан пользователем, который отправил меньше %(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' -- 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/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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>По %s:</h3>\n"
+"<ul>\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 "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/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 "Похоже, ваш броузер не наÑтроен на прием cookies. ПожалуйÑтва, включите cookie, перезагрузите Ñтраницу и попытайтеÑÑŒ Ñнова."
+
+#: 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 "Один или более %(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: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 "ЛогичеÑкое (True или False)"
+
+#: contrib/admin/views/doc.py:279
+#: contrib/admin/views/doc.py:296
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "Строка (до %(maxlength)s Ñимволов)"
+
+#: 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 "ЛогичеÑкое (True, False или None)"
+
+#: 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 "URL"
+
+#: contrib/admin/views/doc.py:301
+msgid "U.S. state (two uppercase letters)"
+msgstr "Штат СШР(две заглавные буквы)"
+
+#: contrib/admin/views/doc.py:302
+msgid "XML text"
+msgstr "ТекÑÑ‚ 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 "ДокументациÑ"
+
+#: 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 "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 "Данный обьект не имеет иÑтории изменений. Возможно, он был добавлен не через данный админиÑтративный Ñайт."
+
+#: 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 <em>(500)</em>"
+msgstr "Ошибка Ñервера <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Вы <a href=\"/password_reset/\">забыли пароль</a>?"
+
+#: 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 " По %(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"
+msgid_plural "%(counter)s results"
+msgstr[0] "1 результат"
+msgstr[1] "%(counter)s результата"
+msgstr[2] "%(counter)s результатов"
+
+#: contrib/admin/templates/admin/search_form.html:10
+msgid "%(full_result_count)s total"
+msgstr "%(full_result_count)s вÑего"
+
+#: 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] "ПожалуйÑта, иÑправьте ошибки ниже."
+msgstr[2] "ПожалуйÑта, иÑправьте ошибки ниже."
+
+#: 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 "Забыли пароль? Введите Ñвой Ð°Ð´Ñ€ÐµÑ Ñлектронной почты ниже, мы очиÑтим ваш Ñтарый пароль и вышлем вам по e-mail новый."
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">Ð”Ð»Ñ ÑƒÑтановки закладок перетащите ÑÑылку к Ñебе на панель\n"
+"закладок или щелкните правой кнопкой мыши по ÑÑылке и добавьте ее в закладки. Теперь у Ð²Ð°Ñ ÐµÑÑ‚ÑŒ возможноÑÑ‚ÑŒ\n"
+"выбрать закладку Ñ Ð»ÑŽÐ±Ð¾Ð¹ Ñтраницы Ñайта. Обратите внимание: некоторые из Ñтих\n"
+"закладок требуют, чтобы вы проÑматривали Ñайт Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°, определенного\n"
+"как \"внутренний\" (уточните у Ñвоего ÑиÑтемного админиÑтратора, еÑли не уверены, ÑвлÑетÑÑ Ð»Ð¸\n"
+"ваш компьютер \"внутренним\").</p>\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 "ПеренаправлÑет Ð²Ð°Ñ Ñ Ð»ÑŽÐ±Ð¾Ð¹ Ñтраницы к документации view, который генерирует Ñту Ñтраницу."
+
+#: 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/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 "Это должен быть абÑолютный путь (как выше) или полный URL, начинающийÑÑ Ñ '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 "Пример: '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/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/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 "Ð¸Ð¼Ñ ÐºÐ»Ð°ÑÑа python модулÑ"
+
+#: 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 "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 "Понедельник"
+
+#: 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] "лет"
+
+#: utils/timesince.py:13
+msgid "month"
+msgid_plural "months"
+msgstr[0] "меÑÑц"
+msgstr[1] "меÑÑца"
+msgstr[2] "меÑÑцев"
+
+#: utils/timesince.py:14
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] "неделÑ"
+msgstr[1] "недели"
+msgstr[2] "недель"
+
+#: utils/timesince.py:15
+msgid "day"
+msgid_plural "days"
+msgstr[0] "день"
+msgstr[1] "днÑ"
+msgstr[2] "дней"
+
+#: utils/timesince.py:16
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "чаÑ"
+msgstr[1] "чаÑа"
+msgstr[2] "чаÑов"
+
+#: utils/timesince.py:17
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "минута"
+msgstr[1] "минуты"
+msgstr[2] "минут"
+
+#: 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 "ПожалуйÑта, введите правильный 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 "URL %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 "URL %s не указывает на реальное видео QuickTime."
+
+#: core/validators.py:171
+msgid "A valid URL is required."
+msgstr "Правильный URL обÑзателен."
+
+#: core/validators.py:185
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"Правильный HTML обÑзателен. Специфичные ошибки:\n"
+"%s"
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Ðеверный формат XML: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Ðеверный URL: %s"
+
+#: 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] "Следите за Ñвоими Ñловами! Слово %s здеÑÑŒ запрещено."
+msgstr[1] "Следите за Ñвоими Ñловами! Слова %s здеÑÑŒ запрещены."
+msgstr[2] "Следите за Ñвоими Ñловами! Слова %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 знаками."
+msgstr[2] "ПожалуйÑта, введите корректное деÑÑтичное чиÑло макÑимально Ñ %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 знаками поÑле запÑтой."
+msgstr[2] "ПожалуйÑта, введите корректное деÑÑтичное чиÑло макÑимально Ñ %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 %(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 "ПожалуйÑта, закройте незакрытый Ñ‚Ñг %(tag)s на Ñтроке %(line)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 "\"%(attr)s\" на Ñтроке %(line)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 "\"<%(tag)s>\" на Ñтроке %(line)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 "%(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: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
+msgid "This value must be either True or False."
+msgstr "Значение должно либо True, либо False."
+
+#: db/models/fields/__init__.py:385
+msgid "This field cannot be null."
+msgstr "Это поле не может быть нулевым."
+
+#: 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
+msgid "Separate multiple IDs with commas."
+msgstr "ÐеÑколько значений ID разделите запÑтыми."
+
+#: db/models/fields/related.py:581
+msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr "Удерживайте \"Control\" (или \"Command\" на 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] "ПожалуйÑта, введите корректный ID Ð´Ð»Ñ %(self)s. Значение %(value)r недопуÑтимо."
+msgstr[1] "ПожалуйÑта, введите корректные ID Ð´Ð»Ñ %(self)s. Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ %(value)r недопуÑтимы."
+msgstr[2] "ПожалуйÑта, введите корректные ID Ð´Ð»Ñ %(self)s. Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ %(value)r недопуÑтимы."
+
+#: 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 Ñимвола."
+msgstr[1] "УбедитеÑÑŒ, что длина вашего текÑта меньше %s Ñимволов."
+msgstr[2] "УбедитеÑÑŒ, что длина вашего текÑта меньше %s Ñимволов."
+
+#: 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 "Выберите корректный вариант; '%(data)s' нет в %(choices)s."
+
+#: 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 "Введите целое чиÑло в диапазоне от -32768 до 32767."
+
+#: 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 "Введите целое чиÑло в диапазоне от 0 до 32767."
+
+#: template/defaultfilters.py:379
+msgid "yes,no,maybe"
+msgstr "да,нет,может быть"
+
diff --git a/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..2f9cd61
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..189c000
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.po
@@ -0,0 +1,111 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-03-01 17:11+0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/sk/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..c6ac1c9
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.po
new file mode 100644
index 0000000..4e1dba2
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.po
@@ -0,0 +1,2002 @@
+# Translation of django.po to.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Vladimir Labath <vlado@labath.org>, 2005.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-27 07:31-0400\n"
+"PO-Revision-Date: 2005-11-10 23:22-0500\n"
+"Last-Translator: Vladimir Labath <vlado@labath.org>\n"
+"Language-Team: Slovak <sk@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: contrib/contenttypes/models.py:25
+msgid "python model class name"
+msgstr "pythonové meno triedy modelu"
+
+#: contrib/contenttypes/models.py:28
+msgid "content type"
+msgstr "typ obsahu"
+
+#: contrib/contenttypes/models.py:29
+msgid "content types"
+msgstr "typy obsahu"
+
+#: contrib/auth/models.py:13 contrib/auth/models.py:26
+msgid "name"
+msgstr "meno"
+
+#: contrib/auth/models.py:15
+msgid "codename"
+msgstr "codename"
+
+#: contrib/auth/models.py:17
+msgid "permission"
+msgstr "povolenie"
+
+#: contrib/auth/models.py:18 contrib/auth/models.py:27
+msgid "permissions"
+msgstr "povolenia"
+
+#: contrib/auth/models.py:29
+msgid "group"
+msgstr "skupina"
+
+#: contrib/auth/models.py:30 contrib/auth/models.py:65
+msgid "groups"
+msgstr "skupiny"
+
+#: contrib/auth/models.py:55
+msgid "username"
+msgstr "užívateľské meno"
+
+#: contrib/auth/models.py:56
+msgid "first name"
+msgstr "krstné meno"
+
+#: contrib/auth/models.py:57
+msgid "last name"
+msgstr "priezvisko"
+
+#: contrib/auth/models.py:58
+msgid "e-mail address"
+msgstr "e-mailová adresa"
+
+#: contrib/auth/models.py:59
+msgid "password"
+msgstr "heslo"
+
+#: contrib/auth/models.py:59
+msgid "Use '[algo]$[salt]$[hexdigest]'"
+msgstr "Použi '[algo]$[salt]$[hexdigest]'"
+
+#: contrib/auth/models.py:60
+msgid "staff status"
+msgstr "štatút zamestnanca"
+
+#: contrib/auth/models.py:60
+msgid "Designates whether the user can log into this admin site."
+msgstr "OznaÄenie, ak užívateľ má oprávnenie vstúpiÅ¥ ako administrátor."
+
+#: contrib/auth/models.py:61
+msgid "active"
+msgstr "aktívny"
+
+#: contrib/auth/models.py:62
+msgid "superuser status"
+msgstr "štatút superužívateľa"
+
+#: contrib/auth/models.py:63
+msgid "last login"
+msgstr "posledné prihlásenie"
+
+#: contrib/auth/models.py:64
+msgid "date joined"
+msgstr "dátum registrácie"
+
+#: 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 ""
+"Okrem ruÄne vložených povolení, tento uživateľ dostane vÅ¡etky povolenia "
+"skupin, v ktorých sa nachádza."
+
+#: contrib/auth/models.py:67
+msgid "user permissions"
+msgstr "pridelené povolenia"
+
+#: contrib/auth/models.py:70
+msgid "user"
+msgstr "uživateľ"
+
+#: contrib/auth/models.py:71
+msgid "users"
+msgstr "užívatelia"
+
+#: contrib/auth/models.py:76
+msgid "Personal info"
+msgstr "Osobné údaje"
+
+#: contrib/auth/models.py:77
+msgid "Permissions"
+msgstr "Povolenia"
+
+#: contrib/auth/models.py:78
+msgid "Important dates"
+msgstr "Dôležité údaje"
+
+#: contrib/auth/models.py:79
+msgid "Groups"
+msgstr "Skupiny"
+
+#: contrib/auth/models.py:219
+msgid "message"
+msgstr "zpráva"
+
+#: contrib/auth/forms.py:30
+msgid ""
+"Your Web browser doesn't appear to have cookies enabled. Cookies are "
+"required for logging in."
+msgstr ""
+"Vyzerá, že tvoj web prehliadaÄ nedovoľuje prístup ku cookies. Cookies sú "
+"nevýhnutné aby si sa mohol prihlásiť."
+
+#: contrib/auth/forms.py:36 contrib/auth/forms.py:41
+#: contrib/admin/views/decorators.py:9
+msgid ""
+"Please enter a correct username and password. Note that both fields are case-"
+"sensitive."
+msgstr ""
+"Prosím, vlož spávne meno užvateľa ako aj heslo. Pripomínam, že obe polia "
+"rozlišujú malé a veľké písmena"
+
+#: contrib/redirects/models.py:7
+msgid "redirect from"
+msgstr "presmerovaný z"
+
+#: contrib/redirects/models.py:8
+msgid ""
+"This should be an absolute path, excluding the domain name. Example: '/"
+"events/search/'."
+msgstr ""
+"Tu by sa mala použiť absolútna cesta, bez domény. Napr.: '/events/search/'."
+
+#: contrib/redirects/models.py:9
+msgid "redirect to"
+msgstr "presmerovaný na "
+
+#: contrib/redirects/models.py:10
+msgid ""
+"This can be either an absolute path (as above) or a full URL starting with "
+"'http://'."
+msgstr ""
+"Tu môže byÅ¥ buÄ absolútna cesta (ako hore) alebo plné URL zaÄínajúce s "
+"'http://'."
+
+#: contrib/redirects/models.py:12
+msgid "redirect"
+msgstr "presmerovanie"
+
+#: contrib/redirects/models.py:13
+msgid "redirects"
+msgstr "presmerovania"
+
+#: contrib/comments/models.py:67 contrib/comments/models.py:166
+msgid "object ID"
+msgstr "objekt ID"
+
+#: contrib/comments/models.py:68
+msgid "headline"
+msgstr "titulok"
+
+#: contrib/comments/models.py:69 contrib/comments/models.py:90
+#: contrib/comments/models.py:167
+msgid "comment"
+msgstr "komentár"
+
+#: contrib/comments/models.py:70
+msgid "rating #1"
+msgstr "hodnotenie #1"
+
+#: contrib/comments/models.py:71
+msgid "rating #2"
+msgstr "hodnotenie #2"
+
+#: contrib/comments/models.py:72
+msgid "rating #3"
+msgstr "hodnotenie #3"
+
+#: contrib/comments/models.py:73
+msgid "rating #4"
+msgstr "hodnotenie #4"
+
+#: contrib/comments/models.py:74
+msgid "rating #5"
+msgstr "hodnotenie #5"
+
+#: contrib/comments/models.py:75
+msgid "rating #6"
+msgstr "hodnotenie #6"
+
+#: contrib/comments/models.py:76
+msgid "rating #7"
+msgstr "hodnotenie #7"
+
+#: contrib/comments/models.py:77
+msgid "rating #8"
+msgstr "hodnotenie #8"
+
+#: contrib/comments/models.py:82
+msgid "is valid rating"
+msgstr "je platné hodnotenie"
+
+#: contrib/comments/models.py:83 contrib/comments/models.py:169
+msgid "date/time submitted"
+msgstr "dátum/Äas poslania"
+
+#: contrib/comments/models.py:84 contrib/comments/models.py:170
+msgid "is public"
+msgstr "je zveréjnený"
+
+#: contrib/comments/models.py:85 contrib/admin/views/doc.py:292
+msgid "IP address"
+msgstr "IP adresa"
+
+#: contrib/comments/models.py:86
+msgid "is removed"
+msgstr "je vymazaný"
+
+#: 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 ""
+"Ak je tento komentár nevhodný, tu ho poznaÄ. \"Tento komentár bol vymazaný"
+"\" táto správa sa objaví namiesto neho."
+
+#: contrib/comments/models.py:91
+msgid "comments"
+msgstr "komentáre"
+
+#: contrib/comments/models.py:131 contrib/comments/models.py:207
+msgid "Content object"
+msgstr "Obsah objektu"
+
+#: 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 ""
+"Od %(user)s dňa %(date)s\n"
+"\n"
+"%(comment)s\n"
+"\n"
+"http://%(domain)s%(url)s"
+
+#: contrib/comments/models.py:168
+msgid "person's name"
+msgstr "osobné meno"
+
+#: contrib/comments/models.py:171
+msgid "ip address"
+msgstr "ip adresa"
+
+#: contrib/comments/models.py:173
+msgid "approved by staff"
+msgstr "je zamestnancom"
+
+#: contrib/comments/models.py:176
+msgid "free comment"
+msgstr "voľný komentár"
+
+#: contrib/comments/models.py:177
+msgid "free comments"
+msgstr "voľné komentáre"
+
+#: contrib/comments/models.py:233
+msgid "score"
+msgstr "stav"
+
+#: contrib/comments/models.py:234
+msgid "score date"
+msgstr "údaje stavu"
+
+#: contrib/comments/models.py:237
+msgid "karma score"
+msgstr "karma údaj"
+
+#: contrib/comments/models.py:238
+msgid "karma scores"
+msgstr "karma údaje"
+
+#: contrib/comments/models.py:242
+#, python-format
+msgid "%(score)d rating by %(user)s"
+msgstr "%(score)d hodnotiteľ %(user)s"
+
+#: contrib/comments/models.py:258
+#, python-format
+msgid ""
+"This comment was flagged by %(user)s:\n"
+"\n"
+"%(text)s"
+msgstr ""
+"Tento komentár bol oznaÄený užívateľom %(user)s:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/models.py:265
+msgid "flag date"
+msgstr "dátumové návestie"
+
+#: contrib/comments/models.py:268
+msgid "user flag"
+msgstr "návestie uživateľa"
+
+#: contrib/comments/models.py:269
+msgid "user flags"
+msgstr "návestia užívateľa"
+
+#: contrib/comments/models.py:273
+#, python-format
+msgid "Flag by %r"
+msgstr " %r návestie"
+
+#: contrib/comments/models.py:278
+msgid "deletion date"
+msgstr "odstránené dátumy"
+
+#: contrib/comments/models.py:280
+msgid "moderator deletion"
+msgstr "moderátor odstránenia"
+
+#: contrib/comments/models.py:281
+msgid "moderator deletions"
+msgstr "moderátor odstránení"
+
+#: contrib/comments/models.py:285
+#, python-format
+msgid "Moderator deletion by %r"
+msgstr "Moderátor odstránenia %r"
+
+#: contrib/comments/views/karma.py:19
+msgid "Anonymous users cannot vote"
+msgstr "Hlasovať môžu len prihlásení užívatelia"
+
+#: contrib/comments/views/karma.py:23
+msgid "Invalid comment ID"
+msgstr "Chybné ID komentára"
+
+#: contrib/comments/views/karma.py:25
+msgid "No voting for yourself"
+msgstr "Nemôžeš hlasovať za seba"
+
+#: contrib/comments/views/comments.py:28
+msgid ""
+"This rating is required because you've entered at least one other rating."
+msgstr "Toto hlasovanie je nevyhnutné, lebo súvisí s predchádzjúcou voľbou."
+
+#: 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] ""
+"Tento komentár je od užívateľa, ktorý doteraz poslal minimálne %(count)s "
+"komentár:\n"
+"\n"
+"%(text)s"
+msgstr[1] ""
+"Tento komentár je od užívateľa, ktorý doteraz poslal najmenej %(count)s "
+"komentárov:\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 ""
+"Tento komentár je od veľmi náznakového užívateľa:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/views/comments.py:189
+#: contrib/comments/views/comments.py:280
+msgid "Only POSTs are allowed"
+msgstr "Dovolené sú len 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 "Jedno alebo viac povinných polí nebolo vložených"
+
+#: contrib/comments/views/comments.py:197
+#: contrib/comments/views/comments.py:286
+msgid "Somebody tampered with the comment form (security violation)"
+msgstr "Niekto skúšal manipulovaÅ¥ s formulárom komentára (poruÅ¡ená bezpeÄnosÅ¥)"
+
+#: 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 ""
+"Formulár komentára ma chybný 'cieľový' parameter -- the objekt ID bol "
+"poškodený"
+
+#: 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ár komentára neposkytuje odpoveÄ buÄ 'prezri' alebo 'poÅ¡li'"
+
+#: contrib/comments/templates/comments/freeform.html:4
+msgid "Your name:"
+msgstr "Tvoje meno:"
+
+#: contrib/comments/templates/comments/freeform.html:5
+#: contrib/comments/templates/comments/form.html:27
+msgid "Comment:"
+msgstr "Komentár:"
+
+#: contrib/comments/templates/comments/freeform.html:9
+#: contrib/comments/templates/comments/form.html:32
+msgid "Preview comment"
+msgstr "Pozri komentá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 "Meno:"
+
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/admin/templates/admin/login.html:20
+msgid "Password:"
+msgstr "Heslo:"
+
+#: contrib/comments/templates/comments/form.html:6
+msgid "Forgotten your password?"
+msgstr "Zabudol si svoje heslo?"
+
+#: 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/change_form.html:10
+#: contrib/admin/templates/admin/base.html:23
+#: contrib/admin/templates/admin/delete_confirmation.html:3
+#: 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ásenie"
+
+#: contrib/comments/templates/comments/form.html:12
+msgid "Ratings"
+msgstr "Hodnotenia"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Required"
+msgstr "Požadované"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Optional"
+msgstr "Voliteľné"
+
+#: contrib/comments/templates/comments/form.html:23
+msgid "Post a photo"
+msgstr "Pošli foto"
+
+#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:303
+msgid "URL"
+msgstr "URL"
+
+#: contrib/flatpages/models.py:8
+msgid ""
+"Example: '/about/contact/'. Make sure to have leading and trailing slashes."
+msgstr ""
+"Príklad: '/about/contact/'. Uisti sa, že máš vložené ako úvodné tak aj "
+"závereÄné lomítka."
+
+#: contrib/flatpages/models.py:9
+msgid "title"
+msgstr "názov"
+
+#: contrib/flatpages/models.py:10
+msgid "content"
+msgstr "obsah"
+
+#: contrib/flatpages/models.py:11
+msgid "enable comments"
+msgstr "povolené komentáre"
+
+#: contrib/flatpages/models.py:12
+msgid "template name"
+msgstr "meno predlohy"
+
+#: contrib/flatpages/models.py:13
+msgid ""
+"Example: 'flatpages/contact_page'. If this isn't provided, the system will "
+"use 'flatpages/default'."
+msgstr ""
+"Príklad: 'flatpages/contact_page'. Ak sa toto nevykonalo, systém použije "
+"'flatpages/default'."
+
+#: contrib/flatpages/models.py:14
+msgid "registration required"
+msgstr "musíte byť zaregistrovaný"
+
+#: contrib/flatpages/models.py:14
+msgid "If this is checked, only logged-in users will be able to view the page."
+msgstr ""
+"Ak je toto oznaÄené, potom len prihlásený užívateľ môže vidieÅ¥ túto stránku."
+
+#: contrib/flatpages/models.py:18
+msgid "flat page"
+msgstr "plochá stránka"
+
+#: contrib/flatpages/models.py:19
+msgid "flat pages"
+msgstr "ploché stránky"
+
+#: contrib/sessions/models.py:35
+msgid "session key"
+msgstr "kÄ¾ÃºÄ sedenia"
+
+#: contrib/sessions/models.py:36
+msgid "session data"
+msgstr "údaje sedenia"
+
+#: contrib/sessions/models.py:37
+msgid "expire date"
+msgstr "dátum konca platnosti"
+
+#: contrib/sessions/models.py:41
+msgid "session"
+msgstr "sedenie"
+
+#: contrib/sessions/models.py:42
+msgid "sessions"
+msgstr "sedenia"
+
+#: contrib/sites/models.py:10
+msgid "domain name"
+msgstr "meno domény"
+
+#: contrib/sites/models.py:11
+msgid "display name"
+msgstr "zobrazené meno"
+
+#: contrib/sites/models.py:15
+msgid "site"
+msgstr "web"
+
+#: contrib/sites/models.py:16
+msgid "sites"
+msgstr "weby"
+
+#: contrib/admin/filterspecs.py:40
+#, python-format
+msgid ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Od %s:</h3>\n"
+"<ul>\n"
+
+#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88
+#: contrib/admin/filterspecs.py:143
+msgid "All"
+msgstr "VÅ¡etko"
+
+#: contrib/admin/filterspecs.py:109
+msgid "Any date"
+msgstr "Všetky dátumy"
+
+#: 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 mesiac"
+
+#: contrib/admin/filterspecs.py:117
+msgid "This year"
+msgstr "Tento rok"
+
+#: contrib/admin/filterspecs.py:143
+msgid "Yes"
+msgstr "Ãno"
+
+#: contrib/admin/filterspecs.py:143
+msgid "No"
+msgstr "Nie"
+
+#: contrib/admin/filterspecs.py:150
+msgid "Unknown"
+msgstr "Neznámy"
+
+#: contrib/admin/models.py:16
+msgid "action time"
+msgstr "Äas udalosti"
+
+#: 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 "návestie udalosti"
+
+#: contrib/admin/models.py:22
+msgid "change message"
+msgstr "zmeň zprávu"
+
+#: contrib/admin/models.py:25
+msgid "log entry"
+msgstr "záznam priebehu"
+
+#: contrib/admin/models.py:26
+msgid "log entries"
+msgstr "záznamy priebehu"
+
+#: contrib/admin/templatetags/admin_list.py:228
+msgid "All dates"
+msgstr "Všetky dátumy"
+
+#: contrib/admin/views/decorators.py:23
+#: contrib/admin/templates/admin/login.html:25
+msgid "Log in"
+msgstr "Prihlásenie"
+
+#: 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 ""
+"Prosím prihlas sa znovu, lebo Äas tvojho sedenia vyprÅ¡al. Nemaj obavy: tvoje "
+"údaje su uchované."
+
+#: 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 ""
+"Vyzerá, že tvoj prehliadaÄ nemá povolené cookies. Prosím, povoľ cookies, "
+"znovu naÄítaj túto stránku a skús ÄinnosÅ¥ znovu."
+
+#: contrib/admin/views/decorators.py:82
+msgid "Usernames cannot contain the '@' character."
+msgstr "Meno užívateľa nemože obsahovať znak '@' ."
+
+#: contrib/admin/views/decorators.py:84
+#, python-format
+msgid "Your e-mail address is not your username. Try '%s' instead."
+msgstr "Tvoja e-mailova adresa nie je tvoje užívateľské meno. Skús '%s'."
+
+#: contrib/admin/views/main.py:226
+msgid "Site administration"
+msgstr "Administrácia webu"
+
+#: contrib/admin/views/main.py:260
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was added successfully."
+msgstr "Objekt %(name)s \"%(obj)s\" bol úspešne pridaný."
+
+#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348
+msgid "You may edit it again below."
+msgstr "Môžeš urobiť zmeny zase nižšie."
+
+#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357
+#, python-format
+msgid "You may add another %s below."
+msgstr "MôžeÅ¡ pridaÅ¥ Äalší %s nižšie."
+
+#: contrib/admin/views/main.py:290
+#, python-format
+msgid "Add %s"
+msgstr "Pridaj %s"
+
+#: contrib/admin/views/main.py:336
+#, python-format
+msgid "Added %s."
+msgstr "Bol pridaný %s."
+
+#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338
+#: contrib/admin/views/main.py:340
+msgid "and"
+msgstr "a"
+
+#: contrib/admin/views/main.py:338
+#, python-format
+msgid "Changed %s."
+msgstr "Bol zmenený %s"
+
+#: contrib/admin/views/main.py:340
+#, python-format
+msgid "Deleted %s."
+msgstr "Bol vymazaný %s."
+
+#: contrib/admin/views/main.py:343
+msgid "No fields changed."
+msgstr "Polia neboli zmenené."
+
+#: contrib/admin/views/main.py:346
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was changed successfully."
+msgstr "Objekt %(name)s \"%(obj)s\" boli úspešne zmenený."
+
+#: contrib/admin/views/main.py:354
+#, python-format
+msgid ""
+"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
+msgstr ""
+"Objekt %(name)s \"%(obj)s\" bol úspešne zmenený. Ďalšie zmeny môžeš urobiť "
+"zase nižšie."
+
+#: contrib/admin/views/main.py:392
+#, python-format
+msgid "Change %s"
+msgstr "Zmeň %s"
+
+#: contrib/admin/views/main.py:470
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
+msgstr "Jeden alebo viac %(fieldname)s v %(name)s: %(obj)s"
+
+#: contrib/admin/views/main.py:475
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "Jeden alebo viac %(fieldname)s v %(name)s:"
+
+#: contrib/admin/views/main.py:508
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was deleted successfully."
+msgstr "Objekt %(name)s \"%(obj)s\" bol úspešne vymazaný."
+
+#: contrib/admin/views/main.py:511
+msgid "Are you sure?"
+msgstr "Si si istý?"
+
+#: contrib/admin/views/main.py:533
+#, python-format
+msgid "Change history: %s"
+msgstr "Zmeň históriu: %s"
+
+#: contrib/admin/views/main.py:565
+#, python-format
+msgid "Select %s"
+msgstr "Výber %s"
+
+#: contrib/admin/views/main.py:565
+#, python-format
+msgid "Select %s to change"
+msgstr "Ktorý %s sa má zmeniť?"
+
+#: 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 "CeloÄíselná hodnota"
+
+#: contrib/admin/views/doc.py:280
+msgid "Boolean (Either True or False)"
+msgstr "Logická hodnota (buÄ True alebo False)"
+
+#: contrib/admin/views/doc.py:281 contrib/admin/views/doc.py:299
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "Dĺžka reťazca (maximálne do %(maxlength)s)"
+
+#: contrib/admin/views/doc.py:282
+msgid "Comma-separated integers"
+msgstr "ÄŒiarka oddeľuje celé Äísla"
+
+#: contrib/admin/views/doc.py:283
+msgid "Date (without time)"
+msgstr "Dátum (bez Äasu)"
+
+#: contrib/admin/views/doc.py:284
+msgid "Date (with time)"
+msgstr "Dátum ( a Äas)"
+
+#: contrib/admin/views/doc.py:285
+msgid "E-mail address"
+msgstr "E-mailová adresa"
+
+#: contrib/admin/views/doc.py:286 contrib/admin/views/doc.py:287
+#: contrib/admin/views/doc.py:290
+msgid "File path"
+msgstr "Cesta k súboru"
+
+#: contrib/admin/views/doc.py:288
+msgid "Decimal number"
+msgstr "Desiatkové Äíslo"
+
+#: contrib/admin/views/doc.py:294
+msgid "Boolean (Either True, False or None)"
+msgstr "Logická hodnota (buÄ True, False alebo None)"
+
+#: contrib/admin/views/doc.py:295
+msgid "Relation to parent model"
+msgstr "Má vzÅ¥ah na rodiÄovský model"
+
+#: contrib/admin/views/doc.py:296
+msgid "Phone number"
+msgstr "Číslo telefónu"
+
+#: contrib/admin/views/doc.py:301
+msgid "Text"
+msgstr "Text"
+
+#: contrib/admin/views/doc.py:302
+msgid "Time"
+msgstr "ÄŒas"
+
+#: contrib/admin/views/doc.py:304
+msgid "U.S. state (two uppercase letters)"
+msgstr "U.S. štát (dve veľké písmena)"
+
+#: contrib/admin/views/doc.py:305
+msgid "XML text"
+msgstr "XML text"
+
+#: contrib/admin/templates/widget/file.html:2
+msgid "Currently:"
+msgstr "Aktuálny:"
+
+#: contrib/admin/templates/widget/file.html:3
+msgid "Change:"
+msgstr "Zmeň:"
+
+#: contrib/admin/templates/widget/date_time.html:3
+msgid "Date:"
+msgstr "Dátum:"
+
+#: contrib/admin/templates/widget/date_time.html:4
+msgid "Time:"
+msgstr "ÄŒas:"
+
+#: 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:23
+#: contrib/admin/templates/admin/delete_confirmation.html:3
+#: 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ácia"
+
+#: 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:23
+#: contrib/admin/templates/admin/delete_confirmation.html:3
+#: 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 "Zmeň heslo"
+
+#: 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:28
+#: contrib/admin/templates/admin/delete_confirmation.html:6
+#: 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 "ZaÄiatok"
+
+#: 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 "Dátum/Äas"
+
+#: contrib/admin/templates/admin/object_history.html:19
+msgid "User"
+msgstr "Uživateľ"
+
+#: contrib/admin/templates/admin/object_history.html:20
+msgid "Action"
+msgstr "Udalosť"
+
+#: contrib/admin/templates/admin/object_history.html:26
+msgid "DATE_WITH_TIME_FULL"
+msgstr "PLNY_DATUM_AJ_CAS"
+
+#: 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 object nemá históriu zmien. Možno nebol pridaný prostredníctvom tohoto "
+"web admina"
+
+#: contrib/admin/templates/admin/change_list.html:11
+#, python-format
+msgid "Add %(name)s"
+msgstr "Pridaj %(name)s"
+
+#: contrib/admin/templates/admin/filter.html:2
+#, python-format
+msgid " By %(title)s "
+msgstr " Od %(title)s "
+
+#: contrib/admin/templates/admin/500.html:4
+msgid "Server error"
+msgstr "Chyba servera"
+
+#: contrib/admin/templates/admin/500.html:6
+msgid "Server error (500)"
+msgstr "Chyba servera (500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "Chyba servera <em>(500)</em>"
+
+#: 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 ""
+"Vznikla chyba. Prostredníctvom e-mailu bol o nej informovaný správca a táto "
+"by mala byť o chviľu odstránená. Ďakujeme za tvoju trpezlivosť."
+
+#: contrib/admin/templates/admin/search_form.html:8
+msgid "Go"
+msgstr "ChoÄ"
+
+#: contrib/admin/templates/admin/base_site.html:4
+msgid "Django site admin"
+msgstr "Django web admin"
+
+#: contrib/admin/templates/admin/base_site.html:7
+msgid "Django administration"
+msgstr "Administrácia Django"
+
+#: contrib/admin/templates/admin/index.html:17
+#, python-format
+msgid "Models available in the %(name)s application."
+msgstr "Model je prístupný v %(name)s aplikácií."
+
+#: contrib/admin/templates/admin/index.html:28
+#: contrib/admin/templates/admin/change_form.html:15
+msgid "Add"
+msgstr "Pridaj"
+
+#: contrib/admin/templates/admin/index.html:34
+msgid "Change"
+msgstr "Zmeň"
+
+#: contrib/admin/templates/admin/index.html:44
+msgid "You don't have permission to edit anything."
+msgstr "Nemáš povolenie na zmeny ."
+
+#: contrib/admin/templates/admin/index.html:52
+msgid "Recent Actions"
+msgstr "Posledné udalosti"
+
+#: contrib/admin/templates/admin/index.html:53
+msgid "My Actions"
+msgstr "Moje udalosti"
+
+#: contrib/admin/templates/admin/index.html:57
+msgid "None available"
+msgstr "Nepovolené"
+
+#: contrib/admin/templates/admin/404.html:4
+#: contrib/admin/templates/admin/404.html:8
+msgid "Page not found"
+msgstr "Stránka nebola nájdená"
+
+#: contrib/admin/templates/admin/404.html:10
+msgid "We're sorry, but the requested page could not be found."
+msgstr "Ľutujeme, ale požadovaná stránka nebola nájdená."
+
+#: contrib/admin/templates/admin/login.html:22
+msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Zabudol si<a href=\"/password_reset/\"> svoje heslo</a>?"
+
+#: contrib/admin/templates/admin/change_form.html:21
+msgid "View on site"
+msgstr "Pozri na webe"
+
+#: contrib/admin/templates/admin/change_form.html:30
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Oprav chybu, Äo je nižšie, prosím."
+msgstr[1] "Oprav chyby, Äo sú nižšie, prosím."
+
+#: contrib/admin/templates/admin/change_form.html:48
+msgid "Ordering"
+msgstr "UrÄenie"
+
+#: contrib/admin/templates/admin/change_form.html:51
+msgid "Order:"
+msgstr "Poradie:"
+
+#: contrib/admin/templates/admin/base.html:23
+msgid "Welcome,"
+msgstr "Vítajte,"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:9
+#: contrib/admin/templates/admin/submit_line.html:3
+msgid "Delete"
+msgstr "Vymazať"
+
+#: 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 ""
+"Vymazaním objektu %(object_name)s '%(object)s' môžeš spôsobiť vymazanie "
+"súvisiacich objektov, ale tvoj úÄet nemá povolenie na mazanie nasledujúcich "
+"typov objektov:"
+
+#: 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 ""
+"Si si istý, že chceš vymazať %(object_name)s \"%(object)s\"? Všetky "
+"nasledujúce objekty budú tiež vymazané :"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:26
+msgid "Yes, I'm sure"
+msgstr "Ano, som si istý"
+
+#: contrib/admin/templates/admin/submit_line.html:4
+msgid "Save as new"
+msgstr "Zapísať ako nový"
+
+#: contrib/admin/templates/admin/submit_line.html:5
+msgid "Save and add another"
+msgstr "ZapísaÅ¥ a pridaÅ¥ Äaší"
+
+#: contrib/admin/templates/admin/submit_line.html:6
+msgid "Save and continue editing"
+msgstr "ZapísaÅ¥ a pokraÄovaÅ¥ v zmenách"
+
+#: contrib/admin/templates/admin/submit_line.html:7
+msgid "Save"
+msgstr "Zápis"
+
+#: 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 "Zmeň heslo"
+
+#: contrib/admin/templates/registration/password_change_done.html:6
+#: contrib/admin/templates/registration/password_change_done.html:10
+msgid "Password change successful"
+msgstr "Heslo bolo úspešne zmenené"
+
+#: contrib/admin/templates/registration/password_change_done.html:12
+msgid "Your password was changed."
+msgstr "Tvoje heslo bolo zmenené."
+
+#: 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 "Generácia nového 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 ""
+"Zabudol si svoje heslo? Vlož nižšie tvoju e-mail adresu, a nové heslo ti "
+"bude na ňu zaslané ."
+
+#: 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 "Obnova môjho hesla"
+
+#: contrib/admin/templates/registration/logged_out.html:8
+msgid "Thanks for spending some quality time with the Web site today."
+msgstr "ÄŽakujeme ti, za stráveny Äas na naÅ¡ej stránke."
+
+#: contrib/admin/templates/registration/logged_out.html:10
+msgid "Log in again"
+msgstr "Prihlás sa znovu"
+
+#: contrib/admin/templates/registration/password_reset_done.html:6
+#: contrib/admin/templates/registration/password_reset_done.html:10
+msgid "Password reset successful"
+msgstr "Heslo bolo úspešne vygenerované"
+
+#: 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 sme ti, nové heslo na tebou uvedenú emailovú adresu. Mal by si ho "
+"dostaÅ¥ Äo najskôr."
+
+#: 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 ""
+"Kvôli bezpeÄnosti vlož prosím tvoje staré heslo, a potom dvakrát tvoje nové "
+"heslo, tým môžeme skontrolovať jeho správnosť."
+
+#: 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 "Potvrdenie hesla:"
+
+#: contrib/admin/templates/registration/password_change_form.html:23
+msgid "Change my password"
+msgstr "Zmeň svoje 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 si túto správu preto, lebo si požadoval vygenerovať tvoje heslo"
+
+#: contrib/admin/templates/registration/password_reset_email.html:3
+#, python-format
+msgid "for your user account at %(site_name)s"
+msgstr "pre tvoj užívateľský úÄ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 "Tvoje 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ôžeš zmeniť toto heslo na nasledujúcej stránke:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:11
+msgid "Your username, in case you've forgotten:"
+msgstr "Tvoje užívateľské meno, ak si ho zabudol:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:13
+msgid "Thanks for using our site!"
+msgstr "Ďakujeme, že používaš naše stránky!"
+
+#: contrib/admin/templates/registration/password_reset_email.html:15
+#, python-format
+msgid "The %(site_name)s team"
+msgstr "Skupina %(site_name)s"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:3
+msgid "Bookmarklets"
+msgstr "Záložky"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:5
+msgid "Documentation bookmarklets"
+msgstr "Dokumentácia záložiek"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:9
+msgid ""
+"\n"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">Na inštaláciu záložiek, potiahni linku do tvojho "
+"bookmarks\n"
+"toolbar, alebo klikni pravou myšou na linku a pridaj ju do tvojho "
+"bookmarks.\n"
+"Teraz si môžeš vybrať záložku pre ľubovoľnú stránku na webe. Poznámka:\n"
+"niektoré záložky vyžadujú aby si prezeral web z poÄítaÄa oznaÄeného \n"
+"ako \"internal\" (opýtaj sa vášho systémového administrátora ak si si nie "
+"istý/á, \n"
+"že tvoj poÄítaÄ je oznaÄený ako \"internal\").</p>\n"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:19
+msgid "Documentation for this page"
+msgstr "Dokumnentácia tejto stránky"
+
+#: 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 ""
+"SkoÄ z ľubovoľnej stránky do dokumentácie, kde je popísaná generácia tejto "
+"stránky."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:22
+msgid "Show object ID"
+msgstr "Ukáž 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 ""
+"Ukáž typ obsahu a jednoznaÄné ID pre stránky, ktoré zatupujú jednoduché "
+"objekty."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:25
+msgid "Edit this object (current window)"
+msgstr "Edituj tento object (aktuálne okno)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:26
+msgid "Jumps to the admin page for pages that represent a single object."
+msgstr "SkoÄ na stránku admina, ktorá zastupuje jednoduchý objekt"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:28
+msgid "Edit this object (new window)"
+msgstr "Edituj tento objekt (nové okno)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:29
+msgid "As above, but opens the admin page in a new window."
+msgstr "Ako vyššie, ale stranka admina sa otvorí v novom okne."
+
+#: 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/dates.py:6
+msgid "Monday"
+msgstr "Pondelok"
+
+#: utils/dates.py:6
+msgid "Tuesday"
+msgstr "Utorok"
+
+#: utils/dates.py:6
+msgid "Wednesday"
+msgstr "Streda"
+
+#: utils/dates.py:6
+msgid "Thursday"
+msgstr "Å tvrtok"
+
+#: utils/dates.py:6
+msgid "Friday"
+msgstr "Piatok"
+
+#: utils/dates.py:7
+msgid "Saturday"
+msgstr "Sobota"
+
+#: utils/dates.py:7
+msgid "Sunday"
+msgstr "Nedeľa"
+
+#: 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 "Marec"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "April"
+msgstr "Apríl"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "May"
+msgstr "Máj"
+
+#: 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 "August"
+
+#: utils/dates.py:15
+msgid "September"
+msgstr "September"
+
+#: 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
+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 "máj"
+
+#: 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 ""
+
+#: utils/dates.py:20
+msgid "sep"
+msgstr ""
+
+#: 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 "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] "rok"
+msgstr[1] "rokov"
+
+#: utils/timesince.py:13
+msgid "month"
+msgid_plural "months"
+msgstr[0] "mesiac"
+msgstr[1] "mesiacov"
+
+#: utils/timesince.py:14
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: utils/timesince.py:15
+msgid "day"
+msgid_plural "days"
+msgstr[0] "Äeň"
+msgstr[1] "dní"
+
+#: utils/timesince.py:16
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "hodina"
+msgstr[1] "hodín"
+
+#: utils/timesince.py:17
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "minúta"
+msgstr[1] "minút"
+
+#: conf/global_settings.py:37
+msgid "Bengali"
+msgstr "Bengálsky"
+
+#: conf/global_settings.py:38
+msgid "Czech"
+msgstr "Český"
+
+#: conf/global_settings.py:39
+msgid "Welsh"
+msgstr "Waleský"
+
+#: conf/global_settings.py:40
+msgid "Danish"
+msgstr "Dánsky"
+
+#: conf/global_settings.py:41
+msgid "German"
+msgstr "Nemecký"
+
+#: conf/global_settings.py:42
+msgid "Greek"
+msgstr "Grécký"
+
+#: conf/global_settings.py:43
+msgid "English"
+msgstr "Anglický"
+
+#: conf/global_settings.py:44
+msgid "Spanish"
+msgstr "Å panielsky"
+
+#: conf/global_settings.py:45
+msgid "Argentinean Spanish"
+msgstr "Argentínska Å¡panielÄina"
+
+#: conf/global_settings.py:46
+msgid "French"
+msgstr "Francúzsky"
+
+#: conf/global_settings.py:47
+msgid "Galician"
+msgstr "Galicijský"
+
+#: conf/global_settings.py:48
+msgid "Hungarian"
+msgstr "MaÄarský"
+
+#: conf/global_settings.py:49
+msgid "Hebrew"
+msgstr "Hebrejský"
+
+#: conf/global_settings.py:50
+msgid "Icelandic"
+msgstr "Islandský"
+
+#: conf/global_settings.py:51
+msgid "Italian"
+msgstr "Taliansky"
+
+#: conf/global_settings.py:52
+msgid "Japanese"
+msgstr "Japónsky"
+
+#: conf/global_settings.py:53
+msgid "Dutch"
+msgstr "Holándsky"
+
+#: conf/global_settings.py:54
+msgid "Norwegian"
+msgstr "Nórsky"
+
+#: conf/global_settings.py:55
+msgid "Brazilian"
+msgstr "Brazílsky"
+
+#: conf/global_settings.py:56
+msgid "Romanian"
+msgstr "Rumúnsky"
+
+#: conf/global_settings.py:57
+msgid "Russian"
+msgstr "Ruský"
+
+#: conf/global_settings.py:58
+msgid "Slovak"
+msgstr "Slovenský"
+
+#: conf/global_settings.py:59
+msgid "Slovenian"
+msgstr "Slovinský"
+
+#: conf/global_settings.py:60
+msgid "Serbian"
+msgstr "Srbský"
+
+#: conf/global_settings.py:61
+msgid "Swedish"
+msgstr "Švédsky"
+
+#: conf/global_settings.py:62
+msgid "Ukrainian"
+msgstr "Ukrajínsky"
+
+#: conf/global_settings.py:63
+msgid "Simplified Chinese"
+msgstr "ZjednoduÅ¡ená ÄinÅ¡tina "
+
+#: conf/global_settings.py:64
+msgid "Traditional Chinese"
+msgstr "TradiÄná ÄínÅ¡tina"
+
+#: 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 %(type)s už existuje pre prvok %(field)s."
+
+#: db/models/fields/__init__.py:40
+#, python-format
+msgid "%(optname)s with this %(fieldname)s already exists."
+msgstr "%(optname)s s %(fieldname)s už existuje."
+
+#: 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 "Toto pole je nevyhnutné."
+
+#: db/models/fields/__init__.py:337
+msgid "This value must be an integer."
+msgstr "Táto hodnota musí byť integer."
+
+#: db/models/fields/__init__.py:369
+msgid "This value must be either True or False."
+msgstr "Táto hodnota musí byÅ¥ buÄ True alebo False."
+
+#: db/models/fields/__init__.py:385
+msgid "This field cannot be null."
+msgstr "Toto pole nemôže obsahovať null."
+
+#: db/models/fields/__init__.py:468 core/validators.py:132
+msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
+msgstr "Vlož platný dátum/Äas vo formáte RRRR-MM-DD HH:MM"
+
+#: db/models/fields/__init__.py:562
+msgid "Enter a valid filename."
+msgstr "Vlož platné meno súboru."
+
+#: db/models/fields/related.py:43
+#, python-format
+msgid "Please enter a valid %s."
+msgstr "Prosím vlož platné %s."
+
+#: db/models/fields/related.py:579
+msgid "Separate multiple IDs with commas."
+msgstr "Oddeľ viacnásobné ID Äiarkami."
+
+#: db/models/fields/related.py:581
+msgid ""
+"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr ""
+" Podržte \"Control\", alebo \"Command\" na Mac_u, na výber viac ako jednej "
+"položky."
+
+#: 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] "Prosím vlož platné %(self)s IDs. Hodnota %(value)r je neplatná."
+msgstr[1] "Prosím vlož platné %(self)s IDs. Hodnoty %(value)r sú neplatné."
+
+#: 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] "ZabezpeÄ aby tvoj text bol menší ako %s znak."
+msgstr[1] "ZabezpeÄ aby tvoj text bol menší ako %s znakov."
+
+#: forms/__init__.py:385
+msgid "Line breaks are not allowed here."
+msgstr "Nový riadok tu nieje povolený."
+
+#: 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 "Vyber si platnú voľbu; '%(data)s' nie je v %(choices)s."
+
+#: forms/__init__.py:645
+msgid "The submitted file is empty."
+msgstr "Poslaný súbor je prázdný."
+
+#: forms/__init__.py:699
+msgid "Enter a whole number between -32,768 and 32,767."
+msgstr "Vlož celé Äíslo s hodnotou medzi -32768 a 32767."
+
+#: forms/__init__.py:708
+msgid "Enter a positive number."
+msgstr "Vlož celé kladné Äíslo."
+
+#: forms/__init__.py:717
+msgid "Enter a whole number between 0 and 32,767."
+msgstr "Vlož celé Äíslo s hodnotou medzi 0 a 32767."
+
+#: core/validators.py:60
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Toto môže obsahovaÅ¥ len písmená, Äíslice a podÄiarkovníky."
+
+#: core/validators.py:64
+msgid ""
+"This value must contain only letters, numbers, underscores, dashes or "
+"slashes."
+msgstr ""
+"Toto môže obsahovaÅ¥ len písmena, Äíslice, podÄiarkovniky, pomlÄky a lomítka."
+
+#: core/validators.py:72
+msgid "Uppercase letters are not allowed here."
+msgstr "Veľké písmená tu nie sú povolené."
+
+#: core/validators.py:76
+msgid "Lowercase letters are not allowed here."
+msgstr "Malé písmena tu nie sú povolené."
+
+#: core/validators.py:83
+msgid "Enter only digits separated by commas."
+msgstr "Vlož len Äíslice, oddelené Äiarkami."
+
+#: core/validators.py:95
+msgid "Enter valid e-mail addresses separated by commas."
+msgstr "Vlož platné e-mail adresy oddelené Äiarkami."
+
+#: core/validators.py:99
+msgid "Please enter a valid IP address."
+msgstr "Prosím vlož platnú IP adresu."
+
+#: core/validators.py:103
+msgid "Empty values are not allowed here."
+msgstr "Prázdne hodnoty tu nie sú povolené."
+
+#: core/validators.py:107
+msgid "Non-numeric characters aren't allowed here."
+msgstr "Znaky, ktoré nie sú Äíslicami, tu nie sú povolené."
+
+#: core/validators.py:111
+msgid "This value can't be comprised solely of digits."
+msgstr "Tento údaj nemôže byÅ¥ vytvorený len z Äíslic."
+
+#: core/validators.py:116
+msgid "Enter a whole number."
+msgstr "Vlož celé Äíslo."
+
+#: core/validators.py:120
+msgid "Only alphabetical characters are allowed here."
+msgstr "Tu sú povolené len alfanumerické znaky."
+
+#: core/validators.py:124
+msgid "Enter a valid date in YYYY-MM-DD format."
+msgstr "Vlož platný dátum vo formáte RRRR-MM-DD."
+
+#: core/validators.py:128
+msgid "Enter a valid time in HH:MM format."
+msgstr "Vlož platný Äas vo formáte HH:MM."
+
+#: core/validators.py:136
+msgid "Enter a valid e-mail address."
+msgstr "Vlož platnú e-mail adresu."
+
+#: core/validators.py:148
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+"Nahraj platný obrázok. Súbor, ktorý si nahral buÄ nebol obrázok alebo je "
+"nahratý poškodený obrázok."
+
+#: core/validators.py:155
+#, python-format
+msgid "The URL %s does not point to a valid image."
+msgstr "URL %s neukazuje na platný obrázok."
+
+#: core/validators.py:159
+#, python-format
+msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
+msgstr ""
+"Telefónne Äíslo musí maÅ¥ formát XXX-XXX-XXXX. Číslo \"%s\" je neplatné."
+
+#: core/validators.py:167
+#, python-format
+msgid "The URL %s does not point to a valid QuickTime video."
+msgstr "URL %s neukazuje na platné QuickTime video."
+
+#: core/validators.py:171
+msgid "A valid URL is required."
+msgstr "Platné URL je požadované."
+
+#: core/validators.py:185
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"Je požadovaná bezchybná stránka HTML. Zistené chyby sú:\n"
+"%s"
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Chybne formované XML: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Neplatné URL: %s"
+
+#: core/validators.py:206 core/validators.py:208
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "Odkaz na URL %s je neplatný."
+
+#: core/validators.py:214
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "Vlož platnú skratku U.S. štátu."
+
+#: 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] "Vyjadruj sa slušne! Slovo %s tu nie je dovolené použivať."
+msgstr[1] "Vyjadruj sa slušne! Slová %s tu nie je dovolené použivať."
+
+#: core/validators.py:236
+#, python-format
+msgid "This field must match the '%s' field."
+msgstr "Toto pole sa musí zhodovať s poľom '%s'. "
+
+#: core/validators.py:255
+msgid "Please enter something for at least one field."
+msgstr "Prosím vlož nieÄo aspoň pre jedno pole."
+
+#: core/validators.py:264 core/validators.py:275
+msgid "Please enter both fields or leave them both empty."
+msgstr "Prosím vlož obidve polia, alebo nechaj ich obe prázdne. "
+
+#: core/validators.py:282
+#, python-format
+msgid "This field must be given if %(field)s is %(value)s"
+msgstr "Toto pole musí byť vyplnené tak, že %(field)s obsahuje %(value)s"
+
+#: core/validators.py:294
+#, python-format
+msgid "This field must be given if %(field)s is not %(value)s"
+msgstr ""
+"Toto pole musí byť vyplnené tak, že %(field)s nesmie obsahovať %(value)s"
+
+#: core/validators.py:313
+msgid "Duplicate values are not allowed."
+msgstr "Duplicitné hodnoty nie sú povolené."
+
+#: core/validators.py:336
+#, python-format
+msgid "This value must be a power of %s."
+msgstr "Táto hodnota musí byť mocninou %s."
+
+#: core/validators.py:347
+msgid "Please enter a valid decimal number."
+msgstr "Prosím vlož platné desiatkové Äíslo. "
+
+#: 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] "Prosím vlož platné desiatkové Äíslo s najviac %s Äíslicou."
+msgstr[1] "Prosím vlož platné desiatkové Äíslo s najviac %s Äíslicami."
+
+#: 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] "Prosím vlož platné desatinné Äíslo s najviac %s desatinným miestom."
+msgstr[1] ""
+"Prosím vlož platné desatinné Äíslo s najviac %s desatinnými miestami."
+
+#: core/validators.py:362
+#, python-format
+msgid "Make sure your uploaded file is at least %s bytes big."
+msgstr "PresvedÄ sa, že posielaný súbor nemá menej ako %s bytov."
+
+#: core/validators.py:363
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr "PresvedÄ sa, že posielaný súbor nemá viac ako %s bytov."
+
+#: core/validators.py:376
+msgid "The format for this field is wrong."
+msgstr "Formát pre toto pole je chybný."
+
+#: core/validators.py:391
+msgid "This field is invalid."
+msgstr "Toto pole nie je platné."
+
+#: core/validators.py:426
+#, python-format
+msgid "Could not retrieve anything from %s."
+msgstr "NiÄ som nemohol získaÅ¥ 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 vrátilo neplatnú hlaviÄku 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 ""
+"Prosím zavri nezavretý %(tag)s popisovaÄ v riadku %(line)s. (Riadok zaÄína "
+"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 ""
+"Nejaký text zaÄínajúci na riadku %(line)s nie je povolený v tomto kontexte. "
+"(Riadok zaÄína 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 ""
+"\"%(attr)s\" na riadku %(line)s je neplatný atribút. (Riadok zaÄína 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 ""
+"\"<%(tag)s>\" na riadku %(line)s je neplatný popisovaÄ. (Riadok zaÄína 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 ""
+"PopisovaÄu na riadku %(line)s chýba jeden alebo viac atribútov. (Riadok "
+"zaÄína 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 ""
+"Atribút \"%(attr)s\" na riadku %(line)s má neplatnú hodnotu. (Riadok zaÄína "
+"s \"%(start)s\".)"
+
+#: template/defaultfilters.py:383
+msgid "yes,no,maybe"
+msgstr "ano,nie,možno"
+
+#~ msgid "Comment"
+#~ msgstr "Komentár"
+
+#~ msgid "Comments"
+#~ msgstr "Komentáre"
+
+#~ msgid "Delete this file."
+#~ msgstr "Vymaž tento súbor."
+
+#~ msgid "label"
+#~ msgstr "popis"
+
+#~ msgid "package"
+#~ msgstr "balík"
+
+#~ msgid "packages"
+#~ msgstr "balíky"
+
+#~ msgid "String (up to 50)"
+#~ msgstr "Reťazec (do 50 )"
diff --git a/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..a4f5b84
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..df24a19
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.po
@@ -0,0 +1,111 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# VLADO LABATH <vlado@labath.org>, 2005.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2005-12-18 19:38-0500\n"
+"PO-Revision-Date: 2005-12-18 19:26-0500\n"
+"Last-Translator: VLADO LABATH <vlado@labath.org>\n"
+"Language-Team: LANGUAGE <sk@li.org>\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 "Možný %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:41
+msgid "Choose all"
+msgstr "Vyber všetko"
+
+#: contrib/admin/media/js/SelectFilter2.js:46
+msgid "Add"
+msgstr "Pridaj"
+
+#: contrib/admin/media/js/SelectFilter2.js:48
+msgid "Remove"
+msgstr "Vymaž"
+
+#: 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 "Vyber si svoju voľbu a klikni"
+
+#: contrib/admin/media/js/SelectFilter2.js:59
+msgid "Clear all"
+msgstr "VyÄisti vÅ¡etko"
+
+#: 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 Marec Apríl Máj Jún Júl August September Október November "
+"December"
+
+#: contrib/admin/media/js/dateparse.js:27
+msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
+msgstr "Nedeľa Pondelok Utorok Streda Štvrtok Piatok Sobota"
+
+#: contrib/admin/media/js/calendar.js:25
+msgid "S M T W T F S"
+msgstr "N P U S Å  P S"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
+msgid "Now"
+msgstr "Práve teraz"
+
+#: 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 "Vyber Äas"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
+msgid "Midnight"
+msgstr "Polnoc"
+
+#: 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 "Poludnie"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
+msgid "Cancel"
+msgstr "Zruš"
+
+#: 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ár"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
+msgid "Yesterday"
+msgstr "VÄera"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
+msgid "Tomorrow"
+msgstr "Zajtra"
diff --git a/google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..a96fd55
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.po
new file mode 100644
index 0000000..b0617a5
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.po
@@ -0,0 +1,1929 @@
+# translation of django.po to Slovenian
+# Igor Kolar <ike@email.si), 2006.
+# Nena Kojadin <nena@kiberpipa.org), 2006.
+# Jure Cuhalev <gandalf@owca.info>, 2006, 2007.
+# Gasper Koren <gasper@fdvinfo.net>, 2007.
+# Jozko Skrablin <jozko.skrablin@gmail.com>, 2007.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-16 10:13+0200\n"
+"PO-Revision-Date: 2007-02-15 21:47+0100\n"
+"Last-Translator: Gasper Koren <gasper@fdvinfo.net>\n"
+"Language-Team: Slovenian <lugos-slo@lugos.si>\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=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
+
+#: contrib/comments/models.py:67
+#: contrib/comments/models.py:166
+msgid "object ID"
+msgstr "ID objekta"
+
+#: contrib/comments/models.py:68
+msgid "headline"
+msgstr "naslov"
+
+#: contrib/comments/models.py:69
+#: contrib/comments/models.py:90
+#: contrib/comments/models.py:167
+msgid "comment"
+msgstr "komentar"
+
+#: contrib/comments/models.py:70
+msgid "rating #1"
+msgstr "rating #1"
+
+#: contrib/comments/models.py:71
+msgid "rating #2"
+msgstr "rating #2"
+
+#: contrib/comments/models.py:72
+msgid "rating #3"
+msgstr "rating #3"
+
+#: contrib/comments/models.py:73
+msgid "rating #4"
+msgstr "rating #4"
+
+#: contrib/comments/models.py:74
+msgid "rating #5"
+msgstr "rating #5"
+
+#: contrib/comments/models.py:75
+msgid "rating #6"
+msgstr "rating #6"
+
+#: contrib/comments/models.py:76
+msgid "rating #7"
+msgstr "rating #7"
+
+#: contrib/comments/models.py:77
+msgid "rating #8"
+msgstr "rating #8"
+
+#: contrib/comments/models.py:82
+msgid "is valid rating"
+msgstr "je veljavni rating"
+
+#: contrib/comments/models.py:83
+#: contrib/comments/models.py:169
+msgid "date/time submitted"
+msgstr "datum/Äas vnosa"
+
+#: contrib/comments/models.py:84
+#: contrib/comments/models.py:170
+msgid "is public"
+msgstr "je javno"
+
+#: contrib/comments/models.py:85
+#: contrib/admin/views/doc.py:289
+msgid "IP address"
+msgstr "IP naslov"
+
+#: contrib/comments/models.py:86
+msgid "is removed"
+msgstr "je odstranjen/-a"
+
+#: 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 "Odkljukaj, Äe je komentar neprimeren. Namesto komentarja bo vidno obvestilo \"Ta komentar je bil odstranjen\"."
+
+#: contrib/comments/models.py:91
+msgid "comments"
+msgstr "komentarji"
+
+#: contrib/comments/models.py:131
+#: contrib/comments/models.py:207
+msgid "Content object"
+msgstr "Objekt z vsebino"
+
+#: 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 ""
+"Poslal uporabnik %(user)s ob %(date)s\n"
+"\n"
+"%(comment)s\n"
+"\n"
+"http://%(domain)s%(url)s"
+
+#: contrib/comments/models.py:168
+msgid "person's name"
+msgstr "ime osebe"
+
+#: contrib/comments/models.py:171
+msgid "ip address"
+msgstr "ip naslov"
+
+#: contrib/comments/models.py:173
+msgid "approved by staff"
+msgstr "osebje je potrdilo"
+
+#: contrib/comments/models.py:176
+msgid "free comment"
+msgstr "anonimen komentar"
+
+#: contrib/comments/models.py:177
+msgid "free comments"
+msgstr "anonimni komentarji"
+
+#: contrib/comments/models.py:233
+msgid "score"
+msgstr "ocena"
+
+#: contrib/comments/models.py:234
+msgid "score date"
+msgstr "datum ocene"
+
+#: contrib/comments/models.py:237
+msgid "karma score"
+msgstr "karma toÄke"
+
+#: contrib/comments/models.py:238
+msgid "karma scores"
+msgstr "karma toÄke"
+
+#: contrib/comments/models.py:242
+#, python-format
+msgid "%(score)d rating by %(user)s"
+msgstr "oceno %(score)d podelil %(user)s"
+
+#: contrib/comments/models.py:258
+#, python-format
+msgid ""
+"This comment was flagged by %(user)s:\n"
+"\n"
+"%(text)s"
+msgstr ""
+"Komentar je z zastavico oznaÄil %(user)s:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/models.py:265
+msgid "flag date"
+msgstr "datum oznaÄitve (zastavice)"
+
+#: contrib/comments/models.py:268
+msgid "user flag"
+msgstr "uporabniška zastavica"
+
+#: contrib/comments/models.py:269
+msgid "user flags"
+msgstr "uporabniške zastavice"
+
+#: contrib/comments/models.py:273
+#, python-format
+msgid "Flag by %r"
+msgstr "Avtor zastavice je %r"
+
+#: contrib/comments/models.py:278
+msgid "deletion date"
+msgstr "datum izbrisa"
+
+#: contrib/comments/models.py:280
+msgid "moderator deletion"
+msgstr "izbris s strani moderatorja"
+
+#: contrib/comments/models.py:281
+msgid "moderator deletions"
+msgstr "izbrisi s strani moderatorja"
+
+#: contrib/comments/models.py:285
+#, python-format
+msgid "Moderator deletion by %r"
+msgstr "Izbris opravil moderator %r"
+
+#: contrib/comments/views/karma.py:19
+msgid "Anonymous users cannot vote"
+msgstr "Anonimni uporabniki ne morejo glasovati"
+
+#: contrib/comments/views/karma.py:23
+msgid "Invalid comment ID"
+msgstr "Neveljavni ID komentarja"
+
+#: contrib/comments/views/karma.py:25
+msgid "No voting for yourself"
+msgstr "Ni mogoÄe glasovati zase"
+
+#: contrib/comments/views/comments.py:28
+msgid "This rating is required because you've entered at least one other rating."
+msgstr "To oceno moraš podati, ker si podal vsaj še eno drugo oceno."
+
+#: 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] ""
+"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentarjev:\n"
+"\n"
+"%(text)s"
+msgstr[1] ""
+"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentarja:\n"
+"\n"
+"%(text)s"
+msgstr[2] ""
+"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentarje:\n"
+"\n"
+"%(text)s"
+msgstr[3] ""
+"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentarjev:\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 ""
+"Ta komentar je poslal sumljiv uporabnik:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/views/comments.py:189
+#: contrib/comments/views/comments.py:280
+msgid "Only POSTs are allowed"
+msgstr "Dovoljena je le POST metoda"
+
+#: contrib/comments/views/comments.py:193
+#: contrib/comments/views/comments.py:284
+msgid "One or more of the required fields wasn't submitted"
+msgstr "Eno ali veÄ obveznih polj ni vpisanih"
+
+#: contrib/comments/views/comments.py:197
+#: contrib/comments/views/comments.py:286
+msgid "Somebody tampered with the comment form (security violation)"
+msgstr "Nekdo se je poigraval z obrazcem za komentarje (varnostna kršitev)"
+
+#: 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 "Obrazec s komentarji ima neveljavni parameter 'target' -- ID objekta je neveljaven."
+
+#: contrib/comments/views/comments.py:257
+#: contrib/comments/views/comments.py:321
+msgid "The comment form didn't provide either 'preview' or 'post'"
+msgstr "Obrazec s komentarji ni podal niti 'preview' niti 'post' akcije."
+
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/comments/templates/comments/form.html:8
+#: contrib/admin/templates/admin/login.html:17
+msgid "Username:"
+msgstr "Uporabniško ime:"
+
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/admin/templates/admin/login.html:20
+msgid "Password:"
+msgstr "Geslo:"
+
+#: contrib/comments/templates/comments/form.html:6
+msgid "Forgotten your password?"
+msgstr "Ste pozabili geslo?"
+
+#: 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 "Odjava"
+
+#: contrib/comments/templates/comments/form.html:12
+msgid "Ratings"
+msgstr "Ocene"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Required"
+msgstr "Obvezno"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Optional"
+msgstr "Neobvezno"
+
+#: contrib/comments/templates/comments/form.html:23
+msgid "Post a photo"
+msgstr "Objavi sliko"
+
+#: contrib/comments/templates/comments/form.html:27
+#: contrib/comments/templates/comments/freeform.html:5
+msgid "Comment:"
+msgstr "Komentar:"
+
+#: contrib/comments/templates/comments/form.html:32
+#: contrib/comments/templates/comments/freeform.html:9
+msgid "Preview comment"
+msgstr "Predogled komentarja"
+
+#: contrib/comments/templates/comments/freeform.html:4
+msgid "Your name:"
+msgstr "Vaše ime:"
+
+#: contrib/admin/filterspecs.py:40
+#, python-format
+msgid ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Avtor: %s</h3>\n"
+"<ul>\n"
+
+#: contrib/admin/filterspecs.py:70
+#: contrib/admin/filterspecs.py:88
+#: contrib/admin/filterspecs.py:143
+msgid "All"
+msgstr "Vse"
+
+#: contrib/admin/filterspecs.py:109
+msgid "Any date"
+msgstr "Kadarkoli"
+
+#: contrib/admin/filterspecs.py:110
+msgid "Today"
+msgstr "Danes"
+
+#: contrib/admin/filterspecs.py:113
+msgid "Past 7 days"
+msgstr "Zadnjih 7 dni"
+
+#: contrib/admin/filterspecs.py:115
+msgid "This month"
+msgstr "Ta mesec"
+
+#: contrib/admin/filterspecs.py:117
+msgid "This year"
+msgstr "Letos"
+
+#: contrib/admin/filterspecs.py:143
+msgid "Yes"
+msgstr "Da"
+
+#: contrib/admin/filterspecs.py:143
+msgid "No"
+msgstr "Ne"
+
+#: contrib/admin/filterspecs.py:150
+msgid "Unknown"
+msgstr "Neznano"
+
+#: contrib/admin/models.py:16
+msgid "action time"
+msgstr "Äas dejanja"
+
+#: contrib/admin/models.py:19
+msgid "object id"
+msgstr "id objekta"
+
+#: contrib/admin/models.py:20
+msgid "object repr"
+msgstr "predstavitev objekta"
+
+#: contrib/admin/models.py:21
+msgid "action flag"
+msgstr "zastavica dejanja"
+
+#: contrib/admin/models.py:22
+msgid "change message"
+msgstr "spremeni sporoÄilo"
+
+#: contrib/admin/models.py:25
+msgid "log entry"
+msgstr "vnos v dnevniku"
+
+#: contrib/admin/models.py:26
+msgid "log entries"
+msgstr "vnosi v dnevniku"
+
+#: contrib/admin/templatetags/admin_list.py:228
+msgid "All dates"
+msgstr "Vsi datumi"
+
+#: 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 "Prosimo, vnesite veljavno uporabniÅ¡ko ime in geslo. Opomba: obe polji upoÅ¡tevata velikost Ärk."
+
+#: contrib/admin/views/decorators.py:23
+#: contrib/admin/templates/admin/login.html:25
+msgid "Log in"
+msgstr "Prijavite se"
+
+#: 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 "Vaša seja je pretekla; prosimo da se ponovno prijavite. Brez skrbi, vaše objave so varno shranjene."
+
+#: 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 "Izgleda, da vaÅ¡ brskalnik nima podpore za piÅ¡kotke. Prosimo, vkljuÄite piÅ¡kotke, osvežite stran in poskusite Å¡e enkrat."
+
+#: contrib/admin/views/decorators.py:82
+msgid "Usernames cannot contain the '@' character."
+msgstr "Uporabniška imena ne smejo vsebovati znaka '@'."
+
+#: contrib/admin/views/decorators.py:84
+#, python-format
+msgid "Your e-mail address is not your username. Try '%s' instead."
+msgstr "Vaš e-main naslov ni vaše uporabniško ime. Poskusite uporabiti '%s'."
+
+#: contrib/admin/views/main.py:226
+msgid "Site administration"
+msgstr "Administracija strani"
+
+#: contrib/admin/views/main.py:260
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was added successfully."
+msgstr "%(name)s \"%(obj)s\" je bil uspešno dodan."
+
+#: contrib/admin/views/main.py:264
+#: contrib/admin/views/main.py:348
+msgid "You may edit it again below."
+msgstr "Vsebino lahko znova uredite spodaj."
+
+#: contrib/admin/views/main.py:272
+#: contrib/admin/views/main.py:357
+#, python-format
+msgid "You may add another %s below."
+msgstr "Spodaj lahko dodate Å¡e en %s."
+
+#: 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 "Dodan %s."
+
+#: contrib/admin/views/main.py:336
+#: contrib/admin/views/main.py:338
+#: contrib/admin/views/main.py:340
+msgid "and"
+msgstr "in"
+
+#: contrib/admin/views/main.py:338
+#, python-format
+msgid "Changed %s."
+msgstr "Spremenjen %s."
+
+#: contrib/admin/views/main.py:340
+#, python-format
+msgid "Deleted %s."
+msgstr "Izbrisn %s."
+
+#: contrib/admin/views/main.py:343
+msgid "No fields changed."
+msgstr "Nobeno polje ni bilo spremenjeno."
+
+#: contrib/admin/views/main.py:346
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was changed successfully."
+msgstr "%(name)s \"%(obj)s\" je bil uspešno spremenjeno."
+
+#: 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\" je bil uspešno dodano. Ponovno ga lahko urejdite spodaj."
+
+#: contrib/admin/views/main.py:392
+#, python-format
+msgid "Change %s"
+msgstr "Spremeni %s"
+
+#: contrib/admin/views/main.py:470
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
+msgstr "Eden ali veÄ %(fieldname)s v %(name)s: %(obj)s"
+
+#: contrib/admin/views/main.py:475
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "Eden ali veÄ %(fieldname)s v %(name)s:"
+
+#: contrib/admin/views/main.py:508
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was deleted successfully."
+msgstr "%(name)s \"%(obj)s\" je bilo uspešno izbrisan."
+
+#: contrib/admin/views/main.py:511
+msgid "Are you sure?"
+msgstr "Ste prepriÄani?"
+
+#: contrib/admin/views/main.py:533
+#, python-format
+msgid "Change history: %s"
+msgstr "Zgodovina sprememb: %s"
+
+#: contrib/admin/views/main.py:565
+#, python-format
+msgid "Select %s"
+msgstr "Izberite %s"
+
+#: contrib/admin/views/main.py:565
+#, python-format
+msgid "Select %s to change"
+msgstr "Izberite %s, ki ga želite spremeniti"
+
+#: 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 "Celo Å¡tevilo (integer)"
+
+#: contrib/admin/views/doc.py:278
+msgid "Boolean (Either True or False)"
+msgstr "Boolean (True ali False)"
+
+#: contrib/admin/views/doc.py:279
+#: contrib/admin/views/doc.py:296
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "Niz (vse do %(maxlength)s)"
+
+#: contrib/admin/views/doc.py:280
+msgid "Comma-separated integers"
+msgstr "Z vejico loÄena cela Å¡tevila (integer)"
+
+#: contrib/admin/views/doc.py:281
+msgid "Date (without time)"
+msgstr "Datum (brez ure)"
+
+#: contrib/admin/views/doc.py:282
+msgid "Date (with time)"
+msgstr "Datum (z uro)"
+
+#: contrib/admin/views/doc.py:283
+msgid "E-mail address"
+msgstr "E-mail naslov"
+
+#: contrib/admin/views/doc.py:284
+#: contrib/admin/views/doc.py:287
+msgid "File path"
+msgstr "Pot do datoteke"
+
+#: contrib/admin/views/doc.py:285
+msgid "Decimal number"
+msgstr "Decimalno Å¡tevilo"
+
+#: contrib/admin/views/doc.py:291
+msgid "Boolean (Either True, False or None)"
+msgstr "Boolean (True, False ali None)"
+
+#: contrib/admin/views/doc.py:292
+msgid "Relation to parent model"
+msgstr "Razmerje s starševskim modelom"
+
+#: contrib/admin/views/doc.py:293
+msgid "Phone number"
+msgstr "Telefonska Å¡tevilka"
+
+#: contrib/admin/views/doc.py:298
+msgid "Text"
+msgstr "Besedilo"
+
+#: contrib/admin/views/doc.py:299
+msgid "Time"
+msgstr "ÄŒas"
+
+#: contrib/admin/views/doc.py:300
+#: contrib/flatpages/models.py:7
+msgid "URL"
+msgstr "URL (spletni naslov)"
+
+#: contrib/admin/views/doc.py:301
+msgid "U.S. state (two uppercase letters)"
+msgstr "Koda ameriÅ¡ke zvezne države (dve veliki Ärki)"
+
+#: contrib/admin/views/doc.py:302
+msgid "XML text"
+msgstr "XML besedilo"
+
+#: 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 "Dokumentacija"
+
+#: 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 "Spremeni geslo"
+
+#: 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 "Domov"
+
+#: contrib/admin/templates/admin/object_history.html:5
+#: contrib/admin/templates/admin/change_form.html:20
+msgid "History"
+msgstr "Zgodovina"
+
+#: 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 "Uporabnik"
+
+#: contrib/admin/templates/admin/object_history.html:20
+msgid "Action"
+msgstr "Dejanje"
+
+#: 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 "Ta objekt nima zgodovine sprememb. Verjetno ni bil dodan preko te strani za administracijo."
+
+#: contrib/admin/templates/admin/base_site.html:4
+msgid "Django site admin"
+msgstr "Vmesnik za administracijo Django strani"
+
+#: contrib/admin/templates/admin/base_site.html:7
+msgid "Django administration"
+msgstr "Django administracija"
+
+#: contrib/admin/templates/admin/500.html:4
+msgid "Server error"
+msgstr "Napaka strežnika"
+
+#: contrib/admin/templates/admin/500.html:6
+msgid "Server error (500)"
+msgstr "Napaka strežnika (500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "Napaka strežnika <em>(500)</em>"
+
+#: 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 "PriÅ¡lo je do nepriÄakovane napake. Administrator je preko e-poÅ¡te prejel obvestilo o napaki in jo bo v kratkem odpravil. Hvala za potrpljenje."
+
+#: contrib/admin/templates/admin/404.html:4
+#: contrib/admin/templates/admin/404.html:8
+msgid "Page not found"
+msgstr "Strani ni mogoÄe najti"
+
+#: contrib/admin/templates/admin/404.html:10
+msgid "We're sorry, but the requested page could not be found."
+msgstr "OpraviÄujemo se, a zahtevane strani ni mogoÄe najti."
+
+#: contrib/admin/templates/admin/index.html:17
+#, python-format
+msgid "Models available in the %(name)s application."
+msgstr "Modeli na voljo v %(name)s aplikaciji"
+
+#: 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 "Spremeni"
+
+#: contrib/admin/templates/admin/index.html:44
+msgid "You don't have permission to edit anything."
+msgstr "Nimate dovoljenja za urejanje Äesar koli."
+
+#: contrib/admin/templates/admin/index.html:52
+msgid "Recent Actions"
+msgstr "Zadnja dejanja"
+
+#: contrib/admin/tempalates/admin/index.html:53
+msgid "My Actions"
+msgstr "Moja dejanja"
+
+#: contrib/admin/templates/admin/index.html:57
+msgid "None available"
+msgstr "Ni na voljo"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Ste <a href=\"/password_reset/\">pozabili geslo</a>?"
+
+#: contrib/admin/templates/admin/base.html:23
+msgid "Welcome,"
+msgstr "Dobrodošli,"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:9
+#: contrib/admin/templates/admin/submit_line.html:3
+msgid "Delete"
+msgstr "Izbriši"
+
+#: 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 "Izbris %(object_name)s '%(object)s' bi pomenil izbris povezanih objektov, vendar nimate dovoljenja za izbris naslednjih tipov objektov:"
+
+#: 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 "Ste prepriÄani, da želite izbrisati %(object_name)s \"%(object)s\"?Vsi naslednji povezani elementi bodo izbrisani:"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:26
+msgid "Yes, I'm sure"
+msgstr "Ja, sem prepriÄan"
+
+#: contrib/admin/templates/admin/filter.html:2
+#, python-format
+msgid " By %(title)s "
+msgstr " Po %(title)s"
+
+#: contrib/admin/templates/admin/search_form.html:8
+msgid "Go"
+msgstr "Pojdi"
+
+#: contrib/admin/templates/admin/change_form.html:21
+msgid "View on site"
+msgstr "Poglej na strani"
+
+#: contrib/admin/templates/admin/change_form.html:30
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Prosimo, odpravite sledeÄo napako."
+msgstr[1] "Prosimo, odpravite sledeÄi napaki."
+msgstr[2] "Prosimo, odpravite sledeÄe napake."
+msgstr[3] "Prosimo, odpravite sledeÄe napake."
+
+#: contrib/admin/templates/admin/change_form.html:48
+msgid "Ordering"
+msgstr "RazvrÅ¡Äanje"
+
+#: contrib/admin/templates/admin/change_form.html:51
+msgid "Order:"
+msgstr "Razvrsti:"
+
+#: contrib/admin/templates/admin/submit_line.html:4
+msgid "Save as new"
+msgstr "Shrani kot novo"
+
+#: contrib/admin/templates/admin/submit_line.html:5
+msgid "Save and add another"
+msgstr "Shrani in dodaj Å¡e eno"
+
+#: contrib/admin/templates/admin/submit_line.html:6
+msgid "Save and continue editing"
+msgstr "Shrani in nadaljuj z urejanjem"
+
+#: contrib/admin/templates/admin/submit_line.html:7
+msgid "Save"
+msgstr "Shrani"
+
+#: 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 "Sprememba gesla"
+
+#: contrib/admin/templates/registration/password_change_done.html:6
+#: contrib/admin/templates/registration/password_change_done.html:10
+msgid "Password change successful"
+msgstr "Sprememba gesla je uspela"
+
+#: contrib/admin/templates/registration/password_change_done.html:12
+msgid "Your password was changed."
+msgstr "Vaše geslo je bilo spremenjeno."
+
+#: 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 "Ponastavitev gesla"
+
+#: 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 "Ste pozabili geslo? Vnesite vaš e-mail naslov in poslali vam bomo novo geslo."
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "E-mail address:"
+msgstr "Naslov e-pošte:"
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "Reset my password"
+msgstr "Ponastavi moje geslo"
+
+#: contrib/admin/templates/registration/logged_out.html:8
+msgid "Thanks for spending some quality time with the Web site today."
+msgstr "Hvala, ker ste si danes vzeli nekaj Äasa za to spletno stran."
+
+#: contrib/admin/templates/registration/logged_out.html:10
+msgid "Log in again"
+msgstr "Ponovna prijava"
+
+#: contrib/admin/templates/registration/password_reset_done.html:6
+#: contrib/admin/templates/registration/password_reset_done.html:10
+msgid "Password reset successful"
+msgstr "Ponastavitev gesla je uspela"
+
+#: 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 "Po e-pošti smo vam poslali novo geslo. Morali bi ga prejeti v kratkem"
+
+#: 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 "Prosim, vnesite vaše staro geslo (zaradi varnosti) in nato še dvakrat novo (da preverimo, da se niste zatipkali)"
+
+#: contrib/admin/templates/registration/password_change_form.html:17
+msgid "Old password:"
+msgstr "Staro geslo:"
+
+#: contrib/admin/templates/registration/password_change_form.html:19
+msgid "New password:"
+msgstr "Novo geslo:"
+
+#: contrib/admin/templates/registration/password_change_form.html:21
+msgid "Confirm password:"
+msgstr "Potrditev gesla:"
+
+#: contrib/admin/templates/registration/password_change_form.html:23
+msgid "Change my password"
+msgstr "Spremeni moje geslo"
+
+#: contrib/admin/templates/registration/password_reset_email.html:2
+msgid "You're receiving this e-mail because you requested a password reset"
+msgstr "Ta e-mail ste dobili, ker ste zahtevali ponastavitev gesla"
+
+#: contrib/admin/templates/registration/password_reset_email.html:3
+#, python-format
+msgid "for your user account at %(site_name)s"
+msgstr "za vaÅ¡ uporabniÅ¡ki raÄun 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 novo geslo 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 "Geslo lahko spremenite z obiskom strani:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:11
+msgid "Your username, in case you've forgotten:"
+msgstr "Vaše uporabniško ime (za vsak primer):"
+
+#: contrib/admin/templates/registration/password_reset_email.html:13
+msgid "Thanks for using our site!"
+msgstr "Hvala, ker uporabljate našo stran!"
+
+#: contrib/admin/templates/registration/password_reset_email.html:15
+#, python-format
+msgid "The %(site_name)s team"
+msgstr "Ekipa strani %(site_name)s"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:3
+msgid "Bookmarklets"
+msgstr "Zaznamkice"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:5
+msgid "Documentation bookmarklets"
+msgstr "Dokumentacijske zaznamkice"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:9
+#, fuzzy
+msgid ""
+"\n"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">Za inÅ¡talacijo zaznamkic povleÄite povezavo v orodno vrstico\n"
+"z zaznamki, ali kliknite z desno miÅ¡kino tipko na povezavo in jo dodajte med zaznamke. Zdaj lahko izberete zaznamkico s katerekoli strani. Opomba: nekatere izmed teh strani lahko gledate le z raÄunalnika, ki je oznaÄen kot \"notranji\" (v kolikor niste prepriÄani, Äe je vaÅ¡ raÄunalnik oznaÄen kot \"notranji\"se obrnite na sistemskega administratorja).</p>\n"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:19
+msgid "Documentation for this page"
+msgstr "Dokumentacija te strani"
+
+#: 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 "Skok na stran z dokumentacijo za pogled (view), ki generira trenutno stran."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:22
+msgid "Show object ID"
+msgstr "Pokaži ID objekta"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:23
+msgid "Shows the content-type and unique ID for pages that represent a single object."
+msgstr "Pokaže content-type in unikatni ID za strani, ki predstavljajo en objekt."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:25
+msgid "Edit this object (current window)"
+msgstr "Uredi objekt (v trenutnem oknu)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:26
+msgid "Jumps to the admin page for pages that represent a single object."
+msgstr "Skok na administracijsko stran za vse strani, ki predstavljajo en objekt."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:28
+msgid "Edit this object (new window)"
+msgstr "Uredi ta objekt (v novem oknu)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:29
+msgid "As above, but opens the admin page in a new window."
+msgstr "Kot zgoraj, le da odpre administracijsko stran v novem oknu."
+
+#: contrib/admin/templates/widget/date_time.html:3
+msgid "Date:"
+msgstr "Datum:"
+
+#: contrib/admin/templates/widget/date_time.html:4
+msgid "Time:"
+msgstr "Ura:"
+
+#: contrib/admin/templates/widget/file.html:2
+msgid "Currently:"
+msgstr "Trenutno:"
+
+#: contrib/admin/templates/widget/file.html:3
+msgid "Change:"
+msgstr "Sprememba:"
+
+#: contrib/redirects/models.py:7
+msgid "redirect from"
+msgstr "preusmeritev iz"
+
+#: contrib/redirects/models.py:8
+msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'."
+msgstr "Ta pot mora biti absolutna, brez imena domene. Primer: '/events/search'"
+
+#: contrib/redirects/models.py:9
+msgid "redirect to"
+msgstr "preusmeri na"
+
+#: contrib/redirects/models.py:10
+msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'."
+msgstr "To je lahko absolutna pot (kot zgoraj) ali popoln URL naslov (ki se zaÄne s 'http://')"
+
+#: contrib/redirects/models.py:12
+msgid "redirect"
+msgstr "preusmeritev"
+
+#: contrib/redirects/models.py:13
+msgid "redirects"
+msgstr "preusmeritve"
+
+#: contrib/flatpages/models.py:8
+msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes."
+msgstr "Primer: '/about/contact/'. Preverite ali vsebuje / (poÅ¡evnico) na zaÄetku in koncu vnosa."
+
+#: contrib/flatpages/models.py:9
+msgid "title"
+msgstr "naslov"
+
+#: contrib/flatpages/models.py:10
+msgid "content"
+msgstr "vsebina"
+
+#: contrib/flatpages/models.py:11
+msgid "enable comments"
+msgstr "omogoÄi komentarje"
+
+#: contrib/flatpages/models.py:12
+msgid "template name"
+msgstr "ime predloge"
+
+#: contrib/flatpages/models.py:13
+msgid "Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."
+msgstr "Primer: 'flatpages/contact_page'. ÄŒe to polje ni izpolnjeno, bo sistem uporabil 'flatpages/default'."
+
+#: contrib/flatpages/models.py:14
+msgid "registration required"
+msgstr "obvezna registracija"
+
+#: contrib/flatpages/models.py:14
+msgid "If this is checked, only logged-in users will be able to view the page."
+msgstr "ÄŒe je to polje izbrano, si bodo to stran lahko ogledali le prijavljeni uporabniki."
+
+#: contrib/flatpages/models.py:18
+msgid "flat page"
+msgstr "enostavna stran"
+
+#: contrib/flatpages/models.py:19
+msgid "flat pages"
+msgstr "enostavne strani"
+
+#: contrib/auth/models.py:13
+#: contrib/auth/models.py:26
+msgid "name"
+msgstr "ime"
+
+#: contrib/auth/models.py:15
+msgid "codename"
+msgstr "kodno ime"
+
+#: contrib/auth/models.py:17
+msgid "permission"
+msgstr "dovoljenje"
+
+#: contrib/auth/models.py:18
+#: contrib/auth/models.py:27
+msgid "permissions"
+msgstr "dovoljenja"
+
+#: contrib/auth/models.py:29
+msgid "group"
+msgstr "skupina"
+
+#: contrib/auth/models.py:30
+#: contrib/auth/models.py:65
+msgid "groups"
+msgstr "skupine"
+
+#: contrib/auth/models.py:55
+msgid "username"
+msgstr "uporabniško ime"
+
+#: contrib/auth/models.py:56
+msgid "first name"
+msgstr "ime"
+
+#: contrib/auth/models.py:57
+msgid "last name"
+msgstr "priimek"
+
+#: contrib/auth/models.py:58
+msgid "e-mail address"
+msgstr "e-mail naslov"
+
+#: contrib/auth/models.py:59
+msgid "password"
+msgstr "geslo"
+
+#: contrib/auth/models.py:59
+msgid "Use '[algo]$[salt]$[hexdigest]'"
+msgstr "Uporabi '[algo]$[salt]$[hexdigest]'"
+
+#: contrib/auth/models.py:60
+msgid "staff status"
+msgstr "status osebja"
+
+#: contrib/auth/models.py:60
+msgid "Designates whether the user can log into this admin site."
+msgstr "DoloÄi, Äe se sme uporabnik prijaviti v to administracijsko stran."
+
+#: contrib/auth/models.py:61
+msgid "active"
+msgstr "aktiven"
+
+#: contrib/auth/models.py:62
+msgid "superuser status"
+msgstr "status superuporabnika"
+
+#: contrib/auth/models.py:63
+msgid "last login"
+msgstr "zadnja prijava"
+
+#: contrib/auth/models.py:64
+msgid "date joined"
+msgstr "Älan od"
+
+#: 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 "Poleg roÄno doloÄenih dovoljenj bo ta uporabnik dobil tudi vsa dovoljenja, ki pripadajo skupinam, katerih Älan je."
+
+#: contrib/auth/models.py:67
+msgid "user permissions"
+msgstr "uporabniška dovoljenja"
+
+#: contrib/auth/models.py:70
+msgid "user"
+msgstr "uporabnik"
+
+#: contrib/auth/models.py:71
+msgid "users"
+msgstr "uporabniki"
+
+#: contrib/auth/models.py:76
+msgid "Personal info"
+msgstr "Osebni podatki"
+
+#: contrib/auth/models.py:77
+msgid "Permissions"
+msgstr "Dovoljenja"
+
+#: contrib/auth/models.py:78
+msgid "Important dates"
+msgstr "Pomembni datumi"
+
+#: contrib/auth/models.py:79
+msgid "Groups"
+msgstr "Skupine"
+
+#: contrib/auth/models.py:219
+msgid "message"
+msgstr "sporoÄilo"
+
+#: contrib/auth/forms.py:30
+msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."
+msgstr "Izgleda, da vaÅ¡ brskalnik nima omogoÄenih piÅ¡kotkov. PiÅ¡kotki so potrebni za prijavo."
+
+#: contrib/contenttypes/models.py:25
+msgid "python model class name"
+msgstr "python ime razreda modela"
+
+#: contrib/contenttypes/models.py:28
+msgid "content type"
+msgstr "tip vsebine"
+
+#: contrib/contenttypes/models.py:29
+msgid "content types"
+msgstr "tipi vsebine"
+
+#: contrib/sessions/models.py:35
+msgid "session key"
+msgstr "kljuÄ seje"
+
+#: contrib/sessions/models.py:36
+msgid "session data"
+msgstr "podatki seje"
+
+#: contrib/sessions/models.py:37
+msgid "expire date"
+msgstr "Äaz izteka"
+
+#: contrib/sessions/models.py:41
+msgid "session"
+msgstr "seja"
+
+#: contrib/sessions/models.py:42
+msgid "sessions"
+msgstr "seje"
+
+#: contrib/sites/models.py:10
+msgid "domain name"
+msgstr "domena"
+
+#: contrib/sites/models.py:11
+msgid "display name"
+msgstr "prikazno ime"
+
+#: contrib/sites/models.py:15
+msgid "site"
+msgstr "stran"
+
+#: contrib/sites/models.py:16
+msgid "sites"
+msgstr "strani"
+
+#: 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 "ponedeljek"
+
+#: utils/dates.py:6
+msgid "Tuesday"
+msgstr "torek"
+
+#: utils/dates.py:6
+msgid "Wednesday"
+msgstr "sreda"
+
+#: utils/dates.py:6
+msgid "Thursday"
+msgstr "Äetrtek"
+
+#: utils/dates.py:6
+msgid "Friday"
+msgstr "petek"
+
+#: utils/dates.py:7
+msgid "Saturday"
+msgstr "sobota"
+
+#: utils/dates.py:7
+msgid "Sunday"
+msgstr "nedelja"
+
+#: 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 "marec"
+
+#: 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 "junij"
+
+#: utils/dates.py:15
+#: utils/dates.py:27
+msgid "July"
+msgstr "julij"
+
+#: utils/dates.py:15
+msgid "August"
+msgstr "avgust"
+
+#: 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 "avg"
+
+#: 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 "avg."
+
+#: utils/dates.py:28
+msgid "Sept."
+msgstr "sep."
+
+#: 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] "leto"
+msgstr[1] "leti"
+msgstr[2] "leta"
+msgstr[3] "let"
+
+#: utils/timesince.py:13
+msgid "month"
+msgid_plural "months"
+msgstr[0] "mesec"
+msgstr[1] "meseca"
+msgstr[2] "meseci"
+msgstr[3] "mesecev"
+
+#: utils/timesince.py:14
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] "teden"
+msgstr[1] "tedna"
+msgstr[2] "tedni"
+msgstr[3] "tednov"
+
+#: utils/timesince.py:15
+msgid "day"
+msgid_plural "days"
+msgstr[0] "dan"
+msgstr[1] "dneva"
+msgstr[2] "dnevi"
+msgstr[3] "dni"
+
+#: utils/timesince.py:16
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "ura"
+msgstr[1] "uri"
+msgstr[2] "ure"
+msgstr[3] "ur"
+
+#: utils/timesince.py:17
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "minuta"
+msgstr[1] "minuti"
+msgstr[2] "minute"
+msgstr[3] "minut"
+
+#: conf/global_settings.py:37
+msgid "Bengali"
+msgstr "Bengalski"
+
+#: conf/global_settings.py:38
+msgid "Czech"
+msgstr "Češki"
+
+#: conf/global_settings.py:39
+msgid "Welsh"
+msgstr "Valežanski"
+
+#: conf/global_settings.py:40
+msgid "Danish"
+msgstr "Danski"
+
+#: conf/global_settings.py:41
+msgid "German"
+msgstr "Nemški"
+
+#: conf/global_settings.py:42
+msgid "Greek"
+msgstr "Grški"
+
+#: conf/global_settings.py:43
+msgid "English"
+msgstr "Angleški"
+
+#: conf/global_settings.py:44
+msgid "Spanish"
+msgstr "Å panski"
+
+#: conf/global_settings.py:45
+msgid "French"
+msgstr "Francoski"
+
+#: conf/global_settings.py:46
+msgid "Galician"
+msgstr "GaliÄanski"
+
+#: conf/global_settings.py:47
+msgid "Hungarian"
+msgstr "Madžarski"
+
+#: conf/global_settings.py:48
+msgid "Hebrew"
+msgstr "Hebrejski"
+
+#: conf/global_settings.py:49
+msgid "Icelandic"
+msgstr "Islandski"
+
+#: conf/global_settings.py:50
+msgid "Italian"
+msgstr "Italijanski"
+
+#: conf/global_settings.py:51
+msgid "Japanese"
+msgstr "Japonski"
+
+#: conf/global_settings.py:52
+msgid "Dutch"
+msgstr "Nizozemski"
+
+#: conf/global_settings.py:53
+msgid "Norwegian"
+msgstr "Norveški"
+
+#: conf/global_settings.py:54
+msgid "Brazilian"
+msgstr "Brazilski"
+
+#: conf/global_settings.py:55
+msgid "Romanian"
+msgstr "Romunski"
+
+#: conf/global_settings.py:56
+msgid "Russian"
+msgstr "Ruski"
+
+#: conf/global_settings.py:57
+msgid "Slovak"
+msgstr "Slovaški"
+
+#: conf/global_settings.py:58
+msgid "Slovenian"
+msgstr "Slovenski"
+
+#: conf/global_settings.py:59
+msgid "Serbian"
+msgstr "Srbski"
+
+#: conf/global_settings.py:60
+msgid "Swedish"
+msgstr "Å vedski"
+
+#: conf/global_settings.py:61
+msgid "Ukrainian"
+msgstr "Ukrajinski"
+
+#: conf/global_settings.py:62
+msgid "Simplified Chinese"
+msgstr "Poenostavljeni kitajski"
+
+#: conf/global_settings.py:63
+msgid "Traditional Chinese"
+msgstr "Tradicionalni kitajski"
+
+#: core/validators.py:60
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Ta vrednost mora vsebovati le Ärke, Å¡tevila in podÄrtaje (_)."
+
+#: core/validators.py:64
+msgid "This value must contain only letters, numbers, underscores, dashes or slashes."
+msgstr "Ta vrednost mora vsebovati le Ärke, Å¡tevila, podÄrtaje, poÅ¡evnice ali pomiÅ¡ljaje."
+
+#: core/validators.py:72
+msgid "Uppercase letters are not allowed here."
+msgstr "Velike tiskane Ärke niso dovoljene."
+
+#: core/validators.py:76
+msgid "Lowercase letters are not allowed here."
+msgstr "Majhne tiskane Ärke niso dovoljene."
+
+#: core/validators.py:83
+msgid "Enter only digits separated by commas."
+msgstr "Vnesite samo Å¡tevila, loÄena z vejicami."
+
+#: core/validators.py:95
+msgid "Enter valid e-mail addresses separated by commas."
+msgstr "Vnesite veljavne e-mail naslove, loÄene z vejicami."
+
+#: core/validators.py:99
+msgid "Please enter a valid IP address."
+msgstr "Prosimo, vnesite veljavni IP naslov."
+
+#: core/validators.py:103
+msgid "Empty values are not allowed here."
+msgstr "Prazne vrednosti tu niso dovoljene."
+
+#: core/validators.py:107
+msgid "Non-numeric characters aren't allowed here."
+msgstr "NenumeriÄni znaki tukaj niso dovoljne."
+
+#: core/validators.py:111
+msgid "This value can't be comprised solely of digits."
+msgstr "Ta vrednost ne sme vsebovati le Å¡tevk."
+
+#: core/validators.py:116
+msgid "Enter a whole number."
+msgstr "Vnesite celo Å¡tevilo."
+
+#: core/validators.py:120
+msgid "Only alphabetical characters are allowed here."
+msgstr "Tukaj so dovoljene samo Ärke."
+
+#: core/validators.py:124
+msgid "Enter a valid date in YYYY-MM-DD format."
+msgstr "Vnesite veljavni datum v zapisu YYYY-MM-DD (leto-mesec-dan)."
+
+#: core/validators.py:128
+msgid "Enter a valid time in HH:MM format."
+msgstr "Vnesite veljavni Äas v zapisu HH:MM (ura:minuta)."
+
+#: core/validators.py:132
+#: db/models/fields/__init__.py:468
+msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
+msgstr "Vnesite veljavni datum/Äas v zapisu YYYY-MM-DD HH:MM (leto-mesec-dan ura:minuta)"
+
+#: core/validators.py:136
+msgid "Enter a valid e-mail address."
+msgstr "Vnesite veljaven 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 "Naložite veljavno sliko. Naložena datoteka ni bila slika ali pa je bila le-ta okvarjena."
+
+#: core/validators.py:155
+#, python-format
+msgid "The URL %s does not point to a valid image."
+msgstr "URL %s ne kaže na veljavno sliko."
+
+#: core/validators.py:159
+#, python-format
+msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
+msgstr "Telefonska Å¡tevilka mora biti v zapisu XXX-XXX-XXXX. \"%s\" ni vredu."
+
+#: core/validators.py:167
+#, python-format
+msgid "The URL %s does not point to a valid QuickTime video."
+msgstr "URL %s ne kaže na veljavni QuickTime video."
+
+#: core/validators.py:171
+msgid "A valid URL is required."
+msgstr "Potreben je veljaven URL naslov."
+
+#: core/validators.py:185
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"Potreben je veljaven HTML. Trenutni ima sledeÄe napake:\n"
+"%s"
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Nepravilen XML: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Neveljaven URL naslov: %s"
+
+#: core/validators.py:206
+#: core/validators.py:208
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "URL povezava %s ne deluje."
+
+#: core/validators.py:214
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "Vnesi veljavno okrajšavo za ameriško zvezno državo."
+
+#: 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] "Pazite na jezik! Beseda %s tu ni dovoljena."
+msgstr[1] "Pazite na jezik! Besedi %s tu nista dovoljeni."
+msgstr[2] "Pazite na jezik! Besede %s tu niso dovoljene."
+msgstr[3] "Pazite na jezik! Besede %s tu niso dovoljene."
+
+#: core/validators.py:236
+#, python-format
+msgid "This field must match the '%s' field."
+msgstr "To polje mora ustrezati polju '%s'."
+
+#: core/validators.py:255
+msgid "Please enter something for at least one field."
+msgstr "Prosim, vnesite nekaj v vsaj eno izmed polj."
+
+#: core/validators.py:264
+#: core/validators.py:275
+msgid "Please enter both fields or leave them both empty."
+msgstr "Prosimo, izpolnite obe polji ali pa pustite obe prazni."
+
+#: core/validators.py:282
+#, python-format
+msgid "This field must be given if %(field)s is %(value)s"
+msgstr "To polje mora biti podano, Äe je %(field)s %(value)s"
+
+#: core/validators.py:294
+#, python-format
+msgid "This field must be given if %(field)s is not %(value)s"
+msgstr "To polje mora biti podano, Äe ni %(field)s %(value)s"
+
+#: core/validators.py:313
+msgid "Duplicate values are not allowed."
+msgstr "Podvojitve niso dovoljene."
+
+#: core/validators.py:336
+#, python-format
+msgid "This value must be a power of %s."
+msgstr "Ta vrednost mora biti potenca od %s."
+
+#: core/validators.py:347
+msgid "Please enter a valid decimal number."
+msgstr "Prosim vnesite veljavno decimalno Å¡tevilo."
+
+#: 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] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevko."
+msgstr[1] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkama."
+msgstr[2] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkami."
+msgstr[3] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkami."
+
+#: 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] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnim mestom."
+msgstr[1] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnima mestoma."
+msgstr[2] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnimi mesti."
+msgstr[3] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnimi mesti."
+
+#: core/validators.py:362
+#, python-format
+msgid "Make sure your uploaded file is at least %s bytes big."
+msgstr "Prosimo, poskrbite, da bo naložena datoteka velika vsaj %s bajtov."
+
+#: core/validators.py:363
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr "Poskrbite, da bo naložena datoteka velika najveÄ %s bajtov."
+
+#: core/validators.py:376
+msgid "The format for this field is wrong."
+msgstr "Format za to polje je napaÄen."
+
+#: core/validators.py:391
+msgid "This field is invalid."
+msgstr "To polje ni veljavno."
+
+#: core/validators.py:426
+#, python-format
+msgid "Could not retrieve anything from %s."
+msgstr "Iz %s nisem mogel pridobiti niÄesar."
+
+#: core/validators.py:429
+#, python-format
+msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
+msgstr "URL %(url)s je vrnil neveljavni 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 "Prosimo, zaprite %(tag)s oznako v vrstici %(line)s. (Vrstica se zaÄne z \"%(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 "Tekst z zaÄetka vrstice %(line)s ni dovoljen v tem kontekstu. (Vrstica se zaÄne z \"%(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\" v vrstici %(line)s je neveljaven atribut. (Vrstica se zaÄne z \"%(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>\" v vrstici %(line)s je neveljavna oznaka. (Vrstica se zaÄne z \"%(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 "Oznaki v vrstici %(line)s manjka eden ali veÄ zahtevanih parametrov. (Vrstica se zaÄne z \"%(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 "Parameter \"%(attr)s\" v vrstici %(line)s vsebuje neveljavno vrednost. (Vrstica se zaÄne z \"%(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 s tem %(type)s že obstaja za dane %(field)s."
+
+#: db/models/fields/__init__.py:40
+#, python-format
+msgid "%(optname)s with this %(fieldname)s already exists."
+msgstr "%(optname)s s tem %(fieldname)s že obstaja."
+
+#: 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 polje je obvezno"
+
+#: db/models/fields/__init__.py:337
+msgid "This value must be an integer."
+msgstr "Ta vrednost mora biti celo Å¡tevilo."
+
+#: db/models/fields/__init__.py:369
+msgid "This value must be either True or False."
+msgstr "Ta vrednost mora biti \"True\" ali \"False\"."
+
+#: db/models/fields/__init__.py:385
+msgid "This field cannot be null."
+msgstr "To polje ne more biti prazno."
+
+#: db/models/fields/__init__.py:562
+msgid "Enter a valid filename."
+msgstr "Vnesite veljavno ime datoteke."
+
+#: db/models/fields/related.py:43
+#, python-format
+msgid "Please enter a valid %s."
+msgstr "Prosimo, vnesite veljaven %s."
+
+#: db/models/fields/related.py:579
+msgid "Separate multiple IDs with commas."
+msgstr "VeÄ ID-jev loÄite z vejicami."
+
+#: db/models/fields/related.py:581
+msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr "Držite \"Control\" (ali \"Command\" na Mac-u), za izbiro veÄ kot enega."
+
+#: 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] "Prosimo, vnesite veljavne %(self)s ID-e. Vrednost %(value)r ni veljavna."
+msgstr[1] "Prosimo, vnesite veljavne %(self)s ID-je. Vrednosti %(value)r nista veljavni."
+msgstr[2] "Prosimo, vnesite veljavne %(self)s ID-je. Vrednosti %(value)r niso veljavne."
+msgstr[3] "Prosimo, vnesite veljavne %(self)s ID-je. Vrednosti %(value)r niso veljavne."
+
+#: 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] "Poskrbite, da bo tekst krajši od %s znaka."
+msgstr[1] "Poskrbite, da bo tekst krajši od %s znakov."
+msgstr[2] "Poskrbite, da bo tekst krajši od %s znakov."
+msgstr[3] "Poskrbite, da bo tekst krajši od %s znakov."
+
+#: forms/__init__.py:385
+msgid "Line breaks are not allowed here."
+msgstr "Prelomi vrstice tu niso dovoljeni."
+
+#: 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 "Izberite veljavno možnost; '%(data)s' ni v %(choices)s."
+
+#: forms/__init__.py:645
+msgid "The submitted file is empty."
+msgstr "Poslana datoteka je prazna."
+
+#: forms/__init__.py:699
+msgid "Enter a whole number between -32,768 and 32,767."
+msgstr "Vnesite celo Å¡tevilo med -32.768 in 32.767."
+
+#: forms/__init__.py:708
+msgid "Enter a positive number."
+msgstr "Vnesite pozitivno Å¡tevilo."
+
+#: forms/__init__.py:717
+msgid "Enter a whole number between 0 and 32,767."
+msgstr "Vnesite celo Å¡tevilo med 0 in 32.767."
+
+#: template/defaultfilters.py:379
+msgid "yes,no,maybe"
+msgstr "da,ne,morda"
+
+msgid "Comment"
+msgstr "Komentar"
+
+msgid "Comments"
+msgstr "Komentarji"
+
+msgid "String (up to 50)"
+msgstr "Niz (do 50 znakov)"
+
+msgid "label"
+msgstr "oznaka"
+
+msgid "package"
+msgstr "paket"
+
+msgid "packages"
+msgstr "paketi"
+
+msgid "Slovene"
+msgstr "Slovensko"
+
diff --git a/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..c942838
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.po
new file mode 100644
index 0000000..3d7cef4
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.po
@@ -0,0 +1,1916 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Django Serbian (latin) translation v1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-16 10:11+0200\n"
+"PO-Revision-Date: 2007-02-20 18:50+0100\n"
+"Last-Translator: Petar Marić <petar.maric@gmail.com>\n"
+"Language-Team: Nesh <nesh@studioquatro.co.yu> & Petar <petar.maric@gmail.com>\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: YUGOSLAVIA\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-Basepath: ../../../../\n"
+
+#: contrib/comments/models.py:67
+#: contrib/comments/models.py:166
+msgid "object ID"
+msgstr "ID objekta"
+
+#: contrib/comments/models.py:68
+msgid "headline"
+msgstr "naslov"
+
+#: contrib/comments/models.py:69
+#: contrib/comments/models.py:90
+#: contrib/comments/models.py:167
+msgid "comment"
+msgstr "komentar"
+
+#: 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 "da li je ocena validna"
+
+#: contrib/comments/models.py:83
+#: contrib/comments/models.py:169
+msgid "date/time submitted"
+msgstr "datum/vreme slanja"
+
+#: contrib/comments/models.py:84
+#: contrib/comments/models.py:170
+msgid "is public"
+msgstr "da li je javni"
+
+#: contrib/comments/models.py:85
+#: contrib/admin/views/doc.py:289
+msgid "IP address"
+msgstr "IP adresa"
+
+#: contrib/comments/models.py:86
+msgid "is removed"
+msgstr "da li je obrisan"
+
+#: 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 "Izaberite ako je komentar neodgovarajući. Biće ispisano \"Ovaj komentar je obrisan\" umesto teksta komentara."
+
+#: contrib/comments/models.py:91
+msgid "comments"
+msgstr "komentari"
+
+#: contrib/comments/models.py:131
+#: contrib/comments/models.py:207
+msgid "Content object"
+msgstr "Objekat sa sadržajem"
+
+#: 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 ""
+"Poslao %(user)s dana %(date)s\n"
+"\n"
+"%(comment)s\n"
+"\n"
+"http://%(domain)s%(url)s"
+
+#: contrib/comments/models.py:168
+msgid "person's name"
+msgstr "ime"
+
+#: contrib/comments/models.py:171
+msgid "ip address"
+msgstr "ip adresa"
+
+#: contrib/comments/models.py:173
+msgid "approved by staff"
+msgstr "odobreno od strane moderatora"
+
+#: contrib/comments/models.py:176
+msgid "free comment"
+msgstr "slobodan komentar"
+
+#: contrib/comments/models.py:177
+msgid "free comments"
+msgstr "slobodni komentari"
+
+#: contrib/comments/models.py:233
+msgid "score"
+msgstr "rezultat"
+
+#: contrib/comments/models.py:234
+msgid "score date"
+msgstr "datum rezultata"
+
+#: contrib/comments/models.py:237
+msgid "karma score"
+msgstr "\"karma\" rezultat"
+
+#: contrib/comments/models.py:238
+msgid "karma scores"
+msgstr "\"karma\" rezultati"
+
+#: contrib/comments/models.py:242
+#, python-format
+msgid "%(score)d rating by %(user)s"
+msgstr "Ocena %(score)d od strane %(user)s"
+
+#: contrib/comments/models.py:258
+#, python-format
+msgid ""
+"This comment was flagged by %(user)s:\n"
+"\n"
+"%(text)s"
+msgstr ""
+"Ovaj komentar je oznaÄen od %(user)s:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/models.py:265
+msgid "flag date"
+msgstr "datum oznaÄavanja"
+
+#: contrib/comments/models.py:268
+msgid "user flag"
+msgstr "korisniÄka oznaka"
+
+#: contrib/comments/models.py:269
+msgid "user flags"
+msgstr "korisniÄke oznake"
+
+#: contrib/comments/models.py:273
+#, python-format
+msgid "Flag by %r"
+msgstr "%r je oznaÄio"
+
+#: contrib/comments/models.py:278
+msgid "deletion date"
+msgstr "datum brisanja"
+
+#: contrib/comments/models.py:280
+msgid "moderator deletion"
+msgstr "brisanje od strane moderatora"
+
+#: contrib/comments/models.py:281
+msgid "moderator deletions"
+msgstr "brisanja od strane moderatora"
+
+#: contrib/comments/models.py:285
+#, python-format
+msgid "Moderator deletion by %r"
+msgstr "Obrisao moderator %r"
+
+#: contrib/comments/views/karma.py:19
+msgid "Anonymous users cannot vote"
+msgstr "Anonimni korisnici ne mogu da glasaju"
+
+#: contrib/comments/views/karma.py:23
+msgid "Invalid comment ID"
+msgstr "Neispravan ID komentara"
+
+#: contrib/comments/views/karma.py:25
+msgid "No voting for yourself"
+msgstr "Ne možete glasati sami za sebe"
+
+# nesh: grrrrr, ala je rogobatno
+# petar: malo sam ga izmenio da bude jasniji
+#: contrib/comments/views/comments.py:28
+msgid "This rating is required because you've entered at least one other rating."
+msgstr "Ova ocena je obavezna pošto ste uneli bar jednu ocenu."
+
+#: 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] ""
+"Ovaj komentar je poslat od korisnika koji je poslao manje od %(count)s komentara:\n"
+"\n"
+"%(text)s"
+msgstr[1] ""
+"Ovaj komentar je poslat od korisnika koji je poslao manje od %(count)s komentara:\n"
+"\n"
+"%(text)s"
+msgstr[2] ""
+"Ovaj komentar je poslat od korisnika koji je poslao manje od %(count)s komentara:\n"
+"\n"
+"%(text)s"
+
+# nesh: skethcy???
+# petar: Pojma nemam sta im to znaci
+#: contrib/comments/views/comments.py:117
+#, python-format
+msgid ""
+"This comment was posted by a sketchy user:\n"
+"\n"
+"%(text)s"
+msgstr ""
+"Komentar je poslat od strane \"sketchy\" korisnika:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/views/comments.py:189
+#: contrib/comments/views/comments.py:280
+msgid "Only POSTs are allowed"
+msgstr "Jedino je POST dozvoljen"
+
+#: 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 ili više obaveznih polja nije poslato"
+
+#: contrib/comments/views/comments.py:197
+#: contrib/comments/views/comments.py:286
+msgid "Somebody tampered with the comment form (security violation)"
+msgstr "Neko je menjao formu za komentare (povreda sigurnosti)"
+
+#: 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 "Forma komentara ima neispravni 'target' parametar -- ID objekta je neispravan"
+
+#: contrib/comments/views/comments.py:257
+#: contrib/comments/views/comments.py:321
+msgid "The comment form didn't provide either 'preview' or 'post'"
+msgstr "Ovaj komentar nije koristio ni 'preview' ni '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 "KorisniÄko ime:"
+
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/admin/templates/admin/login.html:20
+msgid "Password:"
+msgstr "Lozinka:"
+
+#: contrib/comments/templates/comments/form.html:6
+msgid "Forgotten your password?"
+msgstr "Zaboravili ste lozinku?"
+
+#: 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 "Odjavite se"
+
+#: contrib/comments/templates/comments/form.html:12
+msgid "Ratings"
+msgstr "Ocene"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Required"
+msgstr "Obavezan unos"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Optional"
+msgstr "Opcioni unos"
+
+#: contrib/comments/templates/comments/form.html:23
+msgid "Post a photo"
+msgstr "Pošaljite sliku"
+
+#: contrib/comments/templates/comments/form.html:27
+#: contrib/comments/templates/comments/freeform.html:5
+msgid "Comment:"
+msgstr "Komentar:"
+
+#: contrib/comments/templates/comments/form.html:32
+#: contrib/comments/templates/comments/freeform.html:9
+msgid "Preview comment"
+msgstr "Pregled komentara"
+
+#: contrib/comments/templates/comments/freeform.html:4
+msgid "Your name:"
+msgstr "Vaše ime:"
+
+#: contrib/admin/filterspecs.py:40
+#, python-format
+msgid ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Po %s:</h3>\n"
+"<ul>\n"
+
+#: contrib/admin/filterspecs.py:70
+#: contrib/admin/filterspecs.py:88
+#: contrib/admin/filterspecs.py:143
+msgid "All"
+msgstr "Sve"
+
+#: contrib/admin/filterspecs.py:109
+msgid "Any date"
+msgstr "Bilo koji datum"
+
+#: contrib/admin/filterspecs.py:110
+msgid "Today"
+msgstr "Danas"
+
+#: contrib/admin/filterspecs.py:113
+msgid "Past 7 days"
+msgstr "U zadnjih 7 dana"
+
+#: contrib/admin/filterspecs.py:115
+msgid "This month"
+msgstr "Ovoga meseca"
+
+#: contrib/admin/filterspecs.py:117
+msgid "This year"
+msgstr "Ove godine"
+
+#: contrib/admin/filterspecs.py:143
+msgid "Yes"
+msgstr "Da"
+
+#: contrib/admin/filterspecs.py:143
+msgid "No"
+msgstr "Ne"
+
+#: contrib/admin/filterspecs.py:150
+msgid "Unknown"
+msgstr "Nepoznato"
+
+#: contrib/admin/models.py:16
+msgid "action time"
+msgstr "vreme aktivnosti"
+
+#: contrib/admin/models.py:19
+msgid "object id"
+msgstr "id objekta"
+
+#: contrib/admin/models.py:20
+msgid "object repr"
+msgstr "opis objekta"
+
+#: contrib/admin/models.py:21
+msgid "action flag"
+msgstr "oznaka aktivnosti"
+
+#: contrib/admin/models.py:22
+msgid "change message"
+msgstr "opis izmene"
+
+#: contrib/admin/models.py:25
+msgid "log entry"
+msgstr "unos u dnevniku izmena"
+
+#: contrib/admin/models.py:26
+msgid "log entries"
+msgstr "unosi u dnevniku izmena"
+
+#: contrib/admin/templatetags/admin_list.py:228
+msgid "All dates"
+msgstr "Svi datumi"
+
+#: 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 "Unesite ispravno korisniÄko ime i Å¡ifru. Napomena: oba polja prave razliku izmeÄ‘u velikih i malih slova."
+
+#: contrib/admin/views/decorators.py:23
+#: contrib/admin/templates/admin/login.html:25
+msgid "Log in"
+msgstr "Prijavite se"
+
+#: 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 "Ponovo se prijavite poÅ¡to je vaÅ¡a sesija istekla. Ne brinite, vaÅ¡i podaci su saÄuvani."
+
+#: 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 "VaÅ¡ internet ÄitaÄ nije prihvatio \"cookie\". Nakon aktiviranja odgovarajuće opcije ponovo uÄitajte stranu."
+
+#: contrib/admin/views/decorators.py:82
+msgid "Usernames cannot contain the '@' character."
+msgstr "KorisniÄka imena ne mogu sadržati karakter '@'."
+
+#: contrib/admin/views/decorators.py:84
+#, python-format
+msgid "Your e-mail address is not your username. Try '%s' instead."
+msgstr "VaÅ¡e korisniÄko ime nije data e-mail adresa. PokuÅ¡ajte sa '%s'."
+
+#: contrib/admin/views/main.py:226
+msgid "Site administration"
+msgstr "Administracija sajta"
+
+#: contrib/admin/views/main.py:260
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was added successfully."
+msgstr "Uspešno dodat %(name)s \"%(obj)s\"."
+
+#: contrib/admin/views/main.py:264
+#: contrib/admin/views/main.py:348
+msgid "You may edit it again below."
+msgstr "Možete ga ponovo izmeniti."
+
+#: contrib/admin/views/main.py:272
+#: contrib/admin/views/main.py:357
+#, python-format
+msgid "You may add another %s below."
+msgstr "Možete dodati još jedan %s."
+
+#: contrib/admin/views/main.py:290
+#, python-format
+msgid "Add %s"
+msgstr "Dodajte %s"
+
+#: contrib/admin/views/main.py:336
+#, python-format
+msgid "Added %s."
+msgstr "Dodat %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 "Izmenjen %s."
+
+#: contrib/admin/views/main.py:340
+#, python-format
+msgid "Deleted %s."
+msgstr "Obrisan %s."
+
+#: contrib/admin/views/main.py:343
+msgid "No fields changed."
+msgstr "Nijedno polje nije izmenjeno."
+
+#: contrib/admin/views/main.py:346
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was changed successfully."
+msgstr "Uspešno izmenjen: %(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\" je uspešno dodat. Možete ga ponovo izmeniti."
+
+#: contrib/admin/views/main.py:392
+#, python-format
+msgid "Change %s"
+msgstr "Izmeni %s"
+
+#: contrib/admin/views/main.py:470
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
+msgstr "Jedno ili više %(fieldname)s u %(name)s: %(obj)s"
+
+#: contrib/admin/views/main.py:475
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "Jedan ili više %(fieldname)s u %(name)s:"
+
+#: contrib/admin/views/main.py:508
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was deleted successfully."
+msgstr "Uspešno obrisan: %(name)s \"%(obj)s\"."
+
+#: contrib/admin/views/main.py:511
+msgid "Are you sure?"
+msgstr "Da li ste sigurni?"
+
+#: contrib/admin/views/main.py:533
+#, python-format
+msgid "Change history: %s"
+msgstr "Istorija izmena: %s"
+
+#: contrib/admin/views/main.py:565
+#, python-format
+msgid "Select %s"
+msgstr "Izaberite %s"
+
+#: contrib/admin/views/main.py:565
+#, python-format
+msgid "Select %s to change"
+msgstr "Izaberite %s za izmenu"
+
+#: 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 "Ceo broj"
+
+#: contrib/admin/views/doc.py:278
+msgid "Boolean (Either True or False)"
+msgstr "LogiÄka vrednost (TaÄno ili NetaÄno)"
+
+#: contrib/admin/views/doc.py:279
+#: contrib/admin/views/doc.py:296
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "Niz karaktera (maksimalno %(maxlength)s karaktera)"
+
+#: contrib/admin/views/doc.py:280
+msgid "Comma-separated integers"
+msgstr "Brojevi razdvojeni zarezima"
+
+#: contrib/admin/views/doc.py:281
+msgid "Date (without time)"
+msgstr "Datum (bez vremena)"
+
+#: contrib/admin/views/doc.py:282
+msgid "Date (with time)"
+msgstr "Datum (sa vremenom)"
+
+#: contrib/admin/views/doc.py:283
+msgid "E-mail address"
+msgstr "E-mail adresa"
+
+#: contrib/admin/views/doc.py:284
+#: contrib/admin/views/doc.py:287
+msgid "File path"
+msgstr "Putanja do datoteke"
+
+#: contrib/admin/views/doc.py:285
+msgid "Decimal number"
+msgstr "Decimalni broj"
+
+#: contrib/admin/views/doc.py:291
+msgid "Boolean (Either True, False or None)"
+msgstr "LogiÄka vrednost (TaÄno, NetaÄno ili prazno)"
+
+#: contrib/admin/views/doc.py:292
+msgid "Relation to parent model"
+msgstr "Relacija ka nadređenom objektu"
+
+#: contrib/admin/views/doc.py:293
+msgid "Phone number"
+msgstr "Telefonski broj"
+
+#: contrib/admin/views/doc.py:298
+msgid "Text"
+msgstr "Tekst"
+
+#: contrib/admin/views/doc.py:299
+msgid "Time"
+msgstr "Vreme"
+
+#: 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 "U.S. država (dva VELIKA slova)"
+
+#: 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 "Dokumentacija"
+
+#: 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 "Izmenite lozinku"
+
+#: 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 "PoÄetna strana"
+
+#: contrib/admin/templates/admin/object_history.html:5
+#: contrib/admin/templates/admin/change_form.html:20
+msgid "History"
+msgstr "Istorija"
+
+#: contrib/admin/templates/admin/object_history.html:18
+msgid "Date/time"
+msgstr "Datum/vreme"
+
+#: contrib/admin/templates/admin/object_history.html:19
+msgid "User"
+msgstr "Korisnik"
+
+#: contrib/admin/templates/admin/object_history.html:20
+msgid "Action"
+msgstr "Aktivnost"
+
+#: 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 "Ovaj objekat nema istoriju promena. Najverovatnije nije dodat korišćenjem administracije sajta."
+
+#: contrib/admin/templates/admin/base_site.html:4
+msgid "Django site admin"
+msgstr "Django administracija sajta"
+
+#: contrib/admin/templates/admin/base_site.html:7
+msgid "Django administration"
+msgstr "Django administracija"
+
+#: contrib/admin/templates/admin/500.html:4
+msgid "Server error"
+msgstr "Greška na serveru"
+
+#: contrib/admin/templates/admin/500.html:6
+msgid "Server error (500)"
+msgstr "Greška na serveru (500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "Greška na serveru <em>(500)</em>"
+
+#: 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 "Dogodila se greška koja je prijavljena administratorima."
+
+#: contrib/admin/templates/admin/404.html:4
+#: contrib/admin/templates/admin/404.html:8
+msgid "Page not found"
+msgstr "Strana nije pronađena"
+
+#: contrib/admin/templates/admin/404.html:10
+msgid "We're sorry, but the requested page could not be found."
+msgstr "Tražena strana ne postoji."
+
+#: contrib/admin/templates/admin/index.html:17
+#, python-format
+msgid "Models available in the %(name)s application."
+msgstr "Dostupni modeli u aplikaciji %(name)s."
+
+#: contrib/admin/templates/admin/index.html:28
+#: contrib/admin/templates/admin/change_form.html:15
+msgid "Add"
+msgstr "Dodajte"
+
+#: contrib/admin/templates/admin/index.html:34
+msgid "Change"
+msgstr "Izmenite"
+
+#: contrib/admin/templates/admin/index.html:44
+msgid "You don't have permission to edit anything."
+msgstr "Nemate prava da vršite izmene."
+
+#: contrib/admin/templates/admin/index.html:52
+msgid "Recent Actions"
+msgstr "Poslednje aktivnosti"
+
+#: contrib/admin/templates/admin/index.html:53
+msgid "My Actions"
+msgstr "Moje aktivnosti"
+
+#: contrib/admin/templates/admin/index.html:57
+msgid "None available"
+msgstr "Bez aktivnosti"
+
+#: contrib/admin/templates/admin/change_list.html:11
+#, python-format
+msgid "Add %(name)s"
+msgstr "Dodajte %(name)s"
+
+#: contrib/admin/templates/admin/login.html:22
+msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "Da li ste <a href=\"/password_reset/\">zaboravili vašu lozinku?</a>?"
+
+#: contrib/admin/templates/admin/base.html:23
+msgid "Welcome,"
+msgstr "Dobrodošli,"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:9
+#: contrib/admin/templates/admin/submit_line.html:3
+msgid "Delete"
+msgstr "Obrišite"
+
+#: 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 "Brisanjem %(object_name)s '%(object)s' došlo bi do brisanja pridruženih objekata, ali nemate prava da brišete sledeće objekte:"
+
+#: 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 "Da li ste sigurni da želite da obrišete %(object_name)s \"%(object)s\"? Takođe će biti obrisani sledeći pridruženi objekti:"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:26
+msgid "Yes, I'm sure"
+msgstr "Da, siguran sam"
+
+#: contrib/admin/templates/admin/filter.html:2
+#, python-format
+msgid " By %(title)s "
+msgstr " Po %(title)s "
+
+#: contrib/admin/templates/admin/search_form.html:8
+msgid "Go"
+msgstr "Nađi"
+
+#: contrib/admin/templates/admin/change_form.html:21
+msgid "View on site"
+msgstr "Pogledaj na sajtu"
+
+#: contrib/admin/templates/admin/change_form.html:30
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Ispravite dole navedenu grešku."
+msgstr[1] "Ispravite dole navedene greške."
+msgstr[2] "Ispravite dole navedene greške."
+
+#: contrib/admin/templates/admin/change_form.html:48
+msgid "Ordering"
+msgstr "Redosled"
+
+#: contrib/admin/templates/admin/change_form.html:51
+msgid "Order:"
+msgstr "Red:"
+
+#: contrib/admin/templates/admin/submit_line.html:4
+msgid "Save as new"
+msgstr "Snimite kao novi"
+
+#: contrib/admin/templates/admin/submit_line.html:5
+msgid "Save and add another"
+msgstr "Snimite i dodaj još jedan"
+
+#: contrib/admin/templates/admin/submit_line.html:6
+msgid "Save and continue editing"
+msgstr "Snimite i nastavite sa izmenama"
+
+#: contrib/admin/templates/admin/submit_line.html:7
+msgid "Save"
+msgstr "Snimite"
+
+#: 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 "Izmenite lozinku"
+
+#: contrib/admin/templates/registration/password_change_done.html:6
+#: contrib/admin/templates/registration/password_change_done.html:10
+msgid "Password change successful"
+msgstr "Lozinka je uspešno izmenjena"
+
+#: contrib/admin/templates/registration/password_change_done.html:12
+msgid "Your password was changed."
+msgstr "Vaša lozinka je izmenjena."
+
+#: 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 "Resetovanje lozinke"
+
+#: 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 "Zaboravili ste svoju lozinku? Unesite vašu e-mail adresu i dobićete novu lozinku na dati e-mail."
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "E-mail address:"
+msgstr "E-mail adresa:"
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "Reset my password"
+msgstr "Resetujte moju lozinku"
+
+#: contrib/admin/templates/registration/logged_out.html:8
+msgid "Thanks for spending some quality time with the Web site today."
+msgstr "Hvala Vam na poseti."
+
+#: contrib/admin/templates/registration/logged_out.html:10
+msgid "Log in again"
+msgstr "Prijavite se ponovo"
+
+#: contrib/admin/templates/registration/password_reset_done.html:6
+#: contrib/admin/templates/registration/password_reset_done.html:10
+msgid "Password reset successful"
+msgstr "Vaša lozinka je uspešno resetovana"
+
+#: 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 "Nova lozinka vam je poslata na zadatu e-mail adresu. E-mail bi trebao da stigne u narednih nekoliko minuta."
+
+#: 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 "Unesite staru lozinku, nakon toga unesite novu lozinku dva puta, radi provere ispravnosti unosa."
+
+#: contrib/admin/templates/registration/password_change_form.html:17
+msgid "Old password:"
+msgstr "Stara lozinka:"
+
+#: contrib/admin/templates/registration/password_change_form.html:19
+msgid "New password:"
+msgstr "Nova lozinka:"
+
+#: contrib/admin/templates/registration/password_change_form.html:21
+msgid "Confirm password:"
+msgstr "Potvrdite novu lozinku:"
+
+#: contrib/admin/templates/registration/password_change_form.html:23
+msgid "Change my password"
+msgstr "Izmenite moju lozinku"
+
+#: contrib/admin/templates/registration/password_reset_email.html:2
+msgid "You're receiving this e-mail because you requested a password reset"
+msgstr "Primili ste ovaj e-mail jer ste zatražili resetovanje lozinke"
+
+#: contrib/admin/templates/registration/password_reset_email.html:3
+#, python-format
+msgid "for your user account at %(site_name)s"
+msgstr "za vaÅ¡ korisniÄki nalog 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ša nova lozinka 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 "Lozinku možete izmeniti na sledećoj strani:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:11
+msgid "Your username, in case you've forgotten:"
+msgstr "VaÅ¡e korisniÄko ime, u sluÄaju da ste zaboravili:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:13
+msgid "Thanks for using our site!"
+msgstr "Hvala Vam na poseti!"
+
+#: contrib/admin/templates/registration/password_reset_email.html:15
+#, python-format
+msgid "The %(site_name)s team"
+msgstr "%(site_name)s tim"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:3
+msgid "Bookmarklets"
+msgstr "Bookmarklets"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:5
+msgid "Documentation bookmarklets"
+msgstr "Dokumentacioni \"bookmarklets\""
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:9
+msgid ""
+"\n"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">Da bi ste instalirali \"bookmarklet\", odvucite link u vaÅ¡e \"bookmark\"-e, ili kliknite desnim tasterom i dodajte ga. Sada možete da izaberete \"bookmark\" sa bilo koje strane na sajtu. Napomena: pristup nekima od strana mora biti sa kompjutera Äija je IP adresa oznaÄena kao \"internal\" (kontaktirajte sistem administratora ako niste sigurni da li je vaÅ¡ IP oznaÄen kao \"internal\").</p>\n"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:19
+msgid "Documentation for this page"
+msgstr "Dokumentacija za ovu stranu"
+
+#: 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 "SkaÄe sa bilo koje strane na stranu sa dokumentacijom za \"view\" koji generiÅ¡e tu stranu."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:22
+msgid "Show object ID"
+msgstr "Prikažite ID objekta"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:23
+msgid "Shows the content-type and unique ID for pages that represent a single object."
+msgstr "Prikazuje \"content-type\" i jedinstveni ID strane koje predstavlja jedan objekt."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:25
+msgid "Edit this object (current window)"
+msgstr "Izmena objekta (u aktivnom prozoru)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:26
+msgid "Jumps to the admin page for pages that represent a single object."
+msgstr "SkaÄe na admin stranu za strane koje predstavljaju objekt."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:28
+msgid "Edit this object (new window)"
+msgstr "Izmeni objekat (novi prozor)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:29
+msgid "As above, but opens the admin page in a new window."
+msgstr "Kao iznad, samo otvara admin stranu u novom prozoru."
+
+#: contrib/admin/templates/widget/date_time.html:3
+msgid "Date:"
+msgstr "Datum:"
+
+#: contrib/admin/templates/widget/date_time.html:4
+msgid "Time:"
+msgstr "Vreme:"
+
+#: contrib/admin/templates/widget/file.html:2
+msgid "Currently:"
+msgstr "Trenutno:"
+
+#: contrib/admin/templates/widget/file.html:3
+msgid "Change:"
+msgstr "Izmenite:"
+
+#: contrib/redirects/models.py:7
+msgid "redirect from"
+msgstr "preusmeri od"
+
+#: contrib/redirects/models.py:8
+msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'."
+msgstr "Unesite apsolutnu putanju bez imena domena. Primer: '/dogadjaji/pretraga/'."
+
+#: contrib/redirects/models.py:9
+msgid "redirect to"
+msgstr "preusmeri ka"
+
+#: contrib/redirects/models.py:10
+msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'."
+msgstr "Može biti apsolutna putanja (kao gore) ili puni URL koji poÄinje sa 'http://'."
+
+#: contrib/redirects/models.py:12
+msgid "redirect"
+msgstr "Preusmeravanje"
+
+#: contrib/redirects/models.py:13
+msgid "redirects"
+msgstr "Preusmeravanja"
+
+#: contrib/flatpages/models.py:8
+msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes."
+msgstr "Primer: '/o-nama/kontakt/'. Proverite da li ste uneli '/' na poÄetku i na kraju."
+
+#: contrib/flatpages/models.py:9
+msgid "title"
+msgstr "naslov"
+
+#: contrib/flatpages/models.py:10
+msgid "content"
+msgstr "sadržaj"
+
+#: contrib/flatpages/models.py:11
+msgid "enable comments"
+msgstr "omogućite komentare"
+
+#: contrib/flatpages/models.py:12
+msgid "template name"
+msgstr "naziv templejta"
+
+#: contrib/flatpages/models.py:13
+msgid "Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."
+msgstr "Primer: 'flatpages/kontakt-stranica'. Ako ne zadate sistem će koristiti 'flatpages/default'."
+
+#: contrib/flatpages/models.py:14
+msgid "registration required"
+msgstr "samo za registrovane korisnike"
+
+#: contrib/flatpages/models.py:14
+msgid "If this is checked, only logged-in users will be able to view the page."
+msgstr "Ako izaberete ovu opciju samo prijavljeni korisnici će imati pristup datoj strani."
+
+#: contrib/flatpages/models.py:18
+msgid "flat page"
+msgstr "statiÄna strana"
+
+#: contrib/flatpages/models.py:19
+msgid "flat pages"
+msgstr "statiÄne strane"
+
+#: contrib/auth/models.py:13
+#: contrib/auth/models.py:26
+msgid "name"
+msgstr "ime"
+
+#: contrib/auth/models.py:15
+msgid "codename"
+msgstr "Å¡ifra dozvole"
+
+#: contrib/auth/models.py:17
+msgid "permission"
+msgstr "dozvola"
+
+#: contrib/auth/models.py:18
+#: contrib/auth/models.py:27
+msgid "permissions"
+msgstr "dozvole"
+
+#: contrib/auth/models.py:29
+msgid "group"
+msgstr "grupa"
+
+#: contrib/auth/models.py:30
+#: contrib/auth/models.py:65
+msgid "groups"
+msgstr "grupe"
+
+#: contrib/auth/models.py:55
+msgid "username"
+msgstr "korisniÄko ime"
+
+#: contrib/auth/models.py:56
+msgid "first name"
+msgstr "ime"
+
+#: contrib/auth/models.py:57
+msgid "last name"
+msgstr "prezime"
+
+#: contrib/auth/models.py:58
+msgid "e-mail address"
+msgstr "e-mail adresa"
+
+#: contrib/auth/models.py:59
+msgid "password"
+msgstr "lozinka"
+
+#: contrib/auth/models.py:59
+msgid "Use '[algo]$[salt]$[hexdigest]'"
+msgstr "Koristite '[algo]$[salt]$[hexdigest]'"
+
+#: contrib/auth/models.py:60
+msgid "staff status"
+msgstr "dozvoljen pristup administraciji sajta"
+
+#: contrib/auth/models.py:60
+msgid "Designates whether the user can log into this admin site."
+msgstr "Da li korisnik ima pristup administratorskom delu sajta."
+
+#: contrib/auth/models.py:61
+msgid "active"
+msgstr "aktivan"
+
+#: contrib/auth/models.py:62
+msgid "superuser status"
+msgstr "da li je korisnik administrator"
+
+#: contrib/auth/models.py:63
+msgid "last login"
+msgstr "vreme poslednje posete"
+
+#: contrib/auth/models.py:64
+msgid "date joined"
+msgstr "datum otvaranja naloga"
+
+#: 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 "Uz ruÄno dodata prava, korisnik će dobiti sva prava iz grupa kojima pripada."
+
+#: contrib/auth/models.py:67
+msgid "user permissions"
+msgstr "korisniÄke dozvole"
+
+#: contrib/auth/models.py:70
+msgid "user"
+msgstr "korisnik"
+
+#: contrib/auth/models.py:71
+msgid "users"
+msgstr "korisnici"
+
+#: contrib/auth/models.py:76
+msgid "Personal info"
+msgstr "LiÄne informacije"
+
+#: contrib/auth/models.py:77
+msgid "Permissions"
+msgstr "Dozvole"
+
+#: contrib/auth/models.py:78
+msgid "Important dates"
+msgstr "Važni datumi"
+
+#: contrib/auth/models.py:79
+msgid "Groups"
+msgstr "Grupe"
+
+#: contrib/auth/models.py:219
+msgid "message"
+msgstr "poruka"
+
+#: contrib/auth/forms.py:30
+msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."
+msgstr "VaÅ¡ internet ÄitaÄ nije prihvatio \"cookie\". \"Cookie\" podrÅ¡ka je potrebna da bi ste mogli da se prijavite."
+
+#: contrib/contenttypes/models.py:25
+msgid "python model class name"
+msgstr "naziv python modula"
+
+#: contrib/contenttypes/models.py:28
+msgid "content type"
+msgstr "tip sadržaja"
+
+#: contrib/contenttypes/models.py:29
+msgid "content types"
+msgstr "tipovi sadržaja"
+
+#: contrib/sessions/models.py:35
+msgid "session key"
+msgstr "kljuÄ sesije"
+
+#: contrib/sessions/models.py:36
+msgid "session data"
+msgstr "podaci sesije"
+
+#: contrib/sessions/models.py:37
+msgid "expire date"
+msgstr "datum prestanka važenja sesije"
+
+#: contrib/sessions/models.py:41
+msgid "session"
+msgstr "sesija"
+
+#: contrib/sessions/models.py:42
+msgid "sessions"
+msgstr "sesije"
+
+#: contrib/sites/models.py:10
+msgid "domain name"
+msgstr "ime domena"
+
+#: contrib/sites/models.py:11
+msgid "display name"
+msgstr "naziv"
+
+#: contrib/sites/models.py:15
+msgid "site"
+msgstr "sajt"
+
+#: contrib/sites/models.py:16
+msgid "sites"
+msgstr "sajtovi"
+
+#: utils/translation.py:360
+msgid "DATE_FORMAT"
+msgstr "D, d.m.Y."
+
+#: utils/translation.py:361
+msgid "DATETIME_FORMAT"
+msgstr "d.m.Y. H:i:s"
+
+#: utils/translation.py:362
+msgid "TIME_FORMAT"
+msgstr "H:i:s"
+
+#: utils/dates.py:6
+msgid "Monday"
+msgstr "Ponedeljak"
+
+#: utils/dates.py:6
+msgid "Tuesday"
+msgstr "Utorak"
+
+#: utils/dates.py:6
+msgid "Wednesday"
+msgstr "Sreda"
+
+#: utils/dates.py:6
+msgid "Thursday"
+msgstr "ÄŒetvrtak"
+
+#: utils/dates.py:6
+msgid "Friday"
+msgstr "Petak"
+
+#: utils/dates.py:7
+msgid "Saturday"
+msgstr "Subota"
+
+#: utils/dates.py:7
+msgid "Sunday"
+msgstr "Nedelja"
+
+#: 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 "Mart"
+
+#: 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 "Jun"
+
+#: utils/dates.py:15
+#: utils/dates.py:27
+msgid "July"
+msgstr "Jul"
+
+#: utils/dates.py:15
+msgid "August"
+msgstr "Avgust"
+
+#: utils/dates.py:15
+msgid "September"
+msgstr "Septembar"
+
+#: utils/dates.py:15
+msgid "October"
+msgstr "Oktobar"
+
+#: utils/dates.py:15
+msgid "November"
+msgstr "Novembar"
+
+#: utils/dates.py:16
+msgid "December"
+msgstr "Decembar"
+
+#: 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 "avg"
+
+#: 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 "Avg."
+
+#: 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] "godina"
+msgstr[1] "godine"
+msgstr[2] "godina"
+
+#: utils/timesince.py:13
+msgid "month"
+msgid_plural "months"
+msgstr[0] "mesec"
+msgstr[1] "meseca"
+msgstr[2] "meseci"
+
+#: utils/timesince.py:14
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] "nedelja"
+msgstr[1] "nedelje"
+msgstr[2] "nedelja"
+
+#: utils/timesince.py:15
+msgid "day"
+msgid_plural "days"
+msgstr[0] "dan"
+msgstr[1] "dana"
+msgstr[2] "dana"
+
+#: utils/timesince.py:16
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "sat"
+msgstr[1] "sata"
+msgstr[2] "sati"
+
+#: utils/timesince.py:17
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "minut"
+msgstr[1] "munuta"
+msgstr[2] "minuta"
+
+#: conf/global_settings.py:37
+msgid "Bengali"
+msgstr "Bengalski"
+
+#: conf/global_settings.py:38
+msgid "Czech"
+msgstr "Češki"
+
+#: conf/global_settings.py:39
+msgid "Welsh"
+msgstr "Welšski"
+
+#: conf/global_settings.py:40
+msgid "Danish"
+msgstr "Danski"
+
+#: conf/global_settings.py:41
+msgid "German"
+msgstr "NemaÄki"
+
+#: conf/global_settings.py:42
+msgid "Greek"
+msgstr "GrÄki"
+
+#: conf/global_settings.py:43
+msgid "English"
+msgstr "Engleski"
+
+#: conf/global_settings.py:44
+msgid "Spanish"
+msgstr "Å panski"
+
+#: conf/global_settings.py:45
+msgid "French"
+msgstr "Francuski"
+
+#: conf/global_settings.py:46
+msgid "Galician"
+msgstr "Galski"
+
+#: conf/global_settings.py:47
+msgid "Hungarian"
+msgstr "Mađarski"
+
+#: conf/global_settings.py:48
+msgid "Hebrew"
+msgstr "Hebrejski"
+
+#: conf/global_settings.py:49
+msgid "Icelandic"
+msgstr "Islandski"
+
+#: conf/global_settings.py:50
+msgid "Italian"
+msgstr "Italijanski"
+
+#: conf/global_settings.py:51
+msgid "Japanese"
+msgstr "Japanski"
+
+#: conf/global_settings.py:52
+msgid "Dutch"
+msgstr "Holandski"
+
+#: conf/global_settings.py:53
+msgid "Norwegian"
+msgstr "Norveški"
+
+#: conf/global_settings.py:54
+msgid "Brazilian"
+msgstr "Brazilski"
+
+#: conf/global_settings.py:55
+msgid "Romanian"
+msgstr "Rumunski"
+
+#: conf/global_settings.py:56
+msgid "Russian"
+msgstr "Ruski"
+
+#: conf/global_settings.py:57
+msgid "Slovak"
+msgstr "SlovaÄki"
+
+#: conf/global_settings.py:58
+msgid "Slovenian"
+msgstr "SlovenaÄki"
+
+#: conf/global_settings.py:59
+msgid "Serbian"
+msgstr "Srpski"
+
+#: conf/global_settings.py:60
+msgid "Swedish"
+msgstr "Å vedski"
+
+#: conf/global_settings.py:61
+msgid "Ukrainian"
+msgstr "Ukrajinski"
+
+#: conf/global_settings.py:62
+msgid "Simplified Chinese"
+msgstr "Kineski (pojednostavljen)"
+
+#: conf/global_settings.py:63
+msgid "Traditional Chinese"
+msgstr "Tradicionalni Kineski"
+
+# nesh: Ovo je opis za stari SlugField
+#: core/validators.py:60
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Ovo polje može sadržati samo slova, brojeve i donju crtu (_)."
+
+#: core/validators.py:64
+msgid "This value must contain only letters, numbers, underscores, dashes or slashes."
+msgstr "Ovo polje može sadržati samo slova, brojeve, donju crtu (_), crtu (-) i kose crte."
+
+#: core/validators.py:72
+msgid "Uppercase letters are not allowed here."
+msgstr "Velika slova nisu dozvoljena."
+
+#: core/validators.py:76
+msgid "Lowercase letters are not allowed here."
+msgstr "Mala slova nisu dozvoljena."
+
+#: core/validators.py:83
+msgid "Enter only digits separated by commas."
+msgstr "Unesite brojeve razdvojene zarezima."
+
+#: core/validators.py:95
+msgid "Enter valid e-mail addresses separated by commas."
+msgstr "Unesite ispravne e-mail adrese razdvojene zarezima."
+
+#: core/validators.py:99
+msgid "Please enter a valid IP address."
+msgstr "Unesite ispravnu IP adresu."
+
+#: core/validators.py:103
+msgid "Empty values are not allowed here."
+msgstr "Prazne vrednosti nisu dozvoljene."
+
+#: core/validators.py:107
+msgid "Non-numeric characters aren't allowed here."
+msgstr "Ovde možete uneti samo brojeve."
+
+#: core/validators.py:111
+msgid "This value can't be comprised solely of digits."
+msgstr "Podatak se ne može sastojati samo od brojeva."
+
+#: core/validators.py:116
+msgid "Enter a whole number."
+msgstr "Unesite celi broj."
+
+#: core/validators.py:120
+msgid "Only alphabetical characters are allowed here."
+msgstr "Ovde možete koristiti samo slova."
+
+#: core/validators.py:124
+msgid "Enter a valid date in YYYY-MM-DD format."
+msgstr "Unesite ispravan datum u YYYY-MM-DD formatu."
+
+#: core/validators.py:128
+msgid "Enter a valid time in HH:MM format."
+msgstr "Unesite ispravno vreme u HH:MM formatu."
+
+#: core/validators.py:132
+#: db/models/fields/__init__.py:468
+msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
+msgstr "Unesite ispravan datum i vreme u YYYY-MM-DD HH:MM formatu."
+
+#: core/validators.py:136
+msgid "Enter a valid e-mail address."
+msgstr "Unesite ispravnu e-mail adresu."
+
+#: core/validators.py:148
+msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image."
+msgstr "Pošaljite ispravnu sliku. Fajl koji ste poslali ili nije slika ili je sam fajl oštećen."
+
+#: core/validators.py:155
+#, python-format
+msgid "The URL %s does not point to a valid image."
+msgstr "URL %s ne pokazuje na ispravnu sliku"
+
+# nesh: tel. brojevi su u ameriÄkom formatu, ovo se ionako neće koristiti u i18n delu
+#: core/validators.py:159
+#, python-format
+msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
+msgstr "Telefonski brojevi moraju biti u formatu XXX-XXX-XXXX. \"%s\" je neispravan."
+
+#: core/validators.py:167
+#, python-format
+msgid "The URL %s does not point to a valid QuickTime video."
+msgstr "URL %s ne pokazuje na ispravni QuickTime video fajl."
+
+#: core/validators.py:171
+msgid "A valid URL is required."
+msgstr "Unesite ispravan URL."
+
+#: core/validators.py:185
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"Unesite ispravan HTML. Greške su:\n"
+"%s"
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Neispravan XML: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Neispravan URL: %s"
+
+#: core/validators.py:206
+#: core/validators.py:208
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "URL %s je neispravan link."
+
+# nesh: Ni ovo nije interesantno za i18n
+#: core/validators.py:214
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "Unesite ispravnu skraćenicu za U.S. državu."
+
+#: 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] "Pripazi na jezik! %s reÄ nije ovde dozvoljena."
+msgstr[1] "Pripazi na jezik! %s reÄi nisu ovde dozvoljene."
+msgstr[2] "Pripazi na jezik! %s reÄi nisu ovde dozvoljene."
+
+#: core/validators.py:236
+#, python-format
+msgid "This field must match the '%s' field."
+msgstr "Ovo polje mora biti jednako sa poljem '%s'."
+
+#: core/validators.py:255
+msgid "Please enter something for at least one field."
+msgstr "Morate popuniti barem jedno polje."
+
+#: core/validators.py:264
+#: core/validators.py:275
+msgid "Please enter both fields or leave them both empty."
+msgstr "Popunite oba polja ili oba ostavite prazna."
+
+#: core/validators.py:282
+#, python-format
+msgid "This field must be given if %(field)s is %(value)s"
+msgstr "Ovo polje mora biti uneto ako polje %(field)s ima vrednost %(value)s"
+
+#: core/validators.py:294
+#, python-format
+msgid "This field must be given if %(field)s is not %(value)s"
+msgstr "Ovo polje mora biti uneto ako polje %(field)s nema vrednost %(value)s"
+
+#: core/validators.py:313
+msgid "Duplicate values are not allowed."
+msgstr "Duple vrednosti nisu dozvoljene."
+
+#: core/validators.py:336
+#, python-format
+msgid "This value must be a power of %s."
+msgstr "Vrednost mora biti stepena %s."
+
+#: core/validators.py:347
+msgid "Please enter a valid decimal number."
+msgstr "Unesite ispravan decimalni broj."
+
+#: 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] "Unesite ispravan decimalni broj sa %s cifrom."
+msgstr[1] "Unesite ispravan decimalni broj sa %s cifre."
+msgstr[2] "Unesite ispravan decimalni broj sa %s cifara."
+
+#: 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] "Unesite decimalni broj sa najviše %s decimalnim mestom."
+msgstr[1] "Unesite decimalni broj sa najviše %s decimalna mesta."
+msgstr[2] "Unesite decimalni broj sa najviše %s decimalnih mesta."
+
+#: core/validators.py:362
+#, python-format
+msgid "Make sure your uploaded file is at least %s bytes big."
+msgstr "VeliÄina fajla mora biti najmanje %s bajtova."
+
+#: core/validators.py:363
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr "VeliÄina fajla mora biti najviÅ¡e %s bajtova."
+
+#: core/validators.py:376
+msgid "The format for this field is wrong."
+msgstr "Pogrešan format polja."
+
+#: core/validators.py:391
+msgid "This field is invalid."
+msgstr "Neispravno polje."
+
+#: core/validators.py:426
+#, python-format
+msgid "Could not retrieve anything from %s."
+msgstr "Ništa nije moglo da se skine sa URL-a %s."
+
+#: core/validators.py:429
+#, python-format
+msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
+msgstr "Sa URL-a %(url)s se vratio pogrešan 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 "Zatvorite nezatvoren tag \"%(tag)s\" iz reda %(line)s. (Red poÄinje sa \"%(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 "Tekst koji poÄinje u redu %(line)s nije dozvoljen u ovom kontekstu. (Red poÄinje sa \"%(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 "Atribut \"%(attr)s\" u red %(line)s je neispravan. (Red poÄinje sa \"%(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 \"<%(tag)s>\" u redu %(line)s je neispravan. (Red poÄinje \"%(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-u u redu %(line)s nedostaje jedan ili viÅ¡e atributa. (Red poÄinje sa \"%(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 "Atribut \"%(attr)s\" u redu %(line)s ima neispravnu vrednost. (Red poÄinje sa \"%(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 sa ovim tipom %(type)s već postoji za polje %(field)s."
+
+#: db/models/fields/__init__.py:40
+#, python-format
+msgid "%(optname)s with this %(fieldname)s already exists."
+msgstr "%(optname)s sa ovim %(fieldname)s već postoji."
+
+#: 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 "Obavezno polje."
+
+#: db/models/fields/__init__.py:337
+msgid "This value must be an integer."
+msgstr "Vrednost mora biti celi broj."
+
+#: db/models/fields/__init__.py:369
+msgid "This value must be either True or False."
+msgstr "Vrednost mora biti True ili False."
+
+#: db/models/fields/__init__.py:385
+msgid "This field cannot be null."
+msgstr "Polje ne može sadržati praznu vrednost."
+
+#: db/models/fields/__init__.py:562
+msgid "Enter a valid filename."
+msgstr "Unesite ispravno ime fajla."
+
+#: db/models/fields/related.py:43
+#, python-format
+msgid "Please enter a valid %s."
+msgstr "Unesite ispravan %s."
+
+#: db/models/fields/related.py:579
+msgid "Separate multiple IDs with commas."
+msgstr "Odvojite višestruke ID-ove zarezima."
+
+#: db/models/fields/related.py:581
+msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr "Koristite \"Ctrl\" (PC) ili \"Jabuku\" (Mek) da bi ste selektovali više stavki."
+
+#: 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] "Unesite validne %(self)s ID-ove. Vrednost %(value)r je neispravna."
+msgstr[1] "Unesite validne %(self)s ID-ove. Vrednost %(value)r je neispravna."
+msgstr[2] "Unesite validne %(self)s ID-ove. Vrednost %(value)r je neispravna."
+
+#: 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] "Tekst mora imati manje od %s slova."
+msgstr[1] "Tekst mora imati manje od %s slova."
+msgstr[2] "Tekst mora imati manje od %s slova."
+
+#: forms/__init__.py:385
+msgid "Line breaks are not allowed here."
+msgstr "Novi redovi ovde nisu dozvoljeni."
+
+#: 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 "Izaberite validnu opciju: '%(data)s' nije u %(choices)s."
+
+#: forms/__init__.py:645
+msgid "The submitted file is empty."
+msgstr "Poslati fajl je prazan."
+
+#: forms/__init__.py:699
+msgid "Enter a whole number between -32,768 and 32,767."
+msgstr "Unesite celi broj između -32,768 i 32,767."
+
+#: forms/__init__.py:708
+msgid "Enter a positive number."
+msgstr "Unesite pozitivan broj."
+
+#: forms/__init__.py:717
+msgid "Enter a whole number between 0 and 32,767."
+msgstr "Unesite celi broj između 0 i 32,767."
+
+#: template/defaultfilters.py:379
+msgid "yes,no,maybe"
+msgstr "da,ne,možda"
+
+#~ msgid "Comment"
+#~ msgstr "Komentar"
+#~ msgid "Comments"
+#~ msgstr "Komentari"
+#~ msgid "String (up to 50)"
+#~ msgstr "Niz karaktera (maksimalno 50 karaktera)"
+#~ msgid "label"
+#~ msgstr "labela"
+#~ msgid "package"
+#~ msgstr "paket"
+#~ msgid "packages"
+#~ msgstr "paketi"
+#~ msgid "Error in Template"
+#~ msgstr "Greška u templejtu"
+#~ msgid ""
+#~ "\n"
+#~ "In template %(name)s, error at line %(line)s:\n"
+#~ msgstr ""
+#~ "\n"
+#~ "U templejtu %(name)s, greška u redu %(line)s:\n"
+
+#, fuzzy
+#~ msgid "count"
+#~ msgstr "sadržaj"
+
diff --git a/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..d4036a8
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..a70d878
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.po
@@ -0,0 +1,109 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+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: 2007-02-20 18:51+0100\n"
+"Last-Translator: Petar Marić <petar.maric@gmail.com>\n"
+"Language-Team: Nesh <nesh@studioquatro.co.yu> & Petar <petar.maric@gmail.com> <sr@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Country: YUGOSLAVIA\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"
+
+#: contrib/admin/media/js/SelectFilter2.js:33
+#, perl-format
+msgid "Available %s"
+msgstr "Dostupno %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:41
+msgid "Choose all"
+msgstr "Izaberite sve"
+
+#: contrib/admin/media/js/SelectFilter2.js:46
+msgid "Add"
+msgstr "Dodajte"
+
+#: contrib/admin/media/js/SelectFilter2.js:48
+msgid "Remove"
+msgstr "Izbacite"
+
+#: contrib/admin/media/js/SelectFilter2.js:53
+#, perl-format
+msgid "Chosen %s"
+msgstr "Izabrano %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:54
+msgid "Select your choice(s) and click "
+msgstr "Izaberite potrebno i kliknite"
+
+#: contrib/admin/media/js/SelectFilter2.js:59
+msgid "Clear all"
+msgstr "Obrišite sve"
+
+#: 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 Mart April Maj Jun Jul Avgust Septembar Oktobar Novembar Decembar"
+
+#: contrib/admin/media/js/dateparse.js:27
+msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
+msgstr "Nedelja Ponedeljak Utorak Sreda ÄŒetvrtak Petak Subota"
+
+#: contrib/admin/media/js/calendar.js:25
+msgid "S M T W T F S"
+msgstr "N P U S Č P S"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
+msgid "Now"
+msgstr "Sada"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
+msgid "Clock"
+msgstr "Sat"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77
+msgid "Choose a time"
+msgstr "Izaberite vreme"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
+msgid "Midnight"
+msgstr "Ponoć"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
+msgid "6 a.m."
+msgstr "6 sati"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
+msgid "Noon"
+msgstr "Podne"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
+msgid "Cancel"
+msgstr "Poništi"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162
+msgid "Today"
+msgstr "Danas"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114
+msgid "Calendar"
+msgstr "Kalendar"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
+msgid "Yesterday"
+msgstr "JuÄe"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
+msgid "Tomorrow"
+msgstr "Sutra"
+
diff --git a/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..cd91c18
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.po
new file mode 100644
index 0000000..befde10
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.po
@@ -0,0 +1,2344 @@
+# Swedish translation of Django
+# Copyright (C) 2005
+# This file is distributed under the same license as the Django package.
+#
+#
+# Robin Sonefors <ozamosi@blinkenlights.se>, 2005.
+# Ludvig Ericson <ludvig.ericson@gmail.com>, 2007.
+# Mikko Hellsing <mikko@sorl.net>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-03-06 00:17+0100\n"
+"PO-Revision-Date: 2007-03-06 10:30+0100\n"
+"Last-Translator: Mikko Hellsing <mikko@sorl.net>\n"
+"Language-Team: Django I18N <Django-I18N@googlegroups.com>\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-Poedit-Language: Swedish\n"
+"X-Poedit-Country: SWEDEN\n"
+
+#: oldforms/__init__.py:352 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 "Detta fältet är obligatoriskt."
+
+#: 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] "Se till att din text är kortare än %s tecken."
+msgstr[1] "Se till att din text är kortare än %s tecken."
+
+#: oldforms/__init__.py:392
+msgid "Line breaks are not allowed here."
+msgstr "Radbrytningar är inte tillåtna här."
+
+#: 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 "Välj ett giltigt alternativ. '%(data)s' finns inte bland %(choices)s."
+
+#: oldforms/__init__.py:572 newforms/widgets.py:170
+#: contrib/admin/filterspecs.py:150
+msgid "Unknown"
+msgstr "Okänt"
+
+#: oldforms/__init__.py:572 newforms/widgets.py:170
+#: contrib/admin/filterspecs.py:143
+msgid "Yes"
+msgstr "Ja"
+
+#: oldforms/__init__.py:572 newforms/widgets.py:170
+#: contrib/admin/filterspecs.py:143
+msgid "No"
+msgstr "Nej"
+
+#: oldforms/__init__.py:667 core/validators.py:173 core/validators.py:444
+msgid "No file was submitted. Check the encoding type on the form."
+msgstr "Ingen fil skickad. Kontrollera enkodningen i form taggen."
+
+#: oldforms/__init__.py:669
+msgid "The submitted file is empty."
+msgstr "Den insända filen är tom."
+
+#: oldforms/__init__.py:725
+msgid "Enter a whole number between -32,768 and 32,767."
+msgstr "Fyll i ett heltal mellan -32768 och 32767."
+
+#: oldforms/__init__.py:735
+msgid "Enter a positive number."
+msgstr "Fyll i ett positivt heltal."
+
+#: oldforms/__init__.py:745
+msgid "Enter a whole number between 0 and 32,767."
+msgstr "Fyll i ett heltal mellan 0 och 32767."
+
+#: db/models/manipulators.py:307
+#, python-format
+msgid "%(object)s with this %(type)s already exists for the given %(field)s."
+msgstr "%(object)s med typen %(type)s finns redan för %(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 "och"
+
+#: db/models/fields/__init__.py:42
+#, python-format
+msgid "%(optname)s with this %(fieldname)s already exists."
+msgstr "%(optname)s med det här %(fieldname)s finns redan."
+
+#: db/models/fields/__init__.py:366
+msgid "This value must be an integer."
+msgstr "Det här värdet måste vara ett heltal."
+
+#: db/models/fields/__init__.py:401
+msgid "This value must be either True or False."
+msgstr "Det här värdet måste vara True eller False"
+
+#: db/models/fields/__init__.py:422
+msgid "This field cannot be null."
+msgstr "Det här fältet får inte vara null."
+
+#: db/models/fields/__init__.py:456 core/validators.py:147
+msgid "Enter a valid date in YYYY-MM-DD format."
+msgstr "Fyll i ett giltigt datum i formatet Ã…Ã…Ã…Ã…-MM-DD."
+
+#: db/models/fields/__init__.py:525 core/validators.py:156
+msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
+msgstr "Fyll i en giltig tidpunkt i formatet Ã…Ã…Ã…Ã…-MM-DD HH:MM"
+
+#: db/models/fields/__init__.py:629
+msgid "Enter a valid filename."
+msgstr "Fyll i ett giltigt filnamn."
+
+#: db/models/fields/related.py:53
+#, python-format
+msgid "Please enter a valid %s."
+msgstr "Var god fyll i giltigt %s."
+
+#: db/models/fields/related.py:642
+msgid "Separate multiple IDs with commas."
+msgstr "Separera flera ID:n med kommatecken."
+
+#: db/models/fields/related.py:644
+msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr "Håll ner \"Control\", eller \"Command\" på en Mac, för att välja mer än en."
+
+#: 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] "Var god och fyll giltiga %(self)s ID-nummer. Värdet %(value)r är ogiltigt."
+msgstr[1] "Var god och fyll giltiga %(self)s ID-nummer. Värdena %(value)r är ogiltiga."
+
+#: conf/global_settings.py:39
+msgid "Arabic"
+msgstr "Arabiska"
+
+#: conf/global_settings.py:40
+msgid "Bengali"
+msgstr "Bengaliska"
+
+#: conf/global_settings.py:41
+msgid "Catalan"
+msgstr "Katalanska"
+
+#: conf/global_settings.py:42
+msgid "Czech"
+msgstr "Tjeckiska"
+
+#: conf/global_settings.py:43
+msgid "Welsh"
+msgstr "Walesiska"
+
+#: conf/global_settings.py:44
+msgid "Danish"
+msgstr "Danska"
+
+#: conf/global_settings.py:45
+msgid "German"
+msgstr "Tyska"
+
+#: conf/global_settings.py:46
+msgid "Greek"
+msgstr "Grekiska"
+
+#: conf/global_settings.py:47
+msgid "English"
+msgstr "Engelska"
+
+#: conf/global_settings.py:48
+msgid "Spanish"
+msgstr "Spanska"
+
+#: conf/global_settings.py:49
+msgid "Argentinean Spanish"
+msgstr "Argentisk Spanska"
+
+#: conf/global_settings.py:50
+msgid "Finnish"
+msgstr "Finska"
+
+#: conf/global_settings.py:51
+msgid "French"
+msgstr "Franska"
+
+#: conf/global_settings.py:52
+msgid "Galician"
+msgstr "Galisiska"
+
+#: conf/global_settings.py:53
+msgid "Hungarian"
+msgstr "Ungerska"
+
+#: conf/global_settings.py:54
+msgid "Hebrew"
+msgstr "Hebreiska"
+
+#: conf/global_settings.py:55
+msgid "Icelandic"
+msgstr "Isländska"
+
+#: conf/global_settings.py:56
+msgid "Italian"
+msgstr "Italienska"
+
+#: conf/global_settings.py:57
+msgid "Japanese"
+msgstr "Japanska"
+
+#: conf/global_settings.py:58
+msgid "Kannada"
+msgstr "Kannada"
+
+#: conf/global_settings.py:59
+msgid "Latvian"
+msgstr "Lettiska"
+
+#: conf/global_settings.py:60
+msgid "Macedonian"
+msgstr "Makedonska"
+
+#: conf/global_settings.py:61
+msgid "Dutch"
+msgstr "Holländska"
+
+#: conf/global_settings.py:62
+msgid "Norwegian"
+msgstr "Norska"
+
+#: conf/global_settings.py:63
+msgid "Polish"
+msgstr "Polska"
+
+#: conf/global_settings.py:64
+msgid "Brazilian"
+msgstr "Brasilianska"
+
+#: conf/global_settings.py:65
+msgid "Romanian"
+msgstr "Rumänska"
+
+#: conf/global_settings.py:66
+msgid "Russian"
+msgstr "Ryska"
+
+#: conf/global_settings.py:67
+msgid "Slovak"
+msgstr "Slovakiska"
+
+#: conf/global_settings.py:68
+msgid "Slovenian"
+msgstr "Slovenska"
+
+#: conf/global_settings.py:69
+msgid "Serbian"
+msgstr "Serbiska"
+
+#: conf/global_settings.py:70
+msgid "Swedish"
+msgstr "Svenska"
+
+#: conf/global_settings.py:71
+msgid "Tamil"
+msgstr "Tamil"
+
+#: conf/global_settings.py:72
+msgid "Turkish"
+msgstr "Turkiska"
+
+#: conf/global_settings.py:73
+msgid "Ukrainian"
+msgstr "Ukrainska"
+
+#: conf/global_settings.py:74
+msgid "Simplified Chinese"
+msgstr "Förenklad Kinesiska"
+
+#: conf/global_settings.py:75
+msgid "Traditional Chinese"
+msgstr "Traditionell Kinesiska"
+
+#: core/validators.py:64
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Det här värdet får bara innehålla bokstäver, tal och understräck."
+
+#: core/validators.py:68
+msgid ""
+"This value must contain only letters, numbers, underscores, dashes or "
+"slashes."
+msgstr ""
+"Det här värdet får bara innehålla bokstäver, siffror, understräck, sträck "
+"och snedsträck"
+
+#: core/validators.py:72
+msgid "This value must contain only letters, numbers, underscores or hyphens."
+msgstr "Det här värdet får bara innehålla bokstäver, siffror, understräck eller sträck ."
+
+#: core/validators.py:76
+msgid "Uppercase letters are not allowed here."
+msgstr "Stora bokstäver är inte tillåtna här."
+
+#: core/validators.py:80
+msgid "Lowercase letters are not allowed here."
+msgstr "Små bokstäver är inte tillåtna här."
+
+#: core/validators.py:87
+msgid "Enter only digits separated by commas."
+msgstr "Fyll enbart i siffror avskilda med kommatecken."
+
+#: core/validators.py:99
+msgid "Enter valid e-mail addresses separated by commas."
+msgstr "Fyll i giltiga e-mailadresser avskilda med kommatecken."
+
+#: core/validators.py:103
+msgid "Please enter a valid IP address."
+msgstr "Var god fyll i en giltigt IP-adress."
+
+#: core/validators.py:107
+msgid "Empty values are not allowed here."
+msgstr "Tomma värden är inte tillåtna här."
+
+#: core/validators.py:111
+msgid "Non-numeric characters aren't allowed here."
+msgstr "Icke-numeriska tecken är inte tillåtna här."
+
+#: core/validators.py:115
+msgid "This value can't be comprised solely of digits."
+msgstr "Det här värdet kan inte enbart bestå av siffror."
+
+#: core/validators.py:120 newforms/fields.py:126
+msgid "Enter a whole number."
+msgstr "Fyll i ett heltal."
+
+#: core/validators.py:124
+msgid "Only alphabetical characters are allowed here."
+msgstr "Endast bokstäver är tillåtna här."
+
+#: core/validators.py:139
+msgid "Year must be 1900 or later."
+msgstr "Årtal måste vara 1900 eller senare."
+
+#: core/validators.py:143
+#, python-format
+msgid "Invalid date: %s."
+msgstr "Felaktigt datum: %s"
+
+#: core/validators.py:152
+msgid "Enter a valid time in HH:MM format."
+msgstr "Fyll i en giltig tid i formatet HH:MM"
+
+#: core/validators.py:161 newforms/fields.py:269
+msgid "Enter a valid e-mail address."
+msgstr "Fyll i en giltig e-mailadress."
+
+#: core/validators.py:177
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+"Ladda upp en giltig bild. Filen du laddade upp var antingen ingen bild eller en "
+"korrupt bild."
+
+#: core/validators.py:184
+#, python-format
+msgid "The URL %s does not point to a valid image."
+msgstr "URL:en %s pekar inte på en giltig bild."
+
+#: core/validators.py:188
+#, python-format
+msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
+msgstr ""
+"Telefonnummer måste vara i det amerikanska formatet XXX-XXX-XXXX. \"%s\" är "
+"ogiltigt."
+
+#: core/validators.py:196
+#, python-format
+msgid "The URL %s does not point to a valid QuickTime video."
+msgstr "URL:en %s pekar inte på en giltig QuickTime-video."
+
+#: core/validators.py:200
+msgid "A valid URL is required."
+msgstr "En giltig URL krävs."
+
+#: core/validators.py:214
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"Giltig HTML krävs. Specifika fel är:\n"
+"%s"
+
+#: core/validators.py:221
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Missformad XML: %s"
+
+#: core/validators.py:238
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Felaktig URL: %s"
+
+#: core/validators.py:243 core/validators.py:245
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "URL:en %s är en trasig länk."
+
+#: core/validators.py:251
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "Fyll i en giltig förkortning för en amerikansk delstat"
+
+#: 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] "Akta din tunga! Ordet %s är inte tillåtet här."
+msgstr[1] "Akta din tunga! Orden %s är inte tillåtna här."
+
+#: core/validators.py:272
+#, python-format
+msgid "This field must match the '%s' field."
+msgstr "Det här fältet måste matcha fältet '%s'."
+
+#: core/validators.py:291
+msgid "Please enter something for at least one field."
+msgstr "Fyll i något i minst ett fält."
+
+#: core/validators.py:300 core/validators.py:311
+msgid "Please enter both fields or leave them both empty."
+msgstr "Fyll antingen i båda fälten, eller lämna båda tomma"
+
+#: core/validators.py:319
+#, python-format
+msgid "This field must be given if %(field)s is %(value)s"
+msgstr "Det är fältet måste anges om %(field)s är %(value)s"
+
+#: core/validators.py:332
+#, python-format
+msgid "This field must be given if %(field)s is not %(value)s"
+msgstr "Det här fältet måste anges om %(field)s inte är %(value)s"
+
+#: core/validators.py:351
+msgid "Duplicate values are not allowed."
+msgstr "Upprepade värden är inte tillåtna."
+
+#: core/validators.py:366
+#, python-format
+msgid "This value must be between %(lower)s and %(upper)s."
+msgstr "Det här värdet måste mellan %(lower)s och %(upper)s."
+
+#: core/validators.py:368
+#, python-format
+msgid "This value must be at least %s."
+msgstr "Det här värdet måste minsta vara %s."
+
+#: core/validators.py:370
+#, python-format
+msgid "This value must be no more than %s."
+msgstr "Det här värdet får inte vara mer än %s."
+
+#: core/validators.py:406
+#, python-format
+msgid "This value must be a power of %s."
+msgstr "Det här värdet måste vara en multipel av %s."
+
+#: core/validators.py:417
+msgid "Please enter a valid decimal number."
+msgstr "Fyll i ett giltigt decimaltal."
+
+#: 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] "Fyll i ett giltigt decimaltal med mindre än %s siffra totalt."
+msgstr[1] "Fyll i ett giltigt decimaltal med mindre än %s siffror totalt."
+
+#: 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] "Fyll i ett giltigt decimaltal med en heltalsdel som inte är mer än %s siffra."
+msgstr[1] "Fyll i ett giltigt decimaltal med en heltalsdel som inte är mer än %s siffror."
+
+#: 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] "Fyll i ett giltigt decimaltal med %s decimal som mest."
+msgstr[1] "Fyll i ett giltigt decimaltal med %s decimaler som mest."
+
+#: core/validators.py:437
+#, python-format
+msgid "Make sure your uploaded file is at least %s bytes big."
+msgstr "Se till att filen du laddade upp är minst %s bytes stor."
+
+#: core/validators.py:438
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr "Se till att filen du laddade upp är som mest %s bytes stor."
+
+#: core/validators.py:455
+msgid "The format for this field is wrong."
+msgstr "Formatet på det här fältet är fel."
+
+#: core/validators.py:470
+msgid "This field is invalid."
+msgstr "Det här fältet är ogiltigt."
+
+#: core/validators.py:506
+#, python-format
+msgid "Could not retrieve anything from %s."
+msgstr "Kunde inte hämta något från %s."
+
+#: core/validators.py:509
+#, python-format
+msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
+msgstr "URL:en %(url)s returnerade den ogiltiga Content-Type headern '%(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 ""
+"Var god avsluta den oavslutade taggen %(tag)s på rad %(line)s. (Raden börjar "
+"med \"%(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 ""
+"En del text från rad %(line)s är inte tillåtet i det sammanhanget. (Raden "
+"börjar med \"%(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\" på rad %(line)s är inte ett giltigt attribut. (Raden startar "
+"med \"%(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>\" på rad %(line)s är en ogiltig tagg. (Raden börjar med \"%"
+"(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 ""
+"En tagg på rad %(line)s saknar en eller flera nödvändiga attribut. (Raden "
+"börjar med \"%(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 ""
+"Attributet \"%(attr)s\" på rad %(line)s har ett ogiltigt värde. (Raden "
+"börjar med \"%(start)s\".)"
+
+#: views/generic/create_update.py:43
+#, python-format
+msgid "The %(verbose_name)s was created successfully."
+msgstr "%(verbose_name)s skapades framgångsrikt."
+
+#: views/generic/create_update.py:117
+#, python-format
+msgid "The %(verbose_name)s was updated successfully."
+msgstr " %(verbose_name)s uppdaterades framgångsrikt."
+
+#: views/generic/create_update.py:184
+#, python-format
+msgid "The %(verbose_name)s was deleted."
+msgstr "%(verbose_name)s togs bort."
+
+#: newforms/models.py:164 newforms/fields.py:360
+msgid "Select a valid choice. That choice is not one of the available choices."
+msgstr "Välj ett giltigt alternativ. Det valet finns inte bland tillgängliga alternativ."
+
+#: newforms/models.py:181 newforms/fields.py:378 newforms/fields.py:454
+msgid "Enter a list of values."
+msgstr "Fyll i en lista med värden."
+
+#: newforms/models.py:187 newforms/fields.py:387
+#, python-format
+msgid "Select a valid choice. %s is not one of the available choices."
+msgstr "Välj ett giltigt alternativ. '%s' finns inte bland tillgängliga alternativ."
+
+#: newforms/fields.py:101 newforms/fields.py:254
+#, python-format
+msgid "Ensure this value has at most %d characters."
+msgstr "Se till att din text inte har mer än %d tecken."
+
+#: newforms/fields.py:103 newforms/fields.py:256
+#, python-format
+msgid "Ensure this value has at least %d characters."
+msgstr "Se till att din text har minst %d tecken."
+
+#: newforms/fields.py:128
+#, python-format
+msgid "Ensure this value is less than or equal to %s."
+msgstr "Se till att detta värdet är mindre än eller lika med %s."
+
+#: newforms/fields.py:130
+#, python-format
+msgid "Ensure this value is greater than or equal to %s."
+msgstr "Se till att detta värde är större eller lika med %s."
+
+#: newforms/fields.py:163
+msgid "Enter a valid date."
+msgstr "Fyll i ett giltigt datum."
+
+#: newforms/fields.py:190
+msgid "Enter a valid time."
+msgstr "Fyll i en giltig tid."
+
+#: newforms/fields.py:226
+msgid "Enter a valid date/time."
+msgstr "Fyll i ett giltigt datum/tid."
+
+#: newforms/fields.py:240
+msgid "Enter a valid value."
+msgstr "Fyll i ett giltigt värde."
+
+#: newforms/fields.py:287 newforms/fields.py:309
+msgid "Enter a valid URL."
+msgstr "Fyll i ett giltigt URL."
+
+#: newforms/fields.py:311
+msgid "This URL appears to be a broken link."
+msgstr "Detta URL verkar vara en trasig länk."
+
+#: contrib/contenttypes/models.py:26
+msgid "python model class name"
+msgstr "python modell klass namn"
+
+#: contrib/contenttypes/models.py:29
+msgid "content type"
+msgstr "innehållstyp"
+
+#: contrib/contenttypes/models.py:30
+msgid "content types"
+msgstr "innehållstyper"
+
+#: contrib/auth/views.py:39
+msgid "Logged out"
+msgstr "Utloggad"
+
+#: contrib/auth/models.py:38 contrib/auth/models.py:57
+msgid "name"
+msgstr "namn"
+
+#: contrib/auth/models.py:40
+msgid "codename"
+msgstr "kodnamn"
+
+#: contrib/auth/models.py:42
+msgid "permission"
+msgstr "rättighet"
+
+#: contrib/auth/models.py:43 contrib/auth/models.py:58
+msgid "permissions"
+msgstr "rättigheter"
+
+#: contrib/auth/models.py:60
+msgid "group"
+msgstr "grupp"
+
+#: contrib/auth/models.py:61 contrib/auth/models.py:100
+msgid "groups"
+msgstr "grupper"
+
+#: contrib/auth/models.py:90
+msgid "username"
+msgstr "användarnamn"
+
+#: contrib/auth/models.py:90
+msgid ""
+"Required. 30 characters or fewer. Alphanumeric characters only (letters, "
+"digits and underscores)."
+msgstr "Obligatorisk. 30 tecken eller mindre. Endast bokstäver, siffror eller understräck."
+
+#: contrib/auth/models.py:91
+msgid "first name"
+msgstr "förnamn"
+
+#: contrib/auth/models.py:92
+msgid "last name"
+msgstr "efternamn"
+
+#: contrib/auth/models.py:93
+msgid "e-mail address"
+msgstr "e-mailadress"
+
+#: contrib/auth/models.py:94
+msgid "password"
+msgstr "lösenord"
+
+#: contrib/auth/models.py:94
+msgid ""
+"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
+"password form</a>."
+msgstr "Använd '[algo]$[salt]$[hexdigest]' eller använd <a href=\"password/\">Ändra lösenord</a>."
+
+#: contrib/auth/models.py:95
+msgid "staff status"
+msgstr "personalstatus"
+
+#: contrib/auth/models.py:95
+msgid "Designates whether the user can log into this admin site."
+msgstr "Avgör om användaren kan logga in på den här admin-siten."
+
+#: 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 ""
+"Avgör om användaren kan logga in till Django admin. Av-markera denna "
+"istället för att ta bort konton."
+
+#: contrib/auth/models.py:97
+msgid "superuser status"
+msgstr "superanvändare"
+
+#: contrib/auth/models.py:97
+msgid ""
+"Designates that this user has all permissions without explicitly assigning "
+"them."
+msgstr ""
+"Bestämmer att användaren har alla rättigheter utan att uttryckligen tilldela "
+"dem"
+
+#: contrib/auth/models.py:98
+msgid "last login"
+msgstr "senaste inloggning"
+
+#: contrib/auth/models.py:99
+msgid "date joined"
+msgstr "registreringsdatum"
+
+#: 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 ""
+"Förutom de rättigheterna som utdelas manuellt så kommer användaren dessutom "
+"få samma rättigheter som de grupper där han/hon är medlem."
+
+#: contrib/auth/models.py:102
+msgid "user permissions"
+msgstr "användarättigheter"
+
+#: contrib/auth/models.py:105
+msgid "user"
+msgstr "användare"
+
+#: contrib/auth/models.py:106
+msgid "users"
+msgstr "användare"
+
+#: contrib/auth/models.py:111
+msgid "Personal info"
+msgstr "Personlig information"
+
+#: contrib/auth/models.py:112
+msgid "Permissions"
+msgstr "Rättigheter"
+
+#: contrib/auth/models.py:113
+msgid "Important dates"
+msgstr "Viktiga datum"
+
+#: contrib/auth/models.py:114
+msgid "Groups"
+msgstr "Grupper"
+
+#: contrib/auth/models.py:258
+msgid "message"
+msgstr "meddelande"
+
+#: contrib/auth/forms.py:17 contrib/auth/forms.py:138
+msgid "The two password fields didn't match."
+msgstr "De båda lösenorden stämde inte överens."
+
+#: contrib/auth/forms.py:25
+msgid "A user with that username already exists."
+msgstr "En användare med det användarnamnet finns redan."
+
+#: contrib/auth/forms.py:53
+msgid ""
+"Your Web browser doesn't appear to have cookies enabled. Cookies are "
+"required for logging in."
+msgstr ""
+"Din webläsare verkar inte stödja cookies. Cookie behövs för att kunna logga "
+"in."
+
+#: 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 ""
+"V.G. ange ett korrekt användarnamn och lösenord. Observera att båda fälten gör "
+"skillnad på versaler och gemener."
+
+#: contrib/auth/forms.py:62
+msgid "This account is inactive."
+msgstr "Detta konto är inaktivt."
+
+#: contrib/auth/forms.py:85
+msgid ""
+"That e-mail address doesn't have an associated user account. Are you sure "
+"you've registered?"
+msgstr ""
+"Den e-mailadressen har inte något konto associerat med sig. Är du säker på "
+"att du har registrerat dig?"
+
+#: contrib/auth/forms.py:117
+msgid "The two 'new password' fields didn't match."
+msgstr "De båda nya lösenordsfälten stämde inte överens."
+
+#: contrib/auth/forms.py:124
+msgid "Your old password was entered incorrectly. Please enter it again."
+msgstr "Ditt gamla lösenord var felaktigt ifyllt. Var vänlig fyll i det igen"
+
+#: contrib/redirects/models.py:7
+msgid "redirect from"
+msgstr "omdirigera från"
+
+#: contrib/redirects/models.py:8
+msgid ""
+"This should be an absolute path, excluding the domain name. Example: '/"
+"events/search/'."
+msgstr ""
+"Det här bör vara en absolut sökväg, förutom domännamnet. Exempel: '/"
+"handelser/sok/'."
+
+#: contrib/redirects/models.py:9
+msgid "redirect to"
+msgstr "omdirigera till"
+
+#: contrib/redirects/models.py:10
+msgid ""
+"This can be either an absolute path (as above) or a full URL starting with "
+"'http://'."
+msgstr ""
+"Detta kan vara antingen en absolut sökväg (som ovan), eller en komplett "
+"URL som börjar med 'http://'."
+
+#: contrib/redirects/models.py:13
+msgid "redirect"
+msgstr "omdirigera"
+
+#: contrib/redirects/models.py:14
+msgid "redirects"
+msgstr "omdirigeringar"
+
+#: contrib/comments/models.py:67 contrib/comments/models.py:166
+msgid "object ID"
+msgstr "objektets ID"
+
+#: contrib/comments/models.py:68
+msgid "headline"
+msgstr "rubrik"
+
+#: 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 "betyg #1"
+
+#: contrib/comments/models.py:71
+msgid "rating #2"
+msgstr "betyg #2"
+
+#: contrib/comments/models.py:72
+msgid "rating #3"
+msgstr "betyg #3"
+
+#: contrib/comments/models.py:73
+msgid "rating #4"
+msgstr "betyg #4"
+
+#: contrib/comments/models.py:74
+msgid "rating #5"
+msgstr "betyg #5"
+
+#: contrib/comments/models.py:75
+msgid "rating #6"
+msgstr "betyg #6"
+
+#: contrib/comments/models.py:76
+msgid "rating #7"
+msgstr "betyg #7"
+
+#: contrib/comments/models.py:77
+msgid "rating #8"
+msgstr "betyg #8"
+
+#: contrib/comments/models.py:82
+msgid "is valid rating"
+msgstr "är ett giltigt betyg"
+
+#: contrib/comments/models.py:83 contrib/comments/models.py:169
+msgid "date/time submitted"
+msgstr "datum/tid postat"
+
+#: contrib/comments/models.py:84 contrib/comments/models.py:170
+msgid "is public"
+msgstr "är offentligt"
+
+#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304
+msgid "IP address"
+msgstr "IP-adress"
+
+#: contrib/comments/models.py:86
+msgid "is removed"
+msgstr "är borttaget"
+
+#: 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 ""
+"Bocka för den här rutan om kommentaren är olämplig. Ett \"Den här "
+"kommentaren har tagits bort\"-meddelande kommer visas istället"
+
+#: contrib/comments/models.py:91
+msgid "comments"
+msgstr "kommentarer"
+
+#: contrib/comments/models.py:131 contrib/comments/models.py:207
+msgid "Content object"
+msgstr "Innehållsobjekt"
+
+#: 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 ""
+"Postat av %(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 "personens namn"
+
+#: contrib/comments/models.py:171
+msgid "ip address"
+msgstr "IP-adress"
+
+#: contrib/comments/models.py:173
+msgid "approved by staff"
+msgstr "godkänd av personal"
+
+#: contrib/comments/models.py:176
+msgid "free comment"
+msgstr "fri kommentar"
+
+#: contrib/comments/models.py:177
+msgid "free comments"
+msgstr "fria kommentarer"
+
+#: contrib/comments/models.py:233
+msgid "score"
+msgstr "poäng"
+
+#: contrib/comments/models.py:234
+msgid "score date"
+msgstr "poängdatum"
+
+#: contrib/comments/models.py:237
+msgid "karma score"
+msgstr "karmapoäng"
+
+#: contrib/comments/models.py:238
+msgid "karma scores"
+msgstr "karmapoäng"
+
+#: contrib/comments/models.py:242
+#, python-format
+msgid "%(score)d rating by %(user)s"
+msgstr "Betyget %(score)d av %(user)s"
+
+#: contrib/comments/models.py:258
+#, python-format
+msgid ""
+"This comment was flagged by %(user)s:\n"
+"\n"
+"%(text)s"
+msgstr ""
+"Den här kommentaren flaggades av %(user)s:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/models.py:265
+msgid "flag date"
+msgstr "flaggdatum"
+
+#: contrib/comments/models.py:268
+msgid "user flag"
+msgstr "användares flagga"
+
+#: contrib/comments/models.py:269
+msgid "user flags"
+msgstr "användares flaggor"
+
+#: contrib/comments/models.py:273
+#, python-format
+msgid "Flag by %r"
+msgstr "Flaggad av %r"
+
+#: contrib/comments/models.py:278
+msgid "deletion date"
+msgstr "borttagnings-datum"
+
+#: contrib/comments/models.py:280
+msgid "moderator deletion"
+msgstr "moderator-borttagning"
+
+#: contrib/comments/models.py:281
+msgid "moderator deletions"
+msgstr "moderator-borttagningar"
+
+#: contrib/comments/models.py:285
+#, python-format
+msgid "Moderator deletion by %r"
+msgstr "Moderator-borttagning av %r"
+
+#: contrib/comments/views/karma.py:19
+msgid "Anonymous users cannot vote"
+msgstr "Anonyma användare kan inte rösta"
+
+#: contrib/comments/views/karma.py:23
+msgid "Invalid comment ID"
+msgstr "Ogiltig kommentaridentifikation"
+
+#: contrib/comments/views/karma.py:25
+msgid "No voting for yourself"
+msgstr "Du får inte rösta på dig själv"
+
+#: contrib/comments/views/comments.py:27
+msgid "This rating is required because you've entered at least one other rating."
+msgstr "Det här betyget krävs eftersom du har fyllt i minst ett annat betyg."
+
+#: 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] ""
+"Den här kommentaren postades av en användare som har postat mindre än "
+"%(count)s kommentar:\n"
+"\n"
+"%(text)s"
+msgstr[1] ""
+"Den här kommentaren postades av en användare som har postat mindre än "
+"%(count)s kommentarer:\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 ""
+"Den här kommentaren postades av en oseriös användare:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/views/comments.py:188
+#: contrib/comments/views/comments.py:280
+msgid "Only POSTs are allowed"
+msgstr "Endast POST tillåtet"
+
+#: contrib/comments/views/comments.py:192
+#: contrib/comments/views/comments.py:284
+msgid "One or more of the required fields wasn't submitted"
+msgstr "Ett eller flera av de obligatoriska fälten var inte ifyllda"
+
+#: contrib/comments/views/comments.py:196
+#: contrib/comments/views/comments.py:286
+msgid "Somebody tampered with the comment form (security violation)"
+msgstr "Någon fifflade med kommentarformuläret (säkerhetsbrott)"
+
+#: 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 ""
+"Kommentars-formuläret hade en ogiltig 'mål'-parameter -- objektets ID var "
+"ogiltigt"
+
+#: contrib/comments/views/comments.py:257
+#: contrib/comments/views/comments.py:321
+msgid "The comment form didn't provide either 'preview' or 'post'"
+msgstr "Kommentars-formuläret skickade varken 'förhandsgranska' eller 'post'"
+
+#: contrib/comments/templates/comments/freeform.html:4
+msgid "Your name:"
+msgstr "Ditt namn:"
+
+#: contrib/comments/templates/comments/freeform.html:5
+#: contrib/comments/templates/comments/form.html:28
+msgid "Comment:"
+msgstr "Kommentar:"
+
+#: contrib/comments/templates/comments/freeform.html:10
+#: contrib/comments/templates/comments/form.html:35
+msgid "Preview comment"
+msgstr "Förhandsgranska kommentar"
+
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/comments/templates/comments/form.html:8
+#: contrib/admin/templates/admin/login.html:17
+msgid "Username:"
+msgstr "Användarnamn:"
+
+#: 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 "Logga ut"
+
+#: contrib/comments/templates/comments/form.html:8
+#: contrib/admin/templates/admin/login.html:20
+msgid "Password:"
+msgstr "Lösenord:"
+
+#: contrib/comments/templates/comments/form.html:8
+msgid "Forgotten your password?"
+msgstr "Glömt ditt lösenord?"
+
+#: contrib/comments/templates/comments/form.html:12
+msgid "Ratings"
+msgstr "Betyg"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Required"
+msgstr "Obligatorisk"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Optional"
+msgstr "Valfri"
+
+#: contrib/comments/templates/comments/form.html:23
+msgid "Post a photo"
+msgstr "Lägg till 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 "Exempel: '/om/kontakt/'. Se till att ha inledande och avslutande snedsträck."
+
+#: contrib/flatpages/models.py:9
+msgid "title"
+msgstr "titel"
+
+#: contrib/flatpages/models.py:10
+msgid "content"
+msgstr "innehåll"
+
+#: contrib/flatpages/models.py:11
+msgid "enable comments"
+msgstr "aktivera kommentarer"
+
+#: contrib/flatpages/models.py:12
+msgid "template name"
+msgstr "mallnamn"
+
+#: contrib/flatpages/models.py:13
+msgid ""
+"Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
+"will use 'flatpages/default.html'."
+msgstr ""
+"Exempel: 'sidor/kontaktsida.html'. Om det här inte fylls i kommer systemet "
+"att använda 'sidor/default.html'."
+
+#: contrib/flatpages/models.py:14
+msgid "registration required"
+msgstr "registrering krävs"
+
+#: contrib/flatpages/models.py:14
+msgid "If this is checked, only logged-in users will be able to view the page."
+msgstr "Om det här bockas i kommer endast inloggade användare att kunna visa sidan"
+
+#: contrib/flatpages/models.py:18
+msgid "flat page"
+msgstr "flatsida"
+
+#: contrib/flatpages/models.py:19
+msgid "flat pages"
+msgstr "flatsidor"
+
+#: contrib/sessions/models.py:51
+msgid "session key"
+msgstr "sessionsnyckel"
+
+#: contrib/sessions/models.py:52
+msgid "session data"
+msgstr "sessionsdata"
+
+#: contrib/sessions/models.py:53
+msgid "expire date"
+msgstr "utgångsdatum"
+
+#: contrib/sessions/models.py:57
+msgid "session"
+msgstr "session"
+
+#: contrib/sessions/models.py:58
+msgid "sessions"
+msgstr "sessioner"
+
+#: contrib/sites/models.py:10
+msgid "domain name"
+msgstr "domännamn"
+
+#: contrib/sites/models.py:11
+msgid "display name"
+msgstr "visat namn"
+
+#: contrib/sites/models.py:15
+msgid "site"
+msgstr "site"
+
+#: contrib/sites/models.py:16
+msgid "sites"
+msgstr "siter"
+
+#: contrib/admin/filterspecs.py:40
+#, python-format
+msgid ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>Av %s:</h3>\n"
+"<ul>\n"
+
+#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88
+#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169
+msgid "All"
+msgstr "Alla"
+
+#: contrib/admin/filterspecs.py:109
+msgid "Any date"
+msgstr "Alla datum"
+
+#: contrib/admin/filterspecs.py:110
+msgid "Today"
+msgstr "Idag"
+
+#: contrib/admin/filterspecs.py:113
+msgid "Past 7 days"
+msgstr "Senaste 7 dagarna"
+
+#: contrib/admin/filterspecs.py:115
+msgid "This month"
+msgstr "Den här månaden"
+
+#: contrib/admin/filterspecs.py:117
+msgid "This year"
+msgstr "Det här året"
+
+#: contrib/admin/models.py:16
+msgid "action time"
+msgstr "händelsetid"
+
+#: contrib/admin/models.py:19
+msgid "object id"
+msgstr "objektets id"
+
+#: contrib/admin/models.py:20
+msgid "object repr"
+msgstr "objektets beskrivning"
+
+#: contrib/admin/models.py:21
+msgid "action flag"
+msgstr "händelseflagga"
+
+#: contrib/admin/models.py:22
+msgid "change message"
+msgstr "ändra meddelande"
+
+#: contrib/admin/models.py:25
+msgid "log entry"
+msgstr "loggpost"
+
+#: contrib/admin/models.py:26
+msgid "log entries"
+msgstr "loggposter"
+
+#: contrib/admin/templatetags/admin_list.py:247
+msgid "All dates"
+msgstr "Alla datum"
+
+#: contrib/admin/views/decorators.py:24
+#: contrib/admin/templates/admin/login.html:25
+msgid "Log in"
+msgstr "Logga in"
+
+#: 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 ""
+"V.G. logga in igen, eftersom din session har tagit slut. Oroa dig inte: ditt "
+"bidrag har sparats."
+
+#: 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 ""
+"Det ser ut som om din webläsare inte är konfigurerad att acceptera cookies. "
+"Aktivera cookies, ladda om den här sidan, och försök igen."
+
+#: contrib/admin/views/decorators.py:83
+msgid "Usernames cannot contain the '@' character."
+msgstr "Användarnamn kan inte innehålla tecknet '@'."
+
+#: contrib/admin/views/decorators.py:85
+#, python-format
+msgid "Your e-mail address is not your username. Try '%s' instead."
+msgstr "Din e-mailadress är inte ditt användarnamn. Försök med '%s' istället."
+
+#: 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)set \"%(obj)s\" lades till."
+
+#: 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 "Du kan ändra det igen här under."
+
+#: contrib/admin/views/auth.py:30
+msgid "Add user"
+msgstr "Lägg till användare"
+
+#: contrib/admin/views/auth.py:57
+msgid "Password changed successfully."
+msgstr "Lösenordet ändrades framgångsrikt."
+
+#: contrib/admin/views/auth.py:64
+#, python-format
+msgid "Change password: %s"
+msgstr "Ändra lösenord: %s"
+
+#: contrib/admin/views/main.py:223
+msgid "Site administration"
+msgstr "Administration"
+
+#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356
+#, python-format
+msgid "You may add another %s below."
+msgstr "Du kan lägga till en till %s här under."
+
+#: contrib/admin/views/main.py:289
+#, python-format
+msgid "Add %s"
+msgstr "Lägg till %s"
+
+#: contrib/admin/views/main.py:335
+#, python-format
+msgid "Added %s."
+msgstr "Lade till %s."
+
+#: contrib/admin/views/main.py:337
+#, python-format
+msgid "Changed %s."
+msgstr "Ändrade %s."
+
+#: contrib/admin/views/main.py:339
+#, python-format
+msgid "Deleted %s."
+msgstr "Tog bort %s."
+
+#: contrib/admin/views/main.py:342
+msgid "No fields changed."
+msgstr "Inga fält ändrade."
+
+#: contrib/admin/views/main.py:345
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was changed successfully."
+msgstr "%(name)set \"%(obj)s\" ändrades."
+
+#: 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)set \"%(obj)s\" lades till. Du kan ändra det igen här under."
+
+#: contrib/admin/views/main.py:391
+#, python-format
+msgid "Change %s"
+msgstr "Ändra %s"
+
+#: contrib/admin/views/main.py:476
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
+msgstr "Ett eller flera %(fieldname)s i %(name)s: %(obj)s"
+
+#: contrib/admin/views/main.py:481
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "Ett eller flera %(fieldname)s i %(name)s:"
+
+#: contrib/admin/views/main.py:514
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was deleted successfully."
+msgstr "%(name)set \"%(obj)s\" togs bort."
+
+#: contrib/admin/views/main.py:517
+msgid "Are you sure?"
+msgstr "Är du säker?"
+
+#: contrib/admin/views/main.py:539
+#, python-format
+msgid "Change history: %s"
+msgstr "Ändra historien: %s"
+
+#: contrib/admin/views/main.py:573
+#, python-format
+msgid "Select %s"
+msgstr "Välj %s"
+
+#: contrib/admin/views/main.py:573
+#, python-format
+msgid "Select %s to change"
+msgstr "Välj %s att ändra"
+
+#: contrib/admin/views/main.py:768
+msgid "Database error"
+msgstr "Databas fel"
+
+#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48
+#: contrib/admin/views/doc.py:50
+msgid "tag:"
+msgstr "tagg:"
+
+#: 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 "Vy:"
+
+#: contrib/admin/views/doc.py:164
+#, python-format
+msgid "App %r not found"
+msgstr "Applikation %r hittades inte"
+
+#: contrib/admin/views/doc.py:171
+#, python-format
+msgid "Model %(model_name)r not found in app %(app_label)r"
+msgstr "Modellen %(model_name)r hittades inte i applikation %(app_label)r"
+
+#: contrib/admin/views/doc.py:183
+#, python-format
+msgid "the related `%(app_label)s.%(data_type)s` object"
+msgstr "Det sammalänkade `%(app_label)s.%(data_type)s` objektet"
+
+#: 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 `%(app_label)s.%(object_name)s` objects"
+msgstr "sammanlänkade `%(app_label)s.%(object_name)s` objekt"
+
+#: contrib/admin/views/doc.py:219
+#, python-format
+msgid "all %s"
+msgstr "alla %s"
+
+#: contrib/admin/views/doc.py:224
+#, python-format
+msgid "number of %s"
+msgstr "antal %s"
+
+#: contrib/admin/views/doc.py:229
+#, python-format
+msgid "Fields on %s objects"
+msgstr "Fält på %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 "Heltal"
+
+#: contrib/admin/views/doc.py:292
+msgid "Boolean (Either True or False)"
+msgstr "Boolesk (antingen Sann eller Falsk)"
+
+#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "Sträng (upp till %(maxlength)s)"
+
+#: contrib/admin/views/doc.py:294
+msgid "Comma-separated integers"
+msgstr "Komma-separerade heltal"
+
+#: contrib/admin/views/doc.py:295
+msgid "Date (without time)"
+msgstr "Datum (utan tid)"
+
+#: contrib/admin/views/doc.py:296
+msgid "Date (with time)"
+msgstr "Datum (med tid)"
+
+#: contrib/admin/views/doc.py:297
+msgid "E-mail address"
+msgstr "E-postadress:"
+
+#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299
+#: contrib/admin/views/doc.py:302
+msgid "File path"
+msgstr "Sökväg"
+
+#: contrib/admin/views/doc.py:300
+msgid "Decimal number"
+msgstr "Decimaltal"
+
+#: contrib/admin/views/doc.py:306
+msgid "Boolean (Either True, False or None)"
+msgstr "Boolesk (antingen True, False eller None)"
+
+#: contrib/admin/views/doc.py:307
+msgid "Relation to parent model"
+msgstr "Relation till förälder-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 "Tid"
+
+#: contrib/admin/views/doc.py:316
+msgid "U.S. state (two uppercase letters)"
+msgstr "Stat i USA (två versaler)"
+
+#: 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 verkar inte vara ett urlmönster-objekt"
+
+#: contrib/admin/templates/widget/file.html:2
+msgid "Currently:"
+msgstr "Nuvarande:"
+
+#: contrib/admin/templates/widget/file.html:3
+msgid "Change:"
+msgstr "Ändra:"
+
+#: contrib/admin/templates/widget/date_time.html:3
+msgid "Date:"
+msgstr "Datum:"
+
+#: contrib/admin/templates/widget/date_time.html:4
+msgid "Time:"
+msgstr "Tid:"
+
+#: 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 "Dokumentation"
+
+#: 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 "Ändra lösenord"
+
+#: 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 "Hem"
+
+#: contrib/admin/templates/admin/object_history.html:5
+#: contrib/admin/templates/admin/change_form.html:21
+msgid "History"
+msgstr "Historik"
+
+#: contrib/admin/templates/admin/object_history.html:18
+msgid "Date/time"
+msgstr "Datum/tid"
+
+#: contrib/admin/templates/admin/object_history.html:19
+msgid "User"
+msgstr "Användare"
+
+#: contrib/admin/templates/admin/object_history.html:20
+msgid "Action"
+msgstr "Händelse"
+
+#: contrib/admin/templates/admin/object_history.html:26
+msgid "DATE_WITH_TIME_FULL"
+msgstr "D j F 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 ""
+"Det här objektet har ingen ändringshistorik. Det lades antagligen inte till "
+"i den här admin-siten"
+
+#: contrib/admin/templates/admin/change_list.html:12
+#, python-format
+msgid "Add %(name)s"
+msgstr "Lägg till %(name)s"
+
+#: contrib/admin/templates/admin/filter.html:2
+#, python-format
+msgid " By %(filter_title)s "
+msgstr " Av %(filter_title)s "
+
+#: contrib/admin/templates/admin/500.html:4
+msgid "Server error"
+msgstr "Serverfel"
+
+#: contrib/admin/templates/admin/500.html:6
+msgid "Server error (500)"
+msgstr "Serverfel (500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "Serverfel <em>(500)</em>"
+
+#: 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 ""
+"Ett fel har uppstått. Administratören har meddelats via e-mail och "
+"felet bör åtgärdas snart. Tack för ditt tålamod."
+
+#: 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ågonting är fel med din databasinstallation. Se till att de rätta tabellerna har "
+"skapats och att databasen är läsbar av rätt användare."
+
+#: contrib/admin/templates/admin/search_form.html:8
+msgid "Go"
+msgstr "Utför"
+
+#: contrib/admin/templates/admin/search_form.html:10
+#, python-format
+msgid "1 result"
+msgid_plural "%(counter)s results"
+msgstr[0] "1 resultat"
+msgstr[1] "%(counter)s resultat"
+
+#: contrib/admin/templates/admin/search_form.html:10
+#, python-format
+msgid "%(full_result_count)s total"
+msgstr "%(full_result_count)s totalt"
+
+#: contrib/admin/templates/admin/pagination.html:10
+msgid "Show all"
+msgstr "Visa alla"
+
+#: contrib/admin/templates/admin/base_site.html:4
+msgid "Django site admin"
+msgstr "Django site-administration"
+
+#: contrib/admin/templates/admin/base_site.html:7
+msgid "Django administration"
+msgstr "Django administration"
+
+#: contrib/admin/templates/admin/filters.html:4
+msgid "Filter"
+msgstr "Filter"
+
+#: contrib/admin/templates/admin/404.html:4
+#: contrib/admin/templates/admin/404.html:8
+msgid "Page not found"
+msgstr "Sidan kunde inte hittas"
+
+#: contrib/admin/templates/admin/404.html:10
+msgid "We're sorry, but the requested page could not be found."
+msgstr "Vi är ledsna, men den efterfrågade sidan kunde inte hittas."
+
+#: contrib/admin/templates/admin/index.html:17
+#, python-format
+msgid "Models available in the %(name)s application."
+msgstr "Modeller tillgängliga i %(name)s applikationen."
+
+#: 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 "Lägg&nbsp;till"
+
+#: contrib/admin/templates/admin/index.html:34
+msgid "Change"
+msgstr "Ändra"
+
+#: contrib/admin/templates/admin/index.html:44
+msgid "You don't have permission to edit anything."
+msgstr "Du har inte rättigheter att ändra något."
+
+#: contrib/admin/templates/admin/index.html:52
+msgid "Recent Actions"
+msgstr "Senaste Händelserna"
+
+#: contrib/admin/templates/admin/index.html:53
+msgid "My Actions"
+msgstr "Mina Händelser"
+
+#: contrib/admin/templates/admin/index.html:57
+msgid "None available"
+msgstr "Inga tillgängliga"
+
+#: contrib/admin/templates/admin/change_form.html:22
+msgid "View on site"
+msgstr "Visa på siten"
+
+#: 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] "Rätta till felet nedan."
+msgstr[1] "Rätta till felen nedan."
+
+#: contrib/admin/templates/admin/change_form.html:50
+msgid "Ordering"
+msgstr "Sortering"
+
+#: contrib/admin/templates/admin/change_form.html:53
+msgid "Order:"
+msgstr "Sortera:"
+
+#: contrib/admin/templates/admin/base.html:25
+msgid "Welcome,"
+msgstr "Välkommen,"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:9
+#: contrib/admin/templates/admin/submit_line.html:3
+msgid "Delete"
+msgstr "Ta bort"
+
+#: 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 ""
+"Att ta bort %(object_name)s '%(escaped_object)s' skulle innebära att besläktade "
+"objekt togs bort, men ditt konto har inte rättigheter att ta bort följande "
+"objekttyper:"
+
+#: 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 ""
+"Är du säker på att du vill ta bort %(object_name)s \"%(escaped_object)s\"? Alla "
+"dessa sammanlänkade objekt kommer att tas bort:"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:26
+msgid "Yes, I'm sure"
+msgstr "Ja, jag är säker"
+
+#: contrib/admin/templates/admin/submit_line.html:4
+msgid "Save as new"
+msgstr "Spara som ny"
+
+#: contrib/admin/templates/admin/submit_line.html:5
+msgid "Save and add another"
+msgstr "Spara och lägg till ytterligare en"
+
+#: contrib/admin/templates/admin/submit_line.html:6
+msgid "Save and continue editing"
+msgstr "Spara och fortsätt redigera"
+
+#: contrib/admin/templates/admin/submit_line.html:7
+msgid "Save"
+msgstr "Spara"
+
+#: contrib/admin/templates/admin/auth/user/change_password.html:28
+#, python-format
+msgid "Enter a new password for the user <strong>%(username)s</strong>."
+msgstr "Ange nytt lösenord för användaren <strong>%(username)s</strong>."
+
+#: contrib/admin/templates/admin/auth/user/change_password.html:34
+#: contrib/admin/templates/admin/auth/user/add_form.html:18
+msgid "Password"
+msgstr "Lösenord"
+
+#: contrib/admin/templates/admin/auth/user/change_password.html:39
+#: contrib/admin/templates/admin/auth/user/add_form.html:23
+msgid "Password (again)"
+msgstr "Lösenord (igen)"
+
+#: 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 "Fyll i samma lösenord som ovan för verifiering."
+
+#: 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 ""
+"Ange först ett användarnamn och ett lösenord. Sedan kommer du att kunna ändra "
+"fler användaralternativ."
+
+#: contrib/admin/templates/admin/auth/user/add_form.html:12
+msgid "Username"
+msgstr "Användarnamn"
+
+#: 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 "Ändra lösenord"
+
+#: contrib/admin/templates/registration/password_change_done.html:6
+#: contrib/admin/templates/registration/password_change_done.html:10
+msgid "Password change successful"
+msgstr "Lösenordet ändrades"
+
+#: contrib/admin/templates/registration/password_change_done.html:12
+msgid "Your password was changed."
+msgstr "Ditt lösenord har ändrats."
+
+#: 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 "Nollställ lösenordet"
+
+#: 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 glömt ditt lösenord? Fyll i din e-mailadress nedan, så nollställer vi "
+"ditt lösenord och mailar det nya till dig."
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "E-mail address:"
+msgstr "E-postadress:"
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "Reset my password"
+msgstr "Nollställ mitt lösenord"
+
+#: contrib/admin/templates/registration/logged_out.html:8
+msgid "Thanks for spending some quality time with the Web site today."
+msgstr "Tack för att du spenderade kvalitetstid med web-siten idag."
+
+#: contrib/admin/templates/registration/logged_out.html:10
+msgid "Log in again"
+msgstr "Logga in igen"
+
+#: contrib/admin/templates/registration/password_reset_done.html:6
+#: contrib/admin/templates/registration/password_reset_done.html:10
+msgid "Password reset successful"
+msgstr "Nollställning av lösenordet lyckades"
+
+#: 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 skickat ett nytt lösenord till e-mailadressen du fyllde i. Det bör "
+"anlända snarast."
+
+#: 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 ""
+"Var god fyll i ditt gamla lösenord, för säkerhets skull, och skriv sedan in "
+"det nya lösenordet två gånger så vi kan kontrollera att du skrev det rätt."
+
+#: contrib/admin/templates/registration/password_change_form.html:17
+msgid "Old password:"
+msgstr "Gamla lösenordet:"
+
+#: contrib/admin/templates/registration/password_change_form.html:19
+msgid "New password:"
+msgstr "Nytt lösenord:"
+
+#: contrib/admin/templates/registration/password_change_form.html:21
+msgid "Confirm password:"
+msgstr "Bekräfta lösenord:"
+
+#: contrib/admin/templates/registration/password_change_form.html:23
+msgid "Change my password"
+msgstr "Ändra mitt lösenord"
+
+#: contrib/admin/templates/registration/password_reset_email.html:2
+msgid "You're receiving this e-mail because you requested a password reset"
+msgstr "Du får det här mailet eftersom du bad om att få lösenordet nollställt"
+
+#: contrib/admin/templates/registration/password_reset_email.html:3
+#, python-format
+msgid "for your user account at %(site_name)s"
+msgstr "för ditt användarkonto på %(site_name)s"
+
+#: contrib/admin/templates/registration/password_reset_email.html:5
+#, python-format
+msgid "Your new password is: %(new_password)s"
+msgstr "Ditt nya lösenord är: %(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 ""
+"Känn dig välkommen att ändra det här lösenordet genom att gå till den här "
+"sidan:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:11
+msgid "Your username, in case you've forgotten:"
+msgstr "Ditt användarnamn, om du har glömt:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:13
+msgid "Thanks for using our site!"
+msgstr "Tack för att du använder vår site!"
+
+#: contrib/admin/templates/registration/password_reset_email.html:15
+#, python-format
+msgid "The %(site_name)s team"
+msgstr "%(site_name)s-laget"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:3
+msgid "Bookmarklets"
+msgstr "Smarta bokmärken"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:5
+msgid "Documentation bookmarklets"
+msgstr "Smarta bokmärken för dokumentation"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:9
+msgid ""
+"\n"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">För att installera smarta bokmärken, dra länken till din\n"
+"verktygsrad med bokmärken, eller högerklicka på länken och lägg till den\n"
+"till dina bokmärken. Nu kan du välja det smarta bokmärket från alla sidor\n"
+"på siten. Observera att några av dessa smarta bokmärken kräver att du besöker\n"
+"sidan från en dator som är \"intern\" (tala med din systemadministratör\n"
+"om du inte är säker på om din dator är \"intern\").</p>\n"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:19
+msgid "Documentation for this page"
+msgstr "Dokumentation för den här sidan"
+
+#: 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 ""
+"Förflyttar dig från valfri sida till dokumentationen för vyn som genererar "
+"den sidan."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:22
+msgid "Show object ID"
+msgstr "Visa objektets 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 ""
+"Visa innehållstypen och det unika ID:t för sidor som representerar ett "
+"enskilt objekt."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:25
+msgid "Edit this object (current window)"
+msgstr "Redigera det här objektet (nuvarande fönster)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:26
+msgid "Jumps to the admin page for pages that represent a single object."
+msgstr ""
+"Hoppar till administrationssidan för sidor som representerar ett enskilt "
+"objekt."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:28
+msgid "Edit this object (new window)"
+msgstr "Redigera det här objektet (nytt fönster)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:29
+msgid "As above, but opens the admin page in a new window."
+msgstr "Som ovan, men öppnar administrationssidan i ett nytt fönster."
+
+#: contrib/localflavor/uk/forms.py:18
+msgid "Enter a postcode. A space is required between the two postcode parts."
+msgstr "Fyll i ett postnummer. Du måste ha mellanslag mellan nummerdelarna."
+
+#: contrib/localflavor/usa/forms.py:17
+msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX."
+msgstr "Fyll i zipkod i formatet XXXXX eller XXXXX-XXXX."
+
+#: utils/dates.py:6
+msgid "Monday"
+msgstr "MÃ¥ndag"
+
+#: utils/dates.py:6
+msgid "Tuesday"
+msgstr "Tisdag"
+
+#: 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 "Januari"
+
+#: utils/dates.py:14
+msgid "February"
+msgstr "Februari"
+
+#: 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 "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 "Augusti"
+
+#: 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 "mars"
+
+#: utils/dates.py:19
+msgid "apr"
+msgstr "apr"
+
+#: utils/dates.py:19
+msgid "may"
+msgstr "maj"
+
+#: utils/dates.py:19
+msgid "jun"
+msgstr "juni"
+
+#: utils/dates.py:20
+msgid "jul"
+msgstr "juli"
+
+#: 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ånad"
+msgstr[1] "månader"
+
+#: utils/timesince.py:14
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] "vecka"
+msgstr[1] "veckor"
+
+#: utils/timesince.py:15
+msgid "day"
+msgid_plural "days"
+msgstr[0] "dag"
+msgstr[1] "dagar"
+
+#: utils/timesince.py:16
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "timme"
+msgstr[1] "timmar"
+
+#: utils/timesince.py:17
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "minut"
+msgstr[1] "minuter"
+
+#: 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 ""
+
+#: template/defaultfilters.py:490
+msgid "yes,no,maybe"
+msgstr "ja,nej,kanske"
+
diff --git a/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..5daac63
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..5abc878
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.po
@@ -0,0 +1,125 @@
+# Swedish translation of Django
+# Copyright (C) 2005
+# This file is distributed under the same license as the Django package.
+#
+#
+# Robin Sonefors <ozamosi@blinkenlights.se>, 2005.
+# Mikko Hellsing <mikko@sorl.net>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: djangojs\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-03-06 02:29+0100\n"
+"PO-Revision-Date: 2007-03-06 10:30+0100\n"
+"Last-Translator: Mikko Hellsing <mikko@sorl.net>\n"
+"Language-Team: Django I18N <Django-I18N@googlegroups.com>\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-Poedit-Language: Swedish\n"
+"X-Poedit-Country: SWEDEN\n"
+
+#: contrib/admin/media/js/SelectFilter2.js:33
+#, perl-format
+msgid "Available %s"
+msgstr "Tillgänglig %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:41
+msgid "Choose all"
+msgstr "Välj alla"
+
+#: contrib/admin/media/js/SelectFilter2.js:46
+msgid "Add"
+msgstr "Lägg till"
+
+#: contrib/admin/media/js/SelectFilter2.js:48
+msgid "Remove"
+msgstr "Ta bort"
+
+#: contrib/admin/media/js/SelectFilter2.js:53
+#, perl-format
+msgid "Chosen %s"
+msgstr "Vald %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:54
+msgid "Select your choice(s) and click "
+msgstr "Gör dina val och klicka på "
+
+#: contrib/admin/media/js/SelectFilter2.js:59
+msgid "Clear all"
+msgstr "Ta bort alla"
+
+#: 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 ""
+"Januari Februari Mars April Maj Juni Juli Augusti September Oktober November "
+"December"
+
+#: contrib/admin/media/js/dateparse.js:33
+msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
+msgstr "Söndag Mondag Tisdag 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 "Visa"
+
+#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
+msgid "Hide"
+msgstr "Göm"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
+msgid "Now"
+msgstr "Nu"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
+msgid "Clock"
+msgstr "Klocka"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
+msgid "Choose a time"
+msgstr "Välj en tidpunkt"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
+msgid "Midnight"
+msgstr "Midnatt"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
+msgid "6 a.m."
+msgstr "06.00"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
+msgid "Noon"
+msgstr "Mitt på dagen"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
+msgid "Today"
+msgstr "Idag"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
+msgid "Calendar"
+msgstr "Kalender"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
+msgid "Yesterday"
+msgstr "Igår"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
+msgid "Tomorrow"
+msgstr "Imorgon"
+
diff --git a/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..c1d3cf2
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.po
new file mode 100644
index 0000000..e3c539a
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.po
@@ -0,0 +1,2136 @@
+# translation of django-new.po to tamil
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# PONNUSAMY.A <ponnusamy.simpleman@gmail.com>, 2007.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-new\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-09-25 15:43+0200\n"
+"PO-Revision-Date: 2007-03-15 16:48+0530\n"
+"Last-Translator: PONNUSAMY <ponnusamy.simpleman@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language-Team: tamil <tamilinix@yahoogroups.com>\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=n>1;"
+
+#: 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: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 "பொரà¯à®³à¯ அடகà¯à®• 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 ""
+"%(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 "செலà¯à®²à®¾à®¤ கà¯à®±à®¿à®ªà¯à®ªà¯ ID"
+
+#: 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 "கà¯à®±à®¿à®ªà¯à®ªà¯à®±à¯ˆ படிவதà¯à®¤à®¿à®²à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© இலகà¯à®•à¯ அளவà¯à®°à¯à®•à¯à®•à®µà®¿à®²à¯à®²à¯ˆ -- object 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/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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3> %s ஆலà¯:</h3>\n"
+"<ul>\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/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 "U.S. மாநிலம௠(இரணà¯à®Ÿà¯ மேல௠எழà¯à®¤à¯à®¤à¯à®µà®•à¯ˆ எழà¯à®¤à¯à®¤à¯"
+
+#: 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/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 "தேதியà¯à®®à¯ à®®à¯à®´à¯ நேரமà¯à®®à¯"
+
+#: 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 "சேவையகம௠தவறà¯(500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "சேவையகம௠பிழை<em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "நீஙà¯à®•à®³à¯ தஙà¯à®•à®³à®¤à¯ கடவà¯à®šà¯à®šà¯Šà®²à¯à®²à¯ˆ <a href=\"/password_reset/\"> மறநà¯à®¤à¯ விடà¯à®Ÿà¯€à®°à¯à®•à®³à®¾?"
+
+#: 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 ""
+"நீஙà¯à®•à®³à¯ இநà¯à®¤ \"%(escaped_object)s\" %(object_name)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/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/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 "%(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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\"> பà¯à®¤à¯à®¤à®• கà¯à®±à®¿à®¯à¯€à®Ÿà¯à®•à®³à¯ˆ நிறà¯à®µ இநà¯à®¤ இணைபà¯à®ªà®¿à®©à¯ˆ பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿à®¯à¯€à®Ÿà¯à®Ÿà¯à®ªà¯ \n"
+"படà¯à®Ÿà¯ˆà®•à¯à®•à¯ இழà¯à®•à¯à®•à®µà¯à®®à¯. அலà¯à®²à®¤à¯ வலத௠கிளிக௠செயà¯à®¤à¯ பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿à®¯à¯€à®Ÿà¯à®•à®³à®¿à®²à¯ சேரà¯à®•à¯à®•à®µà¯à®®à¯. \n"
+" இனி தளதà¯à®¤à®¿à®²à¯ எநà¯à®¤à®ªà¯ பகà¯à®•à®¤à¯à®¤à®¿à®²à¯ இரà¯à®¨à¯à®¤à¯à®®à¯ பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿à®¯à¯€à®Ÿà¯à®Ÿà®¿à®©à¯ˆ தேரà¯à®µà¯à®šà¯†à®¯à¯à®¯ à®®à¯à®Ÿà®¿à®¯à¯à®®à¯. \n"
+" நீஙà¯à®•à®³à¯ இநà¯à®¤ தளதà¯à®¤à¯ˆ \"internal\" என கà¯à®±à®¿à®•à¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿ கணிணியில௠இரà¯à®¨à¯à®¤à¯ மடà¯à®Ÿà¯à®®à¯‡ \n"
+" à®’à®°à¯à®šà®¿à®² பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿à®•à®³à¯ˆ செயலà¯à®ªà®Ÿà¯à®¤à¯à®¤à®®à¯à®Ÿà®¿à®¯à¯à®®à¯\n "
+" உஙà¯à®•à®³à¯à®•à¯à®•à¯, கணிணி \"internal\" என உறà¯à®¤à®¿ செயà¯à®¯ கணிணிமேளாலரை அணà¯à®•à®µà¯à®®à¯.</p>\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 "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 "ஒரே object-஠கà¯à®±à®¿à®•à¯à®•à¯à®®à¯ பகà¯à®•à®™à¯à®•à®³à®¿à®©à¯ பொரà¯à®³à®Ÿà®•à¯à®• வகை மறà¯à®±à¯à®®à¯ unique 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 "ஒரே object-஠கà¯à®±à®¿à®•à¯à®•à¯à®®à¯ பகà¯à®•à®™à¯à®•à®³à¯ˆà®•à¯ காண மேலாளர௠பகà¯à®•à®¤à¯à®¤à®¿à®±à¯à®•à¯ செலà¯à®•."
+
+#: 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\"//\" என தொடஙà¯à®•à¯à®®à¯ வலை à®®à¯à®•à®µà®°à®¿à®¯à®¾à®• இரà¯à®•à¯à®•à®²à®¾à®®à¯."
+
+#: 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'. இத௠இலà¯à®²à¯ˆà®¯à¯†à®©à®¿à®²à¯ '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/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]'"
+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 "python model class name"
+
+#: 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 "தேதி_à®®à¯à®±à¯ˆ"
+
+#: 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 "மாதமà¯_நாளà¯_à®®à¯à®±à¯ˆ"
+
+#: 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 "காறà¯à®ªà¯à®³à¯à®³à®¿à®•à®³à®¾à®²à¯ தனிமைபà¯à®ªà®Ÿà¯à®¤à¯à®¤à®¿à®¯ à®®à¯à®±à¯ˆà®¯à®¾à®© e à®®à¯à®•à®µà®°à®¿à®•à®³à¯ மடà¯à®Ÿà¯à®®à¯ எழà¯à®¤à®µà¯à®®à¯"
+
+#: 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 "வரà¯à®Ÿà®®à¯ கணà¯à®Ÿà®¿à®ªà¯à®ªà®¾à®• 1900 அலà¯à®²à®¤à¯ அதறà¯à®•à¯ மேலà¯"
+
+#: 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 "மம-நிநி எனà¯à®± அமைபà¯à®ªà®¿à®²à¯ உளà¯à®³ à®®à¯à®±à¯ˆà®¯à®¾à®© நேரம௠மடà¯à®Ÿà¯à®®à¯‡ எழà¯à®¤à®µà¯à®®à¯"
+
+#: 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 "à®®à¯à®±à¯ˆà®¯à®¾à®© e à®®à¯à®•à®µà®°à®¿à®•à®³à¯ மடà¯à®Ÿà¯à®®à¯ எழà¯à®¤à®µà¯à®®à¯"
+
+#: 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 "அநà¯à®¤ பகà¯à®•à®¤à¯à®¤à®¿à®©à¯ encoding வகையைப௠பரிசோதிகà¯à®•.கோபà¯à®ªà¯ சமரà¯à®ªà®¿à®•à¯à®•à®ªà¯ படà¯à®Ÿà®µà®¿à®²à¯à®²à¯ˆ "
+
+#: 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 எனà¯à®± இணையதள à®®à¯à®•à®µà®°à®¿ சரியான படதà¯à®¤à¯ˆà®šà¯ சà¯à®Ÿà¯à®Ÿà®µà®¿à®²à¯à®²à¯ˆ"
+
+#: 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 எனà¯à®± இணையதள à®®à¯à®•à®µà®°à®¿ à®®à¯à®±à¯ˆà®¯à®¾à®© கà¯à®¯à®¿à®•à¯ டைம௠படகà¯à®•à®¾à®Ÿà¯à®šà®¿à®¯à¯ˆà®šà¯ சà¯à®Ÿà¯à®Ÿà®µà®¿à®²à¯à®²à¯ˆ"
+
+#: 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 ""
+"à®®à¯à®±à¯ˆà®¯à®¾à®© இணையதள à®®à¯à®•à®µà®°à®¿ தேவை. கà¯à®±à®¿à®ªà¯à®ªà®¿à®Ÿà®¤à¯à®¤à®•à¯à®•à®¤à¯ தவறà¯à®•à®³à®¾à®µà®©:\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 "à®®à¯à®±à¯ˆà®ªà¯à®ªà®Ÿà¯à®¤à¯à®¤à®ªà¯à®ªà®Ÿà®¾à®¤ இணையதள à®®à¯à®•à®µà®±à®¿: %s"
+
+#: core/validators.py:234 core/validators.py:236
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "%s எனà¯à®± இணையதள à®®à¯à®•à®µà®°à®¿ உடைநà¯à®¤à¯à®³à¯à®³à®¤à¯"
+
+#: core/validators.py:242
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "à®®à¯à®±à¯ˆà®¯à®¾à®© U.S மாநில பெயர௠சà¯à®°à¯à®•à¯à®•à®®à¯ எழà¯à®¤à®µà¯à®®à¯"
+
+#: 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] "வாரà¯à®¤à¯à®¤à¯ˆà®•à®³à¯ˆ அளனà¯à®¤à¯ பேசà¯à®™à¯à®•à®³à¯! %s எனà¯à®± வாரà¯à®¤à¯à®¤à¯ˆ இஙà¯à®•à¯ அனà¯à®®à®¤à®¿ இலà¯à®²à¯ˆ"
+msgstr[1] "வாரà¯à®¤à¯à®¤à¯ˆà®•à®³à¯ˆ அளநà¯à®¤à¯ பேசà¯à®™à¯à®•à®³à¯! %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."
+msgid_plural "Please enter a valid decimal number with at most %s total digits."
+msgstr[0] "தயவà¯à®šà¯†à®¯à¯à®¤à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© பதினà¯à®® எணà¯à®•à®³à¯à®Ÿà®©à¯ %s மொதà¯à®¤ இலகà¯à®•à®¤à¯à®¤à¯ˆ நà¯à®´à¯ˆà®•à¯à®•à®µà¯à®®à¯"
+msgstr[1] "தயவà¯à®šà¯†à®¯à¯à®¤à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© பதினà¯à®® எணà¯à®•à®³à¯à®Ÿà®©à¯ %s மொதà¯à®¤ இலகà¯à®•à®™à¯à®•à®³à¯ˆà®¯à¯à®®à¯ நà¯à®´à¯ˆà®•à¯à®•à®µà¯à®®à¯"
+
+#: 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] "அதிகபடà¯à®šà®®à¯ %s எணà¯à®£à¯ˆ உளà¯à®³ பதினà¯à®® எணà¯à®£à¯ˆ நà¯à®´à¯ˆ."
+msgstr[1] "அதிகபடà¯à®šà®®à¯ %s எணà¯à®•à®³à¯ உளà¯à®³ பதினà¯à®® எணà¯à®£à¯ˆ நà¯à®´à¯ˆ."
+
+#: 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] "அதிகபடà¯à®šà®®à¯ %s பà¯à®³à¯à®³à®¿ இடம௠உளà¯à®³ பதினà¯à®® எணà¯à®£à¯ˆ நà¯à®´à¯ˆ"
+msgstr[1] "அதிகபடà¯à®šà®®à¯ %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 எனà¯à®ªà®¤à¯ செலà¯à®²à®¾à®¤ உளà¯à®³à®Ÿà®•à¯à®•-வகை தலைபà¯à®ªà®¾à®© '%(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 "%(type)s உடன௠உளà¯à®³ %(object)s à®à®±à¯à®•à®©à®µà¯‡ %(field)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 "Mac இலà¯, ஒனà¯à®±à¯à®•à¯à®•à¯ மேறà¯à®ªà®Ÿà¯à®Ÿà®µà®±à¯à®±à¯ˆ தேரà¯à®µà¯ செயà¯à®¯ \"Control\" அலà¯à®²à®¤à¯ \"Command\" à® à®…à®´à¯à®¤à¯à®¤à®µà¯à®®à¯"
+
+#: 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 எனà¯à®± மதிபà¯à®ªà¯à®•à®³à¯ à®®à¯à®±à¯ˆà®¯à®¾à®©à®¤à®²à¯à®²."
+
+#: 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: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 "-32,768 மறà¯à®±à¯à®®à¯ 32,767 கà¯à®•à¯ நடà¯à®µà®¿à®²à¯ ஒர௠மà¯à®´à¯ எணà¯à®£à¯ˆ எழà¯à®¤à®µà¯à®®à¯"
+
+#: 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 "0 மறà¯à®±à¯à®®à¯ 32,767 கà¯à®•à¯ நடà¯à®µà®¿à®²à¯ ஒர௠மà¯à®´à¯ எணà¯à®£à¯ˆ எழà¯à®¤à®µà¯à®®à¯"
+
+#: template/defaultfilters.py:401
+msgid "yes,no,maybe"
+msgstr "ஆமà¯, இலà¯à®²à¯ˆ, இரà¯à®•à¯à®•à®²à®¾à®®à¯"
+
diff --git a/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..2565a6d
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..03fbece
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.po
@@ -0,0 +1,112 @@
+# translation of djangojs.po to tamil
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# PONNUSAMY.A <ponnusamy.simpleman@gmail.com>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: djangojs\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-09 11:51+0100\n"
+"PO-Revision-Date: 2007-03-14 16:40+0530\n"
+"Last-Translator: PONNUSAMY <ponnusamy.simpleman@gmail.com>\n"
+"Language-Team: tamil <tamilinix@yahoogroups.com>\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: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/te/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..29360bb
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.po
new file mode 100644
index 0000000..0057bf9
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.po
@@ -0,0 +1,2106 @@
+# translation of django.po to Telugu
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# pavithran <pavithran.s@gmail.com>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-09-25 15:43+0200\n"
+"PO-Revision-Date: 2007-02-28 18:35+0530\n"
+"Last-Translator: pavithran <pavithran.s@gmail.com>\n"
+"Language-Team: Telugu <indlinux-telugu@lists.sourceforge.net>\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 "వసà±à°¤à±à°µà± à°à°¡à°¿"
+
+#: 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 "వసà±à°¤à±à°µà± లోనిది"
+
+#: 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 "%(user) రేటింగà±"
+
+#: contrib/comments/models.py:258
+#, python-format
+msgid ""
+"This comment was flagged by %(user)s:\n"
+"\n"
+"%(text)s"
+msgstr ""
+"%(user)s చేత చేయబడà±à°¡ à°µà±à°¯à°¾à°–à±à°¯à°¾à°¨à°®à±à°²à±"
+"\n"
+"%(text)à°²à±"
+
+#: 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"
+msgid_plural ""
+"This comment was posted by a user who has posted fewer than %(count)s "
+"comments:\n"
+"\n"
+"%(text)s"
+msgstr[0] ""
+"à°ˆ à°µà±à°¯à°¾à°–à±à°¯à°¾à°¨à°®à± చేసిన యూఙరౠ%(count)లౠకనà±à°¨ తకà±à°•à±à°µ సమరà±à°ªà°¿à°‚చాడౠ"
+"à°µà±à°¯à°¾à°–à±à°¯à°¾à°¨à°®à±:\n"
+"\n"
+"%(text)s"
+msgstr[1] ""
+"à°ˆ à°µà±à°¯à°¾à°–à±à°¯à°¾à°¨à°®à± చేసిన యూఙరౠ%(count)లౠకనà±à°¨ తకà±à°•à±à°µ సమరà±à°ªà°¿à°‚చాడà±"
+"à°µà±à°¯à°¾à°–à±à°¯à°¾à°¨à°®à±à°²à±:\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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr "<h3> %s తో:</h3>\n"
+"<ul>\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/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)లౠలో %(fieldname)లౠ: %(obj)లౠ"
+
+#: contrib/admin/views/main.py:478
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "à°’à°•à°Ÿà°¿ కాని ,à°…à°‚à°¤ à°•à°¨à±à°¨à°Žà°•à±à°•à±à°µ %(name)లౠలో %(fieldname)à°²à±"
+
+#: contrib/admin/views/main.py:511
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was deleted successfully."
+msgstr "%(name)లౠ\"%(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 "మారà±à°šà°¬à°¡à°¿à°¨ à°ªà±à°°à°¾à°£à°®à±"
+
+#: 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 ""
+
+#: 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 ""
+
+#: 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 "సరà±à°µà°°à± తపà±à°ªà± (500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "సరà±à°µà°°à± తపà±à°ªà± <em>(500)</em>"
+
+#: 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)లో దొరికే à°…à°ªà±à°ªà±à°²à°¿à°•à±‡à°·à°¨à±"
+
+#: 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 "%(name)లౠఙత చేయà±"
+
+#: contrib/admin/templates/admin/login.html:22
+msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "మీరà±<a href=\"/password_reset/\">పాసౠవరà±à°¡ మరà±à°šà°¿à°ªà±‹à°¯à°¾à°°à°¾? "
+
+#: 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_.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 "మీ కొతà±à°¤ పాసౠవరà±à°¡à± : %(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 ""
+
+#: 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"
+"<p class=\"help\">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\").</p>\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 "Example: '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 ""
+
+#: 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 ""
+
+#: 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 ""
+
+#: 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 "సంవతà±à°¸à°°à°®à± 1900 లేక దాని తరà±à°µà°¾à°¤ à°…à°¯à±à°¯à°¿ ఉందాలి "
+
+#: 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 "సరైన 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 ""
+
+#: 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 "à°ˆ ఫీలà±à°¡à± '%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 ""
+
+#: 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 "%s నించి à°à°®à°¿ రాబటà±à°Ÿà°²à±‡à°®à±"
+
+#: 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 "%(verbose_name)లౠఙయపà±à°°à°¦à°‚à°—à°¾ తయారయింది"
+
+#: views/generic/create_update.py:117
+#, python-format
+msgid "The %(verbose_name)s was updated successfully."
+msgstr "%(verbose_name)లౠఙయపà±à°°à°¦à°‚à°—à°¾ @@"
+
+#: views/generic/create_update.py:184
+#, python-format
+msgid "The %(verbose_name)s was deleted."
+msgstr "%(verbose_name)లౠతీసివేయబడినది"
+
+#: db/models/manipulators.py:302
+#, python-format
+msgid "%(object)s with this %(type)s already exists for the given %(field)s."
+msgstr "%(field)à°² లో %(object)తో %(type) ఉనà±à°¨à°¾à°¯à°¿"
+
+#: db/models/fields/__init__.py:40
+#, python-format
+msgid "%(optname)s with this %(fieldname)s already exists."
+msgstr "%(optname)లౠతో %(fieldname) à°®à±à°‚దే ఉనà±à°¨à°¾à°¯à°¿ ."
+
+#: 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 "\"Control\" కాని \"Command\" మాకౠలో నొకà±à°•à°¿ ఉంచండి , à°’à°•à°Ÿà°¿ à°•à°¨à±à°¨ à°Žà°•à±à°•à±à°µ à°Žà°¨à±à°¨à±à°•à±‹à°µà°Ÿà°¾à°¨à°¿à°•à°¿"
+
+#: 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 à°à°¡à°¿à°²à± ఇవà±à°µà°‚à°¡à°¿. "
+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] "మీరౠఇచà±à°šà°¿à°¨ టెకà±à°¸à±à°Ÿ %s ఆకà±à°·à°°à°®à± à°•à°¨à±à°¨ తకà±à°•à±à°µ ఉందాలి"
+msgstr[1] "మీరౠఇచà±à°šà°¿à°¨ టెకà±à°¸à±à°Ÿ %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 "సరైనది à°Žà°‚à°šà±à°•à±‹à°‚à°¡à°¿; %(choices) à°² లో '%(data)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 " -32,768 ఇంకా 32,767 మధà±à°¯à°²à±‹ à°’à°• ఆంకె ఇవà±à°µà°‚à°¡à°¿"
+
+#: 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 "0 ఇంకా 32,767 మధà±à°¯à°²à±‹ à°’à°• పూరౠఇవà±à°µà°‚à°¡à°¿"
+
+#: template/defaultfilters.py:401
+msgid "yes,no,maybe"
+msgstr "à°…à°µà±à°¨à±, కాదౠ, à°à°®à±Š"
+
diff --git a/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..c7c5642
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..7f392fd
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.po
@@ -0,0 +1,110 @@
+# translation of djangojs.po to Telugu
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# pavithran <pavithran.s@gmail.com>, 2007.
+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: 2007-03-06 16:08+0530\n"
+"Last-Translator: pavithran <pavithran.s@gmail.com>\n"
+"Language-Team: Telugu <indlinux-telugu@lists.sourceforge.net>\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: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/tr/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..87ac4d0
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.po
new file mode 100644
index 0000000..a2c080d
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.po
@@ -0,0 +1,2470 @@
+# translation of django.po to Turkish
+# Django 0.95
+# Copyright (C) 2006 Django
+# This file is distributed under the same license as the Django package.
+#
+# Can Burak Çilingir <canburak@cs.bilgi.edu.tr>, 2007. (Slight modifications)
+# Bahadır Kandemir <bahadir@pardus.org.tr>, 2006.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-03-14 01:38+0200\n"
+"PO-Revision-Date: 2007-03-14 02:06+0200\n"
+"Last-Translator: Bahadır Kandemir <bahadir@pardus.org.tr>\n"
+"Language-Team: Turkish <bahadir@pardus.org.tr>\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"
+
+#: 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 "Bu alan gerekli."
+
+#: 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] "Metnin %s karakterden az olmasına dikkat edin."
+msgstr[1] "Metnin %s karakterden az olmasına dikkat edin."
+
+#: oldforms/__init__.py:397
+msgid "Line breaks are not allowed here."
+msgstr "Burada birden fazla satır olamaz."
+
+#: 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 ""
+"Geçerli bir seçimde bulunun; %(choices)s değerleri içinde '%(data)s' yok."
+
+#: oldforms/__init__.py:577 newforms/widgets.py:170
+#: contrib/admin/filterspecs.py:150
+msgid "Unknown"
+msgstr "Bilinmiyor"
+
+#: oldforms/__init__.py:577 newforms/widgets.py:170
+#: contrib/admin/filterspecs.py:143
+msgid "Yes"
+msgstr "Evet"
+
+#: oldforms/__init__.py:577 newforms/widgets.py:170
+#: contrib/admin/filterspecs.py:143
+msgid "No"
+msgstr "Hayır"
+
+#: 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 "Dosya gönderilmedi. Formdaki kodlama türünü kontrol edin."
+
+#: oldforms/__init__.py:674
+msgid "The submitted file is empty."
+msgstr "Gönderilen dosya boş."
+
+#: oldforms/__init__.py:730
+msgid "Enter a whole number between -32,768 and 32,767."
+msgstr "-32,768 ve 32,767 arası bir sayı girin."
+
+#: oldforms/__init__.py:740
+msgid "Enter a positive number."
+msgstr "Pozitif tamsayı girin."
+
+#: oldforms/__init__.py:750
+msgid "Enter a whole number between 0 and 32,767."
+msgstr "0 ve 32,767 arası bir sayı girin."
+
+#: db/models/manipulators.py:307
+#, python-format
+msgid "%(object)s with this %(type)s already exists for the given %(field)s."
+msgstr "%(type)s ve %(field)s değerine sahip %(object)s kaydı zaten var."
+
+#: 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 "ve"
+
+#: db/models/fields/__init__.py:42
+#, python-format
+msgid "%(optname)s with this %(fieldname)s already exists."
+msgstr "%(fieldname)s için %(optname)s değeri zaten seçilmiş."
+
+#: db/models/fields/__init__.py:366
+msgid "This value must be an integer."
+msgstr "Bu değer tamsayı olmalı."
+
+#: db/models/fields/__init__.py:401
+msgid "This value must be either True or False."
+msgstr "Bu deÄŸer True ya da False olabilir."
+
+#: db/models/fields/__init__.py:422
+msgid "This field cannot be null."
+msgstr "Bu alan boş bırakılamaz."
+
+#: db/models/fields/__init__.py:456 core/validators.py:148
+msgid "Enter a valid date in YYYY-MM-DD format."
+msgstr "YYYY-AA-GG formatında tarih girin."
+
+#: db/models/fields/__init__.py:525 core/validators.py:157
+msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
+msgstr "YYYY-AA-GG SS:DD formatında tarih girin."
+
+#: db/models/fields/__init__.py:629
+msgid "Enter a valid filename."
+msgstr "Geçerli bir dosya adı girin."
+
+#: db/models/fields/__init__.py:750
+msgid "This value must be either None, True or False."
+msgstr "Bu deÄŸer None, True ya da False olabilir."
+
+#: db/models/fields/related.py:53
+#, python-format
+msgid "Please enter a valid %s."
+msgstr "Lütfen geçerli bir %s girin."
+
+#: db/models/fields/related.py:642
+msgid "Separate multiple IDs with commas."
+msgstr "Birden fazla numarayı virgül ile ayırın."
+
+#: db/models/fields/related.py:644
+msgid ""
+"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr ""
+"\"Ctrl\" ve Mac'de \"Command\" tuşunu basılı tutarak birden fazla seçimde "
+"bulunabilirsiniz."
+
+#: 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] ""
+"Lütfen geçerli bir %(self)s numarası girin. %(value)r değeri geçersiz."
+msgstr[1] ""
+"Lütfen geçerli %(self)s numaraları girin. %(value)r değerleri geçersiz."
+
+#: conf/global_settings.py:39
+msgid "Arabic"
+msgstr "Arapça"
+
+#: conf/global_settings.py:40
+msgid "Bengali"
+msgstr "Bengali Dili"
+
+#: conf/global_settings.py:41
+msgid "Catalan"
+msgstr ""
+
+#: conf/global_settings.py:42
+msgid "Czech"
+msgstr "Çekçe"
+
+#: conf/global_settings.py:43
+msgid "Welsh"
+msgstr "Galce"
+
+#: conf/global_settings.py:44
+msgid "Danish"
+msgstr "Danca"
+
+#: conf/global_settings.py:45
+msgid "German"
+msgstr "Almanca"
+
+#: conf/global_settings.py:46
+msgid "Greek"
+msgstr "Yunanca"
+
+#: conf/global_settings.py:47
+msgid "English"
+msgstr "Ä°ngilizce"
+
+#: conf/global_settings.py:48
+msgid "Spanish"
+msgstr "Ä°spanyolca"
+
+#: conf/global_settings.py:49
+msgid "Argentinean Spanish"
+msgstr "Arjantin İspanyolcası"
+
+#: conf/global_settings.py:50
+msgid "Finnish"
+msgstr "Fince"
+
+#: conf/global_settings.py:51
+msgid "French"
+msgstr "Fransızca"
+
+#: conf/global_settings.py:52
+msgid "Galician"
+msgstr "Galler Dili"
+
+#: conf/global_settings.py:53
+msgid "Hungarian"
+msgstr "Macarca"
+
+#: conf/global_settings.py:54
+msgid "Hebrew"
+msgstr "Ä°branice"
+
+#: conf/global_settings.py:55
+msgid "Icelandic"
+msgstr "Ä°zlanda dili"
+
+#: conf/global_settings.py:56
+msgid "Italian"
+msgstr "Ä°talyanca"
+
+#: conf/global_settings.py:57
+msgid "Japanese"
+msgstr "Japonca"
+
+#: conf/global_settings.py:58
+msgid "Kannada"
+msgstr ""
+
+#: conf/global_settings.py:59
+msgid "Latvian"
+msgstr ""
+
+#: conf/global_settings.py:60
+msgid "Macedonian"
+msgstr ""
+
+#: conf/global_settings.py:61
+msgid "Dutch"
+msgstr "Flamanca"
+
+#: conf/global_settings.py:62
+msgid "Norwegian"
+msgstr "Norveç Dili"
+
+#: conf/global_settings.py:63
+msgid "Polish"
+msgstr ""
+
+#: conf/global_settings.py:64
+msgid "Brazilian"
+msgstr "Brezilya Dili"
+
+#: conf/global_settings.py:65
+msgid "Romanian"
+msgstr "Romence"
+
+#: conf/global_settings.py:66
+msgid "Russian"
+msgstr "Rusça"
+
+#: conf/global_settings.py:67
+msgid "Slovak"
+msgstr "Slovakça"
+
+#: conf/global_settings.py:68
+msgid "Slovenian"
+msgstr "Slovence"
+
+#: conf/global_settings.py:69
+msgid "Serbian"
+msgstr "Sırpça"
+
+#: conf/global_settings.py:70
+msgid "Swedish"
+msgstr "İsveççe"
+
+#: conf/global_settings.py:71
+msgid "Tamil"
+msgstr "Tamilce"
+
+#: conf/global_settings.py:72
+msgid "Telugu"
+msgstr ""
+
+#: conf/global_settings.py:73
+msgid "Turkish"
+msgstr "Türkçe"
+
+#: conf/global_settings.py:74
+msgid "Ukrainian"
+msgstr "Ukraynaca"
+
+#: conf/global_settings.py:75
+msgid "Simplified Chinese"
+msgstr "Basiteştirilmiş Çince"
+
+#: conf/global_settings.py:76
+msgid "Traditional Chinese"
+msgstr "Gelenelsek Çince"
+
+#: core/validators.py:64
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Bu değer sadece karakter, rakam ve altçizgiden oluşabilir."
+
+#: core/validators.py:68
+msgid ""
+"This value must contain only letters, numbers, underscores, dashes or "
+"slashes."
+msgstr ""
+"Bu değer sadece harf, rakam, altçizgi, bölü ve ters bölüden oluşabilir."
+
+#: core/validators.py:72
+msgid "This value must contain only letters, numbers, underscores or hyphens."
+msgstr "Bu değer sadece harf, rakam, altçizgi veya çizgiden oluşabilir."
+
+#: core/validators.py:76
+msgid "Uppercase letters are not allowed here."
+msgstr "Burada büyük harf kullanılamaz."
+
+#: core/validators.py:80
+msgid "Lowercase letters are not allowed here."
+msgstr "Burada küçük harf kullanılamaz."
+
+#: core/validators.py:87
+msgid "Enter only digits separated by commas."
+msgstr "Sadece virgülle ayrılmış sayılar girin."
+
+#: core/validators.py:99
+msgid "Enter valid e-mail addresses separated by commas."
+msgstr "Virgülle ayrılmış geçerli e-posta adresleri girin."
+
+#: core/validators.py:103
+msgid "Please enter a valid IP address."
+msgstr "Lütfen geçerli bir IP adresi girin."
+
+#: core/validators.py:107
+msgid "Empty values are not allowed here."
+msgstr "Burada boş değer kullanılamaz."
+
+#: core/validators.py:111
+msgid "Non-numeric characters aren't allowed here."
+msgstr "Burada numerik olmayan karakterler kullanılamaz."
+
+#: core/validators.py:115
+msgid "This value can't be comprised solely of digits."
+msgstr "Bu alanda sadece rakam kullanılamaz."
+
+#: core/validators.py:120 newforms/fields.py:126
+msgid "Enter a whole number."
+msgstr "Sayı girin."
+
+#: core/validators.py:124
+msgid "Only alphabetical characters are allowed here."
+msgstr "Burada sadece alfabetik karakterler kullanılabilir."
+
+#: core/validators.py:139
+msgid "Year must be 1900 or later."
+msgstr "Yıl 1900 ya da sonrası olabilir."
+
+#: core/validators.py:143
+#, python-format
+msgid "Invalid date: %s"
+msgstr "Geçersiz tarih: %s"
+
+#: core/validators.py:153
+msgid "Enter a valid time in HH:MM format."
+msgstr "SS:DD formatında geçerli bir saat girin."
+
+#: core/validators.py:162 newforms/fields.py:269
+msgid "Enter a valid e-mail address."
+msgstr "Geçerli bir e-posta adresi girin."
+
+#: core/validators.py:178
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+"Geçerli bir resim girin. Gönderdiğiniz dosya resim değil, ya da bozuk bir "
+"dosya."
+
+#: core/validators.py:185
+#, python-format
+msgid "The URL %s does not point to a valid image."
+msgstr "%s adresi geçerli bir resme işaret etmiyor."
+
+#: core/validators.py:189
+#, python-format
+msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
+msgstr "Telefon numarası XXX-XXX-XXXX formatında olmalı. \"%s\" geçersiz."
+
+#: core/validators.py:197
+#, python-format
+msgid "The URL %s does not point to a valid QuickTime video."
+msgstr "%s adresi geçerli bir QuickTime dosyasına işaret etmiyor."
+
+#: core/validators.py:201
+msgid "A valid URL is required."
+msgstr "Geçerli bir URL gerekli."
+
+#: core/validators.py:215
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"Metnin geçerli bir HTML kodu olması gerekir. Hatalar:\n"
+"%s"
+
+#: core/validators.py:222
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Geçersiz XML kodu: %s"
+
+#: core/validators.py:239
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Geçersiz adres: %s"
+
+#: core/validators.py:244 core/validators.py:246
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "%s kırık bir link."
+
+#: core/validators.py:252
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "Geçerli bir şehir kodu girin."
+
+#: 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] "Söylediğinize dikkat edin! %s kelimesi burada kullanılamaz."
+msgstr[1] "Söylediğinize dikkat edin! %s kelimeleri burada kullanılamaz."
+
+#: core/validators.py:273
+#, python-format
+msgid "This field must match the '%s' field."
+msgstr "Bu alan '%s' ile alanı ile uyuşmalı."
+
+#: core/validators.py:292
+msgid "Please enter something for at least one field."
+msgstr "Lütfen en az bir alana giriş yapın."
+
+#: core/validators.py:301 core/validators.py:312
+msgid "Please enter both fields or leave them both empty."
+msgstr "Lütfen tüm alanları doldurun ya da hepsini boş bırakın."
+
+#: core/validators.py:320
+#, python-format
+msgid "This field must be given if %(field)s is %(value)s"
+msgstr "Bu alan %(field)s alanı %(value)s değerine sahipse doldurulmalı."
+
+#: core/validators.py:333
+#, python-format
+msgid "This field must be given if %(field)s is not %(value)s"
+msgstr "Bu alan %(field)s alanı %(value)s değerine sahip değilse doldurulmalı."
+
+#: core/validators.py:352
+msgid "Duplicate values are not allowed."
+msgstr "Tekrarlanan deÄŸerler kabul edilmez."
+
+#: core/validators.py:367
+#, python-format
+msgid "This value must be between %(lower)s and %(upper)s."
+msgstr "Bu değer %(lower)s ve %(upper)s arasında olabilir."
+
+#: core/validators.py:369
+#, python-format
+msgid "This value must be at least %s."
+msgstr "Bu deÄŸer en az %s olabilir."
+
+#: core/validators.py:371
+#, python-format
+msgid "This value must be no more than %s."
+msgstr ""
+
+#: core/validators.py:407
+#, python-format
+msgid "This value must be a power of %s."
+msgstr "Bu deÄŸer %s ya da kuvvetleri olabilir."
+
+#: core/validators.py:418
+msgid "Please enter a valid decimal number."
+msgstr "Lütfen geçerli bir ondalık sayı girin."
+
+#: 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] "Lütfen en fazla %s basamaklı bir ondalık sayı girin."
+msgstr[1] "Lütfen en fazla %s basamaklı bir ondalık sayı girin."
+
+#: 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] "Lütfen tamsayı kısmı en fazla %s basamaklı bir ondalık sayı girin."
+msgstr[1] "Lütfen tamsayı kısmı en fazla %s basamaklı bir ondalık sayı girin."
+
+#: 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] ""
+"Lütfen ondalıklı kısmı en fazla %s basamaklı bir ondalık sayı girin."
+msgstr[1] ""
+"Lütfen ondalıklı kısmı en fazla %s basamaklı bir ondalık sayı girin."
+
+#: core/validators.py:438
+#, python-format
+msgid "Make sure your uploaded file is at least %s bytes big."
+msgstr "Gönderdiğiniz dosyanın en az %s byte uzunlukta olduğundan emin olun."
+
+#: core/validators.py:439
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr "Gönderdiğiniz dosyanın en fazl %s byte uzunlukta olduğundan emin olun."
+
+#: core/validators.py:456
+msgid "The format for this field is wrong."
+msgstr "Bu alandaki veri formatı hatalı."
+
+#: core/validators.py:471
+msgid "This field is invalid."
+msgstr "Alan geçersiz."
+
+#: core/validators.py:507
+#, python-format
+msgid "Could not retrieve anything from %s."
+msgstr "%s içinden hiçbirşey aktarılamıyor."
+
+#: core/validators.py:510
+#, python-format
+msgid ""
+"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
+msgstr "%(url)s adresi geçersiz içerik türü (%(contenttype)s) gönderdi."
+
+#: core/validators.py:543
+#, python-format
+msgid ""
+"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "
+"\"%(start)s\".)"
+msgstr ""
+"Lütfen %(line)s. satırdaki kapatılmayan %(tag)s etiketini kapatın. (Satır, "
+"\"%(start)s\" ile başlıyor.)"
+
+#: 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 ""
+"%(line)s. satırda başlayan bazı kelimeler içerik olarak kabul edilmiyor. "
+"(Satır, \"%(start)s\" ile başlıyor.)"
+
+#: core/validators.py:552
+#, python-format
+msgid ""
+"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%"
+"(start)s\".)"
+msgstr ""
+"%(line)s. satırdaki \"%(attr)s\" özelliği geçersiz. (Satır, \"%(start)s\" "
+"ile başlıyor.)"
+
+#: core/validators.py:557
+#, python-format
+msgid ""
+"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%"
+"(start)s\".)"
+msgstr ""
+"%(line)s. satırdaki \"<%(tag)s>\" etiketi geçersiz. (Satır, \"%(start)s\" "
+"ile başlıyor.)"
+
+#: 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 ""
+"%(line)s. satırdaki bir etiket eksik ya da eklenmesi gereken özellikleri "
+"var. (Satır, \"%(start)s\" ile başlıyor.)"
+
+#: 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 ""
+"%(line)s. satırdaki \"%(attr)s\" özelliği geçersiz bir değere sahip. (Satır, "
+"\"%(start)s\" ile başlıyor.)"
+
+#: views/generic/create_update.py:43
+#, python-format
+msgid "The %(verbose_name)s was created successfully."
+msgstr "%(verbose_name)s başarıyla yaratıldı."
+
+#: views/generic/create_update.py:117
+#, python-format
+msgid "The %(verbose_name)s was updated successfully."
+msgstr "%(verbose_name)s başarıyla güncellendi."
+
+#: views/generic/create_update.py:184
+#, python-format
+msgid "The %(verbose_name)s was deleted."
+msgstr "%(verbose_name)s silindi."
+
+#: newforms/models.py:164 newforms/fields.py:360
+msgid "Select a valid choice. That choice is not one of the available choices."
+msgstr "Geçerli bir seçimde bulunun; seçiminiz mevcut değerlerden birisi değil."
+
+#: newforms/models.py:181 newforms/fields.py:378 newforms/fields.py:454
+msgid "Enter a list of values."
+msgstr ""
+
+#: newforms/models.py:187 newforms/fields.py:387
+#, python-format
+msgid "Select a valid choice. %s is not one of the available choices."
+msgstr "Geçerli bir seçimde bulunun; %s mevcut değerlerden biri değil."
+
+#: newforms/fields.py:101 newforms/fields.py:254
+#, python-format
+msgid "Ensure this value has at most %d characters."
+msgstr "Bu deÄŸer en fazla %d karakter olabilir."
+
+#: newforms/fields.py:103 newforms/fields.py:256
+#, python-format
+msgid "Ensure this value has at least %d characters."
+msgstr "Bu deÄŸer en az %d arakter olabilir."
+
+#: newforms/fields.py:128
+#, python-format
+msgid "Ensure this value is less than or equal to %s."
+msgstr ""
+
+#: newforms/fields.py:130
+#, python-format
+msgid "Ensure this value is greater than or equal to %s."
+msgstr ""
+
+#: newforms/fields.py:163
+msgid "Enter a valid date."
+msgstr "Geçerli bir tarih girin."
+
+#: 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 "Geçerli bir değer girin."
+
+#: newforms/fields.py:287 newforms/fields.py:309
+msgid "Enter a valid URL."
+msgstr "Geçerli bir URL girin."
+
+#: newforms/fields.py:311
+msgid "This URL appears to be a broken link."
+msgstr "Bu URL kırık bir link gibi duruyor."
+
+#: contrib/humanize/templatetags/humanize.py:17
+msgid "th"
+msgstr ""
+
+#: contrib/humanize/templatetags/humanize.py:17
+msgid "st"
+msgstr ""
+
+#: contrib/humanize/templatetags/humanize.py:17
+msgid "nd"
+msgstr ""
+
+#: contrib/humanize/templatetags/humanize.py:17
+msgid "rd"
+msgstr ""
+
+#: contrib/humanize/templatetags/humanize.py:47
+#, python-format
+msgid "%(value).1f million"
+msgid_plural "%(value).1f million"
+msgstr[0] ""
+msgstr[1] ""
+
+#: contrib/humanize/templatetags/humanize.py:50
+#, python-format
+msgid "%(value).1f billion"
+msgid_plural "%(value).1f billion"
+msgstr[0] ""
+msgstr[1] ""
+
+#: contrib/humanize/templatetags/humanize.py:53
+#, python-format
+msgid "%(value).1f trillion"
+msgid_plural "%(value).1f trillion"
+msgstr[0] ""
+msgstr[1] ""
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "one"
+msgstr "bir"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "two"
+msgstr "iki"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "three"
+msgstr "üç"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "four"
+msgstr "dört"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "five"
+msgstr "beÅŸ"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "six"
+msgstr "altı"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "seven"
+msgstr "yedi"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "eight"
+msgstr "sekiz"
+
+#: contrib/humanize/templatetags/humanize.py:68
+msgid "nine"
+msgstr "dokuz"
+
+#: contrib/redirects/models.py:7
+msgid "redirect from"
+msgstr "eski adres"
+
+#: contrib/redirects/models.py:8
+msgid ""
+"This should be an absolute path, excluding the domain name. Example: '/"
+"events/search/'."
+msgstr ""
+"Buraya tam dosya yolu, alan adı kullanılmadan yazılmalı. Örnek: '/events/"
+"search/'."
+
+#: contrib/redirects/models.py:9
+msgid "redirect to"
+msgstr "yeni adres"
+
+#: contrib/redirects/models.py:10
+msgid ""
+"This can be either an absolute path (as above) or a full URL starting with "
+"'http://'."
+msgstr ""
+"Buraya tam dosya yolu (yukarıdaki gibi), ya da 'http://' ile başlayan tam "
+"adres yazılmalı."
+
+#: contrib/redirects/models.py:13
+msgid "redirect"
+msgstr "yönlendirme"
+
+#: contrib/redirects/models.py:14
+msgid "redirects"
+msgstr "yönlendirmeler"
+
+#: contrib/comments/models.py:67 contrib/comments/models.py:166
+msgid "object ID"
+msgstr "nesne no"
+
+#: contrib/comments/models.py:68
+msgid "headline"
+msgstr "başlık"
+
+#: contrib/comments/models.py:69 contrib/comments/models.py:90
+#: contrib/comments/models.py:167
+msgid "comment"
+msgstr "yorum"
+
+#: contrib/comments/models.py:70
+msgid "rating #1"
+msgstr "reyting 1"
+
+#: contrib/comments/models.py:71
+msgid "rating #2"
+msgstr "reyting 2"
+
+#: contrib/comments/models.py:72
+msgid "rating #3"
+msgstr "reyting 3"
+
+#: contrib/comments/models.py:73
+msgid "rating #4"
+msgstr "reyting 4"
+
+#: contrib/comments/models.py:74
+msgid "rating #5"
+msgstr "reyting 5"
+
+#: contrib/comments/models.py:75
+msgid "rating #6"
+msgstr "reyting 6"
+
+#: contrib/comments/models.py:76
+msgid "rating #7"
+msgstr "reyting 7"
+
+#: contrib/comments/models.py:77
+msgid "rating #8"
+msgstr "reyting 8"
+
+#: contrib/comments/models.py:82
+msgid "is valid rating"
+msgstr "geçerli reyting"
+
+#: contrib/comments/models.py:83 contrib/comments/models.py:169
+msgid "date/time submitted"
+msgstr "gönderim tarihi/saati"
+
+#: contrib/comments/models.py:84 contrib/comments/models.py:170
+msgid "is public"
+msgstr "görünürlük"
+
+#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304
+msgid "IP address"
+msgstr "IP adresi"
+
+#: contrib/comments/models.py:86
+msgid "is removed"
+msgstr "silinmiÅŸ"
+
+#: 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 ""
+"Yorum uygunsuz ise bu işareti kaldırın. \"Yorum silindi\" uyarısı "
+"görüntülenecek."
+
+#: contrib/comments/models.py:91
+msgid "comments"
+msgstr "yorumlar"
+
+#: contrib/comments/models.py:131 contrib/comments/models.py:207
+msgid "Content object"
+msgstr "İçerik nesnesi"
+
+#: 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 ""
+"%(date)s tarihinde %(user)s göndermiş:\n"
+"\n"
+"%(comment)s\n"
+"\n"
+"http://%(domain)s%(url)s"
+
+#: contrib/comments/models.py:168
+msgid "person's name"
+msgstr "isim"
+
+#: contrib/comments/models.py:171
+msgid "ip address"
+msgstr "ip adresi"
+
+#: contrib/comments/models.py:173
+msgid "approved by staff"
+msgstr "yönetici onayı"
+
+#: contrib/comments/models.py:176
+msgid "free comment"
+msgstr "serbest yorum"
+
+#: contrib/comments/models.py:177
+msgid "free comments"
+msgstr "serbest yorumlar"
+
+#: contrib/comments/models.py:233
+msgid "score"
+msgstr "puan"
+
+#: contrib/comments/models.py:234
+msgid "score date"
+msgstr "puan tarihi"
+
+#: contrib/comments/models.py:237
+msgid "karma score"
+msgstr "karma puanı"
+
+#: contrib/comments/models.py:238
+msgid "karma scores"
+msgstr "karma puanları"
+
+#: contrib/comments/models.py:242
+#, python-format
+msgid "%(score)d rating by %(user)s"
+msgstr "%(user)s tarafından %(score)d puan"
+
+#: contrib/comments/models.py:258
+#, python-format
+msgid ""
+"This comment was flagged by %(user)s:\n"
+"\n"
+"%(text)s"
+msgstr ""
+"Bu yorum %(user)s tarafından işaretlenmiş:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/models.py:265
+msgid "flag date"
+msgstr "iÅŸaretleme tarihi"
+
+#: contrib/comments/models.py:268
+msgid "user flag"
+msgstr "kullanıcı işareti"
+
+#: contrib/comments/models.py:269
+msgid "user flags"
+msgstr "kullanıcı işaretleri"
+
+#: contrib/comments/models.py:273
+#, python-format
+msgid "Flag by %r"
+msgstr "%r tarafından işaret"
+
+#: contrib/comments/models.py:278
+msgid "deletion date"
+msgstr "silme tarihi"
+
+#: contrib/comments/models.py:280
+msgid "moderator deletion"
+msgstr "yönetici tarafından silinme"
+
+#: contrib/comments/models.py:281
+msgid "moderator deletions"
+msgstr "yönetici tarafından silinme"
+
+#: contrib/comments/models.py:285
+#, python-format
+msgid "Moderator deletion by %r"
+msgstr "%s tarafından silme işlemi"
+
+#: contrib/comments/views/karma.py:19
+msgid "Anonymous users cannot vote"
+msgstr "Kayıtsız kullanıcılar oy veremez"
+
+#: contrib/comments/views/karma.py:23
+msgid "Invalid comment ID"
+msgstr "Geçersiz yorum numarası"
+
+#: contrib/comments/views/karma.py:25
+msgid "No voting for yourself"
+msgstr "Kendinize oy veremezsiniz"
+
+#: contrib/comments/views/comments.py:27
+msgid ""
+"This rating is required because you've entered at least one other rating."
+msgstr "Reyting gerekli, çünkü en az bir reyting tanımladınız."
+
+#: 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] ""
+"Bu yorum, %(count)s yorumdan daha az gönderide bulunmuş bir kullanıcıya "
+"ait:\n"
+"\n"
+"%(text)s"
+msgstr[1] ""
+"Bu yorum, %(count)s yorumdan daha az gönderide bulunmuş bir kullanıcıya "
+"ait:\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 ""
+"Bu yorum kusurlu bir kullanıcı tarafından gönderildi:\n"
+"\n"
+"%(text)s"
+
+#: contrib/comments/views/comments.py:188
+#: contrib/comments/views/comments.py:280
+msgid "Only POSTs are allowed"
+msgstr "Sadece POST yapılabilir"
+
+#: contrib/comments/views/comments.py:192
+#: contrib/comments/views/comments.py:284
+msgid "One or more of the required fields wasn't submitted"
+msgstr "Bir ya da daha fazla gerekli alan doldurulmadı"
+
+#: contrib/comments/views/comments.py:196
+#: contrib/comments/views/comments.py:286
+msgid "Somebody tampered with the comment form (security violation)"
+msgstr ""
+"Birisi yorum gönderme formunu kötüye kullanmaya çalıştı (güvenlik ihlali)"
+
+#: 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 ""
+"Bu yorumun geçersiz bir 'hedef' parametresi var -- nesne narası geçersiz"
+
+#: contrib/comments/views/comments.py:257
+#: contrib/comments/views/comments.py:321
+msgid "The comment form didn't provide either 'preview' or 'post'"
+msgstr "Yorum görüntülememe mi yoksa gönderme amaçlı mı belirsiz"
+
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/comments/templates/comments/form.html:8
+#: contrib/admin/templates/admin/login.html:17
+msgid "Username:"
+msgstr "Kullanıcı:"
+
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/admin/templates/admin/change_list.html:5
+#: 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/base.html:25
+#: 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/view_detail.html:4
+#: contrib/admin/templates/admin_doc/bookmarklets.html:4
+#: contrib/admin/templates/admin_doc/template_detail.html:4
+#: contrib/admin/templates/admin_doc/template_tag_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
+#: contrib/admin/templates/admin_doc/template_filter_index.html:5
+msgid "Log out"
+msgstr "Çık"
+
+#: contrib/comments/templates/comments/form.html:8
+#: contrib/admin/templates/admin/login.html:20
+msgid "Password:"
+msgstr "Parola:"
+
+#: contrib/comments/templates/comments/form.html:8
+msgid "Forgotten your password?"
+msgstr "Parolanızı mı unuttunuz?"
+
+#: contrib/comments/templates/comments/form.html:12
+msgid "Ratings"
+msgstr "Reytingler"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Required"
+msgstr "Gerekli"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Optional"
+msgstr "Opsiyonel"
+
+#: contrib/comments/templates/comments/form.html:23
+msgid "Post a photo"
+msgstr "Resim gönder"
+
+#: contrib/comments/templates/comments/form.html:28
+#: contrib/comments/templates/comments/freeform.html:5
+msgid "Comment:"
+msgstr "Yorum:"
+
+#: contrib/comments/templates/comments/form.html:35
+#: contrib/comments/templates/comments/freeform.html:10
+msgid "Preview comment"
+msgstr "Yorumu görüntüle"
+
+#: contrib/comments/templates/comments/freeform.html:4
+msgid "Your name:"
+msgstr "Ä°sminiz:"
+
+#: contrib/sites/models.py:10
+msgid "domain name"
+msgstr "alan adı"
+
+#: contrib/sites/models.py:11
+msgid "display name"
+msgstr "görülen isim"
+
+#: contrib/sites/models.py:15
+msgid "site"
+msgstr "site"
+
+#: contrib/sites/models.py:16
+msgid "sites"
+msgstr "siteler"
+
+#: contrib/admin/filterspecs.py:40
+#, python-format
+msgid ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>%s nesnesine göre:</h3>\n"
+"<ul>\n"
+
+#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88
+#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169
+msgid "All"
+msgstr "Tümü"
+
+#: contrib/admin/filterspecs.py:109
+msgid "Any date"
+msgstr "Herhangi bir tarih"
+
+#: contrib/admin/filterspecs.py:110
+msgid "Today"
+msgstr "Bugün"
+
+#: contrib/admin/filterspecs.py:113
+msgid "Past 7 days"
+msgstr "7 gün içinde"
+
+#: contrib/admin/filterspecs.py:115
+msgid "This month"
+msgstr "Bu ay"
+
+#: contrib/admin/filterspecs.py:117
+msgid "This year"
+msgstr "Bu yıl"
+
+#: contrib/admin/models.py:16
+msgid "action time"
+msgstr "işlem zamanı"
+
+#: contrib/admin/models.py:19
+msgid "object id"
+msgstr "nesne no"
+
+#: contrib/admin/models.py:20
+msgid "object repr"
+msgstr "nesne kodu"
+
+#: contrib/admin/models.py:21
+msgid "action flag"
+msgstr "işlem adı"
+
+#: contrib/admin/models.py:22
+msgid "change message"
+msgstr "mesajı değiştir"
+
+#: contrib/admin/models.py:25
+msgid "log entry"
+msgstr "giriÅŸi kaydet"
+
+#: contrib/admin/models.py:26
+msgid "log entries"
+msgstr "giriÅŸleri kaydet"
+
+#: contrib/admin/templatetags/admin_list.py:247
+msgid "All dates"
+msgstr "Tüm tarihler"
+
+#: contrib/admin/views/auth.py:19 contrib/admin/views/main.py:257
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was added successfully."
+msgstr "\"%(obj)s\" isimli %(name)s eklendi."
+
+#: 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 "Tekrar düzenleyebilirsiniz."
+
+#: contrib/admin/views/auth.py:30
+msgid "Add user"
+msgstr "Kullanıcı ekle"
+
+#: contrib/admin/views/auth.py:57
+msgid "Password changed successfully."
+msgstr "Parola başarı ile değiştirildi."
+
+#: contrib/admin/views/auth.py:64
+#, python-format
+msgid "Change password: %s"
+msgstr "Parola deÄŸiÅŸtir: %s"
+
+#: contrib/admin/views/main.py:223
+msgid "Site administration"
+msgstr "Site yönetimi"
+
+#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356
+#, python-format
+msgid "You may add another %s below."
+msgstr "Yeni bir %s ekleyebilirsiniz."
+
+#: contrib/admin/views/main.py:289
+#, python-format
+msgid "Add %s"
+msgstr "%s ekle"
+
+#: contrib/admin/views/main.py:335
+#, python-format
+msgid "Added %s."
+msgstr "%s eklendi."
+
+#: contrib/admin/views/main.py:337
+#, python-format
+msgid "Changed %s."
+msgstr "%s deÄŸiÅŸtirildi."
+
+#: contrib/admin/views/main.py:339
+#, python-format
+msgid "Deleted %s."
+msgstr "%s silindi."
+
+#: contrib/admin/views/main.py:342
+msgid "No fields changed."
+msgstr "Hiçbir alan değiştirilmedi."
+
+#: contrib/admin/views/main.py:345
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was changed successfully."
+msgstr "\"%(obj)s\" isimli %(name)s deÄŸiÅŸtirildi."
+
+#: contrib/admin/views/main.py:353
+#, python-format
+msgid ""
+"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
+msgstr ""
+"\"%(obj)s\" isimli %(name)s eklendi. Aşağıda tekrar düzenleyebilirsiniz."
+
+#: contrib/admin/views/main.py:391
+#, python-format
+msgid "Change %s"
+msgstr "%s deÄŸiÅŸtir"
+
+#: contrib/admin/views/main.py:476
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
+msgstr "%(name)s içinde bir ya da daha fazla %(fieldname)s: %(obj)s"
+
+#: contrib/admin/views/main.py:481
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "%(name)s içinde bir ya da daha fazla %(fieldname)s:"
+
+#: contrib/admin/views/main.py:514
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was deleted successfully."
+msgstr "\"%(obj)s\" isimli %(name)s silindi."
+
+#: contrib/admin/views/main.py:517
+msgid "Are you sure?"
+msgstr "Emin misiniz?"
+
+#: contrib/admin/views/main.py:539
+#, python-format
+msgid "Change history: %s"
+msgstr "%s için değişiklik geçmişi:"
+
+#: contrib/admin/views/main.py:573
+#, python-format
+msgid "Select %s"
+msgstr "%s seç"
+
+#: contrib/admin/views/main.py:573
+#, python-format
+msgid "Select %s to change"
+msgstr "Değiştirilecek %s nesnesini seçin"
+
+#: contrib/admin/views/main.py:768
+msgid "Database error"
+msgstr "Veritabanı hatası"
+
+#: 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 ""
+"Lütfen geçerli bir kullanıcı adı ve parola girin. Tüm alanlar büyük/küçük "
+"harf duyarlıdır."
+
+#: contrib/admin/views/decorators.py:24
+#: contrib/admin/templates/admin/login.html:25
+msgid "Log in"
+msgstr "GiriÅŸ yap"
+
+#: 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 ""
+"Oturumunuzun süresi geçti. Lütfen tekrar giriş yapın. Endişe etmeyin, "
+"gönderiniz kayıt edildi."
+
+#: 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 ""
+"Görünüşe göre tarayıcınız çerezleri kabul etmiyor. Çerez kullanımını aktif "
+"hale getirin ve sayfayı yeniden yükleyin."
+
+#: contrib/admin/views/decorators.py:83
+msgid "Usernames cannot contain the '@' character."
+msgstr "Kullanıcı isminde '@' karakteri bulunamaz."
+
+#: contrib/admin/views/decorators.py:85
+#, python-format
+msgid "Your e-mail address is not your username. Try '%s' instead."
+msgstr "E-posta adresiniz kullanıcı adınız değil. '%s' kullanın."
+
+#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48
+#: contrib/admin/views/doc.py:50
+msgid "tag:"
+msgstr "etiket:"
+
+#: 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 "view:"
+
+#: contrib/admin/views/doc.py:164
+#, python-format
+msgid "App %r not found"
+msgstr "%r uygulaması bulunamadı"
+
+#: contrib/admin/views/doc.py:171
+#, python-format
+msgid "Model %(name)r not found in app %(label)r"
+msgstr "%(name)r modeli %(label)r uygulamasında bulunamadı"
+
+#: contrib/admin/views/doc.py:183
+#, python-format
+msgid "the related `%(label)s.%(type)s` object"
+msgstr "ilgili `%(label)s.%(type)s` nesnesi"
+
+#: 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 `%(label)s.%(name)s` objects"
+msgstr "ilgili `%(label)s.%(name)s` nesneleri"
+
+#: contrib/admin/views/doc.py:219
+#, python-format
+msgid "all %s"
+msgstr "tüm %s"
+
+#: contrib/admin/views/doc.py:224
+#, python-format
+msgid "number of %s"
+msgstr "%s sayısı"
+
+#: contrib/admin/views/doc.py:229
+#, python-format
+msgid "Fields on %s objects"
+msgstr "%s nesnesindeki alanlar"
+
+#: 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 "Tamsayı"
+
+#: contrib/admin/views/doc.py:292
+msgid "Boolean (Either True or False)"
+msgstr "Mantıksal (True ya da False)"
+
+#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "Karakter disizi (en fazla %(maxlength)s)"
+
+#: contrib/admin/views/doc.py:294
+msgid "Comma-separated integers"
+msgstr "Virgülle ayrılmış tamsayılar"
+
+#: contrib/admin/views/doc.py:295
+msgid "Date (without time)"
+msgstr "Tarih (saat yok)"
+
+#: contrib/admin/views/doc.py:296
+msgid "Date (with time)"
+msgstr "Tarih (saat var)"
+
+#: contrib/admin/views/doc.py:297
+msgid "E-mail address"
+msgstr "E-posta adresi"
+
+#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299
+#: contrib/admin/views/doc.py:302
+msgid "File path"
+msgstr "Dosya yolu"
+
+#: contrib/admin/views/doc.py:300
+msgid "Decimal number"
+msgstr "Ondalık sayı:"
+
+#: contrib/admin/views/doc.py:306
+msgid "Boolean (Either True, False or None)"
+msgstr "Mantıksal (True, False, ya da None)"
+
+#: contrib/admin/views/doc.py:307
+msgid "Relation to parent model"
+msgstr "Ana modelle iliÅŸki"
+
+#: contrib/admin/views/doc.py:308
+msgid "Phone number"
+msgstr "Telefon numarası"
+
+#: contrib/admin/views/doc.py:313
+msgid "Text"
+msgstr "Metin"
+
+#: contrib/admin/views/doc.py:314
+msgid "Time"
+msgstr "Saat"
+
+#: 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 "Åžehir Kodu (iki karakter)"
+
+#: contrib/admin/views/doc.py:317
+msgid "XML text"
+msgstr "XML metni"
+
+#: contrib/admin/views/doc.py:343
+#, python-format
+msgid "%s does not appear to be a urlpattern object"
+msgstr "%s geçerli vir adres kalıbı değil"
+
+#: contrib/admin/templates/widget/file.html:2
+msgid "Currently:"
+msgstr "Åžimdiki:"
+
+#: contrib/admin/templates/widget/file.html:3
+msgid "Change:"
+msgstr "DeÄŸiÅŸtir:"
+
+#: contrib/admin/templates/widget/date_time.html:3
+msgid "Date:"
+msgstr "Tarih:"
+
+#: contrib/admin/templates/widget/date_time.html:4
+msgid "Time:"
+msgstr "Saat:"
+
+#: contrib/admin/templates/admin/change_list.html:5
+#: 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/base.html:25
+#: 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 "Dokümantasyon"
+
+#: contrib/admin/templates/admin/change_list.html:5
+#: 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/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_done.html:3
+#: contrib/admin/templates/registration/password_change_form.html:3
+#: contrib/admin/templates/admin_doc/view_detail.html:4
+#: contrib/admin/templates/admin_doc/bookmarklets.html:4
+#: contrib/admin/templates/admin_doc/template_detail.html:4
+#: contrib/admin/templates/admin_doc/template_tag_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
+#: contrib/admin/templates/admin_doc/template_filter_index.html:5
+msgid "Change password"
+msgstr "Parola deÄŸiÅŸtir"
+
+#: 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/change_form.html:13
+#: contrib/admin/templates/admin/delete_confirmation.html:6
+#: contrib/admin/templates/admin/base.html:30
+#: 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 "Anasayfa"
+
+#: contrib/admin/templates/admin/change_list.html:12
+#, python-format
+msgid "Add %(name)s"
+msgstr "%(name)s Ekle"
+
+#: contrib/admin/templates/admin/filter.html:2
+#, python-format
+msgid " By %(filter_title)s "
+msgstr " %(filter_title)s nesnesine göre "
+
+#: contrib/admin/templates/admin/object_history.html:5
+#: contrib/admin/templates/admin/change_form.html:21
+msgid "History"
+msgstr "Geçmiş"
+
+#: contrib/admin/templates/admin/object_history.html:18
+msgid "Date/time"
+msgstr "Tarih/saat"
+
+#: contrib/admin/templates/admin/object_history.html:19
+msgid "User"
+msgstr "Kullanıcı"
+
+#: contrib/admin/templates/admin/object_history.html:20
+msgid "Action"
+msgstr "Ä°ÅŸlem"
+
+#: contrib/admin/templates/admin/object_history.html:26
+msgid "DATE_WITH_TIME_FULL"
+msgstr "DATE_WITH_TIME_FULL"
+
+#: 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 ""
+"Bu nesnenin işlem geçmişi yok. Muhtemelen yönetici sayfası dışında bir "
+"yerden eklendi."
+
+#: contrib/admin/templates/admin/search_form.html:8
+msgid "Go"
+msgstr "Git"
+
+#: contrib/admin/templates/admin/search_form.html:10
+#, python-format
+msgid "1 result"
+msgid_plural "%(counter)s results"
+msgstr[0] "1 sonuç"
+msgstr[1] "%(counter)s sonuç"
+
+#: contrib/admin/templates/admin/search_form.html:10
+#, python-format
+msgid "%(full_result_count)s total"
+msgstr "toplam %(full_result_count)s"
+
+#: contrib/admin/templates/admin/pagination.html:10
+msgid "Show all"
+msgstr "Tümünü göster"
+
+#: contrib/admin/templates/admin/base_site.html:4
+msgid "Django site admin"
+msgstr "Django site yöneticisi"
+
+#: contrib/admin/templates/admin/base_site.html:7
+msgid "Django administration"
+msgstr "Django yönetimi"
+
+#: contrib/admin/templates/admin/500.html:4
+msgid "Server error"
+msgstr "Sunucu hatası"
+
+#: contrib/admin/templates/admin/500.html:6
+msgid "Server error (500)"
+msgstr "Sunucu hatası (500)"
+
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "Sunucu Hatası <em>(500)</em>"
+
+#: 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 ""
+"Bir hata oluştu. Hata, e-psota ile site yöneticisine bildirildi ve kısa süre "
+"içinde çözülecktir. Sabrınız için teşekkürler."
+
+#: 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 ""
+"Veritabanı kurulumu ile ilgili bir problem var. İlgili veritabanı "
+"tablolarının kurulu olduğundan ve veritabanının ilgili kullanıcı tarafından "
+"okunabilir olduÄŸundan emin olun."
+
+#: contrib/admin/templates/admin/index.html:17
+#, python-format
+msgid "Models available in the %(name)s application."
+msgstr "%(name)s uygulamasındaki modeller."
+
+#: 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 "Ekle"
+
+#: contrib/admin/templates/admin/index.html:34
+msgid "Change"
+msgstr "DeÄŸiÅŸtir"
+
+#: contrib/admin/templates/admin/index.html:44
+msgid "You don't have permission to edit anything."
+msgstr "Ä°ÅŸlem yapmaya yetkiniz yok."
+
+#: contrib/admin/templates/admin/index.html:52
+msgid "Recent Actions"
+msgstr "Geçmiş İşlemler"
+
+#: contrib/admin/templates/admin/index.html:53
+msgid "My Actions"
+msgstr "Ä°ÅŸlemlerim"
+
+#: contrib/admin/templates/admin/index.html:57
+msgid "None available"
+msgstr "Hiç yok"
+
+#: contrib/admin/templates/admin/404.html:4
+#: contrib/admin/templates/admin/404.html:8
+msgid "Page not found"
+msgstr "Sayfa bulunamadı"
+
+#: contrib/admin/templates/admin/404.html:10
+msgid "We're sorry, but the requested page could not be found."
+msgstr "Üzgünüm, aradığınız sayfa bulunamadı."
+
+#: contrib/admin/templates/admin/filters.html:4
+msgid "Filter"
+msgstr "Filtrele"
+
+#: contrib/admin/templates/admin/change_form.html:22
+msgid "View on site"
+msgstr "Sitede görüntüle"
+
+#: 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ütfen aşağıdaki hatayı düzeltin."
+msgstr[1] "Lütfen aşağıdaki hataları düzeltin."
+
+#: contrib/admin/templates/admin/change_form.html:50
+msgid "Ordering"
+msgstr "Sıralama:"
+
+#: contrib/admin/templates/admin/change_form.html:53
+msgid "Order:"
+msgstr "Sıra:"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:9
+#: contrib/admin/templates/admin/submit_line.html:3
+msgid "Delete"
+msgstr "Sil"
+
+#: 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' isimli %(object_name)s nesnesini silmek, bağlantılı "
+"nesnelerin silinmesini gerektiriyor, ancak aşağıdaki nesneleri silme "
+"yetkiniz yok."
+
+#: 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 ""
+"\"%(escaped_object)s\" isimli %(object_name)s nesnesini silmek "
+"istediğinizdenemin misiniz? Aşağıdaki bağlantılı öğeler silinecek:"
+
+#: contrib/admin/templates/admin/delete_confirmation.html:26
+msgid "Yes, I'm sure"
+msgstr "Evet, eminim"
+
+#: contrib/admin/templates/admin/base.html:25
+msgid "Welcome,"
+msgstr "HoÅŸgeldin,"
+
+#: contrib/admin/templates/admin/submit_line.html:4
+msgid "Save as new"
+msgstr "Yeni olarak kaydet"
+
+#: contrib/admin/templates/admin/submit_line.html:5
+msgid "Save and add another"
+msgstr "Kaydet ve yeni bir tane ekle"
+
+#: contrib/admin/templates/admin/submit_line.html:6
+msgid "Save and continue editing"
+msgstr "Kaydet ve düzenlemeye devam et"
+
+#: contrib/admin/templates/admin/submit_line.html:7
+msgid "Save"
+msgstr "Kaydet"
+
+#: contrib/admin/templates/admin/auth/user/change_password.html:28
+#, python-format
+msgid "Enter a new password for the user <strong>%(username)s</strong>."
+msgstr "<strong>%(username)s</strong> için yeni parola girin."
+
+#: contrib/admin/templates/admin/auth/user/change_password.html:34
+#: contrib/admin/templates/admin/auth/user/add_form.html:18
+msgid "Password"
+msgstr "Parola"
+
+#: contrib/admin/templates/admin/auth/user/change_password.html:39
+#: contrib/admin/templates/admin/auth/user/add_form.html:23
+msgid "Password (again)"
+msgstr "Parola (tekrar)"
+
+#: 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 "Onaylamak için, yukarıdaki parolanın aynısını girin."
+
+#: 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 ""
+"Önce bir kullanıcı adı ve parola girin. Daha sonra daha fazla bilgi "
+"girebilirsiniz."
+
+#: contrib/admin/templates/admin/auth/user/add_form.html:12
+msgid "Username"
+msgstr "Kullanıcı"
+
+#: 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 "Parola deÄŸiÅŸimi"
+
+#: contrib/admin/templates/registration/password_change_done.html:6
+#: contrib/admin/templates/registration/password_change_done.html:10
+msgid "Password change successful"
+msgstr "Parola değişimi başarılı"
+
+#: contrib/admin/templates/registration/password_change_done.html:12
+msgid "Your password was changed."
+msgstr "Parolanız değiştirildi."
+
+#: 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 "Parolayı sıfırla"
+
+#: 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 ""
+"Parolanızı mı unuttunuz? E-posta adresinizi aşağıya girin, parolanızı "
+"sıfırlayalım ve e-posta adresinize gönderelim."
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "E-mail address:"
+msgstr "E-posta adresi:"
+
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "Reset my password"
+msgstr "Parolamı sıfırla"
+
+#: contrib/admin/templates/registration/logged_out.html:8
+msgid "Thanks for spending some quality time with the Web site today."
+msgstr "Web sitesinde zaman geçirdiğiniz için teşekkür ederiz."
+
+#: contrib/admin/templates/registration/logged_out.html:10
+msgid "Log in again"
+msgstr "Tekrar giriÅŸ yap"
+
+#: contrib/admin/templates/registration/password_reset_done.html:6
+#: contrib/admin/templates/registration/password_reset_done.html:10
+msgid "Password reset successful"
+msgstr "Parola sıfırlandı."
+
+#: 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 ""
+"Yeni şifreniz, e-posta adresinize gönderildi, kısa süre içinde size "
+"ulaşacaktır."
+
+#: 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 ""
+"Güvenliğiniz için, lütfen eski parolanızı girin, sonra da yeni şifrenizi iki "
+"kere girin ve böylece doğru yazdığınızdan emin olun."
+
+#: contrib/admin/templates/registration/password_change_form.html:17
+msgid "Old password:"
+msgstr "Eski parola:"
+
+#: contrib/admin/templates/registration/password_change_form.html:19
+msgid "New password:"
+msgstr "Yeni parola:"
+
+#: contrib/admin/templates/registration/password_change_form.html:21
+msgid "Confirm password:"
+msgstr "Parolayı onayla:"
+
+#: contrib/admin/templates/registration/password_change_form.html:23
+msgid "Change my password"
+msgstr "Parolamı değiştir"
+
+#: contrib/admin/templates/registration/password_reset_email.html:2
+msgid "You're receiving this e-mail because you requested a password reset"
+msgstr "Bu e-postayı aldınız çünkü "
+
+#: contrib/admin/templates/registration/password_reset_email.html:3
+#, python-format
+msgid "for your user account at %(site_name)s"
+msgstr ""
+"%(site_name)s adresindeki kullanıcı hesabınız için parola sıfırlama "
+"talebinde bulundunuz."
+
+#: contrib/admin/templates/registration/password_reset_email.html:5
+#, python-format
+msgid "Your new password is: %(new_password)s"
+msgstr "Yeni parolanız: %(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 "Parolanızı değiştirmek için bu adrese gidebilirsiniz:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:11
+msgid "Your username, in case you've forgotten:"
+msgstr "Unutma ihtimaline karşı, kullanıcı adınız:"
+
+#: contrib/admin/templates/registration/password_reset_email.html:13
+msgid "Thanks for using our site!"
+msgstr "Teşekkürler!"
+
+#: contrib/admin/templates/registration/password_reset_email.html:15
+#, python-format
+msgid "The %(site_name)s team"
+msgstr "%(site_name)s Ekibi"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:3
+msgid "Bookmarklets"
+msgstr "Kısayollar"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:5
+msgid "Documentation bookmarklets"
+msgstr "Doküman kısayolları"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:9
+msgid ""
+"\n"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">Kısayolları kullanabilmek için, bağlantıyı tarayıcınızdaki "
+"araç çubuğuna sürükleyin, ya da sağ tıklayıp sık kullanılan adresler "
+"listenize ekleyin. Bazı kısayollar, uygulamayı çalıştıran sunucu ile aynı "
+"adreste bulunan istemciler tarafından kullanılabilir.</p>\n"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:19
+msgid "Documentation for this page"
+msgstr "Bu sayfa için dokümantasyon"
+
+#: 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 "Sizi, bu sayfayı üreten betiğin dokümantasyonuna yönlendirir."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:22
+msgid "Show object ID"
+msgstr "Nesne numarasını göster"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:23
+msgid ""
+"Shows the content-type and unique ID for pages that represent a single "
+"object."
+msgstr ""
+"Tek bir nesneyi temsil eden sayfaların içerik türünü ve numarasını gösterir."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:25
+msgid "Edit this object (current window)"
+msgstr "Nesneyi düzenle (aynı pencerede)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:26
+msgid "Jumps to the admin page for pages that represent a single object."
+msgstr "Tek bir nesneyi temsil eden sayfaların yönetim sayfasını gösterir."
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:28
+msgid "Edit this object (new window)"
+msgstr "Nesneyi düzenle (yeni pencerede)"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:29
+msgid "As above, but opens the admin page in a new window."
+msgstr "Yukarıdaki gibi, ancak yönetim sayfasını yeni bir pencerede açar."
+
+#: contrib/contenttypes/models.py:36
+msgid "python model class name"
+msgstr "python model sınıfı"
+
+#: contrib/contenttypes/models.py:39
+msgid "content type"
+msgstr "içerik türü"
+
+#: contrib/contenttypes/models.py:40
+msgid "content types"
+msgstr "içerik türleri"
+
+#: contrib/auth/views.py:39
+msgid "Logged out"
+msgstr "Çıkış yapıldı"
+
+#: contrib/auth/models.py:38 contrib/auth/models.py:57
+msgid "name"
+msgstr "isim"
+
+#: contrib/auth/models.py:40
+msgid "codename"
+msgstr "takma ad"
+
+#: contrib/auth/models.py:42
+msgid "permission"
+msgstr "izin"
+
+#: contrib/auth/models.py:43 contrib/auth/models.py:58
+msgid "permissions"
+msgstr "izinler"
+
+#: contrib/auth/models.py:60
+msgid "group"
+msgstr "grup"
+
+#: contrib/auth/models.py:61 contrib/auth/models.py:100
+msgid "groups"
+msgstr "gruplar"
+
+#: contrib/auth/models.py:90
+msgid "username"
+msgstr "kullanıcı adı"
+
+#: contrib/auth/models.py:90
+msgid ""
+"Required. 30 characters or fewer. Alphanumeric characters only (letters, "
+"digits and underscores)."
+msgstr ""
+"Gerekli. 30 karakter ya da da az olmalı. Alfanumerik (harf, rakam ve alt "
+"çizgi) karakterler kullanılabilir."
+
+#: contrib/auth/models.py:91
+msgid "first name"
+msgstr "isim"
+
+#: contrib/auth/models.py:92
+msgid "last name"
+msgstr "soyisim"
+
+#: contrib/auth/models.py:93
+msgid "e-mail address"
+msgstr "e-posta adresi"
+
+#: contrib/auth/models.py:94
+msgid "password"
+msgstr "parola"
+
+#: contrib/auth/models.py:94
+msgid ""
+"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
+"password form</a>."
+msgstr ""
+
+#: contrib/auth/models.py:95
+msgid "staff status"
+msgstr "yönetici modu"
+
+#: contrib/auth/models.py:95
+msgid "Designates whether the user can log into this admin site."
+msgstr "Kullanıcının yönetici sayfasına girip giremeyeceğini belirler."
+
+#: contrib/auth/models.py:96
+msgid "active"
+msgstr "aktif"
+
+#: contrib/auth/models.py:96
+msgid ""
+"Designates whether this user can log into the Django admin. Unselect this "
+"instead of deleting accounts."
+msgstr ""
+"Kullanıcının Django yönetim sayfasına girip giremeyeceğini belirler. "
+"Kullanıcı hesabı silmek yerine işareti kaldırın."
+
+#: contrib/auth/models.py:97
+msgid "superuser status"
+msgstr "süper kullanıcı modu"
+
+#: contrib/auth/models.py:97
+msgid ""
+"Designates that this user has all permissions without explicitly assigning "
+"them."
+msgstr ""
+"Kullanıcının tek tek hak atamasına gerek kalmadan tüm haklara sahip olup "
+"olamayacağını belirler."
+
+#: contrib/auth/models.py:98
+msgid "last login"
+msgstr "son ziyaret"
+
+#: contrib/auth/models.py:99
+msgid "date joined"
+msgstr "kayıt tarihi"
+
+#: 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 ""
+"Özel olarak atanmış hakların yanı sıra, kullanıcının üyesi olduğu grupların "
+"hakları alır."
+
+#: contrib/auth/models.py:102
+msgid "user permissions"
+msgstr "kullanıcı izinleri"
+
+#: contrib/auth/models.py:105
+msgid "user"
+msgstr "kullanıcı"
+
+#: contrib/auth/models.py:106
+msgid "users"
+msgstr "kullanıcılar"
+
+#: contrib/auth/models.py:111
+msgid "Personal info"
+msgstr "KiÅŸisel bilgiler"
+
+#: contrib/auth/models.py:112
+msgid "Permissions"
+msgstr "Ä°zinler"
+
+#: contrib/auth/models.py:113
+msgid "Important dates"
+msgstr "Önemli tarihler"
+
+#: contrib/auth/models.py:114
+msgid "Groups"
+msgstr "Gruplar"
+
+#: contrib/auth/models.py:258
+msgid "message"
+msgstr "mesaj"
+
+#: contrib/auth/forms.py:17 contrib/auth/forms.py:138
+msgid "The two password fields didn't match."
+msgstr "İki parola alanı uyuşmuyor."
+
+#: contrib/auth/forms.py:25
+msgid "A user with that username already exists."
+msgstr "Bu isimde bir kullanıcı zaten var."
+
+#: contrib/auth/forms.py:53
+msgid ""
+"Your Web browser doesn't appear to have cookies enabled. Cookies are "
+"required for logging in."
+msgstr ""
+"Web tarayıcınızın çerezleri desteklemediği görülüyor. Çerezler giriş için "
+"gerekli."
+
+#: contrib/auth/forms.py:62
+msgid "This account is inactive."
+msgstr "Bu hesap aktif deÄŸil."
+
+#: contrib/auth/forms.py:85
+msgid ""
+"That e-mail address doesn't have an associated user account. Are you sure "
+"you've registered?"
+msgstr ""
+"Bu e-posta hesabıyla ilişkili kullanıcı bulunmuyor. Kayıtlı olduğunuzdan "
+"emin misiniz?"
+
+#: contrib/auth/forms.py:117
+msgid "The two 'new password' fields didn't match."
+msgstr "İki parola alanı uyuşmuyor."
+
+#: contrib/auth/forms.py:124
+msgid "Your old password was entered incorrectly. Please enter it again."
+msgstr "Eski parolanız hatalı. Lütfen tekrar girin."
+
+#: contrib/localflavor/uk/forms.py:18
+msgid "Enter a postcode. A space is required between the two postcode parts."
+msgstr "Posta kodu girin. Posta kodunun iki kısmı arasında bir boşluk bırakın."
+
+#: 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 "oturum anahtarı"
+
+#: contrib/sessions/models.py:52
+msgid "session data"
+msgstr "oturum bilgisi"
+
+#: contrib/sessions/models.py:53
+msgid "expire date"
+msgstr "bitiÅŸ tarihi"
+
+#: contrib/sessions/models.py:57
+msgid "session"
+msgstr "oturum"
+
+#: contrib/sessions/models.py:58
+msgid "sessions"
+msgstr "oturumlar"
+
+#: contrib/flatpages/models.py:8
+msgid ""
+"Example: '/about/contact/'. Make sure to have leading and trailing slashes."
+msgstr ""
+"Örnek: '/about/contact/'. Başında ve sonunda bölü işareti olduğundan emin "
+"olun."
+
+#: contrib/flatpages/models.py:9
+msgid "title"
+msgstr "başlık"
+
+#: contrib/flatpages/models.py:10
+msgid "content"
+msgstr "içerik"
+
+#: contrib/flatpages/models.py:11
+msgid "enable comments"
+msgstr "yorumlara izin ver"
+
+#: contrib/flatpages/models.py:12
+msgid "template name"
+msgstr "şablon adı"
+
+#: contrib/flatpages/models.py:13
+msgid ""
+"Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
+"will use 'flatpages/default.html'."
+msgstr ""
+"Örnek: 'flatpages/contact_page.html'. Eğer birşey yazılmazsa, sistem "
+"otomatik olarak 'flatpages/default.html' kullanacak."
+
+#: contrib/flatpages/models.py:14
+msgid "registration required"
+msgstr "kayıt gerekli"
+
+#: contrib/flatpages/models.py:14
+msgid "If this is checked, only logged-in users will be able to view the page."
+msgstr "Bu seçili ise, sadece kayıtlı kullanıcılar sayfayı görüntüleyebilir."
+
+#: contrib/flatpages/models.py:18
+msgid "flat page"
+msgstr "düz sayfa"
+
+#: contrib/flatpages/models.py:19
+msgid "flat pages"
+msgstr "düz sayfalar"
+
+#: utils/dates.py:6
+msgid "Monday"
+msgstr "Pazartesi"
+
+#: utils/dates.py:6
+msgid "Tuesday"
+msgstr "Salı"
+
+#: utils/dates.py:6
+msgid "Wednesday"
+msgstr "Çarşamba"
+
+#: utils/dates.py:6
+msgid "Thursday"
+msgstr "PerÅŸembe"
+
+#: utils/dates.py:6
+msgid "Friday"
+msgstr "Cuma"
+
+#: utils/dates.py:7
+msgid "Saturday"
+msgstr "Cumartesi"
+
+#: utils/dates.py:7
+msgid "Sunday"
+msgstr "Pazar"
+
+#: utils/dates.py:14
+msgid "January"
+msgstr "Ocak"
+
+#: utils/dates.py:14
+msgid "February"
+msgstr "Åžubat"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "March"
+msgstr "Mart"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "April"
+msgstr "Nisan"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "May"
+msgstr "Mayıs"
+
+#: utils/dates.py:14 utils/dates.py:27
+msgid "June"
+msgstr "Haziran"
+
+#: utils/dates.py:15 utils/dates.py:27
+msgid "July"
+msgstr "Temmuz"
+
+#: utils/dates.py:15
+msgid "August"
+msgstr "AÄŸustos"
+
+#: utils/dates.py:15
+msgid "September"
+msgstr "Eylül"
+
+#: utils/dates.py:15
+msgid "October"
+msgstr "Ekim"
+
+#: utils/dates.py:15
+msgid "November"
+msgstr "Kasım"
+
+#: utils/dates.py:16
+msgid "December"
+msgstr "Aralık"
+
+#: utils/dates.py:19
+msgid "jan"
+msgstr "oca"
+
+#: utils/dates.py:19
+msgid "feb"
+msgstr "ÅŸub"
+
+#: utils/dates.py:19
+msgid "mar"
+msgstr "mar"
+
+#: utils/dates.py:19
+msgid "apr"
+msgstr "nis"
+
+#: utils/dates.py:19
+msgid "may"
+msgstr "may"
+
+#: utils/dates.py:19
+msgid "jun"
+msgstr "haz"
+
+#: utils/dates.py:20
+msgid "jul"
+msgstr "tem"
+
+#: utils/dates.py:20
+msgid "aug"
+msgstr "aÄŸu"
+
+#: utils/dates.py:20
+msgid "sep"
+msgstr "eyl"
+
+#: utils/dates.py:20
+msgid "oct"
+msgstr "eki"
+
+#: utils/dates.py:20
+msgid "nov"
+msgstr "kas"
+
+#: utils/dates.py:20
+msgid "dec"
+msgstr "ara"
+
+#: utils/dates.py:27
+msgid "Jan."
+msgstr "Oca."
+
+#: utils/dates.py:27
+msgid "Feb."
+msgstr "Åžub."
+
+#: utils/dates.py:28
+msgid "Aug."
+msgstr "AÄŸu."
+
+#: utils/dates.py:28
+msgid "Sept."
+msgstr "Eyl."
+
+#: utils/dates.py:28
+msgid "Oct."
+msgstr "Eki."
+
+#: utils/dates.py:28
+msgid "Nov."
+msgstr "Kas."
+
+#: utils/dates.py:28
+msgid "Dec."
+msgstr "Ara."
+
+#: utils/timesince.py:12
+msgid "year"
+msgid_plural "years"
+msgstr[0] "yıl"
+msgstr[1] "yıl"
+
+#: utils/timesince.py:13
+msgid "month"
+msgid_plural "months"
+msgstr[0] "ay"
+msgstr[1] "ay"
+
+#: utils/timesince.py:14
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] "hafta"
+msgstr[1] "hafta"
+
+#: utils/timesince.py:15
+msgid "day"
+msgid_plural "days"
+msgstr[0] "gün"
+msgstr[1] "gün"
+
+#: utils/timesince.py:16
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "saat"
+msgstr[1] "saat"
+
+#: utils/timesince.py:17
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "dakika"
+msgstr[1] "dakika"
+
+#: utils/dateformat.py:40
+msgid "p.m."
+msgstr ""
+
+#: utils/dateformat.py:41
+msgid "a.m."
+msgstr ""
+
+#: utils/dateformat.py:46
+msgid "PM"
+msgstr ""
+
+#: utils/dateformat.py:47
+msgid "AM"
+msgstr ""
+
+#: utils/dateformat.py:95
+msgid "midnight"
+msgstr ""
+
+#: utils/dateformat.py:97
+msgid "noon"
+msgstr ""
+
+#: utils/translation/trans_real.py:362
+msgid "DATE_FORMAT"
+msgstr "DATE_FORMAT"
+
+#: utils/translation/trans_real.py:363
+msgid "DATETIME_FORMAT"
+msgstr "DATETIME_FORMAT"
+
+#: utils/translation/trans_real.py:364
+msgid "TIME_FORMAT"
+msgstr "TIME_FORMAT"
+
+#: utils/translation/trans_real.py:380
+msgid "YEAR_MONTH_FORMAT"
+msgstr "YEAR_MONTH_FORMAT"
+
+#: utils/translation/trans_real.py:381
+msgid "MONTH_DAY_FORMAT"
+msgstr "MONTH_DAY_FORMAT"
+
+#: template/defaultfilters.py:491
+msgid "yes,no,maybe"
+msgstr "evet,hayır,olabilir"
+
+#~ msgid ""
+#~ "Please enter a different %s. The one you entered is already being used "
+#~ "for %s."
+#~ msgstr ""
+#~ "Lütfen farklı bir %s girin. Girdiğiniz, %s tarihinde bir kez kullanılmış."
+
+#~ msgid "Use '[algo]$[salt]$[hexdigest]'"
+#~ msgstr "'[algo]$[salt]$[hexdigest]' formatında"
+
+#~ msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+#~ msgstr "<a href=\"/password_reset/\">Åžifrenizi mi unuttunuz?</a>"
diff --git a/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..1119203
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..35928b9
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.po
@@ -0,0 +1,109 @@
+# Django 0.95
+# Copyright (C) 2006 Django
+# This file is distributed under the same license as the Django package.
+# Bahadır Kandemir <bahadir@pardus.org.tr>, 2006.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Django 0.95\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-09-30 01:31+0300\n"
+"PO-Revision-Date: 2006-09-30 01:31+0300\n"
+"Last-Translator: Bahadır Kandemir <bahadir@pardus.org.tr>\n"
+"Language-Team: Bahadır Kandemir <bahadir@pardus.org.tr>\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 "Mevcut %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:41
+msgid "Choose all"
+msgstr "Hepsini seç"
+
+#: contrib/admin/media/js/SelectFilter2.js:46
+msgid "Add"
+msgstr "Ekle"
+
+#: contrib/admin/media/js/SelectFilter2.js:48
+msgid "Remove"
+msgstr "Kaldır"
+
+#: contrib/admin/media/js/SelectFilter2.js:53
+#, perl-format
+msgid "Chosen %s"
+msgstr "Seçilen %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:54
+msgid "Select your choice(s) and click "
+msgstr "Seçiminizi yapın ve tıklayın "
+
+#: contrib/admin/media/js/SelectFilter2.js:59
+msgid "Clear all"
+msgstr "Hepsini temizle"
+
+#: 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 "Ocak Şubat Mart Nisan Mayıs Haziran Temmuz Ağustos Eylül Ekim Kasım "
+"Aralık"
+
+#: contrib/admin/media/js/dateparse.js:27
+msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
+msgstr "Pazar Pazartesi Salı Çarşamba Perşembe Cuma Cumartesi"
+
+#: contrib/admin/media/js/calendar.js:25
+msgid "S M T W T F S"
+msgstr "P P S Ç P C C"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
+msgid "Now"
+msgstr "Åžimdi"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
+msgid "Clock"
+msgstr "Saat"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77
+msgid "Choose a time"
+msgstr "Saat seçin"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
+msgid "Midnight"
+msgstr "Geceyarısı"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
+msgid "6 a.m."
+msgstr "Sabah 6"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
+msgid "Noon"
+msgstr "Öğle"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
+msgid "Cancel"
+msgstr "Ä°ptal"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162
+msgid "Today"
+msgstr "Bugün"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114
+msgid "Calendar"
+msgstr "Takvim"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
+msgid "Yesterday"
+msgstr "Dün"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
+msgid "Tomorrow"
+msgstr "Yarın"
diff --git a/google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..2c7a7cd
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.po
new file mode 100644
index 0000000..3974a86
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.po
@@ -0,0 +1,1970 @@
+# Django, ukrainian translation.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Denis Kuzmichyov <kuzmichyov@gmail.com>, 2006.
+#
+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: 2006-03-20 21:18+0300\n"
+"Last-Translator: Denis Kuzmichyov <kuzmichyov@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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 "рейтінг #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 "рахунок"
+
+#: 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
+#, 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
+msgid "deletion date"
+msgstr "Дата видаленнÑ"
+
+#: contrib/comments/models.py:280
+#, fuzzy
+msgid "moderator deletion"
+msgstr "Видалено модератором"
+
+#: contrib/comments/models.py:281
+#, fuzzy
+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 "Ðевірний ID коментарю"
+
+#: 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] ""
+"Цей коментар доданий кориÑтувачем, Ñкий додав меньше ніж %(count)s коментарÑ"
+msgstr[1] ""
+"Цей коментар доданий кориÑтувачем, Ñкий додав меньше ніж %(count)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 "Тільки 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' -- 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/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
+#, fuzzy
+msgid "Ratings"
+msgstr "рейтінг #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 "Коментар"
+
+#: 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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\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
+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 "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/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 ""
+"ЗдаєтьÑÑ Ð²Ð°Ñˆ броузер не Ñконфігурований приймати 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 ""
+"Ваша e-mail адреÑа не Ñ” вашим ім'Ñм кориÑтувача. ЗаміÑÑ‚ÑŒ Ñпробуйте '%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 "Одне або більше %(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: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 ""
+
+#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "РÑдок (до %(maxlength)s Ñимволів)"
+
+#: 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 "E-mail адреÑа"
+
+#: 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 "текÑÑ‚ 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 "ДокументаціÑ"
+
+#: 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 "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 <em>(500)</em>"
+msgstr "Помилка Ñервера <em>(500)</em>"
+
+#: 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 ""
+"ВідбулаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°. Її було повідомлено адмініÑтраторам Ñайту e-mail'ом Ñ– "
+"вона повинна бути незабаром виправлена. ДÑкуємо за ваше терпіннÑ."
+
+#: 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
+#, python-format
+msgid "Add %(name)s"
+msgstr "Додати %(name)s"
+
+#: contrib/admin/templates/admin/login.html:22
+msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "<a href=\"/password_reset/\">Забули ваш пароль</a>?"
+
+#: 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 "За %(title)s"
+
+#: 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 ""
+"Забули Ñвій пароль? Введить Ñвою e-mail адреÑу Ñ– ми перевÑтановимо ваш "
+"пароль і надішлемо вам новий поштою."
+
+#: 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 "Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ рахунку кориÑтувача на Ñайті %(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"
+"<p class=\"help\">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\").</p>\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 "Показати 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 ""
+"Показує content-type та унікальний 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/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 ""
+"Це може бути або абÑолютний шлÑÑ… (Ñк вище), або повний URL, Ñкий починаєтьÑÑ "
+"з '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 ""
+"Приклад: '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/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 "e-mail адреÑа"
+
+#: 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
+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
+#, 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
+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
+#, 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 "Уведіть коректні e-mail адреÑи, розділені комами."
+
+#: 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 "Уведіть коректну дату у форматі РРРР-ММ-ДД."
+
+#: 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 "URL %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 "URL %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 ""
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Погано Ñформований XML: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Ðевірний URL: %s"
+
+#: 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 ""
+
+#: core/validators.py:363
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr ""
+
+#: 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
+msgid "This value must be either True or False."
+msgstr ""
+
+#: db/models/fields/__init__.py:385
+#, fuzzy
+msgid "This field cannot be null."
+msgstr "Це поле обов'Ñзкове."
+
+#: 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 ""
+
+#: 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 ""
+
+#: 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] "ПереконайтеÑÑ, що ваш текÑÑ‚ меньше, ніж %s Ñимвол"
+msgstr[1] "ПереконайтеÑÑ, що ваш текÑÑ‚ меньше, ніж %s Ñимволів"
+
+#: 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 "Зробить коректний вибір; '%(data)s' немає у %(choices)s."
+
+#: 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 "Уведіть ціле чиÑло між -32,768 та 32,768."
+
+#: 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 "Уведіть ціле чиÑло між 0 та 32,767."
+
+#: template/defaultfilters.py:379
+msgid "yes,no,maybe"
+msgstr "так,ні,можливо"
+
+#~ msgid "Comments"
+#~ msgstr "Коментарі"
+
+#~ msgid "String (up to 50)"
+#~ msgstr "РÑдок (до 50 Ñимволів)"
+
+#~ msgid "label"
+#~ msgstr "мітка"
+
+#~ msgid "package"
+#~ msgstr "пакунок"
+
+#~ msgid "packages"
+#~ msgstr "пакунки"
diff --git a/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..0d6bf90
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.po
new file mode 100644
index 0000000..2552b67
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.po
@@ -0,0 +1,1879 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django v1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-16 10:10+0200\n"
+"PO-Revision-Date: 2006-09-01 22:05+0800\n"
+"Last-Translator: limodou <limodou@gmail.com>\n"
+"Language-Team: Simplified Chinese <limodou@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Chinese\n"
+"X-Poedit-Country: CHINA\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"Plural-Forms: nplurals=1; plural=0;\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
+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 "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 "被 %(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 "无效评论ID"
+
+#: 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] ""
+"此评论由一个å‘表过少于 %(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 "åªå…许张贴"
+
+#: 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' å‚æ•° -- 对象 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/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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>ç”± %s:</h3>\n"
+"<ul>\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 "对象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/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 "看上去你的æµè§ˆå™¨æ²¡æœ‰é…ç½®æˆæŽ¥å— cookie 。请å…许 cookie,é‡æ–°è£…入本页é¢ï¼Œå†è¯•ä¸€æ¬¡ã€‚"
+
+#: 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 "一个或多个 %(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: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 "布尔(True或False)"
+
+#: contrib/admin/views/doc.py:279
+#: contrib/admin/views/doc.py:296
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "字符串(最长 %(maxlength)s)"
+
+#: 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 "布尔(True, False或None)"
+
+#: 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 "URL"
+
+#: contrib/admin/views/doc.py:301
+msgid "U.S. state (two uppercase letters)"
+msgstr "美国州å(两个大写字æ¯)"
+
+#: contrib/admin/views/doc.py:302
+msgid "XML text"
+msgstr "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 "文档"
+
+#: 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 "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 <em>(500)</em>"
+msgstr "æœåŠ¡å™¨é”™è¯¯ <em>(500)</em>"
+
+#: 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 <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "<a href=\"/password_reset/\">忘记你的密ç </a>?"
+
+#: 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 " ç”± %(title)s"
+
+#: 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] "请改正下é¢çš„错误。"
+
+#: 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 "邮箱地å€ï¼š"
+
+#: 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"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">为安装书签,拖动链接到你的工具æ¡ï¼Œ\n"
+"或å³å‡»é¼ æ ‡ï¼Œç„¶åŽå¢žåŠ åˆ°ä½ çš„书签上。现在你å¯ä»¥ä»Žè¿™ä¸ªç«™\n"
+"点的任何页é¢é€‰æ‹©ä¹¦ç­¾ã€‚注æ„一些这样的书签è¦æ±‚你从一个\n"
+"被指定为\"内部\"的计算机上æ¥è¿›è¡ŒæŸ¥çœ‹ï¼ˆå¦‚果你ä¸ç¡®å®šæ˜¯\n"
+"å¦ä½ çš„计算机是å¦æ˜¯\"内部\"的,åŒä½ çš„系统管ç†å‘˜æ²Ÿé€šä¸€\n"
+"下。</p>\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 "对于任何页é¢è·³è½¬åˆ°ç”Ÿæˆè¿™ä¸ªé¡µé¢çš„view所在的文件。"
+
+#: 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 "用于那些表现å•ä¸ªå¯¹è±¡çš„页é¢æ˜¾ç¤º content-type 和唯一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/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: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
+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/auth/forms.py:30
+msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."
+msgstr "ä½ çš„Webæµè§ˆå™¨å¥½è±¡ä¸å…许使用cookie。登录需è¦ä½¿ç”¨cookie。"
+
+#: contrib/contenttypes/models.py:25
+msgid "python model class name"
+msgstr "python模å—ç±»å"
+
+#: 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 "session键字"
+
+#: contrib/sessions/models.py:36
+msgid "session data"
+msgstr "sessionæ•°æ®"
+
+#: contrib/sessions/models.py:37
+msgid "expire date"
+msgstr "过期日期"
+
+#: contrib/sessions/models.py:41
+msgid "session"
+msgstr "会è¯(session)"
+
+#: contrib/sessions/models.py:42
+msgid "sessions"
+msgstr "会è¯(session)"
+
+#: 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 "DATE_FORMAT"
+
+#: utils/translation.py:361
+msgid "DATETIME_FORMAT"
+msgstr "DATETIME_FORMAT"
+
+#: utils/translation.py:362
+msgid "TIME_FORMAT"
+msgstr "TIME_FORMAT"
+
+#: 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] "å¹´"
+
+#: utils/timesince.py:13
+msgid "month"
+msgid_plural "months"
+msgstr[0] "月"
+
+#: utils/timesince.py:14
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] "周"
+
+#: utils/timesince.py:15
+msgid "day"
+msgid_plural "days"
+msgstr[0] "天"
+
+#: utils/timesince.py:16
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "å°æ—¶"
+
+#: utils/timesince.py:17
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "分钟"
+
+#: 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 "请输入一个有效的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 "URL %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 "URL %s 指å‘çš„ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„ QuickTime 视频。"
+
+#: core/validators.py:171
+msgid "A valid URL is required."
+msgstr "需è¦æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„URL。"
+
+#: core/validators.py:185
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"需è¦æœ‰æ•ˆçš„HTML。详细的错误是:\n"
+"%s"
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "æ ¼å¼é”™è¯¯çš„ XML: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "无效 URL: %s"
+
+#: 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 "输入一个有效的 U.S. 州缩写。"
+
+#: 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 ä¸å…许在这里出现。"
+
+#: 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 个数字。 "
+
+#: 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 个å°æ•°ä½ã€‚ "
+
+#: 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 %(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 "请关闭未关闭的 %(tag)s 标签从第 %(line)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 "在行 %(line)s 的\"%(attr)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 "带有这个 %(type)s çš„ %(object)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: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
+msgid "This value must be either True or False."
+msgstr "这个值必须是 True 或 False。"
+
+#: db/models/fields/__init__.py:385
+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
+msgid "Separate multiple IDs with commas."
+msgstr "用逗å·åˆ†éš”多个ID。"
+
+#: db/models/fields/related.py:581
+msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr "按下 \"Control\",或者在Mac上按 \"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 ID。值 %(value)r 无效。"
+
+#: 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 个字符。"
+
+#: 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 "选择一个有效的选项: '%(data)s' ä¸åœ¨ %(choices)s 中。"
+
+#: 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 "输入在 -32,768 到 32,767 之间的一个整数。"
+
+#: 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 "输入在 0 到 32,767 之间的一个整数。"
+
+#: template/defaultfilters.py:379
+msgid "yes,no,maybe"
+msgstr "是ã€å¦ã€ä¹Ÿè®¸"
+
+#~ msgid "Comment"
+#~ msgstr "评论"
+#~ msgid "Comments"
+#~ msgstr "评论"
+#~ msgid "String (up to 50)"
+#~ msgstr "整数(最长50)"
+#~ msgid "label"
+#~ msgstr "标签"
+#~ msgid "package"
+#~ msgstr "包"
+#~ msgid "packages"
+#~ msgstr "包"
+
+#, fuzzy
+#~ msgid "count"
+#~ msgstr "内容"
+
diff --git a/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..ec7580a
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..610e61d
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po
@@ -0,0 +1,105 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Django 0.95\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-03-21 18:43+0800\n"
+"PO-Revision-Date: 2006-09-25 08:35+0800\n"
+"Last-Translator: limodou <limodou@gmail.com>\n"
+"Language-Team: limodou <limodou@gmail.com>\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
+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
+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: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/zh_TW/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..891e4bf
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.mo
Binary files differ
diff --git a/google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.po
new file mode 100644
index 0000000..70ff1bd
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.po
@@ -0,0 +1,1974 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django v1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-16 10:13+0200\n"
+"PO-Revision-Date: 2005-12-28 23:30+0800\n"
+"Last-Translator: yungyuc <yyc@seety.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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
+#, fuzzy
+msgid "karma scores"
+msgstr "Karma 分數"
+
+#: 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
+#, 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 "被 %r 標識"
+
+#: contrib/comments/models.py:278
+msgid "deletion date"
+msgstr "刪除日期"
+
+#: contrib/comments/models.py:280
+#, fuzzy
+msgid "moderator deletion"
+msgstr "仲è£åˆªé™¤"
+
+#: contrib/comments/models.py:281
+#, fuzzy
+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 "無效的評論 ID"
+
+#: 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] ""
+"張貼此評論的使用者之所有評論少於 %(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' åƒæ•¸ -- 物件 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 "此評論表單沒有æä¾› 'preview' 或 '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 "使用者å稱:"
+
+#: 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
+#, fuzzy
+msgid "Ratings"
+msgstr "等級 #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 "è©•è«–"
+
+#: 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 ""
+"<h3>By %s:</h3>\n"
+"<ul>\n"
+msgstr ""
+"<h3>ç”± %s:</h3>\n"
+"<ul>\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 "物件 id"
+
+#: 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 "è«‹å†ç™»å…¥ä¸€æ¬¡ï¼Œå› ç‚ºä½ çš„ session 已經到期。ä¸å¿…擔心: ä½ çš„æ交已被儲存。"
+
+#: 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 ""
+"看起來你的ç€è¦½å™¨æ²’有組態æˆå…許 cookie。請啟用 cookieã€é‡æ–°è¼‰å…¥æ­¤é ï¼Œç„¶å¾Œå†è©¦"
+"一次。"
+
+#: 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 "在 %(name) 裡的一個或多個 %(fieldname)s: %(obj)s"
+
+#: contrib/admin/views/main.py:475
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "在 %(name)s 裡的一個或多個 %(fieldname)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: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 "布林值 (True 或 False)"
+
+#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296
+#, python-format
+msgid "String (up to %(maxlength)s)"
+msgstr "字串 (最長到 %(maxlength)s)"
+
+#: 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 "布林值 (True, False 或 None)"
+
+#: 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 "URL"
+
+#: contrib/admin/views/doc.py:301
+msgid "U.S. state (two uppercase letters)"
+msgstr "U.S. å·žå (兩個大寫字æ¯)"
+
+#: contrib/admin/views/doc.py:302
+msgid "XML text"
+msgstr "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 "文件"
+
+#: 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 "DATE_WITH_TIME_FULL"
+
+#: 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 <em>(500)</em>"
+msgstr "伺æœå™¨éŒ¯èª¤ <em>(500)</em>"
+
+#: 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
+#, python-format
+msgid "Add %(name)s"
+msgstr "新增 %(name)s"
+
+#: contrib/admin/templates/admin/login.html:22
+msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+msgstr "你 <a href=\"/password_reset/\">忘記密碼了嗎</a>?"
+
+#: 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 " 根據 %(title)s "
+
+#: 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 "é›»å­éƒµä»¶åœ°å€:"
+
+#: 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 "Bookmarklets"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:5
+msgid "Documentation bookmarklets"
+msgstr "文件 bookmarklets"
+
+#: contrib/admin/templates/admin_doc/bookmarklets.html:9
+msgid ""
+"\n"
+"<p class=\"help\">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\").</p>\n"
+msgstr ""
+"\n"
+"<p class=\"help\">è¦å®‰è£ bookmarklet,把連çµæ‹–進你的書籤工具列,或å³æ“Šè©²é€£çµ"
+"後新增到你的書籤裡。ç¾åœ¨ä½ å¯ä»¥å¾žç¶²ç«™çš„任何é é¢ä¾†é¸æ“‡ bookmarklet。注æ„其中æŸ"
+"些 bookmarklet è¦æ±‚你必須是從被稱為 \"內部\" 的電腦來檢視網站的 (如果你ä¸ç¢ºå®š"
+"你的電腦是å¦åœ¨ \"內部\",那就和你的系統管ç†å“¡è«‡è«‡)。</p>\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 "顯示物件 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 "顯示用來表示單一物件的é é¢ content-type 與唯一 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/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: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 ""
+"範例: '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/auth/models.py:13 contrib/auth/models.py:26
+msgid "name"
+msgstr "å稱"
+
+#: contrib/auth/models.py:15
+msgid "codename"
+msgstr "codename"
+
+#: 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 "使用 '[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
+#, 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 "你的網é ç€è¦½å™¨çœ‹ä¾†æ²’有啟用 cookie。若è¦ç™»å…¥å‰‡éœ€ä½¿ç”¨ cookie。"
+
+#: contrib/contenttypes/models.py:25
+#, fuzzy
+msgid "python model class name"
+msgstr "python 模組å稱"
+
+#: 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 "session éµå€¼"
+
+#: contrib/sessions/models.py:36
+msgid "session data"
+msgstr "session 資料"
+
+#: contrib/sessions/models.py:37
+msgid "expire date"
+msgstr "到期日期"
+
+#: contrib/sessions/models.py:41
+msgid "session"
+msgstr "session"
+
+#: contrib/sessions/models.py:42
+msgid "sessions"
+msgstr "sessions"
+
+#: 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 "DATE_FORMAT"
+
+#: utils/translation.py:361
+msgid "DATETIME_FORMAT"
+msgstr "DATETIME_FORMAT"
+
+#: utils/translation.py:362
+msgid "TIME_FORMAT"
+msgstr "TIME_FORMAT"
+
+#: 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
+#, 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
+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
+#, 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
+#, fuzzy
+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 "URL %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 "URL %s 未指å‘有效的 QuickTime 視åƒã€‚"
+
+#: core/validators.py:171
+msgid "A valid URL is required."
+msgstr "必須是有效的 URL。"
+
+#: core/validators.py:185
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"必須是有效的 HTML。具體的錯誤是:\n"
+"%s"
+
+#: core/validators.py:192
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "排列錯誤的 XML: %s"
+
+#: core/validators.py:202
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "無效的 URL: %s"
+
+#: 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 "輸入有效的 U.S. å·žå簡稱。"
+
+#: 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 這樣的字眼。"
+
+#: 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 %(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 ""
+"在第 %(line)s 的 \"%(attr)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 "此 %(type)s 的 %(object)s 已存在於給定的 %(field)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:542 db/models/fields/__init__.py:553
+#: forms/__init__.py:346
+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
+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 " 以逗號分隔多個 ID。"
+
+#: db/models/fields/related.py:581
+#, fuzzy
+msgid ""
+"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr " 押下 \"Control\" 或 Mac 下的 \"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 ID。%(value)r 是無效的。"
+msgstr[1] "請輸入有效的 %(self)s ID。%(value)r 是無效的。"
+
+#: 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 個字元。"
+msgstr[1] "確定你的文字少於 %s 個字元。"
+
+#: 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 "進行有效的é¸æ“‡: '%(data)s' ä¸åœ¨ %(choices)s 裡。"
+
+#: 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 "輸入一個介於 -32,768 與 32,767 之間的整數。"
+
+#: 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 "輸入一個介於 0 與 32,767 之間的整數。"
+
+#: template/defaultfilters.py:379
+msgid "yes,no,maybe"
+msgstr "是,å¦,也許"
+
+#~ msgid "Comment"
+#~ msgstr "è©•è«–"
+
+#~ msgid "Comments"
+#~ msgstr "è©•è«–"
+
+#~ msgid "String (up to 50)"
+#~ msgstr "字串 (最長到 50)"
+
+#~ msgid "label"
+#~ msgstr "標示"
+
+#~ msgid "package"
+#~ msgstr "套件"
+
+#~ msgid "packages"
+#~ msgstr "套件"
diff --git a/google_appengine/lib/django/django/conf/project_template/__init__.py b/google_appengine/lib/django/django/conf/project_template/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/project_template/__init__.py
diff --git a/google_appengine/lib/django/django/conf/project_template/manage.py b/google_appengine/lib/django/django/conf/project_template/manage.py
new file mode 100755
index 0000000..5e78ea9
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/project_template/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/google_appengine/lib/django/django/conf/project_template/settings.py b/google_appengine/lib/django/django/conf/project_template/settings.py
new file mode 100755
index 0000000..cadb514
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/project_template/settings.py
@@ -0,0 +1,80 @@
+# Django settings for {{ project_name }} project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+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.
+
+# Local time zone for this installation. Choices can be found here:
+# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+# although not all variations may be possible on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+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'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# 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 = ''
+
+# 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/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = ''
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.middleware.doc.XViewMiddleware',
+)
+
+ROOT_URLCONF = '{{ project_name }}.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+)
diff --git a/google_appengine/lib/django/django/conf/project_template/urls.py b/google_appengine/lib/django/django/conf/project_template/urls.py
new file mode 100755
index 0000000..402dd65
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/project_template/urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ # Example:
+ # (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
+
+ # Uncomment this for admin:
+# (r'^admin/', include('django.contrib.admin.urls')),
+)
diff --git a/google_appengine/lib/django/django/conf/urls/__init__.py b/google_appengine/lib/django/django/conf/urls/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/urls/__init__.py
diff --git a/google_appengine/lib/django/django/conf/urls/defaults.py b/google_appengine/lib/django/django/conf/urls/defaults.py
new file mode 100755
index 0000000..17fe603
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/urls/defaults.py
@@ -0,0 +1,19 @@
+from django.core.urlresolvers import RegexURLPattern, RegexURLResolver
+
+__all__ = ['handler404', 'handler500', 'include', 'patterns']
+
+handler404 = 'django.views.defaults.page_not_found'
+handler500 = 'django.views.defaults.server_error'
+
+include = lambda urlconf_module: [urlconf_module]
+
+def patterns(prefix, *tuples):
+ pattern_list = []
+ for t in tuples:
+ regex, view_or_include = t[:2]
+ default_kwargs = t[2:]
+ if type(view_or_include) == list:
+ pattern_list.append(RegexURLResolver(regex, view_or_include[0], *default_kwargs))
+ else:
+ pattern_list.append(RegexURLPattern(regex, prefix and (prefix + '.' + view_or_include) or view_or_include, *default_kwargs))
+ return pattern_list
diff --git a/google_appengine/lib/django/django/conf/urls/i18n.py b/google_appengine/lib/django/django/conf/urls/i18n.py
new file mode 100755
index 0000000..00e2d60
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/urls/i18n.py
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ (r'^setlang/$', 'django.views.i18n.set_language'),
+)
diff --git a/google_appengine/lib/django/django/conf/urls/shortcut.py b/google_appengine/lib/django/django/conf/urls/shortcut.py
new file mode 100755
index 0000000..f0ed9d9
--- /dev/null
+++ b/google_appengine/lib/django/django/conf/urls/shortcut.py
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.views',
+ (r'^(?P<content_type_id>\d+)/(?P<object_id>\d+)/$', 'defaults.shortcut'),
+)
diff --git a/google_appengine/lib/django/django/contrib/__init__.py b/google_appengine/lib/django/django/contrib/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/admin/__init__.py b/google_appengine/lib/django/django/contrib/admin/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/admin/filterspecs.py b/google_appengine/lib/django/django/contrib/admin/filterspecs.py
new file mode 100755
index 0000000..8c2b821
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/filterspecs.py
@@ -0,0 +1,175 @@
+"""
+FilterSpec encapsulates the logic for displaying filters in the Django admin.
+Filters are specified in models with the "list_filter" option.
+
+Each filter subclass knows how to display a filter for a field that passes a
+certain test -- e.g. being a DateField or ForeignKey.
+"""
+
+from django.db import models
+import datetime
+
+class FilterSpec(object):
+ filter_specs = []
+ def __init__(self, f, request, params, model):
+ self.field = f
+ self.params = params
+
+ def register(cls, test, factory):
+ cls.filter_specs.append((test, factory))
+ register = classmethod(register)
+
+ def create(cls, f, request, params, model):
+ for test, factory in cls.filter_specs:
+ if test(f):
+ return factory(f, request, params, model)
+ create = classmethod(create)
+
+ def has_output(self):
+ return True
+
+ def choices(self, cl):
+ raise NotImplementedError()
+
+ def title(self):
+ return self.field.verbose_name
+
+ def output(self, cl):
+ t = []
+ if self.has_output():
+ t.append(_('<h3>By %s:</h3>\n<ul>\n') % self.title())
+
+ for choice in self.choices(cl):
+ t.append('<li%s><a href="%s">%s</a></li>\n' % \
+ ((choice['selected'] and ' class="selected"' or ''),
+ choice['query_string'] ,
+ choice['display']))
+ t.append('</ul>\n\n')
+ return "".join(t)
+
+class RelatedFilterSpec(FilterSpec):
+ def __init__(self, f, request, params, model):
+ super(RelatedFilterSpec, self).__init__(f, request, params, model)
+ if isinstance(f, models.ManyToManyField):
+ self.lookup_title = f.rel.to._meta.verbose_name
+ else:
+ self.lookup_title = f.verbose_name
+ self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to._meta.pk.name)
+ self.lookup_val = request.GET.get(self.lookup_kwarg, None)
+ self.lookup_choices = f.rel.to._default_manager.all()
+
+ def has_output(self):
+ return len(self.lookup_choices) > 1
+
+ def title(self):
+ return self.lookup_title
+
+ def choices(self, cl):
+ yield {'selected': self.lookup_val is None,
+ 'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
+ 'display': _('All')}
+ for val in self.lookup_choices:
+ pk_val = getattr(val, self.field.rel.to._meta.pk.attname)
+ yield {'selected': self.lookup_val == str(pk_val),
+ 'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}),
+ 'display': val}
+
+FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec)
+
+class ChoicesFilterSpec(FilterSpec):
+ def __init__(self, f, request, params, model):
+ super(ChoicesFilterSpec, self).__init__(f, request, params, model)
+ self.lookup_kwarg = '%s__exact' % f.name
+ self.lookup_val = request.GET.get(self.lookup_kwarg, None)
+
+ def choices(self, cl):
+ yield {'selected': self.lookup_val is None,
+ 'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
+ 'display': _('All')}
+ for k, v in self.field.choices:
+ yield {'selected': str(k) == self.lookup_val,
+ 'query_string': cl.get_query_string({self.lookup_kwarg: k}),
+ 'display': v}
+
+FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
+
+class DateFieldFilterSpec(FilterSpec):
+ def __init__(self, f, request, params, model):
+ super(DateFieldFilterSpec, self).__init__(f, request, params, model)
+
+ self.field_generic = '%s__' % self.field.name
+
+ self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)])
+
+ today = datetime.date.today()
+ one_week_ago = today - datetime.timedelta(days=7)
+ today_str = isinstance(self.field, models.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
+
+ self.links = (
+ (_('Any date'), {}),
+ (_('Today'), {'%s__year' % self.field.name: str(today.year),
+ '%s__month' % self.field.name: str(today.month),
+ '%s__day' % self.field.name: str(today.day)}),
+ (_('Past 7 days'), {'%s__gte' % self.field.name: one_week_ago.strftime('%Y-%m-%d'),
+ '%s__lte' % f.name: today_str}),
+ (_('This month'), {'%s__year' % self.field.name: str(today.year),
+ '%s__month' % f.name: str(today.month)}),
+ (_('This year'), {'%s__year' % self.field.name: str(today.year)})
+ )
+
+ def title(self):
+ return self.field.verbose_name
+
+ def choices(self, cl):
+ for title, param_dict in self.links:
+ yield {'selected': self.date_params == param_dict,
+ 'query_string': cl.get_query_string(param_dict, [self.field_generic]),
+ 'display': title}
+
+FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
+
+class BooleanFieldFilterSpec(FilterSpec):
+ def __init__(self, f, request, params, model):
+ super(BooleanFieldFilterSpec, self).__init__(f, request, params, model)
+ self.lookup_kwarg = '%s__exact' % f.name
+ self.lookup_kwarg2 = '%s__isnull' % f.name
+ self.lookup_val = request.GET.get(self.lookup_kwarg, None)
+ self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)
+
+ def title(self):
+ return self.field.verbose_name
+
+ def choices(self, cl):
+ for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')):
+ yield {'selected': self.lookup_val == v and not self.lookup_val2,
+ 'query_string': cl.get_query_string({self.lookup_kwarg: v}, [self.lookup_kwarg2]),
+ 'display': k}
+ if isinstance(self.field, models.NullBooleanField):
+ yield {'selected': self.lookup_val2 == 'True',
+ 'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
+ 'display': _('Unknown')}
+
+FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)
+
+# This should be registered last, because it's a last resort. For example,
+# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much
+# more appropriate, and the AllValuesFilterSpec won't get used for it.
+class AllValuesFilterSpec(FilterSpec):
+ def __init__(self, f, request, params, model):
+ super(AllValuesFilterSpec, self).__init__(f, request, params, model)
+ self.lookup_val = request.GET.get(f.name, None)
+ self.lookup_choices = model._meta.admin.manager.distinct().order_by(f.name).values(f.name)
+
+ def title(self):
+ return self.field.verbose_name
+
+ def choices(self, cl):
+ yield {'selected': self.lookup_val is None,
+ 'query_string': cl.get_query_string({}, [self.field.name]),
+ 'display': _('All')}
+ for val in self.lookup_choices:
+ val = str(val[self.field.name])
+ yield {'selected': self.lookup_val == val,
+ 'query_string': cl.get_query_string({self.field.name: val}),
+ 'display': val}
+FilterSpec.register(lambda f: True, AllValuesFilterSpec)
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/base.css b/google_appengine/lib/django/django/contrib/admin/media/css/base.css
new file mode 100644
index 0000000..88f7d9a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/base.css
@@ -0,0 +1,14 @@
+/*
+ DJANGO Admin
+ by Wilson Miner wilson@lawrence.com
+*/
+
+/* Block IE 5 */
+@import "null?\"\{";
+
+/* Import other styles */
+@import url('global.css');
+@import url('layout.css');
+
+/* Import patch for IE 6 Windows */
+/*\*/ @import "patch-iewin.css"; /**/ \ No newline at end of file
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/changelists.css b/google_appengine/lib/django/django/contrib/admin/media/css/changelists.css
new file mode 100644
index 0000000..4834be4
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/changelists.css
@@ -0,0 +1,50 @@
+@import url('base.css');
+
+/* CHANGELISTS */
+#changelist { position:relative; width:100%; }
+#changelist table { width:100%; }
+.change-list .filtered table { border-right:1px solid #ddd; }
+.change-list .filtered { min-height:400px; }
+.change-list .filtered { background:white url(../img/admin/changelist-bg.gif) top right repeat-y !important; }
+.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; }
+.change-list .filtered table tbody th { padding-right:1em; }
+#changelist .toplinks { border-bottom:1px solid #ccc !important; }
+#changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/admin/nav-bg.gif) 0 180% repeat-x; overflow:hidden; }
+.change-list .filtered .paginator { border-right:1px solid #ddd; }
+
+/* CHANGELIST TABLES */
+#changelist table thead th { white-space:nowrap; }
+#changelist table tbody td { border-left: 1px solid #ddd; }
+#changelist table tfoot { color: #666; }
+
+/* TOOLBAR */
+#changelist #toolbar { padding:3px; border-bottom:1px solid #ddd; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; color:#666; }
+#changelist #toolbar form input { font-size:11px; padding:1px 2px; }
+#changelist #toolbar form #searchbar { padding:2px; }
+#changelist #changelist-search img { vertical-align:middle; }
+
+/* FILTER COLUMN */
+#changelist-filter { position:absolute; top:0; right:0; z-index:1000; width:160px; border-left:1px solid #ddd; background:#efefef; margin:0; }
+#changelist-filter h2 { font-size:11px; padding:2px 5px; border-bottom:1px solid #ddd; }
+#changelist-filter h3 { font-size:12px; margin-bottom:0; }
+#changelist-filter ul { padding-left:0;margin-left:10px; }
+#changelist-filter li { list-style-type:none; margin-left:0; padding-left:0; }
+#changelist-filter a { color:#999; }
+#changelist-filter a:hover { color:#036; }
+#changelist-filter li.selected { border-left:5px solid #ccc; padding-left:5px;margin-left:-10px; }
+#changelist-filter li.selected a { color:#5b80b2 !important; }
+
+/* DATE DRILLDOWN */
+.change-list ul.toplinks { display:block; background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; border-top:1px solid white; float:left; padding:0 !important; margin:0 !important; width:100%; }
+.change-list ul.toplinks li { float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none; }
+.change-list ul.toplinks .date-back a { color:#999; }
+.change-list ul.toplinks .date-back a:hover { color:#036; }
+
+/* PAGINATOR */
+.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; }
+.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; }
+.paginator a.showall { padding:0 !important; border:none !important; }
+.paginator a.showall:hover { color:#036 !important; background:transparent !important; }
+.paginator .end { border-width:2px !important; margin-right:6px; }
+.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; }
+.paginator a:hover { color:white; background:#5b80b2; border-color:#036; }
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/dashboard.css b/google_appengine/lib/django/django/contrib/admin/media/css/dashboard.css
new file mode 100644
index 0000000..d277973
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/dashboard.css
@@ -0,0 +1,10 @@
+@import url('base.css');
+
+/* DASHBOARD */
+.dashboard .module table th { width:100%; }
+.dashboard .module table td { white-space:nowrap; }
+.dashboard .module table td a { display:block; padding-right:.6em; }
+
+/* RECENT ACTIONS MODULE */
+.module ul.actionlist { margin-left:0; }
+ul.actionlist li { list-style-type:none; } \ No newline at end of file
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/forms.css b/google_appengine/lib/django/django/contrib/admin/media/css/forms.css
new file mode 100644
index 0000000..0cfe2ff
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/forms.css
@@ -0,0 +1,60 @@
+@import url('base.css');
+@import url('widgets.css');
+
+/* FORM ROWS */
+.form-row { overflow:hidden; padding:8px 12px; font-size:11px; border-bottom:1px solid #eee; }
+.form-row img, .form-row input { vertical-align:middle; }
+form .form-row p { padding-left:0; font-size:11px; }
+
+/* FORM LABELS */
+form h4 { margin:0 !important; padding:0 !important; border:none !important; }
+label { font-weight:normal !important; color:#666; font-size:12px; }
+label.inline { margin-left:20px; }
+.required label, label.required { font-weight:bold !important; color:#333 !important; }
+
+/* RADIO BUTTONS */
+form ul.radiolist li { list-style-type:none; }
+form ul.radiolist label { float:none; display:inline; }
+form ul.inline { margin-left:0; padding:0; }
+form ul.inline li { float:left; padding-right:7px; }
+
+/* ALIGNED FIELDSETS */
+.aligned label { display:block; padding:0 1em 3px 0; float:left; width:8em; }
+.aligned label.inline { display:inline; float:none; }
+.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { width:350px; }
+form .aligned p, form .aligned ul { margin-left:7em; padding-left:30px; }
+form .aligned table p { margin-left:0; padding-left:0; }
+form .aligned p.help { padding-left:38px; }
+.aligned .vCheckboxLabel { float:none !important; display:inline; padding-left:4px; }
+.colM .aligned .vLargeTextField, colM .aligned .vXMLLargeTextField { width:610px; }
+.checkbox-row p.help { margin-left:0; padding-left:0 !important; }
+
+/* WIDE FIELDSETS */
+.wide label { width:15em !important; }
+form .wide p { margin-left:15em; }
+form .wide p.help { padding-left:38px; }
+.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { width:450px; }
+
+/* COLLAPSED FIELDSETS */
+fieldset.collapsed * { display:none; }
+fieldset.collapsed h2, fieldset.collapsed { display:block !important; }
+fieldset.collapsed h2 { background-image:url(../img/admin/nav-bg.gif); background-position:bottom left; color:#999; }
+fieldset.collapsed .collapse-toggle { padding:3px 5px !important; background:transparent; display:inline !important;}
+
+/* MONOSPACE TEXTAREAS */
+fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
+
+/* SUBMIT ROW */
+.submit-row { padding:5px 7px; text-align:right; background:white url(../img/admin/nav-bg.gif) 0 100% repeat-x; border:1px solid #ccc; margin:5px 0; }
+.submit-row input { margin:0 0 0 5px; }
+.submit-row p { margin-top:0.3em; }
+.submit-row .deletelink { background:url(../img/admin/icon_deletelink.gif) 0 50% no-repeat; padding-left:14px; }
+
+/* CUSTOM FORM FIELDS */
+.vSelectMultipleField { vertical-align:top !important; }
+.vCheckboxField { border:none; }
+.vDateField, .vTimeField { margin-right:2px; }
+.vURLField { width:30em; }
+.vLargeTextField, .vXMLLargeTextField { width:48em; }
+.flatpages-flatpage #id_content { height:40.2em; }
+.module table .vPositiveSmallIntegerField { width:2.2em; }
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/global.css b/google_appengine/lib/django/django/contrib/admin/media/css/global.css
new file mode 100644
index 0000000..d50601b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/global.css
@@ -0,0 +1,141 @@
+body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
+
+/* LINKS */
+a:link, a:visited { color: #5b80b2; text-decoration:none; }
+a:hover { color: #036; }
+a img { border:none; }
+
+/* GLOBAL DEFAULTS */
+p, ol, ul, dl { margin:.2em 0 .8em 0; }
+p { padding:0; line-height:140%; }
+
+h1,h2,h3,h4,h5 { font-weight:bold; }
+h1 { font-size:18px; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; }
+h2 { font-size:16px; margin:1em 0 .5em 0; }
+h2.subhead { font-weight:normal;margin-top:0; }
+h3 { font-size:14px; margin:.8em 0 .3em 0; color:#666; font-weight:bold; }
+h4 { font-size:12px; margin:1em 0 .8em 0; padding-bottom:3px; }
+h5 { font-size:10px; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; }
+
+ul li { list-style-type:square; padding:1px 0; }
+ul.plainlist { margin-left:0 !important; }
+ul.plainlist li { list-style-type:none; }
+li ul { margin-bottom:0; }
+li, dt, dd { font-size:11px; line-height:14px; }
+dt { font-weight:bold; margin-top:4px; }
+dd { margin-left:0; }
+
+form { margin:0; padding:0; }
+fieldset { margin:0; padding:0; }
+
+blockquote { font-size:11px; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; }
+code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:11px; }
+pre.literal-block { margin:10px; background:#eee; padding:6px 8px; }
+code strong { color:#930; }
+hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; }
+
+/* TEXT STYLES & MODIFIERS */
+.small { font-size:11px; }
+.tiny { font-size:10px; }
+p.tiny { margin-top:-2px; }
+.mini { font-size:9px; }
+p.mini { margin-top:-3px; }
+.help, p.help { font-size:10px !important; color:#999; }
+p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; }
+.quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; }
+.quiet strong { font-weight:bold !important; }
+.float-right { float:right; }
+.float-left { float:left; }
+.clear { clear:both; }
+.align-left { text-align:left; }
+.align-right { text-align:right; }
+.example { margin:10px 0; padding:5px 10px; background:#efefef; }
+.nowrap { white-space:nowrap; }
+
+/* TABLES */
+table { border-collapse:collapse; border-color:#ccc; }
+td, th { font-size:11px; line-height:13px; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; }
+th { text-align:left; font-size:12px; font-weight:bold; }
+thead th,
+tfoot td { color:#666; padding:2px 5px; font-size:11px; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; }
+tfoot td { border-bottom:none; border-top:1px solid #ddd; }
+thead th:first-child,
+tfoot td:first-child { border-left:none !important; }
+thead th.optional { font-weight:normal !important; }
+fieldset table { border-right:1px solid #eee; }
+tr.row-label td { font-size:9px; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; }
+tr.alt { background:#f6f6f6; }
+.row1 { background:#EDF3FE; }
+.row2 { background:white; }
+
+/* SORTABLE TABLES */
+thead th a:link, thead th a:visited { color:#666; display:block; }
+table thead th.sorted { background-position:bottom left !important; }
+table thead th.sorted a { padding-right:13px; }
+table thead th.ascending a { background:url(../img/admin/arrow-down.gif) right .4em no-repeat; }
+table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; }
+
+/* ORDERABLE TABLES */
+table.orderable tbody tr td:hover { cursor:move; }
+table.orderable tbody tr td:first-child { padding-left:14px; background-image:url(../img/admin/nav-bg-grabber.gif); background-repeat:repeat-y; }
+table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; }
+
+/* FORM DEFAULTS */
+input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; }
+textarea { vertical-align:top !important; }
+input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; }
+
+/* FORM BUTTONS */
+input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; }
+input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; }
+input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; }
+input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; }
+
+/* MODULES */
+.module { border:1px solid #ccc; margin-bottom:5px; background:white; }
+.module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; }
+.module blockquote { margin-left:12px; }
+.module ul, .module ol { margin-left:1.5em; }
+.module h3 { margin-top:.6em; }
+.module h2, .module caption { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x; color:white; }
+.module table { border-collapse: collapse; }
+
+/* MESSAGES & ERRORS */
+ul.messagelist { padding:0 0 5px 0; margin:0; }
+ul.messagelist li { font-size:12px; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; }
+.errornote { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
+ul.errorlist { margin:0 !important; padding:0 !important; }
+.errorlist li { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; }
+td ul.errorlist { margin:0 !important; padding:0 !important; }
+td ul.errorlist li { margin:0 !important; }
+.error { background:#ffc; }
+.error input, .error select { border:1px solid red; }
+div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; }
+div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; }
+.description { font-size:12px; padding:5px 0 0 12px; }
+
+/* BREADCRUMBS */
+div.breadcrumbs { background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:11px; color:#999; border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; }
+
+/* ACTION ICONS */
+.addlink { padding-left:12px; background:url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; }
+.changelink { padding-left:12px; background:url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; }
+.deletelink { padding-left:12px; background:url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat; }
+a.deletelink:link, a.deletelink:visited { color:#CC3434; }
+a.deletelink:hover { color:#993333; }
+
+/* OBJECT TOOLS */
+.object-tools { font-size:10px; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
+.form-row .object-tools { margin-top:5px; margin-bottom:5px; float:none; height:2em; padding-left:3.5em; }
+.object-tools li { display:block; float:left; background:url(../img/admin/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; }
+.object-tools li:hover { background:url(../img/admin/tool-left_over.gif) 0 0 no-repeat; }
+.object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; }
+.object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; }
+.object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; }
+.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; }
+.object-tools a.addlink { background:#999 url(../img/admin/tooltag-add.gif) top right no-repeat; padding-right:28px; }
+.object-tools a.addlink:hover { background:#5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; }
+
+/* OBJECT HISTORY */
+table#change-history { width:100%; }
+table#change-history tbody th { width:16em; }
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/layout.css b/google_appengine/lib/django/django/contrib/admin/media/css/layout.css
new file mode 100644
index 0000000..17c5286
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/layout.css
@@ -0,0 +1,29 @@
+/* PAGE STRUCTURE */
+#container { position:relative; width:100%; min-width:760px; padding:0; }
+#content { margin:10px 15px; }
+#header { width:100%; }
+#content-main { float:left; width:100%; }
+#content-related { float:right; width:18em; position:relative; margin-right:-19em; }
+#footer { clear:both; padding:10px; }
+
+/* COLUMN TYPES */
+.colMS { margin-right:20em !important; }
+.colSM { margin-left:20em !important; }
+.colSM #content-related { float:left; margin-right:0; margin-left:-19em; }
+.colSM #content-main { float:right; }
+.popup .colM { width:95%; }
+.subcol { float:left; width:46%; margin-right:15px; }
+.dashboard #content { width:500px; }
+
+/* HEADER */
+#header { background:#417690; color:#ffc; overflow:hidden; }
+#header a:link, #header a:visited { color:white; }
+#header a:hover { text-decoration:underline; }
+#branding h1 { padding:0 10px; font-size:18px; margin:8px 0; font-weight:normal; color:#f4f379; }
+#branding h2 { padding:0 10px; font-size:14px; margin:-8px 0 8px 0; font-weight:normal; color:#ffc; }
+#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; }
+
+/* SIDEBAR */
+#content-related h3 { font-size:12px; color:#666; margin-bottom:3px; }
+#content-related h4 { font-size:11px; }
+#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; } \ No newline at end of file
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/login.css b/google_appengine/lib/django/django/contrib/admin/media/css/login.css
new file mode 100644
index 0000000..f904957
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/login.css
@@ -0,0 +1,13 @@
+@import url('base.css');
+@import url('layout.css');
+
+/* LOGIN FORM */
+body.login { background:#eee; }
+.login #container { background:white; border:1px solid #ccc; width:28em; min-width:300px; margin-left:auto; margin-right:auto; margin-top:100px; }
+.login #content-main { width:100%; }
+.login form { margin-top:1em; }
+.login .form-row { padding:4px 0; float:left; width:100%; }
+.login .form-row label { float:left; width:9em; padding-right:0.5em; line-height:2em; text-align:right; font-size:1em; color:#333; }
+.login .form-row #id_username, .login .form-row #id_password { width:14em; }
+.login span.help { font-size:10px; display:block; }
+.login .submit-row { clear:both; padding:1em 0 0 9.4em; } \ No newline at end of file
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/patch-iewin.css b/google_appengine/lib/django/django/contrib/admin/media/css/patch-iewin.css
new file mode 100644
index 0000000..2de1305
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/patch-iewin.css
@@ -0,0 +1,8 @@
+* html #container { position:static; } /* keep header from flowing off the page */
+* html .colMS #content-related { margin-right:0; margin-left:10px; position:static; } /* put the right sidebars back on the page */
+* html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */
+* html .form-row { height:1%; }
+* html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */
+* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */
+* html #changelist-filter ul { margin-right:-10px; } /* fix right margin for changelist filters in IE6 */
+* html .change-list .filtered { height:400px; } /* IE ignores min-height, but treats height as if it were min-height */ \ No newline at end of file
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/rtl.css b/google_appengine/lib/django/django/contrib/admin/media/css/rtl.css
new file mode 100644
index 0000000..1974e7c
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/rtl.css
@@ -0,0 +1,46 @@
+body { direction: rtl; }
+
+/* login styles */
+
+.login .form-row { float:right; }
+.login .form-row label { float:right; padding-left:0.5em; padding-right:0; text-align:left;}
+.login .submit-row { clear:both; padding:1em 9.4em 0 0; }
+
+
+/* global styles */
+th { text-align: right; }
+.module h2, .module caption { text-align: right; }
+.addlink, .changelink { padding-left:0px; padding-right:12px; background-position:100% 0.2em; }
+.deletelink { padding-left:0px; padding-right:12px; background-position:100% 0.25em; }
+.object-tools { float:left; }
+
+
+/* layout styles */
+#user-tools { right:auto; left:0; text-align:left; }
+div.breadcrumbs { text-align:right; }
+#content-main { float:right;}
+#content-related { float:left; margin-left:-19em; margin-right:auto;}
+.colMS { margin-left:20em !important; margin-right:10px !important;}
+
+/* dashboard styles */
+.dashboard .module table td a { padding-left:.6em; padding-right:12px; }
+
+/* changelists styles */
+.change-list .filtered { background:white url(../img/admin/changelist-bg_rtl.gif) top left repeat-y !important; }
+.change-list .filtered table { border-left:1px solid #ddd; border-right:0px none; }
+#changelist-filter { right:auto; left:0; border-left:0px none; border-right:1px solid #ddd;}
+.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:0px !important; margin-left:160px !important; }
+#changelist-filter li.selected { border-left:0px none; padding-left:0px; margin-left:0; border-right:5px solid #ccc; padding-right:5px;margin-right:-10px; }
+
+/* fomrs styles */
+.aligned label { padding:0 0 3px 1em; float:right; }
+.submit-row { text-align: left }
+.vDateField, .vTimeField { margin-left:2px; }
+
+/* widget styles */
+.calendarnav-previous { top:0; left:auto; right:0; }
+.calendarnav-next { top:0; right:auto; left:0;}
+.calendar caption, .calendarbox h2 { text-align:center; }
+
+.selector { float: right;}
+.selector .selector-filter { text-align: right;}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/css/widgets.css b/google_appengine/lib/django/django/contrib/admin/media/css/widgets.css
new file mode 100644
index 0000000..bf526bf
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/css/widgets.css
@@ -0,0 +1,101 @@
+/* SELECTOR (FILTER INTERFACE) */
+.selector { width:580px; float:left; }
+.selector select { width:270px; height:17.2em; }
+.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; }
+.selector-available h2, .selector-chosen h2 { border:1px solid #ccc; }
+.selector .selector-available h2 { background:white url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
+.selector .selector-filter { background:white; border:1px solid #ccc; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; }
+.selector .selector-chosen .selector-filter { padding:4px 5px; }
+.selector .selector-available input { width:230px; }
+.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/admin/chooser-bg.gif) top center no-repeat; margin:8em 3px 0 3px; padding:0; }
+.selector-chooser li { margin:0; padding:3px; list-style-type:none; }
+.selector select { margin-bottom:5px; margin-top:0; }
+.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; }
+.selector-add { background:url(../img/admin/selector-add.gif) top center no-repeat; margin-bottom:2px; }
+.selector-remove { background:url(../img/admin/selector-remove.gif) top center no-repeat; }
+a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666; padding:3px 0 3px 18px; }
+a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; }
+a.selector-chooseall { width:7em; background:url(../img/admin/selector-addall.gif) left center no-repeat; }
+a.selector-clearall { background:url(../img/admin/selector-removeall.gif) left center no-repeat; }
+
+/* STACKED SELECTORS */
+.stacked { float:left; width:500px; }
+.stacked select { width:480px; height:10.1em; }
+.stacked .selector-available, .stacked .selector-chosen { width:480px; }
+.stacked .selector-available { margin-bottom:0; }
+.stacked .selector-available input { width:442px; }
+.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; }
+.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; }
+.stacked .selector-chooseall, .stacked .selector-clearall { display:none; }
+.stacked .selector-add { background-image:url(../img/admin/selector_stacked-add.gif); }
+.stacked .selector-remove { background-image:url(../img/admin/selector_stacked-remove.gif); }
+
+/* DATE AND TIME */
+p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
+.datetime span { font-size:11px; color:#ccc; font-weight:normal; white-space:nowrap; }
+.vDateField { margin-left:4px; }
+table p.datetime { font-size:10px; margin-left:0; padding-left:0; }
+
+/* FILE UPLOADS */
+p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
+.file-upload a { font-weight:normal; }
+.file-upload .deletelink { margin-left:5px; }
+
+/* CALENDARS & CLOCKS */
+.calendarbox, .clockbox { margin:5px auto; font-size:11px; width:16em; text-align:center; background:white; position:relative; }
+.clockbox { width:9em; }
+.calendar { margin:0; padding: 0; }
+.calendar table { margin:0; padding:0; border-collapse:collapse; background:white; width:99%; }
+.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
+.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; }
+.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; }
+.calendar td.selected a { background: #C9DBED; }
+.calendar td.nonday { background:#efefef; }
+.calendar td.today a { background:#ffc; }
+.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; }
+.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; }
+.calendar td a:active, .timelist a:active { background: #036; color:white; }
+.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; }
+.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; }
+.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; }
+.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; }
+.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; }
+.calendarnav-previous { top:0; left:0; }
+.calendarnav-next { top:0; right:0; }
+.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-top:1px solid #ddd; }
+.calendar-cancel a { padding:2px; color:#999; }
+ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; }
+.timelist a { padding:2px; }
+
+/* INLINE ORDERER */
+ul.orderer { position:relative; padding:0 !important; margin:0 !important; list-style-type:none; }
+ul.orderer li { list-style-type:none; display:block; padding:0; margin:0; border:1px solid #bbb; border-width:0 1px 1px 0; white-space:nowrap; overflow:hidden; background:#e2e2e2 url(../img/admin/nav-bg-grabber.gif) repeat-y; }
+ul.orderer li:hover { cursor:move; background-color:#ddd; }
+ul.orderer li a.selector { margin-left:12px; overflow:hidden; width:83%; font-size:10px !important; padding:0.6em 0; }
+ul.orderer li a:link, ul.orderer li a:visited { color:#333; }
+ul.orderer li .inline-deletelink { position:absolute; right:4px; margin-top:0.6em; }
+ul.orderer li.selected { background-color:#f8f8f8; border-right-color:#f8f8f8; }
+ul.orderer li.deleted { background:#bbb url(../img/admin/deleted-overlay.gif); }
+ul.orderer li.deleted a:link, ul.orderer li.deleted a:visited { color:#888; }
+ul.orderer li.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png); }
+ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover { cursor:default; }
+
+/* EDIT INLINE */
+.inline-deletelink { display:block; text-indent:-9999px; background:transparent url(../img/admin/inline-delete.png) no-repeat; width:15px; height:15px; margin:0.4em 0; border: 0px none; }
+.inline-deletelink:hover { background-position:-15px 0; cursor:pointer; }
+.editinline button.addlink { border: 0px none; color: #5b80b2; font-size: 100%; cursor: pointer; }
+.editinline button.addlink:hover { color: #036; cursor: pointer; }
+.editinline table .help { text-align:right; float:right; padding-left:2em; }
+.editinline tfoot .addlink { white-space:nowrap; }
+.editinline table thead th:last-child { border-left:none; }
+.editinline tr.deleted { background:#ddd url(../img/admin/deleted-overlay.gif); }
+.editinline tr.deleted .inline-deletelink { background-image:url(../img/admin/inline-restore.png); }
+.editinline tr.deleted td:hover { cursor:default; }
+.editinline tr.deleted td:first-child { background-image:none !important; }
+
+/* EDIT INLINE - STACKED */
+.editinline-stacked { min-width:758px; }
+.editinline-stacked .inline-object { margin-left:210px; background:white; }
+.editinline-stacked .inline-source { float:left; width:200px; background:#f8f8f8; }
+.editinline-stacked .inline-splitter { float:left; width:9px; background:#f8f8f8 url(../img/admin/inline-splitter-bg.gif) 50% 50% no-repeat; border-right:1px solid #ccc; }
+.editinline-stacked .controls { clear:both; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; padding:3px 4px; font-size:11px; border-top:1px solid #ddd; } \ No newline at end of file
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-down.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-down.gif
new file mode 100644
index 0000000..a967b9f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-down.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-up.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-up.gif
new file mode 100644
index 0000000..3fe4851
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-up.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/changelist-bg.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/changelist-bg.gif
new file mode 100644
index 0000000..7f46994
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/changelist-bg.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser-bg.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser-bg.gif
new file mode 100644
index 0000000..30e83c2
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser-bg.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif
new file mode 100644
index 0000000..5d104b6
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg-reverse.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg-reverse.gif
new file mode 100644
index 0000000..0873281
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg-reverse.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg.gif
new file mode 100644
index 0000000..003aeca
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/deleted-overlay.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/deleted-overlay.gif
new file mode 100644
index 0000000..dc3828f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/deleted-overlay.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-no.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-no.gif
new file mode 100644
index 0000000..1b4ee58
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-no.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-unknown.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-unknown.gif
new file mode 100644
index 0000000..cfd2b02
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-unknown.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-yes.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-yes.gif
new file mode 100644
index 0000000..7399282
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-yes.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_addlink.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_addlink.gif
new file mode 100644
index 0000000..ee70e1a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_addlink.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_alert.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_alert.gif
new file mode 100644
index 0000000..a1dde26
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_alert.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_calendar.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_calendar.gif
new file mode 100644
index 0000000..7587b30
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_calendar.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_changelink.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_changelink.gif
new file mode 100644
index 0000000..e1b9afd
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_changelink.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_clock.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_clock.gif
new file mode 100644
index 0000000..ff2d57e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_clock.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_deletelink.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_deletelink.gif
new file mode 100644
index 0000000..72523e3
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_deletelink.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_error.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_error.gif
new file mode 100644
index 0000000..3730a00
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_error.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_searchbox.png b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_searchbox.png
new file mode 100644
index 0000000..8ab579e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_searchbox.png
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_success.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_success.gif
new file mode 100644
index 0000000..5cf90a1
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_success.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete-8bit.png b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete-8bit.png
new file mode 100644
index 0000000..95caf59
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete-8bit.png
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete.png b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete.png
new file mode 100644
index 0000000..d59bcd2
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete.png
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore-8bit.png b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore-8bit.png
new file mode 100644
index 0000000..e087c8e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore-8bit.png
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore.png b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore.png
new file mode 100644
index 0000000..efdd92a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore.png
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-splitter-bg.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-splitter-bg.gif
new file mode 100644
index 0000000..32ac5b3
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-splitter-bg.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-grabber.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-grabber.gif
new file mode 100644
index 0000000..0a784fa
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-grabber.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-reverse.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-reverse.gif
new file mode 100644
index 0000000..f11029f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-reverse.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg.gif
new file mode 100644
index 0000000..f8402b8
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-add.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-add.gif
new file mode 100644
index 0000000..50132d1
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-add.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-addall.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-addall.gif
new file mode 100644
index 0000000..d6e7c63
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-addall.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-remove.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-remove.gif
new file mode 100644
index 0000000..2b9b0a2
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-remove.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-removeall.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-removeall.gif
new file mode 100644
index 0000000..5a44219
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-removeall.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-search.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-search.gif
new file mode 100644
index 0000000..6d5f4c7
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-search.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-add.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-add.gif
new file mode 100644
index 0000000..7426169
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-add.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-remove.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-remove.gif
new file mode 100644
index 0000000..60412ce
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-remove.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left.gif
new file mode 100644
index 0000000..011490f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left_over.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left_over.gif
new file mode 100644
index 0000000..937e07b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left_over.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right.gif
new file mode 100644
index 0000000..cdc140c
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right_over.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right_over.gif
new file mode 100644
index 0000000..4db977e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right_over.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add.gif
new file mode 100644
index 0000000..8b53d49
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add_over.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add_over.gif
new file mode 100644
index 0000000..bfc52f1
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add_over.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright.gif
new file mode 100644
index 0000000..cdaaae7
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif
new file mode 100644
index 0000000..7163189
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif
Binary files differ
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/SelectBox.js b/google_appengine/lib/django/django/contrib/admin/media/js/SelectBox.js
new file mode 100644
index 0000000..af8de20
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/SelectBox.js
@@ -0,0 +1,109 @@
+var SelectBox = {
+ cache: new Object(),
+ init: function(id) {
+ var box = document.getElementById(id);
+ var node;
+ SelectBox.cache[id] = new Array();
+ var cache = SelectBox.cache[id];
+ for (var i = 0; (node = box.options[i]); i++) {
+ cache.push({ value: node.value, text: node.text, displayed: 1 });
+ }
+ },
+ redisplay: function(id) {
+ // Repopulate HTML select box from cache
+ var box = document.getElementById(id);
+ box.options.length = 0; // clear all options
+ for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) {
+ var node = SelectBox.cache[id][i];
+ if (node.displayed) {
+ box.options[box.options.length] = new Option(node.text, node.value, false, false);
+ }
+ }
+ },
+ filter: function(id, text) {
+ // Redisplay the HTML select box, displaying only the choices containing ALL
+ // the words in text. (It's an AND search.)
+ var tokens = text.toLowerCase().split(/\s+/);
+ var node, token;
+ for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
+ node.displayed = 1;
+ for (var j = 0; (token = tokens[j]); j++) {
+ if (node.text.toLowerCase().indexOf(token) == -1) {
+ node.displayed = 0;
+ }
+ }
+ }
+ SelectBox.redisplay(id);
+ },
+ delete_from_cache: function(id, value) {
+ var node, delete_index = null;
+ for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
+ if (node.value == value) {
+ delete_index = i;
+ break;
+ }
+ }
+ var j = SelectBox.cache[id].length - 1;
+ for (var i = delete_index; i < j; i++) {
+ SelectBox.cache[id][i] = SelectBox.cache[id][i+1];
+ }
+ SelectBox.cache[id].length--;
+ },
+ add_to_cache: function(id, option) {
+ SelectBox.cache[id].push({ value: option.value, text: option.text, displayed: 1 });
+ },
+ cache_contains: function(id, value) {
+ // Check if an item is contained in the cache
+ var node;
+ for (var i = 0; (node = SelectBox.cache[id][i]); i++) {
+ if (node.value == value) {
+ return true;
+ }
+ }
+ return false;
+ },
+ move: function(from, to) {
+ var from_box = document.getElementById(from);
+ var to_box = document.getElementById(to);
+ var option;
+ for (var i = 0; (option = from_box.options[i]); i++) {
+ if (option.selected && SelectBox.cache_contains(from, option.value)) {
+ SelectBox.add_to_cache(to, { value: option.value, text: option.text, displayed: 1 });
+ SelectBox.delete_from_cache(from, option.value);
+ }
+ }
+ SelectBox.redisplay(from);
+ SelectBox.redisplay(to);
+ },
+ move_all: function(from, to) {
+ var from_box = document.getElementById(from);
+ var to_box = document.getElementById(to);
+ var option;
+ for (var i = 0; (option = from_box.options[i]); i++) {
+ SelectBox.add_to_cache(to, { value: option.value, text: option.text, displayed: 1 });
+ SelectBox.delete_from_cache(from, option.value);
+ }
+ SelectBox.redisplay(from);
+ SelectBox.redisplay(to);
+ },
+ sort: function(id) {
+ SelectBox.cache[id].sort( function(a, b) {
+ a = a.text.toLowerCase();
+ b = b.text.toLowerCase();
+ try {
+ if (a > b) return 1;
+ if (a < b) return -1;
+ }
+ catch (e) {
+ // silently fail on IE 'unknown' exception
+ }
+ return 0;
+ } );
+ },
+ select_all: function(id) {
+ var box = document.getElementById(id);
+ for (var i = 0; i < box.options.length; i++) {
+ box.options[i].selected = 'selected';
+ }
+ }
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter.js b/google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter.js
new file mode 100644
index 0000000..0501920
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter.js
@@ -0,0 +1,81 @@
+/*
+SelectFilter - Turns a multiple-select box into a filter interface.
+
+Requires SelectBox.js and addevent.js.
+*/
+
+function findForm(node) {
+ // returns the node of the form containing the given node
+ if (node.tagName.toLowerCase() != 'form') {
+ return findForm(node.parentNode);
+ }
+ return node;
+}
+
+var SelectFilter = {
+ init: function(field_id) {
+ var from_box = document.getElementById(field_id);
+ from_box.id += '_from'; // change its ID
+ // Create the INPUT input box
+ var input_box = document.createElement('input');
+ input_box.id = field_id + '_input';
+ input_box.setAttribute('type', 'text');
+ from_box.parentNode.insertBefore(input_box, from_box);
+ from_box.parentNode.insertBefore(document.createElement('br'), input_box.nextSibling);
+ // Create the TO box
+ var to_box = document.createElement('select');
+ to_box.id = field_id + '_to';
+ to_box.setAttribute('multiple', 'multiple');
+ to_box.setAttribute('size', from_box.size);
+ from_box.parentNode.insertBefore(to_box, from_box.nextSibling);
+ to_box.setAttribute('name', from_box.getAttribute('name'));
+ from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
+ // Give the filters a CSS hook
+ from_box.setAttribute('class', 'filtered');
+ to_box.setAttribute('class', 'filtered');
+ // Set up the JavaScript event handlers for the select box filter interface
+ addEvent(input_box, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
+ addEvent(input_box, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
+ addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); });
+ addEvent(from_box, 'focus', function() { input_box.focus(); });
+ addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); });
+ addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
+ SelectBox.init(field_id + '_from');
+ SelectBox.init(field_id + '_to');
+ // Move selected from_box options to to_box
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ },
+ filter_key_up: function(event, field_id) {
+ from = document.getElementById(field_id + '_from');
+ // don't submit form if user pressed Enter
+ if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
+ from.selectedIndex = 0;
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ from.selectedIndex = 0;
+ return false;
+ }
+ var temp = from.selectedIndex;
+ SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
+ from.selectedIndex = temp;
+ return true;
+ },
+ filter_key_down: function(event, field_id) {
+ from = document.getElementById(field_id + '_from');
+ // right arrow -- move across
+ if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
+ var old_index = from.selectedIndex;
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
+ return false;
+ }
+ // down arrow -- wrap around
+ if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
+ from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
+ }
+ // up arrow -- wrap around
+ if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
+ from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
+ }
+ return true;
+ }
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter2.js b/google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter2.js
new file mode 100644
index 0000000..8f6079e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter2.js
@@ -0,0 +1,113 @@
+/*
+SelectFilter2 - Turns a multiple-select box into a filter interface.
+
+Different than SelectFilter because this is coupled to the admin framework.
+
+Requires core.js, SelectBox.js and addevent.js.
+*/
+
+function findForm(node) {
+ // returns the node of the form containing the given node
+ if (node.tagName.toLowerCase() != 'form') {
+ return findForm(node.parentNode);
+ }
+ return node;
+}
+
+var SelectFilter = {
+ init: function(field_id, field_name, is_stacked, admin_media_prefix) {
+ var from_box = document.getElementById(field_id);
+ from_box.id += '_from'; // change its ID
+ from_box.className = 'filtered';
+
+ // Remove <p class="info">, because it just gets in the way.
+ var ps = from_box.parentNode.getElementsByTagName('p');
+ for (var i=0; i<ps.length; i++) {
+ from_box.parentNode.removeChild(ps[i]);
+ }
+
+ // <div class="selector"> or <div class="selector stacked">
+ var selector_div = quickElement('div', from_box.parentNode);
+ selector_div.className = is_stacked ? 'selector stacked' : 'selector';
+
+ // <div class="selector-available">
+ var selector_available = quickElement('div', selector_div, '');
+ selector_available.className = 'selector-available';
+ quickElement('h2', selector_available, interpolate(gettext('Available %s'), [field_name]));
+ var filter_p = quickElement('p', selector_available, '');
+ filter_p.className = 'selector-filter';
+ quickElement('img', filter_p, '', 'src', admin_media_prefix + 'img/admin/selector-search.gif');
+ filter_p.appendChild(document.createTextNode(' '));
+ var filter_input = quickElement('input', filter_p, '', 'type', 'text');
+ filter_input.id = field_id + '_input';
+ selector_available.appendChild(from_box);
+ var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); })()');
+ choose_all.className = 'selector-chooseall';
+
+ // <ul class="selector-chooser">
+ var selector_chooser = quickElement('ul', selector_div, '');
+ selector_chooser.className = 'selector-chooser';
+ var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Add'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to");})()');
+ add_link.className = 'selector-add';
+ var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from");})()');
+ remove_link.className = 'selector-remove';
+
+ // <div class="selector-chosen">
+ var selector_chosen = quickElement('div', selector_div, '');
+ selector_chosen.className = 'selector-chosen';
+ quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s'), [field_name]));
+ var selector_filter = quickElement('p', selector_chosen, gettext('Select your choice(s) and click '));
+ selector_filter.className = 'selector-filter';
+ quickElement('img', selector_filter, '', 'src', admin_media_prefix + 'img/admin/selector-add.gif', 'alt', 'Add');
+ var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
+ to_box.className = 'filtered';
+ var clear_all = quickElement('a', selector_chosen, gettext('Clear all'), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from");})()');
+ clear_all.className = 'selector-clearall';
+
+ from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
+
+ // Set up the JavaScript event handlers for the select box filter interface
+ addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
+ addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
+ addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); });
+ addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); });
+ addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
+ SelectBox.init(field_id + '_from');
+ SelectBox.init(field_id + '_to');
+ // Move selected from_box options to to_box
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ },
+ filter_key_up: function(event, field_id) {
+ from = document.getElementById(field_id + '_from');
+ // don't submit form if user pressed Enter
+ if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
+ from.selectedIndex = 0;
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ from.selectedIndex = 0;
+ return false;
+ }
+ var temp = from.selectedIndex;
+ SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
+ from.selectedIndex = temp;
+ return true;
+ },
+ filter_key_down: function(event, field_id) {
+ from = document.getElementById(field_id + '_from');
+ // right arrow -- move across
+ if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
+ var old_index = from.selectedIndex;
+ SelectBox.move(field_id + '_from', field_id + '_to');
+ from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
+ return false;
+ }
+ // down arrow -- wrap around
+ if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
+ from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
+ }
+ // up arrow -- wrap around
+ if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
+ from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
+ }
+ return true;
+ }
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/admin/CollapsedFieldsets.js b/google_appengine/lib/django/django/contrib/admin/media/js/admin/CollapsedFieldsets.js
new file mode 100644
index 0000000..c8426db
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/admin/CollapsedFieldsets.js
@@ -0,0 +1,85 @@
+// Finds all fieldsets with class="collapse", collapses them, and gives each
+// one a "Show" link that uncollapses it. The "Show" link becomes a "Hide"
+// link when the fieldset is visible.
+
+function findForm(node) {
+ // returns the node of the form containing the given node
+ if (node.tagName.toLowerCase() != 'form') {
+ return findForm(node.parentNode);
+ }
+ return node;
+}
+
+var CollapsedFieldsets = {
+ collapse_re: /\bcollapse\b/, // Class of fieldsets that should be dealt with.
+ collapsed_re: /\bcollapsed\b/, // Class that fieldsets get when they're hidden.
+ collapsed_class: 'collapsed',
+ init: function() {
+ var fieldsets = document.getElementsByTagName('fieldset');
+ var collapsed_seen = false;
+ for (var i = 0, fs; fs = fieldsets[i]; i++) {
+ // Collapse this fieldset if it has the correct class, and if it
+ // doesn't have any errors. (Collapsing shouldn't apply in the case
+ // of error messages.)
+ if (fs.className.match(CollapsedFieldsets.collapse_re) && !CollapsedFieldsets.fieldset_has_errors(fs)) {
+ collapsed_seen = true;
+ // Give it an additional class, used by CSS to hide it.
+ fs.className += ' ' + CollapsedFieldsets.collapsed_class;
+ // (<a id="fieldsetcollapser3" class="collapse-toggle" href="#">Show</a>)
+ var collapse_link = document.createElement('a');
+ collapse_link.className = 'collapse-toggle';
+ collapse_link.id = 'fieldsetcollapser' + i;
+ collapse_link.onclick = new Function('CollapsedFieldsets.show('+i+'); return false;');
+ collapse_link.href = '#';
+ collapse_link.innerHTML = gettext('Show');
+ var h2 = fs.getElementsByTagName('h2')[0];
+ h2.appendChild(document.createTextNode(' ('));
+ h2.appendChild(collapse_link);
+ h2.appendChild(document.createTextNode(')'));
+ }
+ }
+ if (collapsed_seen) {
+ // Expand all collapsed fieldsets when form is submitted.
+ addEvent(findForm(document.getElementsByTagName('fieldset')[0]), 'submit', function() { CollapsedFieldsets.uncollapse_all(); });
+ }
+ },
+ fieldset_has_errors: function(fs) {
+ // Returns true if any fields in the fieldset have validation errors.
+ var divs = fs.getElementsByTagName('div');
+ for (var i=0; i<divs.length; i++) {
+ if (divs[i].className.match(/\berror\b/)) {
+ return true;
+ }
+ }
+ return false;
+ },
+ show: function(fieldset_index) {
+ var fs = document.getElementsByTagName('fieldset')[fieldset_index];
+ // Remove the class name that causes the "display: none".
+ fs.className = fs.className.replace(CollapsedFieldsets.collapsed_re, '');
+ // Toggle the "Show" link to a "Hide" link
+ var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index);
+ collapse_link.onclick = new Function('CollapsedFieldsets.hide('+fieldset_index+'); return false;');
+ collapse_link.innerHTML = gettext('Hide');
+ },
+ hide: function(fieldset_index) {
+ var fs = document.getElementsByTagName('fieldset')[fieldset_index];
+ // Add the class name that causes the "display: none".
+ fs.className += ' ' + CollapsedFieldsets.collapsed_class;
+ // Toggle the "Hide" link to a "Show" link
+ var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index);
+ collapse_link.onclick = new Function('CollapsedFieldsets.show('+fieldset_index+'); return false;');
+ collapse_link.innerHTML = gettext('Show');
+ },
+
+ uncollapse_all: function() {
+ var fieldsets = document.getElementsByTagName('fieldset');
+ for (var i=0; i<fieldsets.length; i++) {
+ if (fieldsets[i].className.match(CollapsedFieldsets.collapsed_re)) {
+ CollapsedFieldsets.show(i);
+ }
+ }
+ }
+}
+
+addEvent(window, 'load', CollapsedFieldsets.init);
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/google_appengine/lib/django/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
new file mode 100644
index 0000000..b1504fc
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
@@ -0,0 +1,241 @@
+// Inserts shortcut buttons after all of the following:
+// <input type="text" class="vDateField">
+// <input type="text" class="vTimeField">
+
+var DateTimeShortcuts = {
+ calendars: [],
+ calendarInputs: [],
+ clockInputs: [],
+ calendarDivName1: 'calendarbox', // name of calendar <div> that gets toggled
+ calendarDivName2: 'calendarin', // name of <div> that contains calendar
+ calendarLinkName: 'calendarlink',// name of the link that is used to toggle
+ clockDivName: 'clockbox', // name of clock <div> that gets toggled
+ clockLinkName: 'clocklink', // name of the link that is used to toggle
+ admin_media_prefix: '',
+ init: function() {
+ // Deduce admin_media_prefix by looking at the <script>s in the
+ // current document and finding the URL of *this* module.
+ var scripts = document.getElementsByTagName('script');
+ for (var i=0; i<scripts.length; i++) {
+ if (scripts[i].src.match(/DateTimeShortcuts/)) {
+ var idx = scripts[i].src.indexOf('js/admin/DateTimeShortcuts');
+ DateTimeShortcuts.admin_media_prefix = scripts[i].src.substring(0, idx);
+ break;
+ }
+ }
+
+ var inputs = document.getElementsByTagName('input');
+ for (i=0; i<inputs.length; i++) {
+ var inp = inputs[i];
+ if (inp.getAttribute('type') == 'text' && inp.className.match(/vTimeField/)) {
+ DateTimeShortcuts.addClock(inp);
+ }
+ else if (inp.getAttribute('type') == 'text' && inp.className.match(/vDateField/)) {
+ DateTimeShortcuts.addCalendar(inp);
+ }
+ }
+ },
+ // Add clock widget to a given field
+ addClock: function(inp) {
+ var num = DateTimeShortcuts.clockInputs.length;
+ DateTimeShortcuts.clockInputs[num] = inp;
+
+ // Shortcut links (clock icon and "Now" link)
+ var shortcuts_span = document.createElement('span');
+ inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
+ var now_link = document.createElement('a');
+ now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());");
+ now_link.appendChild(document.createTextNode(gettext('Now')));
+ var clock_link = document.createElement('a');
+ clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
+ clock_link.id = DateTimeShortcuts.clockLinkName + num;
+ quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_clock.gif', 'alt', gettext('Clock'));
+ shortcuts_span.appendChild(document.createTextNode('\240'));
+ shortcuts_span.appendChild(now_link);
+ shortcuts_span.appendChild(document.createTextNode('\240|\240'));
+ shortcuts_span.appendChild(clock_link);
+
+ // Create clock link div
+ //
+ // Markup looks like:
+ // <div id="clockbox1" class="clockbox module">
+ // <h2>Choose a time</h2>
+ // <ul class="timelist">
+ // <li><a href="#">Now</a></li>
+ // <li><a href="#">Midnight</a></li>
+ // <li><a href="#">6 a.m.</a></li>
+ // <li><a href="#">Noon</a></li>
+ // </ul>
+ // <p class="calendar-cancel"><a href="#">Cancel</a></p>
+ // </div>
+
+ var clock_box = document.createElement('div');
+ clock_box.style.display = 'none';
+ clock_box.style.position = 'absolute';
+ clock_box.className = 'clockbox module';
+ clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num);
+ document.body.appendChild(clock_box);
+ addEvent(clock_box, 'click', DateTimeShortcuts.cancelEventPropagation);
+
+ quickElement('h2', clock_box, gettext('Choose a time'));
+ time_list = quickElement('ul', clock_box, '');
+ time_list.className = 'timelist';
+ quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00:00');")
+
+ cancel_p = quickElement('p', clock_box, '');
+ cancel_p.className = 'calendar-cancel';
+ quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');');
+ },
+ openClock: function(num) {
+ var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num)
+ var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num)
+
+ // Recalculate the clockbox position
+ // is it left-to-right or right-to-left layout ?
+ if (getStyle(document.body,'direction')!='rtl') {
+ clock_box.style.left = findPosX(clock_link) + 17 + 'px';
+ }
+ else {
+ // since style's width is in em, it'd be tough to calculate
+ // px value of it. let's use an estimated px for now
+ // TODO: IE returns wrong value for findPosX when in rtl mode
+ // (it returns as it was left aligned), needs to be fixed.
+ clock_box.style.left = findPosX(clock_link) - 110 + 'px';
+ }
+ clock_box.style.top = findPosY(clock_link) - 30 + 'px';
+
+ // Show the clock box
+ clock_box.style.display = 'block';
+ addEvent(window, 'click', function() { DateTimeShortcuts.dismissClock(num); return true; });
+ },
+ dismissClock: function(num) {
+ document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none';
+ window.onclick = null;
+ },
+ handleClockQuicklink: function(num, val) {
+ DateTimeShortcuts.clockInputs[num].value = val;
+ DateTimeShortcuts.dismissClock(num);
+ },
+ // Add calendar widget to a given field.
+ addCalendar: function(inp) {
+ var num = DateTimeShortcuts.calendars.length;
+
+ DateTimeShortcuts.calendarInputs[num] = inp;
+
+ // Shortcut links (calendar icon and "Today" link)
+ var shortcuts_span = document.createElement('span');
+ inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
+ var today_link = document.createElement('a');
+ today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
+ today_link.appendChild(document.createTextNode(gettext('Today')));
+ var cal_link = document.createElement('a');
+ cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');');
+ cal_link.id = DateTimeShortcuts.calendarLinkName + num;
+ quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_calendar.gif', 'alt', gettext('Calendar'));
+ shortcuts_span.appendChild(document.createTextNode('\240'));
+ shortcuts_span.appendChild(today_link);
+ shortcuts_span.appendChild(document.createTextNode('\240|\240'));
+ shortcuts_span.appendChild(cal_link);
+
+ // Create calendarbox div.
+ //
+ // Markup looks like:
+ //
+ // <div id="calendarbox3" class="calendarbox module">
+ // <h2>
+ // <a href="#" class="link-previous">&lsaquo;</a>
+ // <a href="#" class="link-next">&rsaquo;</a> February 2003
+ // </h2>
+ // <div class="calendar" id="calendarin3">
+ // <!-- (cal) -->
+ // </div>
+ // <div class="calendar-shortcuts">
+ // <a href="#">Yesterday</a> | <a href="#">Today</a> | <a href="#">Tomorrow</a>
+ // </div>
+ // <p class="calendar-cancel"><a href="#">Cancel</a></p>
+ // </div>
+ var cal_box = document.createElement('div');
+ cal_box.style.display = 'none';
+ cal_box.style.position = 'absolute';
+ cal_box.className = 'calendarbox module';
+ cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num);
+ document.body.appendChild(cal_box);
+ addEvent(cal_box, 'click', DateTimeShortcuts.cancelEventPropagation);
+
+ // next-prev links
+ var cal_nav = quickElement('div', cal_box, '');
+ var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev('+num+');');
+ cal_nav_prev.className = 'calendarnav-previous';
+ var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext('+num+');');
+ cal_nav_next.className = 'calendarnav-next';
+
+ // main box
+ var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num);
+ cal_main.className = 'calendar';
+ DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num));
+ DateTimeShortcuts.calendars[num].drawCurrent();
+
+ // calendar shortcuts
+ var shortcuts = quickElement('div', cal_box, '');
+ shortcuts.className = 'calendar-shortcuts';
+ quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);');
+ shortcuts.appendChild(document.createTextNode('\240|\240'));
+ quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
+ shortcuts.appendChild(document.createTextNode('\240|\240'));
+ quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);');
+
+ // cancel bar
+ var cancel_p = quickElement('p', cal_box, '');
+ cancel_p.className = 'calendar-cancel';
+ quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');');
+ },
+ openCalendar: function(num) {
+ var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num)
+ var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num)
+
+ // Recalculate the clockbox position
+ // is it left-to-right or right-to-left layout ?
+ if (getStyle(document.body,'direction')!='rtl') {
+ cal_box.style.left = findPosX(cal_link) + 17 + 'px';
+ }
+ else {
+ // since style's width is in em, it'd be tough to calculate
+ // px value of it. let's use an estimated px for now
+ // TODO: IE returns wrong value for findPosX when in rtl mode
+ // (it returns as it was left aligned), needs to be fixed.
+ cal_box.style.left = findPosX(cal_link) - 180 + 'px';
+ }
+ cal_box.style.top = findPosY(cal_link) - 75 + 'px';
+
+ cal_box.style.display = 'block';
+ addEvent(window, 'click', function() { DateTimeShortcuts.dismissCalendar(num); return true; });
+ },
+ dismissCalendar: function(num) {
+ document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'none';
+ },
+ drawPrev: function(num) {
+ DateTimeShortcuts.calendars[num].drawPreviousMonth();
+ },
+ drawNext: function(num) {
+ DateTimeShortcuts.calendars[num].drawNextMonth();
+ },
+ handleCalendarCallback: function(num) {
+ return "function(y, m, d) { DateTimeShortcuts.calendarInputs["+num+"].value = y+'-'+m+'-'+d; document.getElementById(DateTimeShortcuts.calendarDivName1+"+num+").style.display='none';}";
+ },
+ handleCalendarQuickLink: function(num, offset) {
+ var d = new Date();
+ d.setDate(d.getDate() + offset)
+ DateTimeShortcuts.calendarInputs[num].value = d.getISODate();
+ DateTimeShortcuts.dismissCalendar(num);
+ },
+ cancelEventPropagation: function(e) {
+ if (!e) e = window.event;
+ e.cancelBubble = true;
+ if (e.stopPropagation) e.stopPropagation();
+ }
+}
+
+addEvent(window, 'load', DateTimeShortcuts.init);
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/google_appengine/lib/django/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
new file mode 100644
index 0000000..db4ed1a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
@@ -0,0 +1,57 @@
+// Handles related-objects functionality: lookup link for raw_id_admin=True
+// and Add Another links.
+
+function showRelatedObjectLookupPopup(triggeringLink) {
+ var name = triggeringLink.id.replace(/^lookup_/, '');
+ // IE doesn't like periods in the window name, so convert temporarily.
+ name = name.replace(/\./g, '___');
+ var href;
+ if (triggeringLink.href.search(/\?/) >= 0) {
+ href = triggeringLink.href + '&pop=1';
+ } else {
+ href = triggeringLink.href + '?pop=1';
+ }
+ var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+ win.focus();
+ return false;
+}
+
+function dismissRelatedLookupPopup(win, chosenId) {
+ var name = win.name.replace(/___/g, '.');
+ var elem = document.getElementById(name);
+ if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) {
+ elem.value += ',' + chosenId;
+ } else {
+ document.getElementById(name).value = chosenId;
+ }
+ win.close();
+}
+
+function showAddAnotherPopup(triggeringLink) {
+ var name = triggeringLink.id.replace(/^add_/, '');
+ name = name.replace(/\./g, '___');
+ var win = window.open(triggeringLink.href + '?_popup=1', name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+ win.focus();
+ return false;
+}
+
+function dismissAddAnotherPopup(win, newId, newRepr) {
+ var name = win.name.replace(/___/g, '.');
+ var elem = document.getElementById(name);
+ if (elem) {
+ if (elem.nodeName == 'SELECT') {
+ var o = new Option(newRepr, newId);
+ elem.options[elem.options.length] = o;
+ o.selected = true;
+ } else if (elem.nodeName == 'INPUT') {
+ elem.value = newId;
+ }
+ } else {
+ var toId = name + "_to";
+ elem = document.getElementById(toId);
+ var o = new Option(newRepr, newId);
+ SelectBox.add_to_cache(toId, o);
+ SelectBox.redisplay(toId);
+ }
+ win.close();
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/admin/ordering.js b/google_appengine/lib/django/django/contrib/admin/media/js/admin/ordering.js
new file mode 100644
index 0000000..53c42f3
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/admin/ordering.js
@@ -0,0 +1,137 @@
+addEvent(window, 'load', reorder_init);
+
+var lis;
+var top = 0;
+var left = 0;
+var height = 30;
+
+function reorder_init() {
+ lis = document.getElementsBySelector('ul#orderthese li');
+ var input = document.getElementsBySelector('input[name=order_]')[0];
+ setOrder(input.value.split(','));
+ input.disabled = true;
+ draw();
+ // Now initialise the dragging behaviour
+ var limit = (lis.length - 1) * height;
+ for (var i = 0; i < lis.length; i++) {
+ var li = lis[i];
+ var img = document.getElementById('handle'+li.id);
+ li.style.zIndex = 1;
+ Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit);
+ li.onDragStart = startDrag;
+ li.onDragEnd = endDrag;
+ img.style.cursor = 'move';
+ }
+}
+
+function submitOrderForm() {
+ var inputOrder = document.getElementsBySelector('input[name=order_]')[0];
+ inputOrder.value = getOrder();
+ inputOrder.disabled=false;
+}
+
+function startDrag() {
+ this.style.zIndex = '10';
+ this.className = 'dragging';
+}
+
+function endDrag(x, y) {
+ this.style.zIndex = '1';
+ this.className = '';
+ // Work out how far along it has been dropped, using x co-ordinate
+ var oldIndex = this.index;
+ var newIndex = Math.round((y - 10 - top) / height);
+ // 'Snap' to the correct position
+ this.style.top = (10 + top + newIndex * height) + 'px';
+ this.index = newIndex;
+ moveItem(oldIndex, newIndex);
+}
+
+function moveItem(oldIndex, newIndex) {
+ // Swaps two items, adjusts the index and left co-ord for all others
+ if (oldIndex == newIndex) {
+ return; // Nothing to swap;
+ }
+ var direction, lo, hi;
+ if (newIndex > oldIndex) {
+ lo = oldIndex;
+ hi = newIndex;
+ direction = -1;
+ } else {
+ direction = 1;
+ hi = oldIndex;
+ lo = newIndex;
+ }
+ var lis2 = new Array(); // We will build the new order in this array
+ for (var i = 0; i < lis.length; i++) {
+ if (i < lo || i > hi) {
+ // Position of items not between the indexes is unaffected
+ lis2[i] = lis[i];
+ continue;
+ } else if (i == newIndex) {
+ lis2[i] = lis[oldIndex];
+ continue;
+ } else {
+ // Item is between the two indexes - move it along 1
+ lis2[i] = lis[i - direction];
+ }
+ }
+ // Re-index everything
+ reIndex(lis2);
+ lis = lis2;
+ draw();
+// document.getElementById('hiddenOrder').value = getOrder();
+ document.getElementsBySelector('input[name=order_]')[0].value = getOrder();
+}
+
+function reIndex(lis) {
+ for (var i = 0; i < lis.length; i++) {
+ lis[i].index = i;
+ }
+}
+
+function draw() {
+ for (var i = 0; i < lis.length; i++) {
+ var li = lis[i];
+ li.index = i;
+ li.style.position = 'absolute';
+ li.style.left = (10 + left) + 'px';
+ li.style.top = (10 + top + (i * height)) + 'px';
+ }
+}
+
+function getOrder() {
+ var order = new Array(lis.length);
+ for (var i = 0; i < lis.length; i++) {
+ order[i] = lis[i].id.substring(1, 100);
+ }
+ return order.join(',');
+}
+
+function setOrder(id_list) {
+ /* Set the current order to match the lsit of IDs */
+ var temp_lis = new Array();
+ for (var i = 0; i < id_list.length; i++) {
+ var id = 'p' + id_list[i];
+ temp_lis[temp_lis.length] = document.getElementById(id);
+ }
+ reIndex(temp_lis);
+ lis = temp_lis;
+ draw();
+}
+
+function addEvent(elm, evType, fn, useCapture)
+// addEvent and removeEvent
+// cross-browser event handling for IE5+, NS6 and Mozilla
+// By Scott Andrew
+{
+ if (elm.addEventListener){
+ elm.addEventListener(evType, fn, useCapture);
+ return true;
+ } else if (elm.attachEvent){
+ var r = elm.attachEvent("on"+evType, fn);
+ return r;
+ } else {
+ elm['on'+evType] = fn;
+ }
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/calendar.js b/google_appengine/lib/django/django/contrib/admin/media/js/calendar.js
new file mode 100644
index 0000000..9035176
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/calendar.js
@@ -0,0 +1,143 @@
+/*
+calendar.js - Calendar functions by Adrian Holovaty
+*/
+
+function removeChildren(a) { // "a" is reference to an object
+ while (a.hasChildNodes()) a.removeChild(a.lastChild);
+}
+
+// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]);
+function quickElement() {
+ var obj = document.createElement(arguments[0]);
+ if (arguments[2] != '' && arguments[2] != null) {
+ var textNode = document.createTextNode(arguments[2]);
+ obj.appendChild(textNode);
+ }
+ var len = arguments.length;
+ for (var i = 3; i < len; i += 2) {
+ obj.setAttribute(arguments[i], arguments[i+1]);
+ }
+ arguments[1].appendChild(obj);
+ return obj;
+}
+
+// CalendarNamespace -- Provides a collection of HTML calendar-related helper functions
+var CalendarNamespace = {
+ monthsOfYear: gettext('January February March April May June July August September October November December').split(' '),
+ daysOfWeek: gettext('S M T W T F S').split(' '),
+ isLeapYear: function(year) {
+ return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0));
+ },
+ getDaysInMonth: function(month,year) {
+ var days;
+ if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) {
+ days = 31;
+ }
+ else if (month==4 || month==6 || month==9 || month==11) {
+ days = 30;
+ }
+ else if (month==2 && CalendarNamespace.isLeapYear(year)) {
+ days = 29;
+ }
+ else {
+ days = 28;
+ }
+ return days;
+ },
+ draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999
+ month = parseInt(month);
+ year = parseInt(year);
+ var calDiv = document.getElementById(div_id);
+ removeChildren(calDiv);
+ var calTable = document.createElement('table');
+ quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year);
+ var tableBody = quickElement('tbody', calTable);
+
+ // Draw days-of-week header
+ var tableRow = quickElement('tr', tableBody);
+ for (var i = 0; i < 7; i++) {
+ quickElement('th', tableRow, CalendarNamespace.daysOfWeek[i]);
+ }
+
+ var startingPos = new Date(year, month-1, 1).getDay();
+ var days = CalendarNamespace.getDaysInMonth(month, year);
+
+ // Draw blanks before first of month
+ tableRow = quickElement('tr', tableBody);
+ for (var i = 0; i < startingPos; i++) {
+ var _cell = quickElement('td', tableRow, ' ');
+ _cell.style.backgroundColor = '#f3f3f3';
+ }
+
+ // Draw days of month
+ var currentDay = 1;
+ for (var i = startingPos; currentDay <= days; i++) {
+ if (i%7 == 0 && currentDay != 1) {
+ tableRow = quickElement('tr', tableBody);
+ }
+ var cell = quickElement('td', tableRow, '');
+ quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));');
+ currentDay++;
+ }
+
+ // Draw blanks after end of month (optional, but makes for valid code)
+ while (tableRow.childNodes.length < 7) {
+ var _cell = quickElement('td', tableRow, ' ');
+ _cell.style.backgroundColor = '#f3f3f3';
+ }
+
+ calDiv.appendChild(calTable);
+ }
+}
+
+// Calendar -- A calendar instance
+function Calendar(div_id, callback) {
+ // div_id (string) is the ID of the element in which the calendar will
+ // be displayed
+ // callback (string) is the name of a JavaScript function that will be
+ // called with the parameters (year, month, day) when a day in the
+ // calendar is clicked
+ this.div_id = div_id;
+ this.callback = callback;
+ this.today = new Date();
+ this.currentMonth = this.today.getMonth() + 1;
+ this.currentYear = this.today.getFullYear();
+}
+Calendar.prototype = {
+ drawCurrent: function() {
+ CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback);
+ },
+ drawDate: function(month, year) {
+ this.currentMonth = month;
+ this.currentYear = year;
+ this.drawCurrent();
+ },
+ drawPreviousMonth: function() {
+ if (this.currentMonth == 1) {
+ this.currentMonth = 12;
+ this.currentYear--;
+ }
+ else {
+ this.currentMonth--;
+ }
+ this.drawCurrent();
+ },
+ drawNextMonth: function() {
+ if (this.currentMonth == 12) {
+ this.currentMonth = 1;
+ this.currentYear++;
+ }
+ else {
+ this.currentMonth++;
+ }
+ this.drawCurrent();
+ },
+ drawPreviousYear: function() {
+ this.currentYear--;
+ this.drawCurrent();
+ },
+ drawNextYear: function() {
+ this.currentYear++;
+ this.drawCurrent();
+ }
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/core.js b/google_appengine/lib/django/django/contrib/admin/media/js/core.js
new file mode 100644
index 0000000..a17ac8a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/core.js
@@ -0,0 +1,164 @@
+// Core javascript helper functions
+
+// Cross-browser event handlers.
+function addEvent(obj, evType, fn) {
+ if (obj.addEventListener) {
+ obj.addEventListener(evType, fn, false);
+ return true;
+ } else if (obj.attachEvent) {
+ var r = obj.attachEvent("on" + evType, fn);
+ return r;
+ } else {
+ return false;
+ }
+}
+
+function removeEvent(obj, evType, fn) {
+ if (obj.removeEventListener) {
+ obj.removeEventListener(evType, fn, false);
+ return true;
+ } else if (obj.detachEvent) {
+ obj.detachEvent("on" + evType, fn);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]);
+function quickElement() {
+ var obj = document.createElement(arguments[0]);
+ if (arguments[2] != '' && arguments[2] != null) {
+ var textNode = document.createTextNode(arguments[2]);
+ obj.appendChild(textNode);
+ }
+ var len = arguments.length;
+ for (var i = 3; i < len; i += 2) {
+ obj.setAttribute(arguments[i], arguments[i+1]);
+ }
+ arguments[1].appendChild(obj);
+ return obj;
+}
+
+// ----------------------------------------------------------------------------
+// Cross-browser xmlhttp object
+// from http://jibbering.com/2002/4/httprequest.html
+// ----------------------------------------------------------------------------
+var xmlhttp;
+/*@cc_on @*/
+/*@if (@_jscript_version >= 5)
+ try {
+ xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (e) {
+ try {
+ xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (E) {
+ xmlhttp = false;
+ }
+ }
+@else
+ xmlhttp = false;
+@end @*/
+if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
+ xmlhttp = new XMLHttpRequest();
+}
+
+// ----------------------------------------------------------------------------
+// Find-position functions by PPK
+// See http://www.quirksmode.org/js/findpos.html
+// ----------------------------------------------------------------------------
+function findPosX(obj) {
+ var curleft = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curleft += obj.offsetLeft;
+ obj = obj.offsetParent;
+ }
+ } else if (obj.x) {
+ curleft += obj.x;
+ }
+ return curleft;
+}
+
+function findPosY(obj) {
+ var curtop = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curtop += obj.offsetTop;
+ obj = obj.offsetParent;
+ }
+ } else if (obj.y) {
+ curtop += obj.y;
+ }
+ return curtop;
+}
+
+//-----------------------------------------------------------------------------
+// Date object extensions
+// ----------------------------------------------------------------------------
+Date.prototype.getCorrectYear = function() {
+ // Date.getYear() is unreliable --
+ // see http://www.quirksmode.org/js/introdate.html#year
+ var y = this.getYear() % 100;
+ return (y < 38) ? y + 2000 : y + 1900;
+}
+
+Date.prototype.getTwoDigitMonth = function() {
+ return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1);
+}
+
+Date.prototype.getTwoDigitDate = function() {
+ return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate();
+}
+
+Date.prototype.getTwoDigitHour = function() {
+ return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours();
+}
+
+Date.prototype.getTwoDigitMinute = function() {
+ return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes();
+}
+
+Date.prototype.getTwoDigitSecond = function() {
+ return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
+}
+
+Date.prototype.getISODate = function() {
+ return this.getCorrectYear() + '-' + this.getTwoDigitMonth() + '-' + this.getTwoDigitDate();
+}
+
+Date.prototype.getHourMinute = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute();
+}
+
+Date.prototype.getHourMinuteSecond = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond();
+}
+
+// ----------------------------------------------------------------------------
+// String object extensions
+// ----------------------------------------------------------------------------
+String.prototype.pad_left = function(pad_length, pad_string) {
+ var new_string = this;
+ for (var i = 0; new_string.length < pad_length; i++) {
+ new_string = pad_string + new_string;
+ }
+ return new_string;
+}
+
+// ----------------------------------------------------------------------------
+// Get the computed style for and element
+// ----------------------------------------------------------------------------
+function getStyle(oElm, strCssRule){
+ var strValue = "";
+ if(document.defaultView && document.defaultView.getComputedStyle){
+ strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
+ }
+ else if(oElm.currentStyle){
+ strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
+ return p1.toUpperCase();
+ });
+ strValue = oElm.currentStyle[strCssRule];
+ }
+ return strValue;
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/dateparse.js b/google_appengine/lib/django/django/contrib/admin/media/js/dateparse.js
new file mode 100644
index 0000000..e1c870e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/dateparse.js
@@ -0,0 +1,233 @@
+/* 'Magic' date parsing, by Simon Willison (6th October 2003)
+ http://simon.incutio.com/archive/2003/10/06/betterDateInput
+ Adapted for 6newslawrence.com, 28th January 2004
+*/
+
+/* Finds the index of the first occurence of item in the array, or -1 if not found */
+if (typeof Array.prototype.indexOf == 'undefined') {
+ Array.prototype.indexOf = function(item) {
+ var len = this.length;
+ for (var i = 0; i < len; i++) {
+ if (this[i] == item) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+/* Returns an array of items judged 'true' by the passed in test function */
+if (typeof Array.prototype.filter == 'undefined') {
+ Array.prototype.filter = function(test) {
+ var matches = [];
+ var len = this.length;
+ for (var i = 0; i < len; i++) {
+ if (test(this[i])) {
+ matches[matches.length] = this[i];
+ }
+ }
+ return matches;
+ };
+}
+
+var monthNames = gettext("January February March April May June July August September October November December").split(" ");
+var weekdayNames = gettext("Sunday Monday Tuesday Wednesday Thursday Friday Saturday").split(" ");
+
+/* Takes a string, returns the index of the month matching that string, throws
+ an error if 0 or more than 1 matches
+*/
+function parseMonth(month) {
+ var matches = monthNames.filter(function(item) {
+ return new RegExp("^" + month, "i").test(item);
+ });
+ if (matches.length == 0) {
+ throw new Error("Invalid month string");
+ }
+ if (matches.length > 1) {
+ throw new Error("Ambiguous month");
+ }
+ return monthNames.indexOf(matches[0]);
+}
+/* Same as parseMonth but for days of the week */
+function parseWeekday(weekday) {
+ var matches = weekdayNames.filter(function(item) {
+ return new RegExp("^" + weekday, "i").test(item);
+ });
+ if (matches.length == 0) {
+ throw new Error("Invalid day string");
+ }
+ if (matches.length > 1) {
+ throw new Error("Ambiguous weekday");
+ }
+ return weekdayNames.indexOf(matches[0]);
+}
+
+/* Array of objects, each has 're', a regular expression and 'handler', a
+ function for creating a date from something that matches the regular
+ expression. Handlers may throw errors if string is unparseable.
+*/
+var dateParsePatterns = [
+ // Today
+ { re: /^tod/i,
+ handler: function() {
+ return new Date();
+ }
+ },
+ // Tomorrow
+ { re: /^tom/i,
+ handler: function() {
+ var d = new Date();
+ d.setDate(d.getDate() + 1);
+ return d;
+ }
+ },
+ // Yesterday
+ { re: /^yes/i,
+ handler: function() {
+ var d = new Date();
+ d.setDate(d.getDate() - 1);
+ return d;
+ }
+ },
+ // 4th
+ { re: /^(\d{1,2})(st|nd|rd|th)?$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[1], 10));
+ return d;
+ }
+ },
+ // 4th Jan
+ { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[1], 10));
+ d.setMonth(parseMonth(bits[2]));
+ return d;
+ }
+ },
+ // 4th Jan 2003
+ { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[1], 10));
+ d.setMonth(parseMonth(bits[2]));
+ d.setYear(bits[3]);
+ return d;
+ }
+ },
+ // Jan 4th
+ { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[2], 10));
+ d.setMonth(parseMonth(bits[1]));
+ return d;
+ }
+ },
+ // Jan 4th 2003
+ { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
+ handler: function(bits) {
+ var d = new Date();
+ d.setDate(parseInt(bits[2], 10));
+ d.setMonth(parseMonth(bits[1]));
+ d.setYear(bits[3]);
+ return d;
+ }
+ },
+ // next Tuesday - this is suspect due to weird meaning of "next"
+ { re: /^next (\w+)$/i,
+ handler: function(bits) {
+ var d = new Date();
+ var day = d.getDay();
+ var newDay = parseWeekday(bits[1]);
+ var addDays = newDay - day;
+ if (newDay <= day) {
+ addDays += 7;
+ }
+ d.setDate(d.getDate() + addDays);
+ return d;
+ }
+ },
+ // last Tuesday
+ { re: /^last (\w+)$/i,
+ handler: function(bits) {
+ throw new Error("Not yet implemented");
+ }
+ },
+ // mm/dd/yyyy (American style)
+ { re: /(\d{1,2})\/(\d{1,2})\/(\d{4})/,
+ handler: function(bits) {
+ var d = new Date();
+ d.setYear(bits[3]);
+ d.setDate(parseInt(bits[2], 10));
+ d.setMonth(parseInt(bits[1], 10) - 1); // Because months indexed from 0
+ return d;
+ }
+ },
+ // yyyy-mm-dd (ISO style)
+ { re: /(\d{4})-(\d{1,2})-(\d{1,2})/,
+ handler: function(bits) {
+ var d = new Date();
+ d.setYear(parseInt(bits[1]));
+ d.setMonth(parseInt(bits[2], 10) - 1);
+ d.setDate(parseInt(bits[3], 10));
+ return d;
+ }
+ },
+];
+
+function parseDateString(s) {
+ for (var i = 0; i < dateParsePatterns.length; i++) {
+ var re = dateParsePatterns[i].re;
+ var handler = dateParsePatterns[i].handler;
+ var bits = re.exec(s);
+ if (bits) {
+ return handler(bits);
+ }
+ }
+ throw new Error("Invalid date string");
+}
+
+function fmt00(x) {
+ // fmt00: Tags leading zero onto numbers 0 - 9.
+ // Particularly useful for displaying results from Date methods.
+ //
+ if (Math.abs(parseInt(x)) < 10){
+ x = "0"+ Math.abs(x);
+ }
+ return x;
+}
+
+function parseDateStringISO(s) {
+ try {
+ var d = parseDateString(s);
+ return d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' + fmt00(d.getDate())
+ }
+ catch (e) { return s; }
+}
+function magicDate(input) {
+ var messagespan = input.id + 'Msg';
+ try {
+ var d = parseDateString(input.value);
+ input.value = d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' +
+ fmt00(d.getDate());
+ input.className = '';
+ // Human readable date
+ if (document.getElementById(messagespan)) {
+ document.getElementById(messagespan).firstChild.nodeValue = d.toDateString();
+ document.getElementById(messagespan).className = 'normal';
+ }
+ }
+ catch (e) {
+ input.className = 'error';
+ var message = e.message;
+ // Fix for IE6 bug
+ if (message.indexOf('is null or not an object') > -1) {
+ message = 'Invalid date string';
+ }
+ if (document.getElementById(messagespan)) {
+ document.getElementById(messagespan).firstChild.nodeValue = message;
+ document.getElementById(messagespan).className = 'error';
+ }
+ }
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/getElementsBySelector.js b/google_appengine/lib/django/django/contrib/admin/media/js/getElementsBySelector.js
new file mode 100644
index 0000000..ae6d387
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/getElementsBySelector.js
@@ -0,0 +1,167 @@
+/* document.getElementsBySelector(selector)
+ - returns an array of element objects from the current document
+ matching the CSS selector. Selectors can contain element names,
+ class names and ids and can be nested. For example:
+
+ elements = document.getElementsBySelect('div#main p a.external')
+
+ Will return an array of all 'a' elements with 'external' in their
+ class attribute that are contained inside 'p' elements that are
+ contained inside the 'div' element which has id="main"
+
+ New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
+ See http://www.w3.org/TR/css3-selectors/#attribute-selectors
+
+ Version 0.4 - Simon Willison, March 25th 2003
+ -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
+ -- Opera 7 fails
+*/
+
+function getAllChildren(e) {
+ // Returns all children of element. Workaround required for IE5/Windows. Ugh.
+ return e.all ? e.all : e.getElementsByTagName('*');
+}
+
+document.getElementsBySelector = function(selector) {
+ // Attempt to fail gracefully in lesser browsers
+ if (!document.getElementsByTagName) {
+ return new Array();
+ }
+ // Split selector in to tokens
+ var tokens = selector.split(' ');
+ var currentContext = new Array(document);
+ for (var i = 0; i < tokens.length; i++) {
+ token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
+ if (token.indexOf('#') > -1) {
+ // Token is an ID selector
+ var bits = token.split('#');
+ var tagName = bits[0];
+ var id = bits[1];
+ var element = document.getElementById(id);
+ if (tagName && element.nodeName.toLowerCase() != tagName) {
+ // tag with that ID not found, return false
+ return new Array();
+ }
+ // Set currentContext to contain just this element
+ currentContext = new Array(element);
+ continue; // Skip to next token
+ }
+ if (token.indexOf('.') > -1) {
+ // Token contains a class selector
+ var bits = token.split('.');
+ var tagName = bits[0];
+ var className = bits[1];
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Get elements matching tag, filter them for class selector
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ try {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ catch(e) {
+ elements = [];
+ }
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ continue; // Skip to next token
+ }
+ // Code to deal with attribute selectors
+ if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
+ var tagName = RegExp.$1;
+ var attrName = RegExp.$2;
+ var attrOperator = RegExp.$3;
+ var attrValue = RegExp.$4;
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Grab all of the tagName elements within current context
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ var checkFunction; // This function will be used to filter the elements
+ switch (attrOperator) {
+ case '=': // Equality
+ checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
+ break;
+ case '~': // Match one of space seperated words
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
+ break;
+ case '|': // Match start with value followed by optional hyphen
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
+ break;
+ case '^': // Match starts with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
+ break;
+ case '$': // Match ends with value - fails with "Warning" in Opera 7
+ checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
+ break;
+ case '*': // Match ends with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
+ break;
+ default :
+ // Just test for existence of attribute
+ checkFunction = function(e) { return e.getAttribute(attrName); };
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (checkFunction(found[k])) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
+ continue; // Skip to next token
+ }
+ // If we get here, token is JUST an element (not a class or ID selector)
+ tagName = token;
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements = currentContext[h].getElementsByTagName(tagName);
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = found;
+ }
+ return currentContext;
+}
+
+/* That revolting regular expression explained
+/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
+ \---/ \---/\-------------/ \-------/
+ | | | |
+ | | | The value
+ | | ~,|,^,$,* or =
+ | Attribute
+ Tag
+*/
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/timeparse.js b/google_appengine/lib/django/django/contrib/admin/media/js/timeparse.js
new file mode 100644
index 0000000..882f41d
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/timeparse.js
@@ -0,0 +1,94 @@
+var timeParsePatterns = [
+ // 9
+ { re: /^\d{1,2}$/i,
+ handler: function(bits) {
+ if (bits[0].length == 1) {
+ return '0' + bits[0] + ':00';
+ } else {
+ return bits[0] + ':00';
+ }
+ }
+ },
+ // 13:00
+ { re: /^\d{2}[:.]\d{2}$/i,
+ handler: function(bits) {
+ return bits[0].replace('.', ':');
+ }
+ },
+ // 9:00
+ { re: /^\d[:.]\d{2}$/i,
+ handler: function(bits) {
+ return '0' + bits[0].replace('.', ':');
+ }
+ },
+ // 3 am / 3 a.m. / 3am
+ { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i,
+ handler: function(bits) {
+ var hour = parseInt(bits[1]);
+ if (hour == 12) {
+ hour = 0;
+ }
+ if (bits[2].toLowerCase() == 'p') {
+ if (hour == 12) {
+ hour = 0;
+ }
+ return (hour + 12) + ':00';
+ } else {
+ if (hour < 10) {
+ return '0' + hour + ':00';
+ } else {
+ return hour + ':00';
+ }
+ }
+ }
+ },
+ // 3.30 am / 3:15 a.m. / 3.00am
+ { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i,
+ handler: function(bits) {
+ var hour = parseInt(bits[1]);
+ var mins = parseInt(bits[2]);
+ if (mins < 10) {
+ mins = '0' + mins;
+ }
+ if (hour == 12) {
+ hour = 0;
+ }
+ if (bits[3].toLowerCase() == 'p') {
+ if (hour == 12) {
+ hour = 0;
+ }
+ return (hour + 12) + ':' + mins;
+ } else {
+ if (hour < 10) {
+ return '0' + hour + ':' + mins;
+ } else {
+ return hour + ':' + mins;
+ }
+ }
+ }
+ },
+ // noon
+ { re: /^no/i,
+ handler: function(bits) {
+ return '12:00';
+ }
+ },
+ // midnight
+ { re: /^mid/i,
+ handler: function(bits) {
+ return '00:00';
+ }
+ }
+];
+
+function parseTimeString(s) {
+ for (var i = 0; i < timeParsePatterns.length; i++) {
+ var re = timeParsePatterns[i].re;
+ var handler = timeParsePatterns[i].handler;
+ var bits = re.exec(s);
+ if (bits) {
+ return handler(bits);
+ }
+ }
+ return s;
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/media/js/urlify.js b/google_appengine/lib/django/django/contrib/admin/media/js/urlify.js
new file mode 100644
index 0000000..9b87113
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/media/js/urlify.js
@@ -0,0 +1,15 @@
+function URLify(s, num_chars) {
+ // changes, e.g., "Petty theft" to "petty_theft"
+ // remove all these words from the string before urlifying
+ removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from",
+ "is", "in", "into", "like", "of", "off", "on", "onto", "per",
+ "since", "than", "the", "this", "that", "to", "up", "via",
+ "with"];
+ r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
+ s = s.replace(r, '');
+ s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars
+ s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
+ s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
+ s = s.toLowerCase(); // convert to lowercase
+ return s.substring(0, num_chars);// trim to first num_chars chars
+}
diff --git a/google_appengine/lib/django/django/contrib/admin/models.py b/google_appengine/lib/django/django/contrib/admin/models.py
new file mode 100755
index 0000000..022d20b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/models.py
@@ -0,0 +1,51 @@
+from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.models import User
+from django.utils.translation import gettext_lazy as _
+
+ADDITION = 1
+CHANGE = 2
+DELETION = 3
+
+class LogEntryManager(models.Manager):
+ def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
+ e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
+ e.save()
+
+class LogEntry(models.Model):
+ action_time = models.DateTimeField(_('action time'), auto_now=True)
+ user = models.ForeignKey(User)
+ content_type = models.ForeignKey(ContentType, blank=True, null=True)
+ object_id = models.TextField(_('object id'), blank=True, null=True)
+ object_repr = models.CharField(_('object repr'), maxlength=200)
+ action_flag = models.PositiveSmallIntegerField(_('action flag'))
+ change_message = models.TextField(_('change message'), blank=True)
+ objects = LogEntryManager()
+ class Meta:
+ verbose_name = _('log entry')
+ verbose_name_plural = _('log entries')
+ db_table = 'django_admin_log'
+ ordering = ('-action_time',)
+
+ def __repr__(self):
+ return str(self.action_time)
+
+ def is_addition(self):
+ return self.action_flag == ADDITION
+
+ def is_change(self):
+ return self.action_flag == CHANGE
+
+ def is_deletion(self):
+ return self.action_flag == DELETION
+
+ def get_edited_object(self):
+ "Returns the edited object represented by this log entry"
+ return self.content_type.get_object_for_this_type(pk=self.object_id)
+
+ def get_admin_url(self):
+ """
+ Returns the admin URL to edit the object represented by this log entry.
+ This is relative to the Django admin index page.
+ """
+ return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/404.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/404.html
new file mode 100644
index 0000000..9bf4293
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/404.html
@@ -0,0 +1,12 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block title %}{% trans 'Page not found' %}{% endblock %}
+
+{% block content %}
+
+<h2>{% trans 'Page not found' %}</h2>
+
+<p>{% trans "We're sorry, but the requested page could not be found." %}</p>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/500.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/500.html
new file mode 100644
index 0000000..b30e431
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/500.html
@@ -0,0 +1,12 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> &rsaquo; {% trans "Server error" %}</div>{% endblock %}
+
+{% block title %}{% trans 'Server error (500)' %}{% endblock %}
+
+{% block content %}
+<h1>{% trans 'Server Error <em>(500)</em>' %}</h1>
+<p>{% trans "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." %}</p>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/add_form.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/add_form.html
new file mode 100644
index 0000000..139fa6a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/add_form.html
@@ -0,0 +1,28 @@
+{% extends "admin/change_form.html" %}
+{% load i18n %}
+
+{% block after_field_sets %}
+
+<p>{% trans "First, enter a username and password. Then, you'll be able to edit more user options." %}</p>
+
+<fieldset class="module aligned">
+
+<div class="form-row">
+ {{ form.username.html_error_list }}
+ <label for="id_username" class="required">{% trans 'Username' %}:</label> {{ form.username }}
+ <p class="help">{{ username_help_text }}</p>
+</div>
+
+<div class="form-row">
+ {{ form.password1.html_error_list }}
+ <label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
+</div>
+
+<div class="form-row">
+ {{ form.password2.html_error_list }}
+ <label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
+ <p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
+</div>
+
+</fieldset>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/change_password.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/change_password.html
new file mode 100644
index 0000000..3d359ec
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/change_password.html
@@ -0,0 +1,52 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify adminmedia %}
+{% block extrahead %}{{ block.super }}
+<script type="text/javascript" src="../../../../jsi18n/"></script>
+{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
+{% endblock %}
+{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
+{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
+{% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
+ <a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
+ <a href="../">{{ original|truncatewords:"18"|escape }}</a> &rsaquo;
+ {% trans 'Change password' %}
+</div>
+{% endif %}{% endblock %}
+{% block content %}<div id="content-main">
+<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
+<div>
+{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
+{% if form.error_dict %}
+ <p class="errornote">
+ {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ </p>
+{% endif %}
+
+<p>{% blocktrans with original.username|escape as username %}Enter a new password for the user <strong>{{ username }}</strong>.{% endblocktrans %}</p>
+
+<fieldset class="module aligned">
+
+<div class="form-row">
+ {{ form.password1.html_error_list }}
+ <label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
+</div>
+
+<div class="form-row">
+ {{ form.password2.html_error_list }}
+ <label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
+ <p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
+</div>
+
+</fieldset>
+
+<div class="submit-row">
+<input type="submit" value="{% trans 'Change password' %}" class="default" />
+</div>
+
+<script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
+</div>
+</form></div>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/base.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/base.html
new file mode 100644
index 0000000..d3e8c96
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/base.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
+<head>
+<title>{% block title %}{% endblock %}</title>
+<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/base.css{% endblock %}" />
+{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% admin_media_prefix %}css/rtl.css{% endblock %}" />{% endif %}
+{% block extrastyle %}{% endblock %}
+{% block extrahead %}{% endblock %}
+{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
+</head>
+{% load i18n %}
+
+<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}">
+
+<!-- Container -->
+<div id="container">
+
+ {% if not is_popup %}
+ <!-- Header -->
+ <div id="header">
+ <div id="branding">
+ {% block branding %}{% endblock %}
+ </div>
+ {% if user.is_authenticated and user.is_staff %}
+ <div id="user-tools">{% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>. {% block userlinks %}<a href="doc/">{% trans 'Documentation' %}</a> / <a href="password_change/">{% trans 'Change password' %}</a> / <a href="logout/">{% trans 'Log out' %}</a>{% endblock %}</div>
+ {% endif %}
+ {% block nav-global %}{% endblock %}
+ </div>
+ <!-- END Header -->
+ {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} &rsaquo; {{ title|escape }}{% endif %}</div>{% endblock %}
+ {% endif %}
+
+ {% if messages %}
+ <ul class="messagelist">{% for message in messages %}<li>{{ message|escape }}</li>{% endfor %}</ul>
+ {% endif %}
+
+ <!-- Content -->
+ <div id="content" class="{% block coltype %}colM{% endblock %}">
+ {% block pretitle %}{% endblock %}
+ {% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% endblock %}
+ {% block content %}
+ {% block object-tools %}{% endblock %}
+ {{ content }}
+ {% endblock %}
+ {% block sidebar %}{% endblock %}
+ <br class="clear" />
+ </div>
+ <!-- END Content -->
+
+ {% block footer %}<div id="footer"></div>{% endblock %}
+</div>
+<!-- END Container -->
+
+</body>
+</html>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/base_site.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/base_site.html
new file mode 100644
index 0000000..2bc7310
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/base_site.html
@@ -0,0 +1,10 @@
+{% extends "admin/base.html" %}
+{% load i18n %}
+
+{% block title %}{{ title|escape }} | {% trans 'Django site admin' %}{% endblock %}
+
+{% block branding %}
+<h1 id="site-name">{% trans 'Django administration' %}</h1>
+{% endblock %}
+
+{% block nav-global %}{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/change_form.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/change_form.html
new file mode 100644
index 0000000..7e7b639
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/change_form.html
@@ -0,0 +1,70 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify adminmedia %}
+{% block extrahead %}{{ block.super }}
+<script type="text/javascript" src="../../../jsi18n/"></script>
+{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
+{% endblock %}
+{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
+{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
+{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
+{% block userlinks %}<a href="../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="../../../">{% trans "Home" %}</a> &rsaquo;
+ <a href="../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
+ {% if add %}{% trans "Add" %} {{ opts.verbose_name|escape }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %}
+</div>
+{% endif %}{% endblock %}
+{% block content %}<div id="content-main">
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+ <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
+ {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
+ </ul>
+{% endif %}{% endif %}
+{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
+<div>
+{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
+{% if opts.admin.save_on_top %}{% submit_row %}{% endif %}
+{% if form.error_dict %}
+ <p class="errornote">
+ {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ </p>
+{% endif %}
+{% for bound_field_set in bound_field_sets %}
+ <fieldset class="module aligned {{ bound_field_set.classes }}">
+ {% if bound_field_set.name %}<h2>{{ bound_field_set.name }}</h2>{% endif %}
+ {% if bound_field_set.description %}<div class="description">{{ bound_field_set.description }}</div>{% endif %}
+ {% for bound_field_line in bound_field_set %}
+ {% admin_field_line bound_field_line %}
+ {% for bound_field in bound_field_line %}
+ {% filter_interface_script_maybe bound_field %}
+ {% endfor %}
+ {% endfor %}
+ </fieldset>
+{% endfor %}
+{% block after_field_sets %}{% endblock %}
+{% if change %}
+ {% if ordered_objects %}
+ <fieldset class="module"><h2>{% trans "Ordering" %}</h2>
+ <div class="form-row{% if form.order_.errors %} error{% endif %} ">
+ {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
+ <p><label for="id_order_">{% trans "Order:" %}</label> {{ form.order_ }}</p>
+ </div></fieldset>
+ {% endif %}
+{% endif %}
+{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
+{% block after_related_objects %}{% endblock %}
+{% submit_row %}
+{% if add %}
+ <script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
+{% endif %}
+{% if auto_populated_fields %}
+ <script type="text/javascript">
+ {% auto_populated_field_script auto_populated_fields change %}
+ </script>
+{% endif %}
+</div>
+</form></div>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/change_list.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/change_list.html
new file mode 100644
index 0000000..f50a73c
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/change_list.html
@@ -0,0 +1,23 @@
+{% extends "admin/base_site.html" %}
+{% load adminmedia admin_list i18n %}
+{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %}
+{% block bodyclass %}change-list{% endblock %}
+{% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / <a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst|escape }}</div>{% endblock %}{% endif %}
+{% block coltype %}flex{% endblock %}
+{% block content %}
+<div id="content-main">
+{% block object-tools %}
+{% if has_add_permission %}
+<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li></ul>
+{% endif %}
+{% endblock %}
+<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
+{% block search %}{% search_form cl %}{% endblock %}
+{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
+{% block filters %}{% filters cl %}{% endblock %}
+{% block result_list %}{% result_list cl %}{% endblock %}
+{% block pagination %}{% pagination cl %}{% endblock %}
+</div>
+</div>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/change_list_results.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/change_list_results.html
new file mode 100644
index 0000000..3f75578
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/change_list_results.html
@@ -0,0 +1,17 @@
+{% if results %}
+<table cellspacing="0">
+<thead>
+<tr>
+{% for header in result_headers %}<th{{ header.class_attrib }}>
+{% if header.sortable %}<a href="{{ header.url }}">{% endif %}
+{{ header.text|capfirst }}
+{% if header.sortable %}</a>{% endif %}</th>{% endfor %}
+</tr>
+</thead>
+<tbody>
+{% for result in results %}
+<tr class="{% cycle row1,row2 %}">{% for item in result %}{{ item }}{% endfor %}</tr>
+{% endfor %}
+</tbody>
+</table>
+{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/date_hierarchy.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/date_hierarchy.html
new file mode 100644
index 0000000..d2d6961
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/date_hierarchy.html
@@ -0,0 +1,10 @@
+{% if show %}
+<div class="xfull">
+<ul class="toplinks">
+{% if back %}<li class="date-back"><a href="{{ back.link }}">&lsaquo; {{ back.title|escape }}</a></li>{% endif %}
+{% for choice in choices %}
+<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title|escape }}{% if choice.link %}</a>{% endif %}</li>
+{% endfor %}
+</ul><br class="clear" />
+</div>
+{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/delete_confirmation.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/delete_confirmation.html
new file mode 100644
index 0000000..3921ab6
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/delete_confirmation.html
@@ -0,0 +1,30 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
+ <a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
+ <a href="../">{{ object|escape|truncatewords:"18" }}</a> &rsaquo;
+ {% trans 'Delete' %}
+</div>
+{% endblock %}
+{% block content %}
+{% if perms_lacking %}
+ <p>{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
+ <ul>
+ {% for obj in perms_lacking %}
+ <li>{{ obj|escape }}</li>
+ {% endfor %}
+ </ul>
+{% else %}
+ <p>{% blocktrans with object|escape as escaped_object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}</p>
+ <ul>{{ deleted_objects|unordered_list }}</ul>
+ <form action="" method="post">
+ <div>
+ <input type="hidden" name="post" value="yes" />
+ <input type="submit" value="{% trans "Yes, I'm sure" %}" />
+ </div>
+ </form>
+{% endif %}
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_stacked.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_stacked.html
new file mode 100644
index 0000000..48ecc69
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_stacked.html
@@ -0,0 +1,16 @@
+{% load admin_modify %}
+<fieldset class="module aligned">
+ {% for fcw in bound_related_object.form_field_collection_wrappers %}
+ <h2>{{ bound_related_object.relation.opts.verbose_name|capfirst|escape }}&nbsp;#{{ forloop.counter }}</h2>
+ {% if bound_related_object.show_url %}{% if fcw.obj.original %}
+ <p><a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a></p>
+ {% endif %}{% endif %}
+ {% for bound_field in fcw.bound_fields %}
+ {% if bound_field.hidden %}
+ {% field_widget bound_field %}
+ {% else %}
+ {% admin_field_line bound_field %}
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+</fieldset>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_tabular.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_tabular.html
new file mode 100644
index 0000000..3d059c8
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_tabular.html
@@ -0,0 +1,44 @@
+{% load admin_modify %}
+<fieldset class="module">
+ <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}</h2><table>
+ <thead><tr>
+ {% for fw in bound_related_object.field_wrapper_list %}
+ {% if fw.needs_header %}
+ <th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst|escape }}</th>
+ {% endif %}
+ {% endfor %}
+ </tr></thead>
+ {% for fcw in bound_related_object.form_field_collection_wrappers %}
+ {% if change %}{% if original_row_needed %}
+ {% if fcw.obj.original %}
+ <tr class="row-label {% cycle row1,row2 %}"><td colspan="{{ num_headers }}"><strong>{{ fcw.obj.original }}</strong></tr>
+ {% endif %}
+ {% endif %}{% endif %}
+ {% if fcw.obj.errors %}
+ <tr class="errorlist"><td colspan="{{ num_headers }}">
+ {{ fcw.obj.html_combined_error_list }}
+ </tr>
+ {% endif %}
+ <tr class="{% cycle row1,row2 %}">
+ {% for bound_field in fcw.bound_fields %}
+ {% if not bound_field.hidden %}
+ <td {{ bound_field.cell_class_attribute }}>
+ {% field_widget bound_field %}
+ </td>
+ {% endif %}
+ {% endfor %}
+ {% if bound_related_object.show_url %}<td>
+ {% if fcw.obj.original %}<a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a>{% endif %}
+ </td>{% endif %}
+ </tr>
+
+ {% endfor %} </table>
+
+ {% for fcw in bound_related_object.form_field_collection_wrappers %}
+ {% for bound_field in fcw.bound_fields %}
+ {% if bound_field.hidden %}
+ {% field_widget bound_field %}
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+</fieldset>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/field_line.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/field_line.html
new file mode 100644
index 0000000..680830b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/field_line.html
@@ -0,0 +1,10 @@
+{% load admin_modify %}
+<div class="{{ class_names }}" >
+{% for bound_field in bound_fields %}{{ bound_field.html_error_list }}{% endfor %}
+{% for bound_field in bound_fields %}
+ {% if bound_field.has_label_first %}{% field_label bound_field %}{% endif %}
+ {% field_widget bound_field %}
+ {% if not bound_field.has_label_first %}{% field_label bound_field %}{% endif %}
+ {% if bound_field.field.help_text %}<p class="help">{{ bound_field.field.help_text }}</p>{% endif %}
+{% endfor %}
+</div>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/filter.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/filter.html
new file mode 100644
index 0000000..8b5b521
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/filter.html
@@ -0,0 +1,8 @@
+{% load i18n %}
+<h3>{% blocktrans with title|escape as filter_title %} By {{ filter_title }} {% endblocktrans %}</h3>
+<ul>
+{% for choice in choices %}
+ <li{% if choice.selected %} class="selected"{% endif %}>
+ <a href="{{ choice.query_string }}">{{ choice.display|escape }}</a></li>
+{% endfor %}
+</ul>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/filters.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/filters.html
new file mode 100644
index 0000000..3ca763c
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/filters.html
@@ -0,0 +1,7 @@
+{% load admin_list %}
+{% load i18n %}
+{% if cl.has_filters %}<div id="changelist-filter">
+<h2>{% trans 'Filter' %}</h2>
+{% for spec in cl.filter_specs %}
+ {% filter cl spec %}
+{% endfor %}</div>{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/index.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/index.html
new file mode 100644
index 0000000..aa63c14
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/index.html
@@ -0,0 +1,67 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css{% endblock %}
+{% block coltype %}colMS{% endblock %}
+{% block bodyclass %}dashboard{% endblock %}
+{% block breadcrumbs %}{% endblock %}
+{% block content %}
+<div id="content-main">
+
+{% load adminapplist %}
+
+{% get_admin_app_list as app_list %}
+{% if app_list %}
+ {% for app in app_list %}
+ <div class="module">
+ <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
+ <caption>{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</caption>
+ {% for model in app.models %}
+ <tr>
+ {% if model.perms.change %}
+ <th scope="row"><a href="{{ model.admin_url }}">{{ model.name|escape }}</a></th>
+ {% else %}
+ <th scope="row">{{ model.name|escape }}</th>
+ {% endif %}
+
+ {% if model.perms.add %}
+ <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
+ {% else %}
+ <td>&nbsp;</td>
+ {% endif %}
+
+ {% if model.perms.change %}
+ <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
+ {% else %}
+ <td>&nbsp;</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ {% endfor %}
+{% else %}
+ <p>{% trans "You don't have permission to edit anything." %}</p>
+{% endif %}
+</div>
+{% endblock %}
+
+{% block sidebar %}
+<div id="content-related">
+ <div class="module" id="recent-actions-module">
+ <h2>{% trans 'Recent Actions' %}</h2>
+ <h3>{% trans 'My Actions' %}</h3>
+ {% load log %}
+ {% get_admin_log 10 as admin_log for_user user %}
+ {% if not admin_log %}
+ <p>{% trans 'None available' %}</p>
+ {% else %}
+ <ul class="actionlist">
+ {% for entry in admin_log %}
+ <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.content_type.name|capfirst|escape }}</span></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </div>
+</div>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/invalid_setup.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/invalid_setup.html
new file mode 100644
index 0000000..1d7d61f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/invalid_setup.html
@@ -0,0 +1,10 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title|escape }}</div>{% endblock %}
+
+{% block content %}
+
+<p>{% trans "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." %}</p>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/login.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/login.html
new file mode 100644
index 0000000..eab8001
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/login.html
@@ -0,0 +1,32 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/login.css{% endblock %}
+{% block bodyclass %}login{% endblock %}
+{% block content_title %}{% endblock %}
+{% block breadcrumbs %}{% endblock %}
+
+{% block content %}
+
+{% if error_message %}
+<p class="errornote">{{ error_message }}</p>
+{% endif %}
+<div id="content-main">
+<form action="{{ app_path }}" method="post" id="login-form">
+ <div class="form-row">
+ <label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
+ </div>
+ <div class="form-row">
+ <label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
+ <input type="hidden" name="this_is_the_login_form" value="1" />
+ </div>
+ <div class="submit-row">
+ <label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
+ </div>
+</form>
+
+<script type="text/javascript">
+document.getElementById('id_username').focus()
+</script>
+</div>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/object_history.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/object_history.html
new file mode 100644
index 0000000..14a77b8
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/object_history.html
@@ -0,0 +1,43 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> &rsaquo; <a href="../../">{{ module_name|escape }}</a> &rsaquo; <a href="../">{{ object|escape|truncatewords:"18" }}</a> &rsaquo; {% trans 'History' %}</div>
+{% endblock %}
+
+{% block content %}
+
+<div id="content-main">
+<div class="module">
+
+{% if action_list %}
+
+ <table id="change-history">
+ <thead>
+ <tr>
+ <th scope="col">{% trans 'Date/time' %}</th>
+ <th scope="col">{% trans 'User' %}</th>
+ <th scope="col">{% trans 'Action' %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for action in action_list %}
+ <tr>
+ <th scope="row">{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
+ <td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name|escape }} {{ action.user.last_name|escape }}){% endif %}</td>
+ <td>{{ action.change_message|escape }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+
+{% else %}
+
+ <p>{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}</p>
+
+{% endif %}
+
+</div>
+</div>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/pagination.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/pagination.html
new file mode 100644
index 0000000..e1c09b2
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/pagination.html
@@ -0,0 +1,11 @@
+{% load admin_list %}
+{% load i18n %}
+<p class="paginator">
+{% if pagination_required %}
+{% for i in page_range %}
+ {% paginator_number cl i %}
+{% endfor %}
+{% endif %}
+{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural|escape }}{% endifequal %}
+{% if show_all_url %}&nbsp;&nbsp;<a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
+</p>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/search_form.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/search_form.html
new file mode 100644
index 0000000..445cca3
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/search_form.html
@@ -0,0 +1,18 @@
+{% load adminmedia %}
+{% load i18n %}
+{% if cl.lookup_opts.admin.search_fields %}
+<div id="toolbar"><form id="changelist-search" action="" method="get">
+<div><!-- DIV needed for valid HTML -->
+<label for="searchbar"><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
+<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" />
+<input type="submit" value="{% trans 'Go' %}" />
+{% if show_result_count %}
+ <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
+{% endif %}
+{% for pair in cl.params.items %}
+ {% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0|escape }}" value="{{ pair.1|escape }}"/>{% endifnotequal %}
+{% endfor %}
+</div>
+</form></div>
+<script type="text/javascript">document.getElementById("searchbar").focus();</script>
+{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/submit_line.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/submit_line.html
new file mode 100644
index 0000000..25f5819
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/submit_line.html
@@ -0,0 +1,8 @@
+{% load i18n %}
+<div class="submit-row">
+{% if show_delete_link %}<p class="float-left"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
+{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%}
+{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }} />{% endif %}
+{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %}
+{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" {{ onclick_attrib }}/>{% endif %}
+</div>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin/template_validator.html b/google_appengine/lib/django/django/contrib/admin/templates/admin/template_validator.html
new file mode 100644
index 0000000..422e902
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin/template_validator.html
@@ -0,0 +1,31 @@
+{% extends "admin/base_site.html" %}
+
+{% block content %}
+
+<div id="content-main">
+
+<form action="" method="post">
+
+{% if form.error_dict %}
+<p class="errornote">Your template had {{ form.error_dict.items|length }} error{{ form.error_dict.items|pluralize }}:</p>
+{% endif %}
+
+<fieldset class="module aligned">
+<div class="form-row{% if form.site.errors %} error{% endif %} required">
+ {% if form.site.errors %}{{ form.site.html_error_list }}{% endif %}
+ <h4><label for="id_site">Site:</label> {{ form.site }}</h4>
+</div>
+<div class="form-row{% if form.template.errors %} error{% endif %} required">
+ {% if form.template.errors %}{{ form.template.html_error_list }}{% endif %}
+ <h4><label for="id_template">Template:</label> {{ form.template }}</h4>
+</div>
+</fieldset>
+
+<div class="submit-row">
+ <input type="submit" value="Check for errors" class="default" />
+</div>
+
+</form>
+</div>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/bookmarklets.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/bookmarklets.html
new file mode 100644
index 0000000..fa59429
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/bookmarklets.html
@@ -0,0 +1,32 @@
+{% extends "admin/base_site.html" %}
+
+{% block breadcrumbs %}{% load i18n %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../">{% trans "Documentation" %}</a> &rsaquo; {% trans "Bookmarklets" %}</div>{% endblock %}
+{% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block title %}{% trans "Documentation bookmarklets" %}{% endblock %}
+
+{% block content %}
+
+{% blocktrans %}
+<p class="help">To install bookmarklets, drag the link to your bookmarks
+toolbar, or right-click the link and add it to your bookmarks. Now you can
+select the bookmarklet from any page in the site. Note that some of these
+bookmarklets require you to be viewing the site from a computer designated
+as "internal" (talk to your system administrator if you aren't sure if
+your computer is "internal").</p>
+{% endblocktrans %}
+
+<div id="content-main">
+ <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('HEAD',location.href,false);x.send(null);try{view=x.getResponseHeader('x-view');}catch(e){alert('No view found for this page');return;}if(view=='undefined'){alert('No view found for this page');}document.location='{{ admin_url }}doc/views/'+view+'/';})()">{% trans "Documentation for this page" %}</a></h3>
+ <p>{% trans "Jumps you from any page to the documentation for the view that generates that page." %}</p>
+
+ <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{type=x.getResponseHeader('x-object-type');id=x.getResponseHeader('x-object-id');}catch(e){type='(none)';id='(none)';}d=document;b=d.body;e=d.createElement('div');e.id='xxxhhh';s=e.style;s.position='absolute';s.left='10px';s.top='10px';s.font='10px monospace';s.border='1px black solid';s.padding='4px';s.backgroundColor='#eee';e.appendChild(d.createTextNode('Type: '+type));e.appendChild(d.createElement('br'));e.appendChild(d.createTextNode('ID: '+id));e.appendChild(d.createElement('br'));l=d.createElement('a');l.href='#';l.onclick=function(){b.removeChild(e);};l.appendChild(d.createTextNode('[close]'));l.style.textDecoration='none';e.appendChild(l);b.appendChild(e);})();">{% trans "Show object ID" %}</a></h3>
+ <p>{% trans "Shows the content-type and unique ID for pages that represent a single object." %}</p>
+
+ <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}document.location='{{ admin_url }}'+type.split('.').join('/')+'/'+id+'/';})()">{% trans "Edit this object (current window)" %}</a></h3>
+ <p>{% trans "Jumps to the admin page for pages that represent a single object." %}</p>
+
+ <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}window.open('{{ admin_url }}'+type.split('.').join('/')+'/'+id+'/');})()">{% trans "Edit this object (new window)" %}</a></h3>
+ <p>{% trans "As above, but opens the admin page in a new window." %}</p>
+</div>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/index.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/index.html
new file mode 100644
index 0000000..74f9f20
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/index.html
@@ -0,0 +1,28 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
+{% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block title %}Documentation{% endblock %}
+
+{% block content %}
+
+<h1>Documentation</h1>
+
+<div id="content-main">
+ <h3><a href="tags/">Tags</a></h3>
+ <p>List of all the template tags and their functions.</p>
+
+ <h3><a href="filters/">Filters</a></h3>
+ <p>Filters are actions which can be applied to variables in a template to alter the output.</p>
+
+ <h3><a href="models/">Models</a></h3>
+ <p>Models are descriptions of all the objects in the system and their associated fields. Each model has a list of fields which can be accessed as template variables.</p>
+
+ <h3><a href="views/">Views</a></h3>
+ <p>Each page on the public site is generated by a view. The view defines which template is used to generate the page and which objects are available to that template.</p>
+
+ <h3><a href="bookmarklets/">Bookmarklets</a></h3>
+ <p>Tools for your browser to quickly access admin functionality.</p>
+</div>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/missing_docutils.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/missing_docutils.html
new file mode 100644
index 0000000..cdeab0c
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/missing_docutils.html
@@ -0,0 +1,17 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
+{% block userlinks %}<a href="../password_change/">{% trans 'Change password' %}</a> / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block title %}Please install docutils{% endblock %}
+
+{% block content %}
+
+<h1>Documentation</h1>
+
+<div id="content-main">
+ <h3>The admin documentation system requires Python's <a href="http://docutils.sf.net/">docutils</a> library.</h3>
+
+ <p>Please ask your administrators to install <a href="http://docutils.sf.net/">docutils</a>.</p>
+</div>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_detail.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_detail.html
new file mode 100644
index 0000000..70133e2
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_detail.html
@@ -0,0 +1,47 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block extrahead %}
+{{ block.super }}
+<style type="text/css">
+.module table { width:100%; }
+.module table p { padding: 0; margin: 0; }
+</style>
+{% endblock %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Models</a> &rsaquo; {{ name|escape }}</div>{% endblock %}
+
+{% block title %}Model: {{ name|escape }}{% endblock %}
+
+{% block content %}
+<div id="content-main">
+<h1>{{ summary|escape }}</h1>
+
+{% if description %}
+ <p>{% filter escape|linebreaksbr %}{% trans description %}{% endfilter %}</p>
+{% endif %}
+
+<div class="module">
+<table class="model">
+<thead>
+<tr>
+ <th>Field</th>
+ <th>Type</th>
+ <th>Description</th>
+</tr>
+</thead>
+<tbody>
+{% for field in fields|dictsort:"name" %}
+<tr>
+ <td>{{ field.name }}</td>
+ <td>{{ field.data_type }}</td>
+ <td>{% if field.verbose %}{{ field.verbose }}{% endif %}{% if field.help_text %} - {{ field.help_text }}{% endif %}</td>
+</tr>
+{% endfor %}
+</tbody>
+</table>
+</div>
+
+<p class="small"><a href="../">&lsaquo; Back to Models Documentation</a></p>
+</div>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_index.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_index.html
new file mode 100644
index 0000000..c681da7
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_index.html
@@ -0,0 +1,45 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block coltype %}colSM{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Models</div>{% endblock %}
+{% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+
+{% block title %}Models{% endblock %}
+
+{% block content %}
+
+<h1>Model documentation</h1>
+
+{% regroup models by app_label as grouped_models %}
+
+<div id="content-main">
+{% for group in grouped_models %}
+<div class="module">
+<h2 id="{{ group.grouper }}">{{ group.grouper|capfirst }}</h2>
+
+<table class="xfull">
+{% for model in group.list %}
+<tr>
+<th><a href="{{ model.app_label }}.{{ model.object_name.lower }}/">{{ model.object_name }}</a></th>
+</tr>
+{% endfor %}
+</table>
+</div>
+{% endfor %}
+
+</div>
+{% endblock %}
+
+{% block sidebar %}
+<div id="content-related" class="sidebar">
+<div class="module">
+<h2>Model groups</h2>
+<ul>
+{% regroup models by app_label as grouped_models %}
+{% for group in grouped_models %}
+ <li><a href="#{{ group.grouper }}">{{ group.grouper|capfirst }}</a></li>
+{% endfor %}
+</ul>
+</div>
+</div>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_detail.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_detail.html
new file mode 100644
index 0000000..280ea91
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_detail.html
@@ -0,0 +1,22 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; Templates &rsaquo; {{ name|escape }}</div>{% endblock %}
+{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+
+{% block title %}Template: {{ name|escape }}{% endblock %}
+
+{% block content %}
+<h1>Template: "{{ name|escape }}"</h1>
+
+{% regroup templates|dictsort:"site_id" by site as templates_by_site %}
+{% for group in templates_by_site %}
+ <h2>Search path for template "{{ name|escape }}" on {{ group.grouper }}:</h2>
+ <ol>
+ {% for template in group.list|dictsort:"order" %}
+ <li><code>{{ template.file|escape }}</code>{% if not template.exists %} <em>(does not exist)</em>{% endif %}</li>
+ {% endfor %}
+ </ol>
+{% endfor %}
+
+<p class="small"><a href="../../">&lsaquo; Back to Documentation</a></p>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_filter_index.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_filter_index.html
new file mode 100644
index 0000000..72344c1
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_filter_index.html
@@ -0,0 +1,48 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block coltype %}colSM{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; filters</div>{% endblock %}
+{% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block title %}Template filters{% endblock %}
+
+{% block content %}
+
+<h1>Template filter documentation</h1>
+
+<div id="content-main">
+{% regroup filters|dictsort:"library" by library as filter_libraries %}
+{% for library in filter_libraries %}
+<div class="module">
+ <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in filters{% endif %}</h2>
+ {% if library.grouper %}<p class="small quiet">To use these filters, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the filter.</p><hr>{% endif %}
+ {% for filter in library.list|dictsort:"name" %}
+ <h3 id="{{ filter.name }}">{{ filter.name }}</h3>
+ <p>{{ filter.title }}</p>
+ <p>{{ filter.body }}</p>
+ {% if not forloop.last %}<hr />{% endif %}
+ {% endfor %}
+</div>
+{% endfor %}
+</div>
+
+{% endblock %}
+
+{% block sidebar %}
+
+<div id="content-related">
+
+{% regroup filters|dictsort:"library" by library as filter_libraries %}
+{% for library in filter_libraries %}
+<div class="module">
+ <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in filters{% endif %}</h2>
+ <ul>
+ {% for filter in library.list|dictsort:"name" %}
+ <li><a href="#{{ filter.name }}">{{ filter.name }}</a></li>
+ {% endfor %}
+ </ul>
+</div>
+{% endfor %}
+
+</div>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_tag_index.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_tag_index.html
new file mode 100644
index 0000000..287475a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_tag_index.html
@@ -0,0 +1,48 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block coltype %}colSM{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Tags</div>{% endblock %}
+{% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block title %}Template tags{% endblock %}
+
+{% block content %}
+
+<h1>Template tag documentation</h1>
+
+<div id="content-main">
+{% regroup tags|dictsort:"library" by library as tag_libraries %}
+{% for library in tag_libraries %}
+<div class="module">
+ <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in tags{% endif %}</h2>
+ {% if library.grouper %}<p class="small quiet">To use these tags, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the tag.</p><hr>{% endif %}
+ {% for tag in library.list|dictsort:"name" %}
+ <h3 id="{{ tag.name }}">{{ tag.name }}</h3>
+ <h4>{{ tag.title }}</h4>
+ <p>{{ tag.body }}</p>
+ {% if not forloop.last %}<hr />{% endif %}
+ {% endfor %}
+</div>
+{% endfor %}
+</div>
+
+{% endblock %}
+
+{% block sidebar %}
+
+<div id="content-related">
+
+{% regroup tags|dictsort:"library" by library as tag_libraries %}
+{% for library in tag_libraries %}
+<div class="module">
+ <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in tags{% endif %}</h2>
+ <ul>
+ {% for tag in library.list|dictsort:"name" %}
+ <li><a href="#{{ tag.name }}">{{ tag.name }}</a></li>
+ {% endfor %}
+ </ul>
+</div>
+{% endfor %}
+
+</div>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_detail.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_detail.html
new file mode 100644
index 0000000..ba90399
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_detail.html
@@ -0,0 +1,26 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Views</a> &rsaquo; {{ name }}</div>{% endblock %}
+{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block title %}View: {{ name }}{% endblock %}
+
+{% block content %}
+
+<h1>{{ name }}</h1>
+
+<h2 class="subhead">{{ summary }}</h2>
+
+<p>{{ body }}</p>
+
+{% if meta.Context %}
+<h3>Context:</h3>
+<p>{{ meta.Context }}</p>
+{% endif %}
+
+{% if meta.Templates %}
+<h3>Templates:</h3>
+<p>{{ meta.Templates }}</p>
+{% endif %}
+
+<p class="small"><a href="../">&lsaquo; Back to Views Documentation</a></p>
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_index.html b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_index.html
new file mode 100644
index 0000000..caab8a2
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_index.html
@@ -0,0 +1,43 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block coltype %}colSM{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Views</div>{% endblock %}
+{% block userlinks %}<a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block title %}Views{% endblock %}
+
+{% block content %}
+
+<h1>View documentation</h1>
+
+{% regroup views|dictsort:"site_id" by site as views_by_site %}
+
+<div id="content-related" class="sidebar">
+<div class="module">
+<h2>Jump to site</h2>
+<ul>
+ {% for site_views in views_by_site %}
+ <li><a href="#site{{ site_views.grouper.id }}">{{ site_views.grouper.name }}</a></li>
+ {% endfor %}
+</ul>
+</div>
+</div>
+
+<div id="content-main">
+
+{% for site_views in views_by_site %}
+<div class="module">
+<h2 id="site{{ site_views.grouper.id }}">Views by URL on {{ site_views.grouper.name }}</h2>
+
+{% for view in site_views.list|dictsort:"url" %}
+{% ifchanged %}
+<h3><a href="{{ view.module }}.{{ view.name }}/"/>{{ view.url|escape }}</a></h3>
+<p class="small quiet">View function: {{ view.module }}.{{ view.name }}</p>
+<p>{{ view.title }}</p>
+<hr>
+{% endifchanged %}
+{% endfor %}
+</div>
+{% endfor %}
+</div>
+{% endblock %}
+
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/registration/logged_out.html b/google_appengine/lib/django/django/contrib/admin/templates/registration/logged_out.html
new file mode 100644
index 0000000..d339ef0
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/registration/logged_out.html
@@ -0,0 +1,12 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %}
+
+{% block content %}
+
+<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
+
+<p><a href="../">{% trans 'Log in again' %}</a></p>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_done.html b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_done.html
new file mode 100644
index 0000000..2525720
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_done.html
@@ -0,0 +1,14 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / {% trans 'Change password' %} / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
+
+{% block title %}{% trans 'Password change successful' %}{% endblock %}
+
+{% block content %}
+
+<h1>{% trans 'Password change successful' %}</h1>
+
+<p>{% trans 'Your password was changed.' %}</p>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_form.html b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_form.html
new file mode 100644
index 0000000..036d562
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_form.html
@@ -0,0 +1,26 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block userlinks %}<a href="../doc/">{% trans 'Documentation' %}</a> / {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
+
+{% block title %}{% trans 'Password change' %}{% endblock %}
+
+{% block content %}
+
+<h1>{% trans 'Password change' %}</h1>
+
+<p>{% trans "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." %}</p>
+
+<form action="" method="post">
+
+{% if form.old_password.errors %}{{ form.old_password.html_error_list }}{% endif %}
+<p class="aligned wide"><label for="id_old_password">{% trans 'Old password:' %}</label>{{ form.old_password }}</p>
+{% if form.new_password1.errors %}{{ form.new_password1.html_error_list }}{% endif %}
+<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
+{% if form.new_password2.errors %}{{ form.new_password2.html_error_list }}{% endif %}
+<p class="aligned wide"><label for="id_new_password2">{% trans 'Confirm password:' %}</label>{{ form.new_password2 }}</p>
+
+<p><input type="submit" value="{% trans 'Change my password' %}" /></p>
+</form>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_done.html b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_done.html
new file mode 100644
index 0000000..f97b568
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_done.html
@@ -0,0 +1,14 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
+
+{% block title %}{% trans 'Password reset successful' %}{% endblock %}
+
+{% block content %}
+
+<h1>{% trans 'Password reset successful' %}</h1>
+
+<p>{% trans "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_email.html b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_email.html
new file mode 100644
index 0000000..f765dd0
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_email.html
@@ -0,0 +1,15 @@
+{% load i18n %}
+{% trans "You're receiving this e-mail because you requested a password reset" %}
+{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}.
+
+{% blocktrans %}Your new password is: {{ new_password }}{% endblocktrans %}
+
+{% trans "Feel free to change this password by going to this page:" %}
+
+http://{{ domain }}/password_change/
+
+{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
+
+{% trans "Thanks for using our site!" %}
+
+{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_form.html b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_form.html
new file mode 100644
index 0000000..423821b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_form.html
@@ -0,0 +1,19 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
+
+{% block title %}{% trans "Password reset" %}{% endblock %}
+
+{% block content %}
+
+<h1>{% trans "Password reset" %}</h1>
+
+<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." %}</p>
+
+<form action="" method="post">
+{% if form.email.errors %}{{ form.email.html_error_list }}{% endif %}
+<p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
+</form>
+
+{% endblock %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/widget/date_time.html b/google_appengine/lib/django/django/contrib/admin/templates/widget/date_time.html
new file mode 100644
index 0000000..cbd4a2e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/widget/date_time.html
@@ -0,0 +1,5 @@
+{% load i18n %}
+<p class="datetime">
+ {% trans "Date:" %} {{ bound_field.form_fields.0 }}<br />
+ {% trans "Time:" %} {{ bound_field.form_fields.1 }}
+</p>
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/widget/default.html b/google_appengine/lib/django/django/contrib/admin/templates/widget/default.html
new file mode 100644
index 0000000..0af231d
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/widget/default.html
@@ -0,0 +1 @@
+{% load admin_modify %}{% output_all bound_field.form_fields %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/widget/file.html b/google_appengine/lib/django/django/contrib/admin/templates/widget/file.html
new file mode 100644
index 0000000..e584abf
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/widget/file.html
@@ -0,0 +1,4 @@
+{% load admin_modify i18n %}{% if bound_field.original_value %}
+{% trans "Currently:" %} <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value|escape }} </a><br />
+{% trans "Change:" %}{% output_all bound_field.form_fields %}
+{% else %} {% output_all bound_field.form_fields %} {% endif %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/widget/foreign.html b/google_appengine/lib/django/django/contrib/admin/templates/widget/foreign.html
new file mode 100644
index 0000000..301f521
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/widget/foreign.html
@@ -0,0 +1,20 @@
+{% load admin_modify adminmedia %}
+{% output_all bound_field.form_fields %}
+{% if bound_field.raw_id_admin %}
+ {% if bound_field.field.rel.limit_choices_to %}
+ <a href="{{ bound_field.related_url }}?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}&amp;{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
+ {% else %}
+ <a href="{{ bound_field.related_url }}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
+ {% endif %}
+{% else %}
+{% if bound_field.needs_add_label %}
+ <a href="{{ bound_field.related_url }}add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
+{% endif %}{% endif %}
+{% if change %}
+ {% if bound_field.field.primary_key %}
+ {{ bound_field.original_value }}
+ {% endif %}
+ {% if bound_field.raw_id_admin %}
+ {% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}
+ {% endif %}
+{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/widget/many_to_many.html b/google_appengine/lib/django/django/contrib/admin/templates/widget/many_to_many.html
new file mode 100644
index 0000000..a93aa65
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/widget/many_to_many.html
@@ -0,0 +1 @@
+{% include "widget/foreign.html" %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templates/widget/one_to_one.html b/google_appengine/lib/django/django/contrib/admin/templates/widget/one_to_one.html
new file mode 100644
index 0000000..efd0117
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templates/widget/one_to_one.html
@@ -0,0 +1,2 @@
+{% if add %}{% include "widget/foreign.html" %}{% endif %}
+{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/admin/templatetags/__init__.py b/google_appengine/lib/django/django/contrib/admin/templatetags/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templatetags/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/admin/templatetags/admin_list.py b/google_appengine/lib/django/django/contrib/admin/templatetags/admin_list.py
new file mode 100755
index 0000000..5c678fb
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templatetags/admin_list.py
@@ -0,0 +1,279 @@
+from django.conf import settings
+from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE
+from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import models
+from django.utils import dateformat
+from django.utils.html import escape
+from django.utils.text import capfirst
+from django.utils.translation import get_date_formats, get_partial_date_formats
+from django.template import Library
+import datetime
+
+register = Library()
+
+DOT = '.'
+
+def paginator_number(cl,i):
+ if i == DOT:
+ return '... '
+ elif i == cl.page_num:
+ return '<span class="this-page">%d</span> ' % (i+1)
+ else:
+ return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
+paginator_number = register.simple_tag(paginator_number)
+
+def pagination(cl):
+ paginator, page_num = cl.paginator, cl.page_num
+
+ pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
+ if not pagination_required:
+ page_range = []
+ else:
+ ON_EACH_SIDE = 3
+ ON_ENDS = 2
+
+ # If there are 10 or fewer pages, display links to every page.
+ # Otherwise, do some fancy
+ if paginator.pages <= 10:
+ page_range = range(paginator.pages)
+ else:
+ # Insert "smart" pagination links, so that there are always ON_ENDS
+ # links at either end of the list of pages, and there are always
+ # ON_EACH_SIDE links at either end of the "current page" link.
+ page_range = []
+ if page_num > (ON_EACH_SIDE + ON_ENDS):
+ page_range.extend(range(0, ON_EACH_SIDE - 1))
+ page_range.append(DOT)
+ page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
+ else:
+ page_range.extend(range(0, page_num + 1))
+ if page_num < (paginator.pages - ON_EACH_SIDE - ON_ENDS - 1):
+ page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
+ page_range.append(DOT)
+ page_range.extend(range(paginator.pages - ON_ENDS, paginator.pages))
+ else:
+ page_range.extend(range(page_num + 1, paginator.pages))
+
+ need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page
+ return {
+ 'cl': cl,
+ 'pagination_required': pagination_required,
+ 'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}),
+ 'page_range': page_range,
+ 'ALL_VAR': ALL_VAR,
+ '1': 1,
+ }
+pagination = register.inclusion_tag('admin/pagination.html')(pagination)
+
+def result_headers(cl):
+ lookup_opts = cl.lookup_opts
+
+ for i, field_name in enumerate(lookup_opts.admin.list_display):
+ try:
+ f = lookup_opts.get_field(field_name)
+ except models.FieldDoesNotExist:
+ # For non-field list_display values, check for the function
+ # attribute "short_description". If that doesn't exist, fall
+ # back to the method name. And __str__ is a special-case.
+ if field_name == '__str__':
+ header = lookup_opts.verbose_name
+ else:
+ attr = getattr(cl.model, field_name) # Let AttributeErrors propagate.
+ try:
+ header = attr.short_description
+ except AttributeError:
+ header = field_name.replace('_', ' ')
+
+ # It is a non-field, but perhaps one that is sortable
+ if not getattr(getattr(cl.model, field_name), "admin_order_field", None):
+ yield {"text": header}
+ continue
+
+ # So this _is_ a sortable non-field. Go to the yield
+ # after the else clause.
+ else:
+ if isinstance(f.rel, models.ManyToOneRel) and f.null:
+ yield {"text": f.verbose_name}
+ continue
+ else:
+ header = f.verbose_name
+
+ th_classes = []
+ new_order_type = 'asc'
+ if field_name == cl.order_field:
+ th_classes.append('sorted %sending' % cl.order_type.lower())
+ new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()]
+
+ yield {"text": header,
+ "sortable": True,
+ "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
+ "class_attrib": (th_classes and ' class="%s"' % ' '.join(th_classes) or '')}
+
+def _boolean_icon(field_val):
+ BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
+ return '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
+
+def items_for_result(cl, result):
+ first = True
+ pk = cl.lookup_opts.pk.attname
+ for field_name in cl.lookup_opts.admin.list_display:
+ row_class = ''
+ try:
+ f = cl.lookup_opts.get_field(field_name)
+ except models.FieldDoesNotExist:
+ # For non-field list_display values, the value is either a method
+ # or a property.
+ try:
+ attr = getattr(result, field_name)
+ allow_tags = getattr(attr, 'allow_tags', False)
+ boolean = getattr(attr, 'boolean', False)
+ if callable(attr):
+ attr = attr()
+ if boolean:
+ allow_tags = True
+ result_repr = _boolean_icon(attr)
+ else:
+ result_repr = str(attr)
+ except (AttributeError, ObjectDoesNotExist):
+ result_repr = EMPTY_CHANGELIST_VALUE
+ else:
+ # Strip HTML tags in the resulting text, except if the
+ # function has an "allow_tags" attribute set to True.
+ if not allow_tags:
+ result_repr = escape(result_repr)
+ else:
+ field_val = getattr(result, f.attname)
+
+ if isinstance(f.rel, models.ManyToOneRel):
+ if field_val is not None:
+ result_repr = escape(getattr(result, f.name))
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ # Dates and times are special: They're formatted in a certain way.
+ elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
+ if field_val:
+ (date_format, datetime_format, time_format) = get_date_formats()
+ if isinstance(f, models.DateTimeField):
+ result_repr = capfirst(dateformat.format(field_val, datetime_format))
+ elif isinstance(f, models.TimeField):
+ result_repr = capfirst(dateformat.time_format(field_val, time_format))
+ else:
+ result_repr = capfirst(dateformat.format(field_val, date_format))
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ row_class = ' class="nowrap"'
+ # Booleans are special: We use images.
+ elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
+ result_repr = _boolean_icon(field_val)
+ # FloatFields are special: Zero-pad the decimals.
+ elif isinstance(f, models.FloatField):
+ if field_val is not None:
+ result_repr = ('%%.%sf' % f.decimal_places) % field_val
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ # Fields with choices are special: Use the representation
+ # of the choice.
+ elif f.choices:
+ result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
+ else:
+ result_repr = escape(str(field_val))
+ if result_repr == '':
+ result_repr = '&nbsp;'
+ # If list_display_links not defined, add the link tag to the first field
+ if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links:
+ table_tag = {True:'th', False:'td'}[first]
+ first = False
+ url = cl.url_for_result(result)
+ result_id = str(getattr(result, pk)) # str() is needed in case of 23L (long ints)
+ yield ('<%s%s><a href="%s"%s>%s</a></%s>' % \
+ (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr, table_tag))
+ else:
+ yield ('<td%s>%s</td>' % (row_class, result_repr))
+
+def results(cl):
+ for res in cl.result_list:
+ yield list(items_for_result(cl,res))
+
+def result_list(cl):
+ return {'cl': cl,
+ 'result_headers': list(result_headers(cl)),
+ 'results': list(results(cl))}
+result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
+
+def date_hierarchy(cl):
+ if cl.lookup_opts.admin.date_hierarchy:
+ field_name = cl.lookup_opts.admin.date_hierarchy
+ year_field = '%s__year' % field_name
+ month_field = '%s__month' % field_name
+ day_field = '%s__day' % field_name
+ field_generic = '%s__' % field_name
+ year_lookup = cl.params.get(year_field)
+ month_lookup = cl.params.get(month_field)
+ day_lookup = cl.params.get(day_field)
+ year_month_format, month_day_format = get_partial_date_formats()
+
+ link = lambda d: cl.get_query_string(d, [field_generic])
+
+ if year_lookup and month_lookup and day_lookup:
+ day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))
+ return {
+ 'show': True,
+ 'back': {
+ 'link': link({year_field: year_lookup, month_field: month_lookup}),
+ 'title': dateformat.format(day, year_month_format)
+ },
+ 'choices': [{'title': dateformat.format(day, month_day_format)}]
+ }
+ elif year_lookup and month_lookup:
+ days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day')
+ return {
+ 'show': True,
+ 'back': {
+ 'link': link({year_field: year_lookup}),
+ 'title': year_lookup
+ },
+ 'choices': [{
+ 'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}),
+ 'title': dateformat.format(day, month_day_format)
+ } for day in days]
+ }
+ elif year_lookup:
+ months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month')
+ return {
+ 'show' : True,
+ 'back': {
+ 'link' : link({}),
+ 'title': _('All dates')
+ },
+ 'choices': [{
+ 'link': link({year_field: year_lookup, month_field: month.month}),
+ 'title': dateformat.format(month, year_month_format)
+ } for month in months]
+ }
+ else:
+ years = cl.query_set.dates(field_name, 'year')
+ return {
+ 'show': True,
+ 'choices': [{
+ 'link': link({year_field: year.year}),
+ 'title': year.year
+ } for year in years]
+ }
+date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy)
+
+def search_form(cl):
+ return {
+ 'cl': cl,
+ 'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
+ 'search_var': SEARCH_VAR
+ }
+search_form = register.inclusion_tag('admin/search_form.html')(search_form)
+
+def filter(cl, spec):
+ return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
+filter = register.inclusion_tag('admin/filter.html')(filter)
+
+def filters(cl):
+ return {'cl': cl}
+filters = register.inclusion_tag('admin/filters.html')(filters)
diff --git a/google_appengine/lib/django/django/contrib/admin/templatetags/admin_modify.py b/google_appengine/lib/django/django/contrib/admin/templatetags/admin_modify.py
new file mode 100755
index 0000000..e708b87
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templatetags/admin_modify.py
@@ -0,0 +1,247 @@
+from django import template
+from django.contrib.admin.views.main import AdminBoundField
+from django.template import loader
+from django.utils.text import capfirst
+from django.db import models
+from django.db.models.fields import Field
+from django.db.models.related import BoundRelatedObject
+from django.conf import settings
+import re
+
+register = template.Library()
+
+word_re = re.compile('[A-Z][a-z]+')
+absolute_url_re = re.compile(r'^(?:http(?:s)?:/)?/', re.IGNORECASE)
+
+def class_name_to_underscored(name):
+ return '_'.join([s.lower() for s in word_re.findall(name)[:-1]])
+
+def include_admin_script(script_path):
+ """
+ Returns an HTML script element for including a script from the admin
+ media url (or other location if an absolute url is given).
+
+ Example usage::
+
+ {% include_admin_script "js/calendar.js" %}
+
+ could return::
+
+ <script type="text/javascript" src="/media/admin/js/calendar.js">
+ """
+ if not absolute_url_re.match(script_path):
+ script_path = '%s%s' % (settings.ADMIN_MEDIA_PREFIX, script_path)
+ return '<script type="text/javascript" src="%s"></script>' % script_path
+include_admin_script = register.simple_tag(include_admin_script)
+
+def submit_row(context):
+ opts = context['opts']
+ change = context['change']
+ is_popup = context['is_popup']
+ return {
+ 'onclick_attrib': (opts.get_ordered_objects() and change
+ and 'onclick="submitOrderForm();"' or ''),
+ 'show_delete_link': (not is_popup and context['has_delete_permission']
+ and (change or context['show_delete'])),
+ 'show_save_as_new': not is_popup and change and opts.admin.save_as,
+ 'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']),
+ 'show_save_and_continue': not is_popup and context['has_change_permission'],
+ 'show_save': True
+ }
+submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row)
+
+def field_label(bound_field):
+ class_names = []
+ if isinstance(bound_field.field, models.BooleanField):
+ class_names.append("vCheckboxLabel")
+ colon = ""
+ else:
+ if not bound_field.field.blank:
+ class_names.append('required')
+ if not bound_field.first:
+ class_names.append('inline')
+ colon = ":"
+ class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
+ return '<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \
+ capfirst(bound_field.field.verbose_name), colon)
+field_label = register.simple_tag(field_label)
+
+class FieldWidgetNode(template.Node):
+ nodelists = {}
+ default = None
+
+ def __init__(self, bound_field_var):
+ self.bound_field_var = bound_field_var
+
+ def get_nodelist(cls, klass):
+ if not cls.nodelists.has_key(klass):
+ try:
+ field_class_name = klass.__name__
+ template_name = "widget/%s.html" % class_name_to_underscored(field_class_name)
+ nodelist = loader.get_template(template_name).nodelist
+ except template.TemplateDoesNotExist:
+ super_klass = bool(klass.__bases__) and klass.__bases__[0] or None
+ if super_klass and super_klass != Field:
+ nodelist = cls.get_nodelist(super_klass)
+ else:
+ if not cls.default:
+ cls.default = loader.get_template("widget/default.html").nodelist
+ nodelist = cls.default
+
+ cls.nodelists[klass] = nodelist
+ return nodelist
+ else:
+ return cls.nodelists[klass]
+ get_nodelist = classmethod(get_nodelist)
+
+ def render(self, context):
+ bound_field = template.resolve_variable(self.bound_field_var, context)
+
+ context.push()
+ context['bound_field'] = bound_field
+
+ output = self.get_nodelist(bound_field.field.__class__).render(context)
+ context.pop()
+ return output
+
+class FieldWrapper(object):
+ def __init__(self, field ):
+ self.field = field
+
+ def needs_header(self):
+ return not isinstance(self.field, models.AutoField)
+
+ def header_class_attribute(self):
+ return self.field.blank and ' class="optional"' or ''
+
+ def use_raw_id_admin(self):
+ return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \
+ and self.field.rel.raw_id_admin
+
+class FormFieldCollectionWrapper(object):
+ def __init__(self, field_mapping, fields, index):
+ self.field_mapping = field_mapping
+ self.fields = fields
+ self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original'])
+ for field in self.fields]
+ self.index = index
+
+class TabularBoundRelatedObject(BoundRelatedObject):
+ def __init__(self, related_object, field_mapping, original):
+ super(TabularBoundRelatedObject, self).__init__(related_object, field_mapping, original)
+ self.field_wrapper_list = [FieldWrapper(field) for field in self.relation.editable_fields()]
+
+ fields = self.relation.editable_fields()
+
+ self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields, i)
+ for (i,field_mapping) in self.field_mappings.items() ]
+ self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list])
+ self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
+
+ def template_name(self):
+ return "admin/edit_inline_tabular.html"
+
+class StackedBoundRelatedObject(BoundRelatedObject):
+ def __init__(self, related_object, field_mapping, original):
+ super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original)
+ fields = self.relation.editable_fields()
+ self.field_mappings.fill()
+ self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields, i)
+ for (i,field_mapping) in self.field_mappings.items()]
+ self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
+
+ def template_name(self):
+ return "admin/edit_inline_stacked.html"
+
+class EditInlineNode(template.Node):
+ def __init__(self, rel_var):
+ self.rel_var = rel_var
+
+ def render(self, context):
+ relation = template.resolve_variable(self.rel_var, context)
+ context.push()
+ if relation.field.rel.edit_inline == models.TABULAR:
+ bound_related_object_class = TabularBoundRelatedObject
+ elif relation.field.rel.edit_inline == models.STACKED:
+ bound_related_object_class = StackedBoundRelatedObject
+ else:
+ bound_related_object_class = relation.field.rel.edit_inline
+ original = context.get('original', None)
+ bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
+ context['bound_related_object'] = bound_related_object
+ t = loader.get_template(bound_related_object.template_name())
+ output = t.render(context)
+ context.pop()
+ return output
+
+def output_all(form_fields):
+ return ''.join([str(f) for f in form_fields])
+output_all = register.simple_tag(output_all)
+
+def auto_populated_field_script(auto_pop_fields, change = False):
+ t = []
+ for field in auto_pop_fields:
+ if change:
+ t.append('document.getElementById("id_%s")._changed = true;' % field.name)
+ else:
+ t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
+
+ add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
+ for f in field.prepopulate_from:
+ t.append('document.getElementById("id_%s").onkeyup = function() {' \
+ ' var e = document.getElementById("id_%s");' \
+ ' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % (
+ f, field.name, add_values, field.maxlength))
+ return ''.join(t)
+auto_populated_field_script = register.simple_tag(auto_populated_field_script)
+
+def filter_interface_script_maybe(bound_field):
+ f = bound_field.field
+ if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
+ return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
+ ' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
+ f.name, f.verbose_name.replace('"', '\\"'), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
+ else:
+ return ''
+filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)
+
+def field_widget(parser, token):
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
+ return FieldWidgetNode(bits[1])
+field_widget = register.tag(field_widget)
+
+def edit_inline(parser, token):
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
+ return EditInlineNode(bits[1])
+edit_inline = register.tag(edit_inline)
+
+def admin_field_line(context, argument_val):
+ if isinstance(argument_val, AdminBoundField):
+ bound_fields = [argument_val]
+ else:
+ bound_fields = [bf for bf in argument_val]
+ add = context['add']
+ change = context['change']
+
+ class_names = ['form-row']
+ for bound_field in bound_fields:
+ for f in bound_field.form_fields:
+ if f.errors():
+ class_names.append('errors')
+ break
+
+ # Assumes BooleanFields won't be stacked next to each other!
+ if isinstance(bound_fields[0].field, models.BooleanField):
+ class_names.append('checkbox-row')
+
+ return {
+ 'add': context['add'],
+ 'change': context['change'],
+ 'bound_fields': bound_fields,
+ 'class_names': " ".join(class_names),
+ }
+admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line)
diff --git a/google_appengine/lib/django/django/contrib/admin/templatetags/adminapplist.py b/google_appengine/lib/django/django/contrib/admin/templatetags/adminapplist.py
new file mode 100755
index 0000000..10e09ca
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templatetags/adminapplist.py
@@ -0,0 +1,79 @@
+from django import template
+from django.db.models import get_models
+
+register = template.Library()
+
+class AdminApplistNode(template.Node):
+ def __init__(self, varname):
+ self.varname = varname
+
+ def render(self, context):
+ from django.db import models
+ from django.utils.text import capfirst
+ app_list = []
+ user = context['user']
+
+ for app in models.get_apps():
+ # Determine the app_label.
+ app_models = get_models(app)
+ if not app_models:
+ continue
+ app_label = app_models[0]._meta.app_label
+
+ has_module_perms = user.has_module_perms(app_label)
+
+ if has_module_perms:
+ model_list = []
+ for m in app_models:
+ if m._meta.admin:
+ perms = {
+ 'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())),
+ 'change': user.has_perm("%s.%s" % (app_label, m._meta.get_change_permission())),
+ 'delete': user.has_perm("%s.%s" % (app_label, m._meta.get_delete_permission())),
+ }
+
+ # Check whether user has any perm for this module.
+ # If so, add the module to the model_list.
+ if True in perms.values():
+ model_list.append({
+ 'name': capfirst(m._meta.verbose_name_plural),
+ 'admin_url': '%s/%s/' % (app_label, m.__name__.lower()),
+ 'perms': perms,
+ })
+
+ if model_list:
+ # Sort using verbose decorate-sort-undecorate pattern
+ # instead of key argument to sort() for python 2.3 compatibility
+ decorated = [(x['name'], x) for x in model_list]
+ decorated.sort()
+ model_list = [x for key, x in decorated]
+
+ app_list.append({
+ 'name': app_label.title(),
+ 'has_module_perms': has_module_perms,
+ 'models': model_list,
+ })
+ context[self.varname] = app_list
+ return ''
+
+def get_admin_app_list(parser, token):
+ """
+ Returns a list of installed applications and models for which the current user
+ has at least one permission.
+
+ Syntax::
+
+ {% get_admin_app_list as [context_var_containing_app_list] %}
+
+ Example usage::
+
+ {% get_admin_app_list as admin_app_list %}
+ """
+ tokens = token.contents.split()
+ if len(tokens) < 3:
+ raise template.TemplateSyntaxError, "'%s' tag requires two arguments" % tokens[0]
+ if tokens[1] != 'as':
+ raise template.TemplateSyntaxError, "First argument to '%s' tag must be 'as'" % tokens[0]
+ return AdminApplistNode(tokens[2])
+
+register.tag('get_admin_app_list', get_admin_app_list)
diff --git a/google_appengine/lib/django/django/contrib/admin/templatetags/adminmedia.py b/google_appengine/lib/django/django/contrib/admin/templatetags/adminmedia.py
new file mode 100755
index 0000000..7786343
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templatetags/adminmedia.py
@@ -0,0 +1,14 @@
+from django.template import Library
+
+register = Library()
+
+def admin_media_prefix():
+ """
+ Returns the string contained in the setting ADMIN_MEDIA_PREFIX.
+ """
+ try:
+ from django.conf import settings
+ except ImportError:
+ return ''
+ return settings.ADMIN_MEDIA_PREFIX
+admin_media_prefix = register.simple_tag(admin_media_prefix)
diff --git a/google_appengine/lib/django/django/contrib/admin/templatetags/log.py b/google_appengine/lib/django/django/contrib/admin/templatetags/log.py
new file mode 100755
index 0000000..5caba2b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/templatetags/log.py
@@ -0,0 +1,53 @@
+from django import template
+from django.contrib.admin.models import LogEntry
+
+register = template.Library()
+
+class AdminLogNode(template.Node):
+ def __init__(self, limit, varname, user):
+ self.limit, self.varname, self.user = limit, varname, user
+
+ def __repr__(self):
+ return "<GetAdminLog Node>"
+
+ def render(self, context):
+ if self.user is not None and not self.user.isdigit():
+ self.user = context[self.user].id
+ context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
+ return ''
+
+class DoGetAdminLog:
+ """
+ Populates a template variable with the admin log for the given criteria.
+
+ Usage::
+
+ {% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %}
+
+ Examples::
+
+ {% get_admin_log 10 as admin_log for_user 23 %}
+ {% get_admin_log 10 as admin_log for_user user %}
+ {% get_admin_log 10 as admin_log %}
+
+ Note that ``context_var_containing_user_obj`` can be a hard-coded integer
+ (user ID) or the name of a template context variable containing the user
+ object whose ID you want.
+ """
+ def __init__(self, tag_name):
+ self.tag_name = tag_name
+
+ def __call__(self, parser, token):
+ tokens = token.contents.split()
+ if len(tokens) < 4:
+ raise template.TemplateSyntaxError, "'%s' statements require two arguments" % self.tag_name
+ if not tokens[1].isdigit():
+ raise template.TemplateSyntaxError, "First argument in '%s' must be an integer" % self.tag_name
+ if tokens[2] != 'as':
+ raise template.TemplateSyntaxError, "Second argument in '%s' must be 'as'" % self.tag_name
+ if len(tokens) > 4:
+ if tokens[4] != 'for_user':
+ raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name
+ return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None))
+
+register.tag('get_admin_log', DoGetAdminLog('get_admin_log'))
diff --git a/google_appengine/lib/django/django/contrib/admin/urls.py b/google_appengine/lib/django/django/contrib/admin/urls.py
new file mode 100755
index 0000000..508bb3a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/urls.py
@@ -0,0 +1,43 @@
+from django.conf import settings
+from django.conf.urls.defaults import *
+
+if settings.USE_I18N:
+ i18n_view = 'django.views.i18n.javascript_catalog'
+else:
+ i18n_view = 'django.views.i18n.null_javascript_catalog'
+
+urlpatterns = patterns('',
+ ('^$', 'django.contrib.admin.views.main.index'),
+ ('^r/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
+ ('^jsi18n/$', i18n_view, {'packages': 'django.conf'}),
+ ('^logout/$', 'django.contrib.auth.views.logout'),
+ ('^password_change/$', 'django.contrib.auth.views.password_change'),
+ ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
+ ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'),
+
+ # Documentation
+ ('^doc/$', 'django.contrib.admin.views.doc.doc_index'),
+ ('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'),
+ ('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'),
+ ('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'),
+ ('^doc/views/$', 'django.contrib.admin.views.doc.view_index'),
+ ('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
+ ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
+ ('^doc/models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
+# ('^doc/templates/$', 'django.views.admin.doc.template_index'),
+ ('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_detail'),
+
+ # "Add user" -- a special-case view
+ ('^auth/user/add/$', 'django.contrib.admin.views.auth.user_add_stage'),
+ # "Change user password" -- another special-case view
+ ('^auth/user/(\d+)/password/$', 'django.contrib.admin.views.auth.user_change_password'),
+
+ # Add/change/delete/history
+ ('^([^/]+)/([^/]+)/$', 'django.contrib.admin.views.main.change_list'),
+ ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
+ ('^([^/]+)/([^/]+)/(.+)/history/$', 'django.contrib.admin.views.main.history'),
+ ('^([^/]+)/([^/]+)/(.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
+ ('^([^/]+)/([^/]+)/(.+)/$', 'django.contrib.admin.views.main.change_stage'),
+)
+
+del i18n_view
diff --git a/google_appengine/lib/django/django/contrib/admin/utils.py b/google_appengine/lib/django/django/contrib/admin/utils.py
new file mode 100755
index 0000000..9adf09b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/utils.py
@@ -0,0 +1,102 @@
+"Misc. utility functions/classes for admin documentation generator."
+
+import re
+from email.Parser import HeaderParser
+from email.Errors import HeaderParseError
+try:
+ import docutils.core
+ import docutils.nodes
+ import docutils.parsers.rst.roles
+except ImportError:
+ docutils_is_available = False
+else:
+ docutils_is_available = True
+
+def trim_docstring(docstring):
+ """
+ Uniformly trims leading/trailing whitespace from docstrings.
+
+ Based on http://www.python.org/peps/pep-0257.html#handling-docstring-indentation
+ """
+ if not docstring or not docstring.strip():
+ return ''
+ # Convert tabs to spaces and split into lines
+ lines = docstring.expandtabs().splitlines()
+ indent = min([len(line) - len(line.lstrip()) for line in lines if line.lstrip()])
+ trimmed = [lines[0].lstrip()] + [line[indent:].rstrip() for line in lines[1:]]
+ return "\n".join(trimmed).strip()
+
+def parse_docstring(docstring):
+ """
+ Parse out the parts of a docstring. Returns (title, body, metadata).
+ """
+ docstring = trim_docstring(docstring)
+ parts = re.split(r'\n{2,}', docstring)
+ title = parts[0]
+ if len(parts) == 1:
+ body = ''
+ metadata = {}
+ else:
+ parser = HeaderParser()
+ try:
+ metadata = parser.parsestr(parts[-1])
+ except HeaderParseError:
+ metadata = {}
+ body = "\n\n".join(parts[1:])
+ else:
+ metadata = dict(metadata.items())
+ if metadata:
+ body = "\n\n".join(parts[1:-1])
+ else:
+ body = "\n\n".join(parts[1:])
+ return title, body, metadata
+
+def parse_rst(text, default_reference_context, thing_being_parsed=None, link_base='../..'):
+ """
+ Convert the string from reST to an XHTML fragment.
+ """
+ overrides = {
+ 'doctitle_xform' : True,
+ 'inital_header_level' : 3,
+ "default_reference_context" : default_reference_context,
+ "link_base" : link_base,
+ }
+ if thing_being_parsed:
+ thing_being_parsed = "<%s>" % thing_being_parsed
+ parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
+ destination_path=None, writer_name='html',
+ settings_overrides=overrides)
+ return parts['fragment']
+
+#
+# reST roles
+#
+ROLES = {
+ 'model' : '%s/models/%s/',
+ 'view' : '%s/views/%s/',
+ 'template' : '%s/templates/%s/',
+ 'filter' : '%s/filters/#%s',
+ 'tag' : '%s/tags/#%s',
+}
+
+def create_reference_role(rolename, urlbase):
+ def _role(name, rawtext, text, lineno, inliner, options=None, content=None):
+ if options is None: options = {}
+ if content is None: content = []
+ node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text.lower())), **options)
+ return [node], []
+ docutils.parsers.rst.roles.register_canonical_role(rolename, _role)
+
+def default_reference_role(name, rawtext, text, lineno, inliner, options=None, content=None):
+ if options is None: options = {}
+ if content is None: content = []
+ context = inliner.document.settings.default_reference_context
+ node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text.lower())), **options)
+ return [node], []
+
+if docutils_is_available:
+ docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role)
+ docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference'
+
+ for name, urlbase in ROLES.items():
+ create_reference_role(name, urlbase)
diff --git a/google_appengine/lib/django/django/contrib/admin/views/__init__.py b/google_appengine/lib/django/django/contrib/admin/views/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/views/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/admin/views/auth.py b/google_appengine/lib/django/django/contrib/admin/views/auth.py
new file mode 100755
index 0000000..bea1f85
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/views/auth.py
@@ -0,0 +1,77 @@
+from django.contrib.admin.views.decorators import staff_member_required
+from django.contrib.auth.forms import UserCreationForm, AdminPasswordChangeForm
+from django.contrib.auth.models import User
+from django.core.exceptions import PermissionDenied
+from django import oldforms, template
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect
+from django.utils.html import escape
+
+def user_add_stage(request):
+ if not request.user.has_perm('auth.change_user'):
+ raise PermissionDenied
+ manipulator = UserCreationForm()
+ if request.method == 'POST':
+ new_data = request.POST.copy()
+ errors = manipulator.get_validation_errors(new_data)
+ if not errors:
+ new_user = manipulator.save(new_data)
+ msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
+ if request.POST.has_key("_addanother"):
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect(request.path)
+ else:
+ request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
+ return HttpResponseRedirect('../%s/' % new_user.id)
+ else:
+ errors = new_data = {}
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('admin/auth/user/add_form.html', {
+ 'title': _('Add user'),
+ 'form': form,
+ 'is_popup': request.REQUEST.has_key('_popup'),
+ 'add': True,
+ 'change': False,
+ 'has_delete_permission': False,
+ 'has_change_permission': True,
+ 'has_file_field': False,
+ 'has_absolute_url': False,
+ 'auto_populated_fields': (),
+ 'bound_field_sets': (),
+ 'first_form_field_id': 'id_username',
+ 'opts': User._meta,
+ 'username_help_text': User._meta.get_field('username').help_text,
+ }, context_instance=template.RequestContext(request))
+user_add_stage = staff_member_required(user_add_stage)
+
+def user_change_password(request, id):
+ if not request.user.has_perm('auth.change_user'):
+ raise PermissionDenied
+ user = get_object_or_404(User, pk=id)
+ manipulator = AdminPasswordChangeForm(user)
+ if request.method == 'POST':
+ new_data = request.POST.copy()
+ errors = manipulator.get_validation_errors(new_data)
+ if not errors:
+ new_user = manipulator.save(new_data)
+ msg = _('Password changed successfully.')
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect('..')
+ else:
+ errors = new_data = {}
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('admin/auth/user/change_password.html', {
+ 'title': _('Change password: %s') % escape(user.username),
+ 'form': form,
+ 'is_popup': request.REQUEST.has_key('_popup'),
+ 'add': True,
+ 'change': False,
+ 'has_delete_permission': False,
+ 'has_change_permission': True,
+ 'has_absolute_url': False,
+ 'first_form_field_id': 'id_password1',
+ 'opts': User._meta,
+ 'original': user,
+ 'show_save': True,
+ }, context_instance=template.RequestContext(request))
+user_change_password = staff_member_required(user_change_password)
diff --git a/google_appengine/lib/django/django/contrib/admin/views/decorators.py b/google_appengine/lib/django/django/contrib/admin/views/decorators.py
new file mode 100755
index 0000000..e68ac1b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/views/decorators.py
@@ -0,0 +1,73 @@
+from django import http, template
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate, login
+from django.shortcuts import render_to_response
+from django.utils.html import escape
+from django.utils.translation import gettext_lazy
+import base64, datetime
+
+ERROR_MESSAGE = gettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
+LOGIN_FORM_KEY = 'this_is_the_login_form'
+
+def _display_login_form(request, error_message=''):
+ request.session.set_test_cookie()
+ return render_to_response('admin/login.html', {
+ 'title': _('Log in'),
+ 'app_path': escape(request.path),
+ 'error_message': error_message
+ }, context_instance=template.RequestContext(request))
+
+def staff_member_required(view_func):
+ """
+ Decorator for views that checks that the user is logged in and is a staff
+ member, displaying the login page if necessary.
+ """
+ def _checklogin(request, *args, **kwargs):
+ if request.user.is_authenticated() and request.user.is_staff:
+ # The user is valid. Continue to the admin page.
+ return view_func(request, *args, **kwargs)
+
+ assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+
+ # If this isn't already the login page, display it.
+ if not request.POST.has_key(LOGIN_FORM_KEY):
+ if request.POST:
+ message = _("Please log in again, because your session has expired.")
+ else:
+ message = ""
+ return _display_login_form(request, message)
+
+ # Check that the user accepts cookies.
+ if not request.session.test_cookie_worked():
+ message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
+ return _display_login_form(request, message)
+
+ # Check the password.
+ username = request.POST.get('username', None)
+ password = request.POST.get('password', None)
+ user = authenticate(username=username, password=password)
+ if user is None:
+ message = ERROR_MESSAGE
+ if '@' in username:
+ # Mistakenly entered e-mail address instead of username? Look it up.
+ try:
+ user = User.objects.get(email=username)
+ except User.DoesNotExist:
+ message = _("Usernames cannot contain the '@' character.")
+ else:
+ message = _("Your e-mail address is not your username. Try '%s' instead.") % user.username
+ return _display_login_form(request, message)
+
+ # The user data is correct; log in the user in and continue.
+ else:
+ if user.is_active and user.is_staff:
+ login(request, user)
+ # TODO: set last_login with an event.
+ user.last_login = datetime.datetime.now()
+ user.save()
+ return http.HttpResponseRedirect(request.path)
+ else:
+ return _display_login_form(request, ERROR_MESSAGE)
+
+ return _checklogin
diff --git a/google_appengine/lib/django/django/contrib/admin/views/doc.py b/google_appengine/lib/django/django/contrib/admin/views/doc.py
new file mode 100755
index 0000000..6adfb57
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/views/doc.py
@@ -0,0 +1,365 @@
+from django import template, templatetags
+from django.template import RequestContext
+from django.conf import settings
+from django.contrib.admin.views.decorators import staff_member_required
+from django.db import models
+from django.shortcuts import render_to_response
+from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
+from django.http import Http404, get_host
+from django.core import urlresolvers
+from django.contrib.admin import utils
+from django.contrib.sites.models import Site
+import inspect, os, re
+
+# Exclude methods starting with these strings from documentation
+MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
+
+class GenericSite(object):
+ domain = 'example.com'
+ name = 'my site'
+
+def doc_index(request):
+ if not utils.docutils_is_available:
+ return missing_docutils_page(request)
+ return render_to_response('admin_doc/index.html', context_instance=RequestContext(request))
+doc_index = staff_member_required(doc_index)
+
+def bookmarklets(request):
+ # Hack! This couples this view to the URL it lives at.
+ admin_root = request.path[:-len('doc/bookmarklets/')]
+ return render_to_response('admin_doc/bookmarklets.html', {
+ 'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', get_host(request), admin_root),
+ }, context_instance=RequestContext(request))
+bookmarklets = staff_member_required(bookmarklets)
+
+def template_tag_index(request):
+ if not utils.docutils_is_available:
+ return missing_docutils_page(request)
+
+ load_all_installed_template_libraries()
+
+ tags = []
+ for module_name, library in template.libraries.items():
+ for tag_name, tag_func in library.tags.items():
+ title, body, metadata = utils.parse_docstring(tag_func.__doc__)
+ if title:
+ title = utils.parse_rst(title, 'tag', _('tag:') + tag_name)
+ if body:
+ body = utils.parse_rst(body, 'tag', _('tag:') + tag_name)
+ for key in metadata:
+ metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name)
+ if library in template.builtins:
+ tag_library = None
+ else:
+ tag_library = module_name.split('.')[-1]
+ tags.append({
+ 'name': tag_name,
+ 'title': title,
+ 'body': body,
+ 'meta': metadata,
+ 'library': tag_library,
+ })
+
+ return render_to_response('admin_doc/template_tag_index.html', {'tags': tags}, context_instance=RequestContext(request))
+template_tag_index = staff_member_required(template_tag_index)
+
+def template_filter_index(request):
+ if not utils.docutils_is_available:
+ return missing_docutils_page(request)
+
+ load_all_installed_template_libraries()
+
+ filters = []
+ for module_name, library in template.libraries.items():
+ for filter_name, filter_func in library.filters.items():
+ title, body, metadata = utils.parse_docstring(filter_func.__doc__)
+ if title:
+ title = utils.parse_rst(title, 'filter', _('filter:') + filter_name)
+ if body:
+ body = utils.parse_rst(body, 'filter', _('filter:') + filter_name)
+ for key in metadata:
+ metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name)
+ if library in template.builtins:
+ tag_library = None
+ else:
+ tag_library = module_name.split('.')[-1]
+ filters.append({
+ 'name': filter_name,
+ 'title': title,
+ 'body': body,
+ 'meta': metadata,
+ 'library': tag_library,
+ })
+ return render_to_response('admin_doc/template_filter_index.html', {'filters': filters}, context_instance=RequestContext(request))
+template_filter_index = staff_member_required(template_filter_index)
+
+def view_index(request):
+ if not utils.docutils_is_available:
+ return missing_docutils_page(request)
+
+ if settings.ADMIN_FOR:
+ settings_modules = [__import__(m, {}, {}, ['']) for m in settings.ADMIN_FOR]
+ else:
+ settings_modules = [settings]
+
+ views = []
+ for settings_mod in settings_modules:
+ urlconf = __import__(settings_mod.ROOT_URLCONF, {}, {}, [''])
+ view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
+ if Site._meta.installed:
+ site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
+ else:
+ site_obj = GenericSite()
+ for (func, regex) in view_functions:
+ views.append({
+ 'name': func.__name__,
+ 'module': func.__module__,
+ 'site_id': settings_mod.SITE_ID,
+ 'site': site_obj,
+ 'url': simplify_regex(regex),
+ })
+ return render_to_response('admin_doc/view_index.html', {'views': views}, context_instance=RequestContext(request))
+view_index = staff_member_required(view_index)
+
+def view_detail(request, view):
+ if not utils.docutils_is_available:
+ return missing_docutils_page(request)
+
+ mod, func = urlresolvers.get_mod_func(view)
+ try:
+ view_func = getattr(__import__(mod, {}, {}, ['']), func)
+ except (ImportError, AttributeError):
+ raise Http404
+ title, body, metadata = utils.parse_docstring(view_func.__doc__)
+ if title:
+ title = utils.parse_rst(title, 'view', _('view:') + view)
+ if body:
+ body = utils.parse_rst(body, 'view', _('view:') + view)
+ for key in metadata:
+ metadata[key] = utils.parse_rst(metadata[key], 'model', _('view:') + view)
+ return render_to_response('admin_doc/view_detail.html', {
+ 'name': view,
+ 'summary': title,
+ 'body': body,
+ 'meta': metadata,
+ }, context_instance=RequestContext(request))
+view_detail = staff_member_required(view_detail)
+
+def model_index(request):
+ if not utils.docutils_is_available:
+ return missing_docutils_page(request)
+
+ m_list = [m._meta for m in models.get_models()]
+ return render_to_response('admin_doc/model_index.html', {'models': m_list}, context_instance=RequestContext(request))
+model_index = staff_member_required(model_index)
+
+def model_detail(request, app_label, model_name):
+ if not utils.docutils_is_available:
+ return missing_docutils_page(request)
+
+ # Get the model class.
+ try:
+ app_mod = models.get_app(app_label)
+ except ImproperlyConfigured:
+ raise Http404, _("App %r not found") % app_label
+ model = None
+ for m in models.get_models(app_mod):
+ if m._meta.object_name.lower() == model_name:
+ model = m
+ break
+ if model is None:
+ raise Http404, _("Model %(name)r not found in app %(label)r") % {'name': model_name, 'label': app_label}
+
+ opts = model._meta
+
+ # Gather fields/field descriptions.
+ fields = []
+ for field in opts.fields:
+ # ForeignKey is a special case since the field will actually be a
+ # descriptor that returns the other object
+ if isinstance(field, models.ForeignKey):
+ data_type = related_object_name = field.rel.to.__name__
+ app_label = field.rel.to._meta.app_label
+ verbose = utils.parse_rst((_("the related `%(label)s.%(type)s` object") % {'label': app_label, 'type': data_type}), 'model', _('model:') + data_type)
+ else:
+ data_type = get_readable_field_data_type(field)
+ verbose = field.verbose_name
+ fields.append({
+ 'name': field.name,
+ 'data_type': data_type,
+ 'verbose': verbose,
+ 'help_text': field.help_text,
+ })
+
+ # Gather model methods.
+ for func_name, func in model.__dict__.items():
+ if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1):
+ try:
+ for exclude in MODEL_METHODS_EXCLUDE:
+ if func_name.startswith(exclude):
+ raise StopIteration
+ except StopIteration:
+ continue
+ verbose = func.__doc__
+ if verbose:
+ verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.module_name)
+ fields.append({
+ 'name': func_name,
+ 'data_type': get_return_data_type(func_name),
+ 'verbose': verbose,
+ })
+
+ # Gather related objects
+ for rel in opts.get_all_related_objects():
+ verbose = _("related `%(label)s.%(name)s` objects") % {'label': rel.opts.app_label, 'name': rel.opts.object_name}
+ accessor = rel.get_accessor_name()
+ fields.append({
+ 'name' : "%s.all" % accessor,
+ 'data_type' : 'List',
+ 'verbose' : utils.parse_rst(_("all %s") % verbose , 'model', _('model:') + opts.module_name),
+ })
+ fields.append({
+ 'name' : "%s.count" % accessor,
+ 'data_type' : 'Integer',
+ 'verbose' : utils.parse_rst(_("number of %s") % verbose , 'model', _('model:') + opts.module_name),
+ })
+
+ return render_to_response('admin_doc/model_detail.html', {
+ 'name': '%s.%s' % (opts.app_label, opts.object_name),
+ 'summary': _("Fields on %s objects") % opts.object_name,
+ 'description': model.__doc__,
+ 'fields': fields,
+ }, context_instance=RequestContext(request))
+model_detail = staff_member_required(model_detail)
+
+def template_detail(request, template):
+ templates = []
+ for site_settings_module in settings.ADMIN_FOR:
+ settings_mod = __import__(site_settings_module, {}, {}, [''])
+ if Site._meta.installed:
+ site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
+ else:
+ site_obj = GenericSite()
+ for dir in settings_mod.TEMPLATE_DIRS:
+ template_file = os.path.join(dir, "%s.html" % template)
+ templates.append({
+ 'file': template_file,
+ 'exists': os.path.exists(template_file),
+ 'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '',
+ 'site_id': settings_mod.SITE_ID,
+ 'site': site_obj,
+ 'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
+ })
+ return render_to_response('admin_doc/template_detail.html', {
+ 'name': template,
+ 'templates': templates,
+ }, context_instance=RequestContext(request))
+template_detail = staff_member_required(template_detail)
+
+####################
+# Helper functions #
+####################
+
+def missing_docutils_page(request):
+ """Display an error message for people without docutils"""
+ return render_to_response('admin_doc/missing_docutils.html')
+
+def load_all_installed_template_libraries():
+ # Load/register all template tag libraries from installed apps.
+ for e in templatetags.__path__:
+ libraries = [os.path.splitext(p)[0] for p in os.listdir(e) if p.endswith('.py') and p[0].isalpha()]
+ for library_name in libraries:
+ try:
+ lib = template.get_library("django.templatetags.%s" % library_name.split('.')[-1])
+ except template.InvalidTemplateLibrary:
+ pass
+
+def get_return_data_type(func_name):
+ """Return a somewhat-helpful data type given a function name"""
+ if func_name.startswith('get_'):
+ if func_name.endswith('_list'):
+ return 'List'
+ elif func_name.endswith('_count'):
+ return 'Integer'
+ return ''
+
+# Maps Field objects to their human-readable data types, as strings.
+# Column-type strings can contain format strings; they'll be interpolated
+# against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPE_MAPPING = {
+ 'AutoField' : _('Integer'),
+ 'BooleanField' : _('Boolean (Either True or False)'),
+ 'CharField' : _('String (up to %(maxlength)s)'),
+ 'CommaSeparatedIntegerField': _('Comma-separated integers'),
+ 'DateField' : _('Date (without time)'),
+ 'DateTimeField' : _('Date (with time)'),
+ 'EmailField' : _('E-mail address'),
+ 'FileField' : _('File path'),
+ 'FilePathField' : _('File path'),
+ 'FloatField' : _('Decimal number'),
+ 'ForeignKey' : _('Integer'),
+ 'ImageField' : _('File path'),
+ 'IntegerField' : _('Integer'),
+ 'IPAddressField' : _('IP address'),
+ 'ManyToManyField' : '',
+ 'NullBooleanField' : _('Boolean (Either True, False or None)'),
+ 'OneToOneField' : _('Relation to parent model'),
+ 'PhoneNumberField' : _('Phone number'),
+ 'PositiveIntegerField' : _('Integer'),
+ 'PositiveSmallIntegerField' : _('Integer'),
+ 'SlugField' : _('String (up to %(maxlength)s)'),
+ 'SmallIntegerField' : _('Integer'),
+ 'TextField' : _('Text'),
+ 'TimeField' : _('Time'),
+ 'URLField' : _('URL'),
+ 'USStateField' : _('U.S. state (two uppercase letters)'),
+ 'XMLField' : _('XML text'),
+}
+
+def get_readable_field_data_type(field):
+ return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
+
+def extract_views_from_urlpatterns(urlpatterns, base=''):
+ """
+ Return a list of views from a list of urlpatterns.
+
+ Each object in the returned list is a two-tuple: (view_func, regex)
+ """
+ views = []
+ for p in urlpatterns:
+ if hasattr(p, '_get_callback'):
+ try:
+ views.append((p._get_callback(), base + p.regex.pattern))
+ except ViewDoesNotExist:
+ continue
+ elif hasattr(p, '_get_url_patterns'):
+ try:
+ patterns = p.url_patterns
+ except ImportError:
+ continue
+ views.extend(extract_views_from_urlpatterns(patterns, base + p.regex.pattern))
+ else:
+ raise TypeError, _("%s does not appear to be a urlpattern object") % p
+ return views
+
+named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)')
+non_named_group_matcher = re.compile(r'\(.*?\)')
+
+def simplify_regex(pattern):
+ """
+ Clean up urlpattern regexes into something somewhat readable by Mere Humans:
+ turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
+ into "<sport_slug>/athletes/<athlete_slug>/"
+ """
+ # handle named groups first
+ pattern = named_group_matcher.sub(lambda m: m.group(1), pattern)
+
+ # handle non-named groups
+ pattern = non_named_group_matcher.sub("<var>", pattern)
+
+ # clean up any outstanding regex-y characters.
+ pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '')
+ if not pattern.startswith('/'):
+ pattern = '/' + pattern
+ return pattern
diff --git a/google_appengine/lib/django/django/contrib/admin/views/main.py b/google_appengine/lib/django/django/contrib/admin/views/main.py
new file mode 100755
index 0000000..0e962ad
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/views/main.py
@@ -0,0 +1,779 @@
+from django import oldforms, template
+from django.conf import settings
+from django.contrib.admin.filterspecs import FilterSpec
+from django.contrib.admin.views.decorators import staff_member_required
+from django.views.decorators.cache import never_cache
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
+from django.core.paginator import ObjectPaginator, InvalidPage
+from django.shortcuts import get_object_or_404, render_to_response
+from django.db import models
+from django.db.models.query import handle_legacy_orderlist, QuerySet
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.utils.html import escape
+from django.utils.text import capfirst, get_text_list
+import operator
+
+from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
+if not LogEntry._meta.installed:
+ raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application."
+
+if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
+ raise ImproperlyConfigured, "You'll need to put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting before you can use the admin application."
+
+# The system will display a "Show all" link on the change list only if the
+# total result count is less than or equal to this setting.
+MAX_SHOW_ALL_ALLOWED = 200
+
+# Changelist settings
+ALL_VAR = 'all'
+ORDER_VAR = 'o'
+ORDER_TYPE_VAR = 'ot'
+PAGE_VAR = 'p'
+SEARCH_VAR = 'q'
+IS_POPUP_VAR = 'pop'
+ERROR_FLAG = 'e'
+
+# Text to display within change-list table cells if the value is blank.
+EMPTY_CHANGELIST_VALUE = '(None)'
+
+use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin
+
+class IncorrectLookupParameters(Exception):
+ pass
+
+def quote(s):
+ """
+ Ensure that primary key values do not confuse the admin URLs by escaping
+ any '/', '_' and ':' characters. Similar to urllib.quote, except that the
+ quoting is slightly different so that it doesn't get automatically
+ unquoted by the Web browser.
+ """
+ if type(s) != type(''):
+ return s
+ res = list(s)
+ for i in range(len(res)):
+ c = res[i]
+ if c in ':/_':
+ res[i] = '_%02X' % ord(c)
+ return ''.join(res)
+
+def unquote(s):
+ """
+ Undo the effects of quote(). Based heavily on urllib.unquote().
+ """
+ mychr = chr
+ myatoi = int
+ list = s.split('_')
+ res = [list[0]]
+ myappend = res.append
+ del list[0]
+ for item in list:
+ if item[1:2]:
+ try:
+ myappend(mychr(myatoi(item[:2], 16)) + item[2:])
+ except ValueError:
+ myappend('_' + item)
+ else:
+ myappend('_' + item)
+ return "".join(res)
+
+def get_javascript_imports(opts, auto_populated_fields, field_sets):
+# Put in any necessary JavaScript imports.
+ js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
+ if auto_populated_fields:
+ js.append('js/urlify.js')
+ if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField):
+ js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
+ if opts.get_ordered_objects():
+ js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
+ if opts.admin.js:
+ js.extend(opts.admin.js)
+ seen_collapse = False
+ for field_set in field_sets:
+ if not seen_collapse and 'collapse' in field_set.classes:
+ seen_collapse = True
+ js.append('js/admin/CollapsedFieldsets.js')
+
+ for field_line in field_set:
+ try:
+ for f in field_line:
+ if f.rel and isinstance(f, models.ManyToManyField) and f.rel.filter_interface:
+ js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
+ raise StopIteration
+ except StopIteration:
+ break
+ return js
+
+class AdminBoundField(object):
+ def __init__(self, field, field_mapping, original):
+ self.field = field
+ self.original = original
+ self.form_fields = [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
+ self.element_id = self.form_fields[0].get_id()
+ self.has_label_first = not isinstance(self.field, models.BooleanField)
+ self.raw_id_admin = use_raw_id_admin(field)
+ self.is_date_time = isinstance(field, models.DateTimeField)
+ self.is_file_field = isinstance(field, models.FileField)
+ self.needs_add_label = field.rel and (isinstance(field.rel, models.ManyToOneRel) or isinstance(field.rel, models.ManyToManyRel)) and field.rel.to._meta.admin
+ self.hidden = isinstance(self.field, models.AutoField)
+ self.first = False
+
+ classes = []
+ if self.raw_id_admin:
+ classes.append('nowrap')
+ if max([bool(f.errors()) for f in self.form_fields]):
+ classes.append('error')
+ if classes:
+ self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
+ self._repr_filled = False
+
+ if field.rel:
+ self.related_url = '../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
+
+ def original_value(self):
+ if self.original:
+ return self.original.__dict__[self.field.column]
+
+ def existing_display(self):
+ try:
+ return self._display
+ except AttributeError:
+ if isinstance(self.field.rel, models.ManyToOneRel):
+ self._display = getattr(self.original, self.field.name)
+ elif isinstance(self.field.rel, models.ManyToManyRel):
+ self._display = ", ".join([str(obj) for obj in getattr(self.original, self.field.name).all()])
+ return self._display
+
+ def __repr__(self):
+ return repr(self.__dict__)
+
+ def html_error_list(self):
+ return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])
+
+ def original_url(self):
+ if self.is_file_field and self.original and self.field.attname:
+ url_method = getattr(self.original, 'get_%s_url' % self.field.attname)
+ if callable(url_method):
+ return url_method()
+ return ''
+
+class AdminBoundFieldLine(object):
+ def __init__(self, field_line, field_mapping, original):
+ self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line]
+ for bound_field in self:
+ bound_field.first = True
+ break
+
+ def __iter__(self):
+ for bound_field in self.bound_fields:
+ yield bound_field
+
+ def __len__(self):
+ return len(self.bound_fields)
+
+class AdminBoundFieldSet(object):
+ def __init__(self, field_set, field_mapping, original):
+ self.name = field_set.name
+ self.classes = field_set.classes
+ self.description = field_set.description
+ self.bound_field_lines = [field_line.bind(field_mapping, original, AdminBoundFieldLine) for field_line in field_set]
+
+ def __iter__(self):
+ for bound_field_line in self.bound_field_lines:
+ yield bound_field_line
+
+ def __len__(self):
+ return len(self.bound_field_lines)
+
+def render_change_form(model, manipulator, context, add=False, change=False, form_url=''):
+ opts = model._meta
+ app_label = opts.app_label
+ auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
+ field_sets = opts.admin.get_field_sets(opts)
+ original = getattr(manipulator, 'original_object', None)
+ bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets]
+ first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
+ ordered_objects = opts.get_ordered_objects()
+ inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
+ extra_context = {
+ 'add': add,
+ 'change': change,
+ 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()],
+ 'has_change_permission': context['perms'][app_label][opts.get_change_permission()],
+ 'has_file_field': opts.has_field_type(models.FileField),
+ 'has_absolute_url': hasattr(model, 'get_absolute_url'),
+ 'auto_populated_fields': auto_populated_fields,
+ 'bound_field_sets': bound_field_sets,
+ 'first_form_field_id': first_form_field_id,
+ 'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets),
+ 'ordered_objects': ordered_objects,
+ 'inline_related_objects': inline_related_objects,
+ 'form_url': form_url,
+ 'opts': opts,
+ 'content_type_id': ContentType.objects.get_for_model(model).id,
+ }
+ context.update(extra_context)
+ return render_to_response([
+ "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
+ "admin/%s/change_form.html" % app_label,
+ "admin/change_form.html"], context_instance=context)
+
+def index(request):
+ return render_to_response('admin/index.html', {'title': _('Site administration')}, context_instance=template.RequestContext(request))
+index = staff_member_required(never_cache(index))
+
+def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
+ model = models.get_model(app_label, model_name)
+ if model is None:
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
+ opts = model._meta
+
+ if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
+ raise PermissionDenied
+
+ if post_url is None:
+ if request.user.has_perm(app_label + '.' + opts.get_change_permission()):
+ # redirect to list view
+ post_url = '../'
+ else:
+ # Object list will give 'Permission Denied', so go back to admin home
+ post_url = '../../../'
+
+ manipulator = model.AddManipulator()
+ if request.POST:
+ new_data = request.POST.copy()
+
+ if opts.has_field_type(models.FileField):
+ new_data.update(request.FILES)
+
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+
+ if not errors:
+ new_object = manipulator.save(new_data)
+ pk_value = new_object._get_pk_val()
+ LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION)
+ msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
+ # Here, we distinguish between different save types by checking for
+ # the presence of keys in request.POST.
+ if request.POST.has_key("_continue"):
+ request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
+ if request.POST.has_key("_popup"):
+ post_url_continue += "?_popup=1"
+ return HttpResponseRedirect(post_url_continue % pk_value)
+ if request.POST.has_key("_popup"):
+ if type(pk_value) is str: # Quote if string, so JavaScript doesn't think it's a variable.
+ pk_value = '"%s"' % pk_value.replace('"', '\\"')
+ return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
+ (pk_value, str(new_object).replace('"', '\\"')))
+ elif request.POST.has_key("_addanother"):
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ return HttpResponseRedirect(request.path)
+ else:
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect(post_url)
+ else:
+ # Add default data.
+ new_data = manipulator.flatten_data()
+
+ # Override the defaults with GET params, if they exist.
+ new_data.update(dict(request.GET.items()))
+
+ errors = {}
+
+ # Populate the FormWrapper.
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
+
+ c = template.RequestContext(request, {
+ 'title': _('Add %s') % opts.verbose_name,
+ 'form': form,
+ 'is_popup': request.REQUEST.has_key('_popup'),
+ 'show_delete': show_delete,
+ })
+
+ if object_id_override is not None:
+ c['object_id'] = object_id_override
+
+ return render_change_form(model, manipulator, c, add=True)
+add_stage = staff_member_required(never_cache(add_stage))
+
+def change_stage(request, app_label, model_name, object_id):
+ model = models.get_model(app_label, model_name)
+ object_id = unquote(object_id)
+ if model is None:
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
+ opts = model._meta
+
+ if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
+ raise PermissionDenied
+
+ if request.POST and request.POST.has_key("_saveasnew"):
+ return add_stage(request, app_label, model_name, form_url='../../add/')
+
+ try:
+ manipulator = model.ChangeManipulator(object_id)
+ except model.DoesNotExist:
+ raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id)))
+
+ if request.POST:
+ new_data = request.POST.copy()
+
+ if opts.has_field_type(models.FileField):
+ new_data.update(request.FILES)
+
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+
+ if not errors:
+ new_object = manipulator.save(new_data)
+ pk_value = new_object._get_pk_val()
+
+ # Construct the change message.
+ change_message = []
+ if manipulator.fields_added:
+ change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
+ if manipulator.fields_changed:
+ change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
+ if manipulator.fields_deleted:
+ change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
+ change_message = ' '.join(change_message)
+ if not change_message:
+ change_message = _('No fields changed.')
+ LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), CHANGE, change_message)
+
+ msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
+ if request.POST.has_key("_continue"):
+ request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
+ if request.REQUEST.has_key('_popup'):
+ return HttpResponseRedirect(request.path + "?_popup=1")
+ else:
+ return HttpResponseRedirect(request.path)
+ elif request.POST.has_key("_saveasnew"):
+ request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
+ return HttpResponseRedirect("../%s/" % pk_value)
+ elif request.POST.has_key("_addanother"):
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ return HttpResponseRedirect("../add/")
+ else:
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect("../")
+ else:
+ # Populate new_data with a "flattened" version of the current data.
+ new_data = manipulator.flatten_data()
+
+ # TODO: do this in flatten_data...
+ # If the object has ordered objects on its admin page, get the existing
+ # order and flatten it into a comma-separated list of IDs.
+
+ id_order_list = []
+ for rel_obj in opts.get_ordered_objects():
+ id_order_list.extend(getattr(manipulator.original_object, 'get_%s_order' % rel_obj.object_name.lower())())
+ if id_order_list:
+ new_data['order_'] = ','.join(map(str, id_order_list))
+ errors = {}
+
+ # Populate the FormWrapper.
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
+ form.original = manipulator.original_object
+ form.order_objects = []
+
+ #TODO Should be done in flatten_data / FormWrapper construction
+ for related in opts.get_followed_related_objects():
+ wrt = related.opts.order_with_respect_to
+ if wrt and wrt.rel and wrt.rel.to == opts:
+ func = getattr(manipulator.original_object, 'get_%s_list' %
+ related.get_accessor_name())
+ orig_list = func()
+ form.order_objects.extend(orig_list)
+
+ c = template.RequestContext(request, {
+ 'title': _('Change %s') % opts.verbose_name,
+ 'form': form,
+ 'object_id': object_id,
+ 'original': manipulator.original_object,
+ 'is_popup': request.REQUEST.has_key('_popup'),
+ })
+ return render_change_form(model, manipulator, c, change=True)
+change_stage = staff_member_required(never_cache(change_stage))
+
+def _nest_help(obj, depth, val):
+ current = obj
+ for i in range(depth):
+ current = current[-1]
+ current.append(val)
+
+def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth):
+ "Helper function that recursively populates deleted_objects."
+ nh = _nest_help # Bind to local variable for performance
+ if current_depth > 16:
+ return # Avoid recursing too deep.
+ opts_seen = []
+ for related in opts.get_all_related_objects():
+ if related.opts in opts_seen:
+ continue
+ opts_seen.append(related.opts)
+ rel_opts_name = related.get_accessor_name()
+ if isinstance(related.field.rel, models.OneToOneRel):
+ try:
+ sub_obj = getattr(obj, rel_opts_name)
+ except ObjectDoesNotExist:
+ pass
+ else:
+ if related.opts.admin:
+ p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
+ if not user.has_perm(p):
+ perms_needed.add(related.opts.verbose_name)
+ # We don't care about populating deleted_objects now.
+ continue
+ if related.field.rel.edit_inline or not related.opts.admin:
+ # Don't display link to edit, because it either has no
+ # admin or is edited inline.
+ nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), sub_obj), []])
+ else:
+ # Display a link to the admin page.
+ nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
+ (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(),
+ sub_obj._get_pk_val(), sub_obj), []])
+ _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
+ else:
+ has_related_objs = False
+ for sub_obj in getattr(obj, rel_opts_name).all():
+ has_related_objs = True
+ if related.field.rel.edit_inline or not related.opts.admin:
+ # Don't display link to edit, because it either has no
+ # admin or is edited inline.
+ nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), escape(str(sub_obj))), []])
+ else:
+ # Display a link to the admin page.
+ nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
+ (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(str(sub_obj))), []])
+ _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
+ # If there were related objects, and the user doesn't have
+ # permission to delete them, add the missing perm to perms_needed.
+ if related.opts.admin and has_related_objs:
+ p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
+ if not user.has_perm(p):
+ perms_needed.add(related.opts.verbose_name)
+ for related in opts.get_all_related_many_to_many_objects():
+ if related.opts in opts_seen:
+ continue
+ opts_seen.append(related.opts)
+ rel_opts_name = related.get_accessor_name()
+ has_related_objs = False
+
+ # related.get_accessor_name() could return None for symmetrical relationships
+ if rel_opts_name:
+ rel_objs = getattr(obj, rel_opts_name, None)
+ if rel_objs:
+ has_related_objs = True
+
+ if has_related_objs:
+ for sub_obj in rel_objs.all():
+ if related.field.rel.edit_inline or not related.opts.admin:
+ # Don't display link to edit, because it either has no
+ # admin or is edited inline.
+ nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
+ {'fieldname': related.field.verbose_name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []])
+ else:
+ # Display a link to the admin page.
+ nh(deleted_objects, current_depth, [
+ (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.verbose_name, 'name':related.opts.verbose_name}) + \
+ (' <a href="../../../../%s/%s/%s/">%s</a>' % \
+ (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(str(sub_obj)))), []])
+ # If there were related objects, and the user doesn't have
+ # permission to change them, add the missing perm to perms_needed.
+ if related.opts.admin and has_related_objs:
+ p = '%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
+ if not user.has_perm(p):
+ perms_needed.add(related.opts.verbose_name)
+
+def delete_stage(request, app_label, model_name, object_id):
+ import sets
+ model = models.get_model(app_label, model_name)
+ object_id = unquote(object_id)
+ if model is None:
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
+ opts = model._meta
+ if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
+ raise PermissionDenied
+ obj = get_object_or_404(model, pk=object_id)
+
+ # Populate deleted_objects, a data structure of all related objects that
+ # will also be deleted.
+ deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, escape(str(obj))), []]
+ perms_needed = sets.Set()
+ _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
+
+ if request.POST: # The user has already confirmed the deletion.
+ if perms_needed:
+ raise PermissionDenied
+ obj_display = str(obj)
+ obj.delete()
+ LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION)
+ request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': opts.verbose_name, 'obj': obj_display})
+ return HttpResponseRedirect("../../")
+ extra_context = {
+ "title": _("Are you sure?"),
+ "object_name": opts.verbose_name,
+ "object": obj,
+ "deleted_objects": deleted_objects,
+ "perms_lacking": perms_needed,
+ "opts": model._meta,
+ }
+ return render_to_response(["admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower() ),
+ "admin/%s/delete_confirmation.html" % app_label ,
+ "admin/delete_confirmation.html"], extra_context, context_instance=template.RequestContext(request))
+delete_stage = staff_member_required(never_cache(delete_stage))
+
+def history(request, app_label, model_name, object_id):
+ model = models.get_model(app_label, model_name)
+ object_id = unquote(object_id)
+ if model is None:
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
+ action_list = LogEntry.objects.filter(object_id=object_id,
+ content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
+ # If no history was found, see whether this object even exists.
+ obj = get_object_or_404(model, pk=object_id)
+ extra_context = {
+ 'title': _('Change history: %s') % obj,
+ 'action_list': action_list,
+ 'module_name': capfirst(model._meta.verbose_name_plural),
+ 'object': obj,
+ }
+ return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()),
+ "admin/%s/object_history.html" % app_label ,
+ "admin/object_history.html"], extra_context, context_instance=template.RequestContext(request))
+history = staff_member_required(never_cache(history))
+
+class ChangeList(object):
+ def __init__(self, request, model):
+ self.model = model
+ self.opts = model._meta
+ self.lookup_opts = self.opts
+ self.manager = self.opts.admin.manager
+
+ # Get search parameters from the query string.
+ try:
+ self.page_num = int(request.GET.get(PAGE_VAR, 0))
+ except ValueError:
+ self.page_num = 0
+ self.show_all = request.GET.has_key(ALL_VAR)
+ self.is_popup = request.GET.has_key(IS_POPUP_VAR)
+ self.params = dict(request.GET.items())
+ if self.params.has_key(PAGE_VAR):
+ del self.params[PAGE_VAR]
+ if self.params.has_key(ERROR_FLAG):
+ del self.params[ERROR_FLAG]
+
+ self.order_field, self.order_type = self.get_ordering()
+ self.query = request.GET.get(SEARCH_VAR, '')
+ self.query_set = self.get_query_set()
+ self.get_results(request)
+ self.title = (self.is_popup and _('Select %s') % self.opts.verbose_name or _('Select %s to change') % self.opts.verbose_name)
+ self.filter_specs, self.has_filters = self.get_filters(request)
+ self.pk_attname = self.lookup_opts.pk.attname
+
+ def get_filters(self, request):
+ filter_specs = []
+ if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field:
+ filter_fields = [self.lookup_opts.get_field(field_name) \
+ for field_name in self.lookup_opts.admin.list_filter]
+ for f in filter_fields:
+ spec = FilterSpec.create(f, request, self.params, self.model)
+ if spec and spec.has_output():
+ filter_specs.append(spec)
+ return filter_specs, bool(filter_specs)
+
+ def get_query_string(self, new_params=None, remove=None):
+ if new_params is None: new_params = {}
+ if remove is None: remove = []
+ p = self.params.copy()
+ for r in remove:
+ for k in p.keys():
+ if k.startswith(r):
+ del p[k]
+ for k, v in new_params.items():
+ if p.has_key(k) and v is None:
+ del p[k]
+ elif v is not None:
+ p[k] = v
+ return '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
+
+ def get_results(self, request):
+ paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page)
+
+ # Get the number of objects, with admin filters applied.
+ try:
+ result_count = paginator.hits
+ # Naked except! Because we don't have any other way of validating
+ # "params". They might be invalid if the keyword arguments are
+ # incorrect, or if the values are not in the correct type (which would
+ # result in a database error).
+ except:
+ raise IncorrectLookupParameters
+
+ # Get the total number of objects, with no admin filters applied.
+ # Perform a slight optimization: Check to see whether any filters were
+ # given. If not, use paginator.hits to calculate the number of objects,
+ # because we've already done paginator.hits and the value is cached.
+ if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs:
+ full_result_count = result_count
+ else:
+ full_result_count = self.manager.count()
+
+ can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
+ multi_page = result_count > self.lookup_opts.admin.list_per_page
+
+ # Get the list of objects to display on this page.
+ if (self.show_all and can_show_all) or not multi_page:
+ result_list = list(self.query_set)
+ else:
+ try:
+ result_list = paginator.get_page(self.page_num)
+ except InvalidPage:
+ result_list = ()
+
+ self.result_count = result_count
+ self.full_result_count = full_result_count
+ self.result_list = result_list
+ self.can_show_all = can_show_all
+ self.multi_page = multi_page
+ self.paginator = paginator
+
+ def get_ordering(self):
+ lookup_opts, params = self.lookup_opts, self.params
+ # For ordering, first check the "ordering" parameter in the admin options,
+ # then check the object's default ordering. If neither of those exist,
+ # order descending by ID by default. Finally, look for manually-specified
+ # ordering from the query string.
+ ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
+
+ # Normalize it to new-style ordering.
+ ordering = handle_legacy_orderlist(ordering)
+
+ if ordering[0].startswith('-'):
+ order_field, order_type = ordering[0][1:], 'desc'
+ else:
+ order_field, order_type = ordering[0], 'asc'
+ if params.has_key(ORDER_VAR):
+ try:
+ field_name = lookup_opts.admin.list_display[int(params[ORDER_VAR])]
+ try:
+ f = lookup_opts.get_field(field_name)
+ except models.FieldDoesNotExist:
+ # see if field_name is a name of a non-field
+ # that allows sorting
+ try:
+ attr = getattr(lookup_opts.admin.manager.model, field_name)
+ order_field = attr.admin_order_field
+ except IndexError:
+ pass
+ else:
+ if not isinstance(f.rel, models.ManyToOneRel) or not f.null:
+ order_field = f.name
+ except (IndexError, ValueError):
+ pass # Invalid ordering specified. Just use the default.
+ if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
+ order_type = params[ORDER_TYPE_VAR]
+ return order_field, order_type
+
+ def get_query_set(self):
+ qs = self.manager.get_query_set()
+ lookup_params = self.params.copy() # a dictionary of the query string
+ for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
+ if lookup_params.has_key(i):
+ del lookup_params[i]
+
+ # Apply lookup parameters from the query string.
+ qs = qs.filter(**lookup_params)
+
+ # Use select_related() if one of the list_display options is a field
+ # with a relationship.
+ if self.lookup_opts.admin.list_select_related:
+ qs = qs.select_related()
+ else:
+ for field_name in self.lookup_opts.admin.list_display:
+ try:
+ f = self.lookup_opts.get_field(field_name)
+ except models.FieldDoesNotExist:
+ pass
+ else:
+ if isinstance(f.rel, models.ManyToOneRel):
+ qs = qs.select_related()
+ break
+
+ # Calculate lookup_order_field.
+ # If the order-by field is a field with a relationship, order by the
+ # value in the related table.
+ lookup_order_field = self.order_field
+ try:
+ f = self.lookup_opts.get_field(self.order_field, many_to_many=False)
+ except models.FieldDoesNotExist:
+ pass
+ else:
+ if isinstance(f.rel, models.OneToOneRel):
+ # For OneToOneFields, don't try to order by the related object's ordering criteria.
+ pass
+ elif isinstance(f.rel, models.ManyToOneRel):
+ rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column
+ lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering)
+
+ # Set ordering.
+ qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field)
+
+ # Apply keyword searches.
+ def construct_search(field_name):
+ if field_name.startswith('^'):
+ return "%s__istartswith" % field_name[1:]
+ elif field_name.startswith('='):
+ return "%s__iexact" % field_name[1:]
+ elif field_name.startswith('@'):
+ return "%s__search" % field_name[1:]
+ else:
+ return "%s__icontains" % field_name
+
+ if self.lookup_opts.admin.search_fields and self.query:
+ for bit in self.query.split():
+ or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.lookup_opts.admin.search_fields]
+ other_qs = QuerySet(self.model)
+ if qs._select_related:
+ other_qs = other_qs.select_related()
+ other_qs = other_qs.filter(reduce(operator.or_, or_queries))
+ qs = qs & other_qs
+
+ if self.opts.one_to_one_field:
+ qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to)
+
+ return qs
+
+ def url_for_result(self, result):
+ return "%s/" % quote(getattr(result, self.pk_attname))
+
+def change_list(request, app_label, model_name):
+ model = models.get_model(app_label, model_name)
+ if model is None:
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
+ if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()):
+ raise PermissionDenied
+ try:
+ cl = ChangeList(request, model)
+ except IncorrectLookupParameters:
+ # Wacky lookup parameters were given, so redirect to the main
+ # changelist page, without parameters, and pass an 'invalid=1'
+ # parameter via the query string. If wacky parameters were given and
+ # the 'invalid=1' parameter was already in the query string, something
+ # is screwed up with the database, so display an error page.
+ if ERROR_FLAG in request.GET.keys():
+ return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
+ return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
+ c = template.RequestContext(request, {
+ 'title': cl.title,
+ 'is_popup': cl.is_popup,
+ 'cl': cl,
+ })
+ c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}),
+ return render_to_response(['admin/%s/%s/change_list.html' % (app_label, cl.opts.object_name.lower()),
+ 'admin/%s/change_list.html' % app_label,
+ 'admin/change_list.html'], context_instance=c)
+change_list = staff_member_required(never_cache(change_list))
diff --git a/google_appengine/lib/django/django/contrib/admin/views/template.py b/google_appengine/lib/django/django/contrib/admin/views/template.py
new file mode 100755
index 0000000..a3b4538
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/admin/views/template.py
@@ -0,0 +1,72 @@
+from django.contrib.admin.views.decorators import staff_member_required
+from django.core import validators
+from django import template, oldforms
+from django.template import loader
+from django.shortcuts import render_to_response
+from django.contrib.sites.models import Site
+from django.conf import settings
+
+def template_validator(request):
+ """
+ Displays the template validator form, which finds and displays template
+ syntax errors.
+ """
+ # get a dict of {site_id : settings_module} for the validator
+ settings_modules = {}
+ for mod in settings.ADMIN_FOR:
+ settings_module = __import__(mod, {}, {}, [''])
+ settings_modules[settings_module.SITE_ID] = settings_module
+ manipulator = TemplateValidator(settings_modules)
+ new_data, errors = {}, {}
+ if request.POST:
+ new_data = request.POST.copy()
+ errors = manipulator.get_validation_errors(new_data)
+ if not errors:
+ request.user.message_set.create(message='The template is valid.')
+ return render_to_response('admin/template_validator.html', {
+ 'title': 'Template validator',
+ 'form': oldforms.FormWrapper(manipulator, new_data, errors),
+ }, context_instance=template.RequestContext(request))
+template_validator = staff_member_required(template_validator)
+
+class TemplateValidator(oldforms.Manipulator):
+ def __init__(self, settings_modules):
+ self.settings_modules = settings_modules
+ site_list = Site.objects.in_bulk(settings_modules.keys()).values()
+ self.fields = (
+ oldforms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
+ oldforms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
+ )
+
+ def isValidTemplate(self, field_data, all_data):
+ # get the settings module
+ # if the site isn't set, we don't raise an error since the site field will
+ try:
+ site_id = int(all_data.get('site', None))
+ except (ValueError, TypeError):
+ return
+ settings_module = self.settings_modules.get(site_id, None)
+ if settings_module is None:
+ return
+
+ # so that inheritance works in the site's context, register a new function
+ # for "extends" that uses the site's TEMPLATE_DIRS instead.
+ def new_do_extends(parser, token):
+ node = loader.do_extends(parser, token)
+ node.template_dirs = settings_module.TEMPLATE_DIRS
+ return node
+ register = template.Library()
+ register.tag('extends', new_do_extends)
+ template.builtins.append(register)
+
+ # Now validate the template using the new template dirs
+ # making sure to reset the extends function in any case.
+ error = None
+ try:
+ tmpl = loader.get_template_from_string(field_data)
+ tmpl.render(template.Context({}))
+ except template.TemplateSyntaxError, e:
+ error = e
+ template.builtins.remove(register)
+ if error:
+ raise validators.ValidationError, e.args
diff --git a/google_appengine/lib/django/django/contrib/auth/__init__.py b/google_appengine/lib/django/django/contrib/auth/__init__.py
new file mode 100755
index 0000000..dd3b815
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/__init__.py
@@ -0,0 +1,77 @@
+from django.core.exceptions import ImproperlyConfigured
+
+SESSION_KEY = '_auth_user_id'
+BACKEND_SESSION_KEY = '_auth_user_backend'
+LOGIN_URL = '/accounts/login/'
+REDIRECT_FIELD_NAME = 'next'
+
+def load_backend(path):
+ i = path.rfind('.')
+ module, attr = path[:i], path[i+1:]
+ try:
+ mod = __import__(module, {}, {}, [attr])
+ except ImportError, e:
+ raise ImproperlyConfigured, 'Error importing authentication backend %s: "%s"' % (module, e)
+ try:
+ cls = getattr(mod, attr)
+ except AttributeError:
+ raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr)
+ return cls()
+
+def get_backends():
+ from django.conf import settings
+ backends = []
+ for backend_path in settings.AUTHENTICATION_BACKENDS:
+ backends.append(load_backend(backend_path))
+ return backends
+
+def authenticate(**credentials):
+ """
+ If the given credentials are valid, return a User object.
+ """
+ for backend in get_backends():
+ try:
+ user = backend.authenticate(**credentials)
+ except TypeError:
+ # This backend doesn't accept these credentials as arguments. Try the next one.
+ continue
+ if user is None:
+ continue
+ # Annotate the user object with the path of the backend.
+ user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
+ return user
+
+def login(request, user):
+ """
+ Persist a user id and a backend in the request. This way a user doesn't
+ have to reauthenticate on every request.
+ """
+ if user is None:
+ user = request.user
+ # TODO: It would be nice to support different login methods, like signed cookies.
+ request.session[SESSION_KEY] = user.id
+ request.session[BACKEND_SESSION_KEY] = user.backend
+
+def logout(request):
+ """
+ Remove the authenticated user's ID from the request.
+ """
+ try:
+ del request.session[SESSION_KEY]
+ except KeyError:
+ pass
+ try:
+ del request.session[BACKEND_SESSION_KEY]
+ except KeyError:
+ pass
+
+def get_user(request):
+ from django.contrib.auth.models import AnonymousUser
+ try:
+ user_id = request.session[SESSION_KEY]
+ backend_path = request.session[BACKEND_SESSION_KEY]
+ backend = load_backend(backend_path)
+ user = backend.get_user(user_id) or AnonymousUser()
+ except KeyError:
+ user = AnonymousUser()
+ return user
diff --git a/google_appengine/lib/django/django/contrib/auth/backends.py b/google_appengine/lib/django/django/contrib/auth/backends.py
new file mode 100755
index 0000000..4b8efcc
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/backends.py
@@ -0,0 +1,21 @@
+from django.contrib.auth.models import User
+
+class ModelBackend:
+ """
+ Authenticate against django.contrib.auth.models.User
+ """
+ # TODO: Model, login attribute name and password attribute name should be
+ # configurable.
+ def authenticate(self, username=None, password=None):
+ try:
+ user = User.objects.get(username=username)
+ if user.check_password(password):
+ return user
+ except User.DoesNotExist:
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(pk=user_id)
+ except User.DoesNotExist:
+ return None
diff --git a/google_appengine/lib/django/django/contrib/auth/create_superuser.py b/google_appengine/lib/django/django/contrib/auth/create_superuser.py
new file mode 100755
index 0000000..2e93c35
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/create_superuser.py
@@ -0,0 +1,91 @@
+"""
+Helper function for creating superusers in the authentication system.
+
+If run from the command line, this module lets you create a superuser
+interactively.
+"""
+
+from django.core import validators
+from django.contrib.auth.models import User
+import getpass
+import os
+import sys
+
+def createsuperuser(username=None, email=None, password=None):
+ """
+ Helper function for creating a superuser from the command line. All
+ arguments are optional and will be prompted-for if invalid or not given.
+ """
+ try:
+ import pwd
+ except ImportError:
+ default_username = ''
+ else:
+ # Determine the current system user's username, to use as a default.
+ default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower()
+
+ # Determine whether the default username is taken, so we don't display
+ # it as an option.
+ if default_username:
+ try:
+ User.objects.get(username=default_username)
+ except User.DoesNotExist:
+ pass
+ else:
+ default_username = ''
+
+ try:
+ while 1:
+ if not username:
+ input_msg = 'Username'
+ if default_username:
+ input_msg += ' (Leave blank to use %r)' % default_username
+ username = raw_input(input_msg + ': ')
+ if default_username and username == '':
+ username = default_username
+ if not username.isalnum():
+ sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
+ username = None
+ continue
+ try:
+ User.objects.get(username=username)
+ except User.DoesNotExist:
+ break
+ else:
+ sys.stderr.write("Error: That username is already taken.\n")
+ username = None
+ while 1:
+ if not email:
+ email = raw_input('E-mail address: ')
+ try:
+ validators.isValidEmail(email, None)
+ except validators.ValidationError:
+ sys.stderr.write("Error: That e-mail address is invalid.\n")
+ email = None
+ else:
+ break
+ while 1:
+ if not password:
+ password = getpass.getpass()
+ password2 = getpass.getpass('Password (again): ')
+ if password != password2:
+ sys.stderr.write("Error: Your passwords didn't match.\n")
+ password = None
+ continue
+ if password.strip() == '':
+ sys.stderr.write("Error: Blank passwords aren't allowed.\n")
+ password = None
+ continue
+ break
+ except KeyboardInterrupt:
+ sys.stderr.write("\nOperation cancelled.\n")
+ sys.exit(1)
+ u = User.objects.create_user(username, email, password)
+ u.is_staff = True
+ u.is_active = True
+ u.is_superuser = True
+ u.save()
+ print "Superuser created successfully."
+
+if __name__ == "__main__":
+ createsuperuser()
diff --git a/google_appengine/lib/django/django/contrib/auth/decorators.py b/google_appengine/lib/django/django/contrib/auth/decorators.py
new file mode 100755
index 0000000..37e948f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/decorators.py
@@ -0,0 +1,36 @@
+from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
+from django.http import HttpResponseRedirect
+from urllib import quote
+
+def user_passes_test(test_func, login_url=LOGIN_URL):
+ """
+ Decorator for views that checks that the user passes the given test,
+ redirecting to the log-in page if necessary. The test should be a callable
+ that takes the user object and returns True if the user passes.
+ """
+ def _dec(view_func):
+ def _checklogin(request, *args, **kwargs):
+ if test_func(request.user):
+ return view_func(request, *args, **kwargs)
+ return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path())))
+ _checklogin.__doc__ = view_func.__doc__
+ _checklogin.__dict__ = view_func.__dict__
+
+ return _checklogin
+ return _dec
+
+login_required = user_passes_test(lambda u: u.is_authenticated())
+login_required.__doc__ = (
+ """
+ Decorator for views that checks that the user is logged in, redirecting
+ to the log-in page if necessary.
+ """
+ )
+
+def permission_required(perm, login_url=LOGIN_URL):
+ """
+ Decorator for views that checks whether a user has a particular permission
+ enabled, redirecting to the log-in page if necessary.
+ """
+ return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)
+
diff --git a/google_appengine/lib/django/django/contrib/auth/forms.py b/google_appengine/lib/django/django/contrib/auth/forms.py
new file mode 100755
index 0000000..023f9b4
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/forms.py
@@ -0,0 +1,144 @@
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate
+from django.contrib.sites.models import Site
+from django.template import Context, loader
+from django.core import validators
+from django import oldforms
+from django.utils.translation import gettext as _
+
+class UserCreationForm(oldforms.Manipulator):
+ "A form that creates a user, with no privileges, from the given username and password."
+ def __init__(self):
+ self.fields = (
+ oldforms.TextField(field_name='username', length=30, maxlength=30, is_required=True,
+ validator_list=[validators.isAlphaNumeric, self.isValidUsername]),
+ oldforms.PasswordField(field_name='password1', length=30, maxlength=60, is_required=True),
+ oldforms.PasswordField(field_name='password2', length=30, maxlength=60, is_required=True,
+ validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]),
+ )
+
+ def isValidUsername(self, field_data, all_data):
+ try:
+ User.objects.get(username=field_data)
+ except User.DoesNotExist:
+ return
+ raise validators.ValidationError, _('A user with that username already exists.')
+
+ def save(self, new_data):
+ "Creates the user."
+ return User.objects.create_user(new_data['username'], '', new_data['password1'])
+
+class AuthenticationForm(oldforms.Manipulator):
+ """
+ Base class for authenticating users. Extend this to get a form that accepts
+ username/password logins.
+ """
+ def __init__(self, request=None):
+ """
+ If request is passed in, the manipulator will validate that cookies are
+ enabled. Note that the request (a HttpRequest object) must have set a
+ cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
+ running this validator.
+ """
+ self.request = request
+ self.fields = [
+ oldforms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
+ validator_list=[self.isValidUser, self.hasCookiesEnabled]),
+ oldforms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True),
+ ]
+ self.user_cache = None
+
+ def hasCookiesEnabled(self, field_data, all_data):
+ if self.request and not self.request.session.test_cookie_worked():
+ raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
+
+ def isValidUser(self, field_data, all_data):
+ username = field_data
+ password = all_data.get('password', None)
+ self.user_cache = authenticate(username=username, password=password)
+ if self.user_cache is None:
+ raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
+ elif not self.user_cache.is_active:
+ raise validators.ValidationError, _("This account is inactive.")
+
+ def get_user_id(self):
+ if self.user_cache:
+ return self.user_cache.id
+ return None
+
+ def get_user(self):
+ return self.user_cache
+
+class PasswordResetForm(oldforms.Manipulator):
+ "A form that lets a user request a password reset"
+ def __init__(self):
+ self.fields = (
+ oldforms.EmailField(field_name="email", length=40, is_required=True,
+ validator_list=[self.isValidUserEmail]),
+ )
+
+ def isValidUserEmail(self, new_data, all_data):
+ "Validates that a user exists with the given e-mail address"
+ try:
+ self.user_cache = User.objects.get(email__iexact=new_data)
+ except User.DoesNotExist:
+ raise validators.ValidationError, _("That e-mail address doesn't have an associated user account. Are you sure you've registered?")
+
+ def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'):
+ "Calculates a new password randomly and sends it to the user"
+ from django.core.mail import send_mail
+ new_pass = User.objects.make_random_password()
+ self.user_cache.set_password(new_pass)
+ self.user_cache.save()
+ if not domain_override:
+ current_site = Site.objects.get_current()
+ site_name = current_site.name
+ domain = current_site.domain
+ else:
+ site_name = domain = domain_override
+ t = loader.get_template(email_template_name)
+ c = {
+ 'new_password': new_pass,
+ 'email': self.user_cache.email,
+ 'domain': domain,
+ 'site_name': site_name,
+ 'user': self.user_cache,
+ }
+ send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [self.user_cache.email])
+
+class PasswordChangeForm(oldforms.Manipulator):
+ "A form that lets a user change his password."
+ def __init__(self, user):
+ self.user = user
+ self.fields = (
+ oldforms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
+ validator_list=[self.isValidOldPassword]),
+ oldforms.PasswordField(field_name="new_password1", length=30, maxlength=30, is_required=True,
+ validator_list=[validators.AlwaysMatchesOtherField('new_password2', _("The two 'new password' fields didn't match."))]),
+ oldforms.PasswordField(field_name="new_password2", length=30, maxlength=30, is_required=True),
+ )
+
+ def isValidOldPassword(self, new_data, all_data):
+ "Validates that the old_password field is correct."
+ if not self.user.check_password(new_data):
+ raise validators.ValidationError, _("Your old password was entered incorrectly. Please enter it again.")
+
+ def save(self, new_data):
+ "Saves the new password."
+ self.user.set_password(new_data['new_password1'])
+ self.user.save()
+
+class AdminPasswordChangeForm(oldforms.Manipulator):
+ "A form used to change the password of a user in the admin interface."
+ def __init__(self, user):
+ self.user = user
+ self.fields = (
+ oldforms.PasswordField(field_name='password1', length=30, maxlength=60, is_required=True),
+ oldforms.PasswordField(field_name='password2', length=30, maxlength=60, is_required=True,
+ validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]),
+ )
+
+ def save(self, new_data):
+ "Saves the new password."
+ self.user.set_password(new_data['password1'])
+ self.user.save()
diff --git a/google_appengine/lib/django/django/contrib/auth/handlers/__init__.py b/google_appengine/lib/django/django/contrib/auth/handlers/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/handlers/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/auth/handlers/modpython.py b/google_appengine/lib/django/django/contrib/auth/handlers/modpython.py
new file mode 100755
index 0000000..c7d9213
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/handlers/modpython.py
@@ -0,0 +1,52 @@
+from mod_python import apache
+import os
+
+def authenhandler(req, **kwargs):
+ """
+ Authentication handler that checks against Django's auth database.
+ """
+
+ # mod_python fakes the environ, and thus doesn't process SetEnv. This fixes
+ # that so that the following import works
+ os.environ.update(req.subprocess_env)
+
+ # check for PythonOptions
+ _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
+
+ options = req.get_options()
+ permission_name = options.get('DjangoPermissionName', None)
+ staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on"))
+ superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
+ settings_module = options.get('DJANGO_SETTINGS_MODULE', None)
+ if settings_module:
+ os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
+
+ from django.contrib.auth.models import User
+ from django import db
+ db.reset_queries()
+
+ # check that the username is valid
+ kwargs = {'username': req.user, 'is_active': True}
+ if staff_only:
+ kwargs['is_staff'] = True
+ if superuser_only:
+ kwargs['is_superuser'] = True
+ try:
+ try:
+ user = User.objects.get(**kwargs)
+ except User.DoesNotExist:
+ return apache.HTTP_UNAUTHORIZED
+
+ # check the password and any permission given
+ if user.check_password(req.get_basic_auth_pw()):
+ if permission_name:
+ if user.has_perm(permission_name):
+ return apache.OK
+ else:
+ return apache.HTTP_UNAUTHORIZED
+ else:
+ return apache.OK
+ else:
+ return apache.HTTP_UNAUTHORIZED
+ finally:
+ db.connection.close()
diff --git a/google_appengine/lib/django/django/contrib/auth/management.py b/google_appengine/lib/django/django/contrib/auth/management.py
new file mode 100755
index 0000000..3f52681
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/management.py
@@ -0,0 +1,49 @@
+"""
+Creates permissions for all installed apps that need permissions.
+"""
+
+from django.dispatch import dispatcher
+from django.db.models import get_models, signals
+from django.contrib.auth import models as auth_app
+
+def _get_permission_codename(action, opts):
+ return '%s_%s' % (action, opts.object_name.lower())
+
+def _get_all_permissions(opts):
+ "Returns (codename, name) for all permissions in the given opts."
+ perms = []
+ for action in ('add', 'change', 'delete'):
+ perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name)))
+ return perms + list(opts.permissions)
+
+def create_permissions(app, created_models, verbosity):
+ from django.contrib.contenttypes.models import ContentType
+ from django.contrib.auth.models import Permission
+ app_models = get_models(app)
+ if not app_models:
+ return
+ for klass in app_models:
+ ctype = ContentType.objects.get_for_model(klass)
+ for codename, name in _get_all_permissions(klass._meta):
+ p, created = Permission.objects.get_or_create(codename=codename, content_type__pk=ctype.id,
+ defaults={'name': name, 'content_type': ctype})
+ if created and verbosity >= 2:
+ print "Adding permission '%s'" % p
+
+def create_superuser(app, created_models, verbosity, **kwargs):
+ from django.contrib.auth.models import User
+ from django.contrib.auth.create_superuser import createsuperuser as do_create
+ if User in created_models and kwargs.get('interactive', True):
+ msg = "\nYou just installed Django's auth system, which means you don't have " \
+ "any superusers defined.\nWould you like to create one now? (yes/no): "
+ confirm = raw_input(msg)
+ while 1:
+ if confirm not in ('yes', 'no'):
+ confirm = raw_input('Please enter either "yes" or "no": ')
+ continue
+ if confirm == 'yes':
+ do_create()
+ break
+
+dispatcher.connect(create_permissions, signal=signals.post_syncdb)
+dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)
diff --git a/google_appengine/lib/django/django/contrib/auth/middleware.py b/google_appengine/lib/django/django/contrib/auth/middleware.py
new file mode 100755
index 0000000..42dc15a
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/middleware.py
@@ -0,0 +1,12 @@
+class LazyUser(object):
+ def __get__(self, request, obj_type=None):
+ if not hasattr(request, '_cached_user'):
+ from django.contrib.auth import get_user
+ request._cached_user = get_user(request)
+ return request._cached_user
+
+class AuthenticationMiddleware(object):
+ def process_request(self, request):
+ assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+ request.__class__.user = LazyUser()
+ return None
diff --git a/google_appengine/lib/django/django/contrib/auth/models.py b/google_appengine/lib/django/django/contrib/auth/models.py
new file mode 100755
index 0000000..4f4f0b7
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/models.py
@@ -0,0 +1,315 @@
+from django.core import validators
+from django.core.exceptions import ImproperlyConfigured
+from django.db import backend, connection, models
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import gettext_lazy as _
+import datetime
+
+def check_password(raw_password, enc_password):
+ """
+ Returns a boolean of whether the raw_password was correct. Handles
+ encryption formats behind the scenes.
+ """
+ algo, salt, hsh = enc_password.split('$')
+ if algo == 'md5':
+ import md5
+ return hsh == md5.new(salt+raw_password).hexdigest()
+ elif algo == 'sha1':
+ import sha
+ return hsh == sha.new(salt+raw_password).hexdigest()
+ raise ValueError, "Got unknown password algorithm type in password."
+
+class SiteProfileNotAvailable(Exception):
+ pass
+
+class Permission(models.Model):
+ """The permissions system provides a way to assign permissions to specific users and groups of users.
+
+ The permission system is used by the Django admin site, but may also be useful in your own code. The Django admin site uses permissions as follows:
+
+ - The "add" permission limits the user's ability to view the "add" form and add an object.
+ - The "change" permission limits a user's ability to view the change list, view the "change" form and change an object.
+ - The "delete" permission limits the ability to delete an object.
+
+ Permissions are set globally per type of object, not per specific object instance. It is possible to say "Mary may change news stories," but it's not currently possible to say "Mary may change news stories, but only the ones she created herself" or "Mary may only change news stories that have a certain status or publication date."
+
+ Three basic permissions -- add, change and delete -- are automatically created for each Django model.
+ """
+ name = models.CharField(_('name'), maxlength=50)
+ content_type = models.ForeignKey(ContentType)
+ codename = models.CharField(_('codename'), maxlength=100)
+ class Meta:
+ verbose_name = _('permission')
+ verbose_name_plural = _('permissions')
+ unique_together = (('content_type', 'codename'),)
+ ordering = ('content_type', 'codename')
+
+ def __str__(self):
+ return "%s | %s" % (self.content_type, self.name)
+
+class Group(models.Model):
+ """Groups are a generic way of categorizing users to apply permissions, or some other label, to those users. A user can belong to any number of groups.
+
+ A user in a group automatically has all the permissions granted to that group. For example, if the group Site editors has the permission can_edit_home_page, any user in that group will have that permission.
+
+ Beyond permissions, groups are a convenient way to categorize users to apply some label, or extended functionality, to them. For example, you could create a group 'Special users', and you could write code that would do special things to those users -- such as giving them access to a members-only portion of your site, or sending them members-only e-mail messages.
+ """
+ name = models.CharField(_('name'), maxlength=80, unique=True)
+ permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True, filter_interface=models.HORIZONTAL)
+ class Meta:
+ verbose_name = _('group')
+ verbose_name_plural = _('groups')
+ ordering = ('name',)
+ class Admin:
+ search_fields = ('name',)
+
+ def __str__(self):
+ return self.name
+
+class UserManager(models.Manager):
+ def create_user(self, username, email, password):
+ "Creates and saves a User with the given username, e-mail and password."
+ now = datetime.datetime.now()
+ user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
+ user.set_password(password)
+ user.save()
+ return user
+
+ def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
+ "Generates a random password with the given length and given allowed_chars"
+ # Note that default value of allowed_chars does not have "I" or letters
+ # that look like it -- just to avoid confusion.
+ from random import choice
+ return ''.join([choice(allowed_chars) for i in range(length)])
+
+class User(models.Model):
+ """Users within the Django authentication system are represented by this model.
+
+ Username and password are required. Other fields are optional.
+ """
+ username = models.CharField(_('username'), maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric], help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
+ first_name = models.CharField(_('first name'), maxlength=30, blank=True)
+ last_name = models.CharField(_('last name'), maxlength=30, blank=True)
+ email = models.EmailField(_('e-mail address'), blank=True)
+ password = models.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
+ is_staff = models.BooleanField(_('staff status'), default=False, help_text=_("Designates whether the user can log into this admin site."))
+ is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts."))
+ is_superuser = models.BooleanField(_('superuser status'), default=False, help_text=_("Designates that this user has all permissions without explicitly assigning them."))
+ last_login = models.DateTimeField(_('last login'), default=models.LazyDate())
+ date_joined = models.DateTimeField(_('date joined'), default=models.LazyDate())
+ groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True,
+ help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."))
+ user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True, filter_interface=models.HORIZONTAL)
+ objects = UserManager()
+ class Meta:
+ verbose_name = _('user')
+ verbose_name_plural = _('users')
+ ordering = ('username',)
+ class Admin:
+ fields = (
+ (None, {'fields': ('username', 'password')}),
+ (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
+ (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
+ (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
+ (_('Groups'), {'fields': ('groups',)}),
+ )
+ list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
+ list_filter = ('is_staff', 'is_superuser')
+ search_fields = ('username', 'first_name', 'last_name', 'email')
+
+ def __str__(self):
+ return self.username
+
+ def get_absolute_url(self):
+ return "/users/%s/" % self.username
+
+ def is_anonymous(self):
+ "Always returns False. This is a way of comparing User objects to anonymous users."
+ return False
+
+ def is_authenticated(self):
+ """Always return True. This is a way to tell if the user has been authenticated in templates.
+ """
+ return True
+
+ def get_full_name(self):
+ "Returns the first_name plus the last_name, with a space in between."
+ full_name = '%s %s' % (self.first_name, self.last_name)
+ return full_name.strip()
+
+ def set_password(self, raw_password):
+ import sha, random
+ algo = 'sha1'
+ salt = sha.new(str(random.random())).hexdigest()[:5]
+ hsh = sha.new(salt+raw_password).hexdigest()
+ self.password = '%s$%s$%s' % (algo, salt, hsh)
+
+ def check_password(self, raw_password):
+ """
+ Returns a boolean of whether the raw_password was correct. Handles
+ encryption formats behind the scenes.
+ """
+ # Backwards-compatibility check. Older passwords won't include the
+ # algorithm or salt.
+ if '$' not in self.password:
+ import md5
+ is_correct = (self.password == md5.new(raw_password).hexdigest())
+ if is_correct:
+ # Convert the password to the new, more secure format.
+ self.set_password(raw_password)
+ self.save()
+ return is_correct
+ return check_password(raw_password, self.password)
+
+ def get_group_permissions(self):
+ "Returns a list of permission strings that this user has through his/her groups."
+ if not hasattr(self, '_group_perm_cache'):
+ import sets
+ cursor = connection.cursor()
+ # The SQL below works out to the following, after DB quoting:
+ # cursor.execute("""
+ # SELECT ct."app_label", p."codename"
+ # FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct
+ # WHERE p."id" = gp."permission_id"
+ # AND gp."group_id" = ug."group_id"
+ # AND ct."id" = p."content_type_id"
+ # AND ug."user_id" = %s, [self.id])
+ sql = """
+ SELECT ct.%s, p.%s
+ FROM %s p, %s gp, %s ug, %s ct
+ WHERE p.%s = gp.%s
+ AND gp.%s = ug.%s
+ AND ct.%s = p.%s
+ AND ug.%s = %%s""" % (
+ backend.quote_name('app_label'), backend.quote_name('codename'),
+ backend.quote_name('auth_permission'), backend.quote_name('auth_group_permissions'),
+ backend.quote_name('auth_user_groups'), backend.quote_name('django_content_type'),
+ backend.quote_name('id'), backend.quote_name('permission_id'),
+ backend.quote_name('group_id'), backend.quote_name('group_id'),
+ backend.quote_name('id'), backend.quote_name('content_type_id'),
+ backend.quote_name('user_id'),)
+ cursor.execute(sql, [self.id])
+ self._group_perm_cache = sets.Set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
+ return self._group_perm_cache
+
+ def get_all_permissions(self):
+ if not hasattr(self, '_perm_cache'):
+ import sets
+ self._perm_cache = sets.Set(["%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.select_related()])
+ self._perm_cache.update(self.get_group_permissions())
+ return self._perm_cache
+
+ def has_perm(self, perm):
+ "Returns True if the user has the specified permission."
+ if not self.is_active:
+ return False
+ if self.is_superuser:
+ return True
+ return perm in self.get_all_permissions()
+
+ def has_perms(self, perm_list):
+ "Returns True if the user has each of the specified permissions."
+ for perm in perm_list:
+ if not self.has_perm(perm):
+ return False
+ return True
+
+ def has_module_perms(self, app_label):
+ "Returns True if the user has any permissions in the given app label."
+ if not self.is_active:
+ return False
+ if self.is_superuser:
+ return True
+ return bool(len([p for p in self.get_all_permissions() if p[:p.index('.')] == app_label]))
+
+ def get_and_delete_messages(self):
+ messages = []
+ for m in self.message_set.all():
+ messages.append(m.message)
+ m.delete()
+ return messages
+
+ def email_user(self, subject, message, from_email=None):
+ "Sends an e-mail to this User."
+ from django.core.mail import send_mail
+ send_mail(subject, message, from_email, [self.email])
+
+ def get_profile(self):
+ """
+ Returns site-specific profile for this user. Raises
+ SiteProfileNotAvailable if this site does not allow profiles.
+ """
+ if not hasattr(self, '_profile_cache'):
+ from django.conf import settings
+ if not settings.AUTH_PROFILE_MODULE:
+ raise SiteProfileNotAvailable
+ try:
+ app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
+ model = models.get_model(app_label, model_name)
+ self._profile_cache = model._default_manager.get(user__id__exact=self.id)
+ except (ImportError, ImproperlyConfigured):
+ raise SiteProfileNotAvailable
+ return self._profile_cache
+
+class Message(models.Model):
+ """The message system is a lightweight way to queue messages for given users. A message is associated with a User instance (so it is only applicable for registered users). There's no concept of expiration or timestamps. Messages are created by the Django admin after successful actions. For example, "The poll Foo was created successfully." is a message.
+ """
+ user = models.ForeignKey(User)
+ message = models.TextField(_('message'))
+
+ def __str__(self):
+ return self.message
+
+class AnonymousUser(object):
+ id = None
+ username = ''
+
+ def __init__(self):
+ pass
+
+ def __str__(self):
+ return 'AnonymousUser'
+
+ def __eq__(self, other):
+ return isinstance(other, self.__class__)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return 1 # instances always return the same hash value
+
+ def save(self):
+ raise NotImplementedError
+
+ def delete(self):
+ raise NotImplementedError
+
+ def set_password(self, raw_password):
+ raise NotImplementedError
+
+ def check_password(self, raw_password):
+ raise NotImplementedError
+
+ def _get_groups(self):
+ raise NotImplementedError
+ groups = property(_get_groups)
+
+ def _get_user_permissions(self):
+ raise NotImplementedError
+ user_permissions = property(_get_user_permissions)
+
+ def has_perm(self, perm):
+ return False
+
+ def has_module_perms(self, module):
+ return False
+
+ def get_and_delete_messages(self):
+ return []
+
+ def is_anonymous(self):
+ return True
+
+ def is_authenticated(self):
+ return False
diff --git a/google_appengine/lib/django/django/contrib/auth/views.py b/google_appengine/lib/django/django/contrib/auth/views.py
new file mode 100755
index 0000000..fda17b9
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/auth/views.py
@@ -0,0 +1,85 @@
+from django.contrib.auth.forms import AuthenticationForm
+from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm
+from django import oldforms
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.sites.models import Site
+from django.http import HttpResponseRedirect
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
+
+def login(request, template_name='registration/login.html'):
+ "Displays the login form and handles the login action."
+ manipulator = AuthenticationForm(request)
+ redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '')
+ if request.POST:
+ errors = manipulator.get_validation_errors(request.POST)
+ if not errors:
+ # Light security check -- make sure redirect_to isn't garbage.
+ if not redirect_to or '://' in redirect_to or ' ' in redirect_to:
+ redirect_to = '/accounts/profile/'
+ from django.contrib.auth import login
+ login(request, manipulator.get_user())
+ request.session.delete_test_cookie()
+ return HttpResponseRedirect(redirect_to)
+ else:
+ errors = {}
+ request.session.set_test_cookie()
+ return render_to_response(template_name, {
+ 'form': oldforms.FormWrapper(manipulator, request.POST, errors),
+ REDIRECT_FIELD_NAME: redirect_to,
+ 'site_name': Site.objects.get_current().name,
+ }, context_instance=RequestContext(request))
+
+def logout(request, next_page=None, template_name='registration/logged_out.html'):
+ "Logs out the user and displays 'You are logged out' message."
+ from django.contrib.auth import logout
+ logout(request)
+ if next_page is None:
+ return render_to_response(template_name, {'title': _('Logged out')}, context_instance=RequestContext(request))
+ else:
+ # Redirect to this page until the session has been cleared.
+ return HttpResponseRedirect(next_page or request.path)
+
+def logout_then_login(request, login_url=LOGIN_URL):
+ "Logs out the user if he is logged in. Then redirects to the log-in page."
+ return logout(request, login_url)
+
+def redirect_to_login(next, login_url=LOGIN_URL):
+ "Redirects the user to the login page, passing the given 'next' page"
+ return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, next))
+
+def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
+ email_template_name='registration/password_reset_email.html'):
+ new_data, errors = {}, {}
+ form = PasswordResetForm()
+ if request.POST:
+ new_data = request.POST.copy()
+ errors = form.get_validation_errors(new_data)
+ if not errors:
+ if is_admin_site:
+ form.save(domain_override=request.META['HTTP_HOST'])
+ else:
+ form.save(email_template_name=email_template_name)
+ return HttpResponseRedirect('%sdone/' % request.path)
+ return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
+ context_instance=RequestContext(request))
+
+def password_reset_done(request, template_name='registration/password_reset_done.html'):
+ return render_to_response(template_name, context_instance=RequestContext(request))
+
+def password_change(request, template_name='registration/password_change_form.html'):
+ new_data, errors = {}, {}
+ form = PasswordChangeForm(request.user)
+ if request.POST:
+ new_data = request.POST.copy()
+ errors = form.get_validation_errors(new_data)
+ if not errors:
+ form.save(new_data)
+ return HttpResponseRedirect('%sdone/' % request.path)
+ return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
+ context_instance=RequestContext(request))
+password_change = login_required(password_change)
+
+def password_change_done(request, template_name='registration/password_change_done.html'):
+ return render_to_response(template_name, context_instance=RequestContext(request))
diff --git a/google_appengine/lib/django/django/contrib/comments/__init__.py b/google_appengine/lib/django/django/contrib/comments/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/comments/feeds.py b/google_appengine/lib/django/django/contrib/comments/feeds.py
new file mode 100755
index 0000000..34cf3d9
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/feeds.py
@@ -0,0 +1,41 @@
+from django.conf import settings
+from django.contrib.comments.models import Comment, FreeComment
+from django.contrib.syndication.feeds import Feed
+from django.contrib.sites.models import Site
+
+class LatestFreeCommentsFeed(Feed):
+ "Feed of latest comments on the current site."
+
+ comments_class = FreeComment
+
+ def title(self):
+ if not hasattr(self, '_site'):
+ self._site = Site.objects.get_current()
+ return "%s comments" % self._site.name
+
+ def link(self):
+ if not hasattr(self, '_site'):
+ self._site = Site.objects.get_current()
+ return "http://%s/" % (self._site.domain)
+
+ def description(self):
+ if not hasattr(self, '_site'):
+ self._site = Site.objects.get_current()
+ return "Latest comments on %s" % self._site.name
+
+ def items(self):
+ return self.comments_class.objects.filter(site__pk=settings.SITE_ID, is_public=True)[:40]
+
+class LatestCommentsFeed(LatestFreeCommentsFeed):
+ """Feed of latest free comments on the current site"""
+
+ comments_class = Comment
+
+ def items(self):
+ qs = LatestFreeCommentsFeed.items(self)
+ qs = qs.filter(is_removed=False)
+ if settings.COMMENTS_BANNED_USERS_GROUP:
+ where = ['user_id NOT IN (SELECT user_id FROM auth_users_group WHERE group_id = %s)']
+ params = [settings.COMMENTS_BANNED_USERS_GROUP]
+ qs = qs.extra(where=where, params=params)
+ return qs
diff --git a/google_appengine/lib/django/django/contrib/comments/models.py b/google_appengine/lib/django/django/contrib/comments/models.py
new file mode 100755
index 0000000..90a84ba
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/models.py
@@ -0,0 +1,285 @@
+from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.sites.models import Site
+from django.contrib.auth.models import User
+from django.utils.translation import gettext_lazy as _
+from django.conf import settings
+import datetime
+
+MIN_PHOTO_DIMENSION = 5
+MAX_PHOTO_DIMENSION = 1000
+
+# option codes for comment-form hidden fields
+PHOTOS_REQUIRED = 'pr'
+PHOTOS_OPTIONAL = 'pa'
+RATINGS_REQUIRED = 'rr'
+RATINGS_OPTIONAL = 'ra'
+IS_PUBLIC = 'ip'
+
+# what users get if they don't have any karma
+DEFAULT_KARMA = 5
+KARMA_NEEDED_BEFORE_DISPLAYED = 3
+
+class CommentManager(models.Manager):
+ def get_security_hash(self, options, photo_options, rating_options, target):
+ """
+ Returns the MD5 hash of the given options (a comma-separated string such as
+ 'pa,ra') and target (something like 'lcom.eventtimes:5157'). Used to
+ validate that submitted form options have not been tampered-with.
+ """
+ import md5
+ return md5.new(options + photo_options + rating_options + target + settings.SECRET_KEY).hexdigest()
+
+ def get_rating_options(self, rating_string):
+ """
+ Given a rating_string, this returns a tuple of (rating_range, options).
+ >>> s = "scale:1-10|First_category|Second_category"
+ >>> Comment.objects.get_rating_options(s)
+ ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
+ """
+ rating_range, options = rating_string.split('|', 1)
+ rating_range = range(int(rating_range[6:].split('-')[0]), int(rating_range[6:].split('-')[1])+1)
+ choices = [c.replace('_', ' ') for c in options.split('|')]
+ return rating_range, choices
+
+ def get_list_with_karma(self, **kwargs):
+ """
+ Returns a list of Comment objects matching the given lookup terms, with
+ _karma_total_good and _karma_total_bad filled.
+ """
+ extra_kwargs = {}
+ extra_kwargs.setdefault('select', {})
+ extra_kwargs['select']['_karma_total_good'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=1'
+ extra_kwargs['select']['_karma_total_bad'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=-1'
+ return self.filter(**kwargs).extra(**extra_kwargs)
+
+ def user_is_moderator(self, user):
+ if user.is_superuser:
+ return True
+ for g in user.groups.all():
+ if g.id == settings.COMMENTS_MODERATORS_GROUP:
+ return True
+ return False
+
+class Comment(models.Model):
+ user = models.ForeignKey(User, raw_id_admin=True)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.IntegerField(_('object ID'))
+ headline = models.CharField(_('headline'), maxlength=255, blank=True)
+ comment = models.TextField(_('comment'), maxlength=3000)
+ rating1 = models.PositiveSmallIntegerField(_('rating #1'), blank=True, null=True)
+ rating2 = models.PositiveSmallIntegerField(_('rating #2'), blank=True, null=True)
+ rating3 = models.PositiveSmallIntegerField(_('rating #3'), blank=True, null=True)
+ rating4 = models.PositiveSmallIntegerField(_('rating #4'), blank=True, null=True)
+ rating5 = models.PositiveSmallIntegerField(_('rating #5'), blank=True, null=True)
+ rating6 = models.PositiveSmallIntegerField(_('rating #6'), blank=True, null=True)
+ rating7 = models.PositiveSmallIntegerField(_('rating #7'), blank=True, null=True)
+ rating8 = models.PositiveSmallIntegerField(_('rating #8'), blank=True, null=True)
+ # This field designates whether to use this row's ratings in aggregate
+ # functions (summaries). We need this because people are allowed to post
+ # multiple reviews on the same thing, but the system will only use the
+ # latest one (with valid_rating=True) in tallying the reviews.
+ valid_rating = models.BooleanField(_('is valid rating'))
+ submit_date = models.DateTimeField(_('date/time submitted'), auto_now_add=True)
+ is_public = models.BooleanField(_('is public'))
+ ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
+ is_removed = models.BooleanField(_('is removed'), help_text=_('Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.'))
+ site = models.ForeignKey(Site)
+ objects = CommentManager()
+ class Meta:
+ verbose_name = _('comment')
+ verbose_name_plural = _('comments')
+ ordering = ('-submit_date',)
+ class Admin:
+ fields = (
+ (None, {'fields': ('content_type', 'object_id', 'site')}),
+ ('Content', {'fields': ('user', 'headline', 'comment')}),
+ ('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
+ ('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
+ )
+ list_display = ('user', 'submit_date', 'content_type', 'get_content_object')
+ list_filter = ('submit_date',)
+ date_hierarchy = 'submit_date'
+ search_fields = ('comment', 'user__username')
+
+ def __repr__(self):
+ return "%s: %s..." % (self.user.username, self.comment[:100])
+
+ def get_absolute_url(self):
+ return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
+
+ def get_crossdomain_url(self):
+ return "/r/%d/%d/" % (self.content_type_id, self.object_id)
+
+ def get_flag_url(self):
+ return "/comments/flag/%s/" % self.id
+
+ def get_deletion_url(self):
+ return "/comments/delete/%s/" % self.id
+
+ def get_content_object(self):
+ """
+ Returns the object that this comment is a comment on. Returns None if
+ the object no longer exists.
+ """
+ from django.core.exceptions import ObjectDoesNotExist
+ try:
+ return self.content_type.get_object_for_this_type(pk=self.object_id)
+ except ObjectDoesNotExist:
+ return None
+
+ get_content_object.short_description = _('Content object')
+
+ def _fill_karma_cache(self):
+ "Helper function that populates good/bad karma caches"
+ good, bad = 0, 0
+ for k in self.karmascore_set:
+ if k.score == -1:
+ bad +=1
+ elif k.score == 1:
+ good +=1
+ self._karma_total_good, self._karma_total_bad = good, bad
+
+ def get_good_karma_total(self):
+ if not hasattr(self, "_karma_total_good"):
+ self._fill_karma_cache()
+ return self._karma_total_good
+
+ def get_bad_karma_total(self):
+ if not hasattr(self, "_karma_total_bad"):
+ self._fill_karma_cache()
+ return self._karma_total_bad
+
+ def get_karma_total(self):
+ if not hasattr(self, "_karma_total_good") or not hasattr(self, "_karma_total_bad"):
+ self._fill_karma_cache()
+ return self._karma_total_good + self._karma_total_bad
+
+ def get_as_text(self):
+ return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % \
+ {'user': self.user.username, 'date': self.submit_date,
+ 'comment': self.comment, 'domain': self.site.domain, 'url': self.get_absolute_url()}
+
+class FreeComment(models.Model):
+ # A FreeComment is a comment by a non-registered user.
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.IntegerField(_('object ID'))
+ comment = models.TextField(_('comment'), maxlength=3000)
+ person_name = models.CharField(_("person's name"), maxlength=50)
+ submit_date = models.DateTimeField(_('date/time submitted'), auto_now_add=True)
+ is_public = models.BooleanField(_('is public'))
+ ip_address = models.IPAddressField(_('ip address'))
+ # TODO: Change this to is_removed, like Comment
+ approved = models.BooleanField(_('approved by staff'))
+ site = models.ForeignKey(Site)
+ class Meta:
+ verbose_name = _('free comment')
+ verbose_name_plural = _('free comments')
+ ordering = ('-submit_date',)
+ class Admin:
+ fields = (
+ (None, {'fields': ('content_type', 'object_id', 'site')}),
+ ('Content', {'fields': ('person_name', 'comment')}),
+ ('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
+ )
+ list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object')
+ list_filter = ('submit_date',)
+ date_hierarchy = 'submit_date'
+ search_fields = ('comment', 'person_name')
+
+ def __repr__(self):
+ return "%s: %s..." % (self.person_name, self.comment[:100])
+
+ def get_absolute_url(self):
+ return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
+
+ def get_content_object(self):
+ """
+ Returns the object that this comment is a comment on. Returns None if
+ the object no longer exists.
+ """
+ from django.core.exceptions import ObjectDoesNotExist
+ try:
+ return self.content_type.get_object_for_this_type(pk=self.object_id)
+ except ObjectDoesNotExist:
+ return None
+
+ get_content_object.short_description = _('Content object')
+
+class KarmaScoreManager(models.Manager):
+ def vote(self, user_id, comment_id, score):
+ try:
+ karma = self.objects.get(comment__pk=comment_id, user__pk=user_id)
+ except self.model.DoesNotExist:
+ karma = self.model(None, user_id=user_id, comment_id=comment_id, score=score, scored_date=datetime.datetime.now())
+ karma.save()
+ else:
+ karma.score = score
+ karma.scored_date = datetime.datetime.now()
+ karma.save()
+
+ def get_pretty_score(self, score):
+ """
+ Given a score between -1 and 1 (inclusive), returns the same score on a
+ scale between 1 and 10 (inclusive), as an integer.
+ """
+ if score is None:
+ return DEFAULT_KARMA
+ return int(round((4.5 * score) + 5.5))
+
+class KarmaScore(models.Model):
+ user = models.ForeignKey(User)
+ comment = models.ForeignKey(Comment)
+ score = models.SmallIntegerField(_('score'), db_index=True)
+ scored_date = models.DateTimeField(_('score date'), auto_now=True)
+ objects = KarmaScoreManager()
+ class Meta:
+ verbose_name = _('karma score')
+ verbose_name_plural = _('karma scores')
+ unique_together = (('user', 'comment'),)
+
+ def __repr__(self):
+ return _("%(score)d rating by %(user)s") % {'score': self.score, 'user': self.user}
+
+class UserFlagManager(models.Manager):
+ def flag(self, comment, user):
+ """
+ Flags the given comment by the given user. If the comment has already
+ been flagged by the user, or it was a comment posted by the user,
+ nothing happens.
+ """
+ if int(comment.user_id) == int(user.id):
+ return # A user can't flag his own comment. Fail silently.
+ try:
+ f = self.objects.get(user__pk=user.id, comment__pk=comment.id)
+ except self.model.DoesNotExist:
+ from django.core.mail import mail_managers
+ f = self.model(None, user.id, comment.id, None)
+ message = _('This comment was flagged by %(user)s:\n\n%(text)s') % {'user': user.username, 'text': comment.get_as_text()}
+ mail_managers('Comment flagged', message, fail_silently=True)
+ f.save()
+
+class UserFlag(models.Model):
+ user = models.ForeignKey(User)
+ comment = models.ForeignKey(Comment)
+ flag_date = models.DateTimeField(_('flag date'), auto_now_add=True)
+ objects = UserFlagManager()
+ class Meta:
+ verbose_name = _('user flag')
+ verbose_name_plural = _('user flags')
+ unique_together = (('user', 'comment'),)
+
+ def __repr__(self):
+ return _("Flag by %r") % self.user
+
+class ModeratorDeletion(models.Model):
+ user = models.ForeignKey(User, verbose_name='moderator')
+ comment = models.ForeignKey(Comment)
+ deletion_date = models.DateTimeField(_('deletion date'), auto_now_add=True)
+ class Meta:
+ verbose_name = _('moderator deletion')
+ verbose_name_plural = _('moderator deletions')
+ unique_together = (('user', 'comment'),)
+
+ def __repr__(self):
+ return _("Moderator deletion by %r") % self.user
diff --git a/google_appengine/lib/django/django/contrib/comments/templates/comments/form.html b/google_appengine/lib/django/django/contrib/comments/templates/comments/form.html
new file mode 100644
index 0000000..c5aa768
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/templates/comments/form.html
@@ -0,0 +1,38 @@
+{% load i18n %}
+{% if display_form %}
+<form {% if photos_optional or photos_required %}enctype="multipart/form-data" {% endif %}action="/comments/post/" method="post">
+
+{% if user.is_authenticated %}
+<p>{% trans "Username:" %} <strong>{{ user.username }}</strong> (<a href="/accounts/logout/">{% trans "Log out" %}</a>)</p>
+{% else %}
+<p><label for="id_username">{% trans "Username:" %}</label> <input type="text" name="username" id="id_username" /><br />{% trans "Password:" %} <input type="password" name="password" id="id_password" /> (<a href="/accounts/password_reset/">{% trans "Forgotten your password?" %}</a>)</p>
+{% endif %}
+
+{% if ratings_optional or ratings_required %}
+<p>{% trans "Ratings" %} ({% if ratings_required %}{% trans "Required" %}{% else %}{% trans "Optional" %}{% endif %}):</p>
+<table>
+<tr><th>&nbsp;</th>{% for value in rating_range %}<th>{{ value }}</th>{% endfor %}</tr>
+{% for rating in rating_choices %}
+<tr><th>{{ rating }}</th>{% for value in rating_range %}<th><input type="radio" name="rating{{ forloop.parentloop.counter }}" value="{{ value }}" /></th>{% endfor %}</tr>
+{% endfor %}
+</table>
+<input type="hidden" name="rating_options" value="{{ rating_options }}" />
+{% endif %}
+
+{% if photos_optional or photos_required %}
+<p><label for="id_photo">{% trans "Post a photo" %}</label> ({% if photos_required %}{% trans "Required" %}{% else %}{% trans "Optional" %}{% endif %}):
+<input type="file" name="photo" id="id_photo" /></p>
+<input type="hidden" name="photo_options" value="{{ photo_options }}" />
+{% endif %}
+
+<p><label for="id_comment">{% trans "Comment:" %}</label><br />
+<textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p>
+
+<p>
+<input type="hidden" name="options" value="{{ options }}" />
+<input type="hidden" name="target" value="{{ target }}" />
+<input type="hidden" name="gonzo" value="{{ hash }}" />
+<input type="submit" name="preview" value="{% trans "Preview comment" %}" />
+</p>
+</form>
+{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/comments/templates/comments/freeform.html b/google_appengine/lib/django/django/contrib/comments/templates/comments/freeform.html
new file mode 100644
index 0000000..f0d00b9
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/templates/comments/freeform.html
@@ -0,0 +1,13 @@
+{% load i18n %}
+{% if display_form %}
+<form action="/comments/postfree/" method="post">
+<p><label for="id_person_name">{% trans "Your name:" %}</label> <input type="text" id="id_person_name" name="person_name" /></p>
+<p><label for="id_comment">{% trans "Comment:" %}</label><br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p>
+<p>
+<input type="hidden" name="options" value="{{ options }}" />
+<input type="hidden" name="target" value="{{ target }}" />
+<input type="hidden" name="gonzo" value="{{ hash }}" />
+<input type="submit" name="preview" value="{% trans "Preview comment" %}" />
+</p>
+</form>
+{% endif %}
diff --git a/google_appengine/lib/django/django/contrib/comments/templatetags/__init__.py b/google_appengine/lib/django/django/contrib/comments/templatetags/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/templatetags/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/comments/templatetags/comments.py b/google_appengine/lib/django/django/contrib/comments/templatetags/comments.py
new file mode 100755
index 0000000..c3a2fd4
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/templatetags/comments.py
@@ -0,0 +1,322 @@
+from django.contrib.comments.models import Comment, FreeComment
+from django.contrib.comments.models import PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
+from django.contrib.comments.models import MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION
+from django import template
+from django.template import loader
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.contenttypes.models import ContentType
+import re
+
+register = template.Library()
+
+COMMENT_FORM = 'comments/form.html'
+FREE_COMMENT_FORM = 'comments/freeform.html'
+
+class CommentFormNode(template.Node):
+ def __init__(self, content_type, obj_id_lookup_var, obj_id, free,
+ photos_optional=False, photos_required=False, photo_options='',
+ ratings_optional=False, ratings_required=False, rating_options='',
+ is_public=True):
+ self.content_type = content_type
+ self.obj_id_lookup_var, self.obj_id, self.free = obj_id_lookup_var, obj_id, free
+ self.photos_optional, self.photos_required = photos_optional, photos_required
+ self.ratings_optional, self.ratings_required = ratings_optional, ratings_required
+ self.photo_options, self.rating_options = photo_options, rating_options
+ self.is_public = is_public
+
+ def render(self, context):
+ from django.utils.text import normalize_newlines
+ import base64
+ context.push()
+ if self.obj_id_lookup_var is not None:
+ try:
+ self.obj_id = template.resolve_variable(self.obj_id_lookup_var, context)
+ except template.VariableDoesNotExist:
+ return ''
+ # Validate that this object ID is valid for this content-type.
+ # We only have to do this validation if obj_id_lookup_var is provided,
+ # because do_comment_form() validates hard-coded object IDs.
+ try:
+ self.content_type.get_object_for_this_type(pk=self.obj_id)
+ except ObjectDoesNotExist:
+ context['display_form'] = False
+ else:
+ context['display_form'] = True
+ else:
+ context['display_form'] = True
+ context['target'] = '%s:%s' % (self.content_type.id, self.obj_id)
+ options = []
+ for var, abbr in (('photos_required', PHOTOS_REQUIRED),
+ ('photos_optional', PHOTOS_OPTIONAL),
+ ('ratings_required', RATINGS_REQUIRED),
+ ('ratings_optional', RATINGS_OPTIONAL),
+ ('is_public', IS_PUBLIC)):
+ context[var] = getattr(self, var)
+ if getattr(self, var):
+ options.append(abbr)
+ context['options'] = ','.join(options)
+ if self.free:
+ context['hash'] = Comment.objects.get_security_hash(context['options'], '', '', context['target'])
+ default_form = loader.get_template(FREE_COMMENT_FORM)
+ else:
+ context['photo_options'] = self.photo_options
+ context['rating_options'] = normalize_newlines(base64.encodestring(self.rating_options).strip())
+ if self.rating_options:
+ context['rating_range'], context['rating_choices'] = Comment.objects.get_rating_options(self.rating_options)
+ context['hash'] = Comment.objects.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
+ default_form = loader.get_template(COMMENT_FORM)
+ output = default_form.render(context)
+ context.pop()
+ return output
+
+class CommentCountNode(template.Node):
+ def __init__(self, package, module, context_var_name, obj_id, var_name, free):
+ self.package, self.module = package, module
+ self.context_var_name, self.obj_id = context_var_name, obj_id
+ self.var_name, self.free = var_name, free
+
+ def render(self, context):
+ from django.conf import settings
+ manager = self.free and FreeComment.objects or Comment.objects
+ if self.context_var_name is not None:
+ self.obj_id = template.resolve_variable(self.context_var_name, context)
+ comment_count = manager.filter(object_id__exact=self.obj_id,
+ content_type__app_label__exact=self.package,
+ content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count()
+ context[self.var_name] = comment_count
+ return ''
+
+class CommentListNode(template.Node):
+ def __init__(self, package, module, context_var_name, obj_id, var_name, free, ordering, extra_kwargs=None):
+ self.package, self.module = package, module
+ self.context_var_name, self.obj_id = context_var_name, obj_id
+ self.var_name, self.free = var_name, free
+ self.ordering = ordering
+ self.extra_kwargs = extra_kwargs or {}
+
+ def render(self, context):
+ from django.conf import settings
+ get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma
+ if self.context_var_name is not None:
+ try:
+ self.obj_id = template.resolve_variable(self.context_var_name, context)
+ except template.VariableDoesNotExist:
+ return ''
+ kwargs = {
+ 'object_id__exact': self.obj_id,
+ 'content_type__app_label__exact': self.package,
+ 'content_type__model__exact': self.module,
+ 'site__id__exact': settings.SITE_ID,
+ }
+ kwargs.update(self.extra_kwargs)
+ if not self.free and settings.COMMENTS_BANNED_USERS_GROUP:
+ kwargs['select'] = {'is_hidden': 'user_id IN (SELECT user_id FROM auth_user_groups WHERE group_id = %s)' % settings.COMMENTS_BANNED_USERS_GROUP}
+ comment_list = get_list_function(**kwargs).order_by(self.ordering + 'submit_date').select_related()
+
+ if not self.free:
+ if context.has_key('user') and context['user'].is_authenticated():
+ user_id = context['user'].id
+ context['user_can_moderate_comments'] = Comment.objects.user_is_moderator(context['user'])
+ else:
+ user_id = None
+ context['user_can_moderate_comments'] = False
+ # Only display comments by banned users to those users themselves.
+ if settings.COMMENTS_BANNED_USERS_GROUP:
+ comment_list = [c for c in comment_list if not c.is_hidden or (user_id == c.user_id)]
+
+ context[self.var_name] = comment_list
+ return ''
+
+class DoCommentForm:
+ """
+ Displays a comment form for the given params.
+
+ Syntax::
+
+ {% comment_form for [pkg].[py_module_name] [context_var_containing_obj_id] with [list of options] %}
+
+ Example usage::
+
+ {% comment_form for lcom.eventtimes event.id with is_public yes photos_optional thumbs,200,400 ratings_optional scale:1-5|first_option|second_option %}
+
+ ``[context_var_containing_obj_id]`` can be a hard-coded integer or a variable containing the ID.
+ """
+ def __init__(self, free):
+ self.free = free
+
+ def __call__(self, parser, token):
+ tokens = token.contents.split()
+ if len(tokens) < 4:
+ raise template.TemplateSyntaxError, "%r tag requires at least 3 arguments" % tokens[0]
+ if tokens[1] != 'for':
+ raise template.TemplateSyntaxError, "Second argument in %r tag must be 'for'" % tokens[0]
+ try:
+ package, module = tokens[2].split('.')
+ except ValueError: # unpack list of wrong size
+ raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
+ try:
+ content_type = ContentType.objects.get(app_label__exact=package, model__exact=module)
+ except ContentType.DoesNotExist:
+ raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
+ obj_id_lookup_var, obj_id = None, None
+ if tokens[3].isdigit():
+ obj_id = tokens[3]
+ try: # ensure the object ID is valid
+ content_type.get_object_for_this_type(pk=obj_id)
+ except ObjectDoesNotExist:
+ raise template.TemplateSyntaxError, "%r tag refers to %s object with ID %s, which doesn't exist" % (tokens[0], content_type.name, obj_id)
+ else:
+ obj_id_lookup_var = tokens[3]
+ kwargs = {}
+ if len(tokens) > 4:
+ if tokens[4] != 'with':
+ raise template.TemplateSyntaxError, "Fourth argument in %r tag must be 'with'" % tokens[0]
+ for option, args in zip(tokens[5::2], tokens[6::2]):
+ if option in ('photos_optional', 'photos_required') and not self.free:
+ # VALIDATION ##############################################
+ option_list = args.split(',')
+ if len(option_list) % 3 != 0:
+ raise template.TemplateSyntaxError, "Incorrect number of comma-separated arguments to %r tag" % tokens[0]
+ for opt in option_list[::3]:
+ if not opt.isalnum():
+ raise template.TemplateSyntaxError, "Invalid photo directory name in %r tag: '%s'" % (tokens[0], opt)
+ for opt in option_list[1::3] + option_list[2::3]:
+ if not opt.isdigit() or not (MIN_PHOTO_DIMENSION <= int(opt) <= MAX_PHOTO_DIMENSION):
+ raise template.TemplateSyntaxError, "Invalid photo dimension in %r tag: '%s'. Only values between %s and %s are allowed." % (tokens[0], opt, MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION)
+ # VALIDATION ENDS #########################################
+ kwargs[option] = True
+ kwargs['photo_options'] = args
+ elif option in ('ratings_optional', 'ratings_required') and not self.free:
+ # VALIDATION ##############################################
+ if 2 < len(args.split('|')) > 9:
+ raise template.TemplateSyntaxError, "Incorrect number of '%s' options in %r tag. Use between 2 and 8." % (option, tokens[0])
+ if re.match('^scale:\d+\-\d+\:$', args.split('|')[0]):
+ raise template.TemplateSyntaxError, "Invalid 'scale' in %r tag's '%s' options" % (tokens[0], option)
+ # VALIDATION ENDS #########################################
+ kwargs[option] = True
+ kwargs['rating_options'] = args
+ elif option in ('is_public'):
+ kwargs[option] = (args == 'true')
+ else:
+ raise template.TemplateSyntaxError, "%r tag got invalid parameter '%s'" % (tokens[0], option)
+ return CommentFormNode(content_type, obj_id_lookup_var, obj_id, self.free, **kwargs)
+
+class DoCommentCount:
+ """
+ Gets comment count for the given params and populates the template context
+ with a variable containing that value, whose name is defined by the 'as'
+ clause.
+
+ Syntax::
+
+ {% get_comment_count for [pkg].[py_module_name] [context_var_containing_obj_id] as [varname] %}
+
+ Example usage::
+
+ {% get_comment_count for lcom.eventtimes event.id as comment_count %}
+
+ Note: ``[context_var_containing_obj_id]`` can also be a hard-coded integer, like this::
+
+ {% get_comment_count for lcom.eventtimes 23 as comment_count %}
+ """
+ def __init__(self, free):
+ self.free = free
+
+ def __call__(self, parser, token):
+ tokens = token.contents.split()
+ # Now tokens is a list like this:
+ # ['get_comment_list', 'for', 'lcom.eventtimes', 'event.id', 'as', 'comment_list']
+ if len(tokens) != 6:
+ raise template.TemplateSyntaxError, "%r tag requires 5 arguments" % tokens[0]
+ if tokens[1] != 'for':
+ raise template.TemplateSyntaxError, "Second argument in %r tag must be 'for'" % tokens[0]
+ try:
+ package, module = tokens[2].split('.')
+ except ValueError: # unpack list of wrong size
+ raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
+ try:
+ content_type = ContentType.objects.get(app_label__exact=package, model__exact=module)
+ except ContentType.DoesNotExist:
+ raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
+ var_name, obj_id = None, None
+ if tokens[3].isdigit():
+ obj_id = tokens[3]
+ try: # ensure the object ID is valid
+ content_type.get_object_for_this_type(pk=obj_id)
+ except ObjectDoesNotExist:
+ raise template.TemplateSyntaxError, "%r tag refers to %s object with ID %s, which doesn't exist" % (tokens[0], content_type.name, obj_id)
+ else:
+ var_name = tokens[3]
+ if tokens[4] != 'as':
+ raise template.TemplateSyntaxError, "Fourth argument in %r must be 'as'" % tokens[0]
+ return CommentCountNode(package, module, var_name, obj_id, tokens[5], self.free)
+
+class DoGetCommentList:
+ """
+ Gets comments for the given params and populates the template context with a
+ special comment_package variable, whose name is defined by the ``as``
+ clause.
+
+ Syntax::
+
+ {% get_comment_list for [pkg].[py_module_name] [context_var_containing_obj_id] as [varname] (reversed) %}
+
+ Example usage::
+
+ {% get_comment_list for lcom.eventtimes event.id as comment_list %}
+
+ Note: ``[context_var_containing_obj_id]`` can also be a hard-coded integer, like this::
+
+ {% get_comment_list for lcom.eventtimes 23 as comment_list %}
+
+ To get a list of comments in reverse order -- that is, most recent first --
+ pass ``reversed`` as the last param::
+
+ {% get_comment_list for lcom.eventtimes event.id as comment_list reversed %}
+ """
+ def __init__(self, free):
+ self.free = free
+
+ def __call__(self, parser, token):
+ tokens = token.contents.split()
+ # Now tokens is a list like this:
+ # ['get_comment_list', 'for', 'lcom.eventtimes', 'event.id', 'as', 'comment_list']
+ if not len(tokens) in (6, 7):
+ raise template.TemplateSyntaxError, "%r tag requires 5 or 6 arguments" % tokens[0]
+ if tokens[1] != 'for':
+ raise template.TemplateSyntaxError, "Second argument in %r tag must be 'for'" % tokens[0]
+ try:
+ package, module = tokens[2].split('.')
+ except ValueError: # unpack list of wrong size
+ raise template.TemplateSyntaxError, "Third argument in %r tag must be in the format 'package.module'" % tokens[0]
+ try:
+ content_type = ContentType.objects.get(app_label__exact=package,model__exact=module)
+ except ContentType.DoesNotExist:
+ raise template.TemplateSyntaxError, "%r tag has invalid content-type '%s.%s'" % (tokens[0], package, module)
+ var_name, obj_id = None, None
+ if tokens[3].isdigit():
+ obj_id = tokens[3]
+ try: # ensure the object ID is valid
+ content_type.get_object_for_this_type(pk=obj_id)
+ except ObjectDoesNotExist:
+ raise template.TemplateSyntaxError, "%r tag refers to %s object with ID %s, which doesn't exist" % (tokens[0], content_type.name, obj_id)
+ else:
+ var_name = tokens[3]
+ if tokens[4] != 'as':
+ raise template.TemplateSyntaxError, "Fourth argument in %r must be 'as'" % tokens[0]
+ if len(tokens) == 7:
+ if tokens[6] != 'reversed':
+ raise template.TemplateSyntaxError, "Final argument in %r must be 'reversed' if given" % tokens[0]
+ ordering = "-"
+ else:
+ ordering = ""
+ return CommentListNode(package, module, var_name, obj_id, tokens[5], self.free, ordering)
+
+# registration comments
+register.tag('get_comment_list', DoGetCommentList(False))
+register.tag('comment_form', DoCommentForm(False))
+register.tag('get_comment_count', DoCommentCount(False))
+# free comments
+register.tag('get_free_comment_list', DoGetCommentList(True))
+register.tag('free_comment_form', DoCommentForm(True))
+register.tag('get_free_comment_count', DoCommentCount(True))
diff --git a/google_appengine/lib/django/django/contrib/comments/urls/__init__.py b/google_appengine/lib/django/django/contrib/comments/urls/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/urls/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/comments/urls/comments.py b/google_appengine/lib/django/django/contrib/comments/urls/comments.py
new file mode 100755
index 0000000..bbb4c43
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/urls/comments.py
@@ -0,0 +1,12 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.contrib.comments.views',
+ (r'^post/$', 'comments.post_comment'),
+ (r'^postfree/$', 'comments.post_free_comment'),
+ (r'^posted/$', 'comments.comment_was_posted'),
+ (r'^karma/vote/(?P<comment_id>\d+)/(?P<vote>up|down)/$', 'karma.vote'),
+ (r'^flag/(?P<comment_id>\d+)/$', 'userflags.flag'),
+ (r'^flag/(?P<comment_id>\d+)/done/$', 'userflags.flag_done'),
+ (r'^delete/(?P<comment_id>\d+)/$', 'userflags.delete'),
+ (r'^delete/(?P<comment_id>\d+)/done/$', 'userflags.delete_done'),
+)
diff --git a/google_appengine/lib/django/django/contrib/comments/views/__init__.py b/google_appengine/lib/django/django/contrib/comments/views/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/views/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/comments/views/comments.py b/google_appengine/lib/django/django/contrib/comments/views/comments.py
new file mode 100755
index 0000000..12330af
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/views/comments.py
@@ -0,0 +1,340 @@
+from django.core import validators
+from django import oldforms
+from django.core.mail import mail_admins, mail_managers
+from django.http import Http404
+from django.core.exceptions import ObjectDoesNotExist
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.comments.models import Comment, FreeComment, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.forms import AuthenticationForm
+from django.http import HttpResponseRedirect
+from django.utils.text import normalize_newlines
+from django.conf import settings
+from django.utils.translation import ngettext
+import base64, datetime
+
+COMMENTS_PER_PAGE = 20
+
+class PublicCommentManipulator(AuthenticationForm):
+ "Manipulator that handles public registered comments"
+ def __init__(self, user, ratings_required, ratings_range, num_rating_choices):
+ AuthenticationForm.__init__(self)
+ self.ratings_range, self.num_rating_choices = ratings_range, num_rating_choices
+ choices = [(c, c) for c in ratings_range]
+ def get_validator_list(rating_num):
+ if rating_num <= num_rating_choices:
+ return [validators.RequiredIfOtherFieldsGiven(['rating%d' % i for i in range(1, 9) if i != rating_num], _("This rating is required because you've entered at least one other rating."))]
+ else:
+ return []
+ self.fields.extend([
+ oldforms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+ validator_list=[self.hasNoProfanities]),
+ oldforms.RadioSelectField(field_name="rating1", choices=choices,
+ is_required=ratings_required and num_rating_choices > 0,
+ validator_list=get_validator_list(1),
+ ),
+ oldforms.RadioSelectField(field_name="rating2", choices=choices,
+ is_required=ratings_required and num_rating_choices > 1,
+ validator_list=get_validator_list(2),
+ ),
+ oldforms.RadioSelectField(field_name="rating3", choices=choices,
+ is_required=ratings_required and num_rating_choices > 2,
+ validator_list=get_validator_list(3),
+ ),
+ oldforms.RadioSelectField(field_name="rating4", choices=choices,
+ is_required=ratings_required and num_rating_choices > 3,
+ validator_list=get_validator_list(4),
+ ),
+ oldforms.RadioSelectField(field_name="rating5", choices=choices,
+ is_required=ratings_required and num_rating_choices > 4,
+ validator_list=get_validator_list(5),
+ ),
+ oldforms.RadioSelectField(field_name="rating6", choices=choices,
+ is_required=ratings_required and num_rating_choices > 5,
+ validator_list=get_validator_list(6),
+ ),
+ oldforms.RadioSelectField(field_name="rating7", choices=choices,
+ is_required=ratings_required and num_rating_choices > 6,
+ validator_list=get_validator_list(7),
+ ),
+ oldforms.RadioSelectField(field_name="rating8", choices=choices,
+ is_required=ratings_required and num_rating_choices > 7,
+ validator_list=get_validator_list(8),
+ ),
+ ])
+ if user.is_authenticated():
+ self["username"].is_required = False
+ self["username"].validator_list = []
+ self["password"].is_required = False
+ self["password"].validator_list = []
+ self.user_cache = user
+
+ def hasNoProfanities(self, field_data, all_data):
+ if settings.COMMENTS_ALLOW_PROFANITIES:
+ return
+ return validators.hasNoProfanities(field_data, all_data)
+
+ def get_comment(self, new_data):
+ "Helper function"
+ return Comment(None, self.get_user_id(), new_data["content_type_id"],
+ new_data["object_id"], new_data.get("headline", "").strip(),
+ new_data["comment"].strip(), new_data.get("rating1", None),
+ new_data.get("rating2", None), new_data.get("rating3", None),
+ new_data.get("rating4", None), new_data.get("rating5", None),
+ new_data.get("rating6", None), new_data.get("rating7", None),
+ new_data.get("rating8", None), new_data.get("rating1", None) is not None,
+ datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, settings.SITE_ID)
+
+ def save(self, new_data):
+ today = datetime.date.today()
+ c = self.get_comment(new_data)
+ for old in Comment.objects.filter(content_type__id__exact=new_data["content_type_id"],
+ object_id__exact=new_data["object_id"], user__id__exact=self.get_user_id()):
+ # Check that this comment isn't duplicate. (Sometimes people post
+ # comments twice by mistake.) If it is, fail silently by pretending
+ # the comment was posted successfully.
+ if old.submit_date.date() == today and old.comment == c.comment \
+ and old.rating1 == c.rating1 and old.rating2 == c.rating2 \
+ and old.rating3 == c.rating3 and old.rating4 == c.rating4 \
+ and old.rating5 == c.rating5 and old.rating6 == c.rating6 \
+ and old.rating7 == c.rating7 and old.rating8 == c.rating8:
+ return old
+ # If the user is leaving a rating, invalidate all old ratings.
+ if c.rating1 is not None:
+ old.valid_rating = False
+ old.save()
+ c.save()
+ # If the commentor has posted fewer than COMMENTS_FIRST_FEW comments,
+ # send the comment to the managers.
+ if self.user_cache.comment_set.count() <= settings.COMMENTS_FIRST_FEW:
+ message = ngettext('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', settings.COMMENTS_FIRST_FEW) % \
+ {'count': settings.COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
+ mail_managers("Comment posted by rookie user", message)
+ if settings.COMMENTS_SKETCHY_USERS_GROUP and settings.COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.get_group_list()]:
+ message = _('This comment was posted by a sketchy user:\n\n%(text)s') % {'text': c.get_as_text()}
+ mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text())
+ return c
+
+class PublicFreeCommentManipulator(oldforms.Manipulator):
+ "Manipulator that handles public free (unregistered) comments"
+ def __init__(self):
+ self.fields = (
+ oldforms.TextField(field_name="person_name", maxlength=50, is_required=True,
+ validator_list=[self.hasNoProfanities]),
+ oldforms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+ validator_list=[self.hasNoProfanities]),
+ )
+
+ def hasNoProfanities(self, field_data, all_data):
+ if settings.COMMENTS_ALLOW_PROFANITIES:
+ return
+ return validators.hasNoProfanities(field_data, all_data)
+
+ def get_comment(self, new_data):
+ "Helper function"
+ return FreeComment(None, new_data["content_type_id"],
+ new_data["object_id"], new_data["comment"].strip(),
+ new_data["person_name"].strip(), datetime.datetime.now(), new_data["is_public"],
+ new_data["ip_address"], False, settings.SITE_ID)
+
+ def save(self, new_data):
+ today = datetime.date.today()
+ c = self.get_comment(new_data)
+ # Check that this comment isn't duplicate. (Sometimes people post
+ # comments twice by mistake.) If it is, fail silently by pretending
+ # the comment was posted successfully.
+ for old_comment in FreeComment.objects.filter(content_type__id__exact=new_data["content_type_id"],
+ object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"],
+ submit_date__year=today.year, submit_date__month=today.month,
+ submit_date__day=today.day):
+ if old_comment.comment == c.comment:
+ return old_comment
+ c.save()
+ return c
+
+def post_comment(request):
+ """
+ Post a comment
+
+ Redirects to the `comments.comments.comment_was_posted` view upon success.
+
+ Templates: `comment_preview`
+ Context:
+ comment
+ the comment being posted
+ comment_form
+ the comment form
+ options
+ comment options
+ target
+ comment target
+ hash
+ security hash (must be included in a posted form to succesfully
+ post a comment).
+ rating_options
+ comment ratings options
+ ratings_optional
+ are ratings optional?
+ ratings_required
+ are ratings required?
+ rating_range
+ range of ratings
+ rating_choices
+ choice of ratings
+ """
+ if not request.POST:
+ raise Http404, _("Only POSTs are allowed")
+ try:
+ options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo']
+ except KeyError:
+ raise Http404, _("One or more of the required fields wasn't submitted")
+ photo_options = request.POST.get('photo_options', '')
+ rating_options = normalize_newlines(request.POST.get('rating_options', ''))
+ if Comment.objects.get_security_hash(options, photo_options, rating_options, target) != security_hash:
+ raise Http404, _("Somebody tampered with the comment form (security violation)")
+ # Now we can be assured the data is valid.
+ if rating_options:
+ rating_range, rating_choices = Comment.objects.get_rating_options(base64.decodestring(rating_options))
+ else:
+ rating_range, rating_choices = [], []
+ content_type_id, object_id = target.split(':') # target is something like '52:5157'
+ try:
+ obj = ContentType.objects.get(pk=content_type_id).get_object_for_this_type(pk=object_id)
+ except ObjectDoesNotExist:
+ raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid")
+ option_list = options.split(',') # options is something like 'pa,ra'
+ new_data = request.POST.copy()
+ new_data['content_type_id'] = content_type_id
+ new_data['object_id'] = object_id
+ new_data['ip_address'] = request.META.get('REMOTE_ADDR')
+ new_data['is_public'] = IS_PUBLIC in option_list
+ manipulator = PublicCommentManipulator(request.user,
+ ratings_required=RATINGS_REQUIRED in option_list,
+ ratings_range=rating_range,
+ num_rating_choices=len(rating_choices))
+ errors = manipulator.get_validation_errors(new_data)
+ # If user gave correct username/password and wasn't already logged in, log them in
+ # so they don't have to enter a username/password again.
+ if manipulator.get_user() and not manipulator.get_user().is_authenticated() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']):
+ from django.contrib.auth import login
+ login(request, manipulator.get_user())
+ if errors or request.POST.has_key('preview'):
+ class CommentFormWrapper(oldforms.FormWrapper):
+ def __init__(self, manipulator, new_data, errors, rating_choices):
+ oldforms.FormWrapper.__init__(self, manipulator, new_data, errors)
+ self.rating_choices = rating_choices
+ def ratings(self):
+ field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))]
+ for i, f in enumerate(field_list):
+ f.choice = rating_choices[i]
+ return field_list
+ comment = errors and '' or manipulator.get_comment(new_data)
+ comment_form = CommentFormWrapper(manipulator, new_data, errors, rating_choices)
+ return render_to_response('comments/preview.html', {
+ 'comment': comment,
+ 'comment_form': comment_form,
+ 'options': options,
+ 'target': target,
+ 'hash': security_hash,
+ 'rating_options': rating_options,
+ 'ratings_optional': RATINGS_OPTIONAL in option_list,
+ 'ratings_required': RATINGS_REQUIRED in option_list,
+ 'rating_range': rating_range,
+ 'rating_choices': rating_choices,
+ }, context_instance=RequestContext(request))
+ elif request.POST.has_key('post'):
+ # If the IP is banned, mail the admins, do NOT save the comment, and
+ # serve up the "Thanks for posting" page as if the comment WAS posted.
+ if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
+ mail_admins("Banned IP attempted to post comment", str(request.POST) + "\n\n" + str(request.META))
+ else:
+ manipulator.do_html2python(new_data)
+ comment = manipulator.save(new_data)
+ return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id))
+ else:
+ raise Http404, _("The comment form didn't provide either 'preview' or 'post'")
+
+def post_free_comment(request):
+ """
+ Post a free comment (not requiring a log in)
+
+ Redirects to `comments.comments.comment_was_posted` view on success.
+
+ Templates: `comment_free_preview`
+ Context:
+ comment
+ comment being posted
+ comment_form
+ comment form object
+ options
+ comment options
+ target
+ comment target
+ hash
+ security hash (must be included in a posted form to succesfully
+ post a comment).
+ """
+ if not request.POST:
+ raise Http404, _("Only POSTs are allowed")
+ try:
+ options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo']
+ except KeyError:
+ raise Http404, _("One or more of the required fields wasn't submitted")
+ if Comment.objects.get_security_hash(options, '', '', target) != security_hash:
+ raise Http404, _("Somebody tampered with the comment form (security violation)")
+ content_type_id, object_id = target.split(':') # target is something like '52:5157'
+ content_type = ContentType.objects.get(pk=content_type_id)
+ try:
+ obj = content_type.get_object_for_this_type(pk=object_id)
+ except ObjectDoesNotExist:
+ raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid")
+ option_list = options.split(',')
+ new_data = request.POST.copy()
+ new_data['content_type_id'] = content_type_id
+ new_data['object_id'] = object_id
+ new_data['ip_address'] = request.META['REMOTE_ADDR']
+ new_data['is_public'] = IS_PUBLIC in option_list
+ manipulator = PublicFreeCommentManipulator()
+ errors = manipulator.get_validation_errors(new_data)
+ if errors or request.POST.has_key('preview'):
+ comment = errors and '' or manipulator.get_comment(new_data)
+ return render_to_response('comments/free_preview.html', {
+ 'comment': comment,
+ 'comment_form': oldforms.FormWrapper(manipulator, new_data, errors),
+ 'options': options,
+ 'target': target,
+ 'hash': security_hash,
+ }, context_instance=RequestContext(request))
+ elif request.POST.has_key('post'):
+ # If the IP is banned, mail the admins, do NOT save the comment, and
+ # serve up the "Thanks for posting" page as if the comment WAS posted.
+ if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
+ from django.core.mail import mail_admins
+ mail_admins("Practical joker", str(request.POST) + "\n\n" + str(request.META))
+ else:
+ manipulator.do_html2python(new_data)
+ comment = manipulator.save(new_data)
+ return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id))
+ else:
+ raise Http404, _("The comment form didn't provide either 'preview' or 'post'")
+
+def comment_was_posted(request):
+ """
+ Display "comment was posted" success page
+
+ Templates: `comment_posted`
+ Context:
+ object
+ The object the comment was posted on
+ """
+ obj = None
+ if request.GET.has_key('c'):
+ content_type_id, object_id = request.GET['c'].split(':')
+ try:
+ content_type = ContentType.objects.get(pk=content_type_id)
+ obj = content_type.get_object_for_this_type(pk=object_id)
+ except ObjectDoesNotExist:
+ pass
+ return render_to_response('comments/posted.html', {'object': obj}, context_instance=RequestContext(request))
diff --git a/google_appengine/lib/django/django/contrib/comments/views/karma.py b/google_appengine/lib/django/django/contrib/comments/views/karma.py
new file mode 100755
index 0000000..8c18523
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/views/karma.py
@@ -0,0 +1,29 @@
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.comments.models import Comment, KarmaScore
+
+def vote(request, comment_id, vote):
+ """
+ Rate a comment (+1 or -1)
+
+ Templates: `karma_vote_accepted`
+ Context:
+ comment
+ `comments.comments` object being rated
+ """
+ rating = {'up': 1, 'down': -1}.get(vote, False)
+ if not rating:
+ raise Http404, "Invalid vote"
+ if not request.user.is_authenticated():
+ raise Http404, _("Anonymous users cannot vote")
+ try:
+ comment = Comment.objects.get(pk=comment_id)
+ except Comment.DoesNotExist:
+ raise Http404, _("Invalid comment ID")
+ if comment.user.id == request.user.id:
+ raise Http404, _("No voting for yourself")
+ KarmaScore.objects.vote(request.user.id, comment_id, rating)
+ # Reload comment to ensure we have up to date karma count
+ comment = Comment.objects.get(pk=comment_id)
+ return render_to_response('comments/karma_vote_accepted.html', {'comment': comment}, context_instance=RequestContext(request))
diff --git a/google_appengine/lib/django/django/contrib/comments/views/userflags.py b/google_appengine/lib/django/django/contrib/comments/views/userflags.py
new file mode 100755
index 0000000..76f14ef
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/comments/views/userflags.py
@@ -0,0 +1,54 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+from django.http import Http404
+from django.contrib.comments.models import Comment, ModeratorDeletion, UserFlag
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponseRedirect
+from django.conf import settings
+
+def flag(request, comment_id):
+ """
+ Flags a comment. Confirmation on GET, action on POST.
+
+ Templates: `comments/flag_verify`, `comments/flag_done`
+ Context:
+ comment
+ the flagged `comments.comments` object
+ """
+ comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
+ if request.POST:
+ UserFlag.objects.flag(comment, request.user)
+ return HttpResponseRedirect('%sdone/' % request.path)
+ return render_to_response('comments/flag_verify.html', {'comment': comment}, context_instance=RequestContext(request))
+flag = login_required(flag)
+
+def flag_done(request, comment_id):
+ comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
+ return render_to_response('comments/flag_done.html', {'comment': comment}, context_instance=RequestContext(request))
+
+def delete(request, comment_id):
+ """
+ Deletes a comment. Confirmation on GET, action on POST.
+
+ Templates: `comments/delete_verify`, `comments/delete_done`
+ Context:
+ comment
+ the flagged `comments.comments` object
+ """
+ comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
+ if not Comment.objects.user_is_moderator(request.user):
+ raise Http404
+ if request.POST:
+ # If the comment has already been removed, silently fail.
+ if not comment.is_removed:
+ comment.is_removed = True
+ comment.save()
+ m = ModeratorDeletion(None, request.user.id, comment.id, None)
+ m.save()
+ return HttpResponseRedirect('%sdone/' % request.path)
+ return render_to_response('comments/delete_verify.html', {'comment': comment}, context_instance=RequestContext(request))
+delete = login_required(delete)
+
+def delete_done(request, comment_id):
+ comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
+ return render_to_response('comments/delete_done.html', {'comment': comment}, context_instance=RequestContext(request))
diff --git a/google_appengine/lib/django/django/contrib/contenttypes/__init__.py b/google_appengine/lib/django/django/contrib/contenttypes/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/contenttypes/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/contenttypes/management.py b/google_appengine/lib/django/django/contrib/contenttypes/management.py
new file mode 100755
index 0000000..3572d93
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/contenttypes/management.py
@@ -0,0 +1,33 @@
+"""
+Creates content types for all installed models.
+"""
+
+from django.dispatch import dispatcher
+from django.db.models import get_apps, get_models, signals
+
+def create_contenttypes(app, created_models, verbosity=2):
+ from django.contrib.contenttypes.models import ContentType
+ ContentType.objects.clear_cache()
+ app_models = get_models(app)
+ if not app_models:
+ return
+ for klass in app_models:
+ opts = klass._meta
+ try:
+ ContentType.objects.get(app_label=opts.app_label,
+ model=opts.object_name.lower())
+ except ContentType.DoesNotExist:
+ ct = ContentType(name=str(opts.verbose_name),
+ app_label=opts.app_label, model=opts.object_name.lower())
+ ct.save()
+ if verbosity >= 2:
+ print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
+
+def create_all_contenttypes(verbosity=2):
+ for app in get_apps():
+ create_contenttypes(app, None, verbosity)
+
+dispatcher.connect(create_contenttypes, signal=signals.post_syncdb)
+
+if __name__ == "__main__":
+ create_all_contenttypes()
diff --git a/google_appengine/lib/django/django/contrib/contenttypes/models.py b/google_appengine/lib/django/django/contrib/contenttypes/models.py
new file mode 100755
index 0000000..0a5e68f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/contenttypes/models.py
@@ -0,0 +1,60 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+CONTENT_TYPE_CACHE = {}
+class ContentTypeManager(models.Manager):
+ def get_for_model(self, model):
+ """
+ Returns the ContentType object for the given model, creating the
+ ContentType if necessary.
+ """
+ opts = model._meta
+ key = (opts.app_label, opts.object_name.lower())
+ try:
+ ct = CONTENT_TYPE_CACHE[key]
+ except KeyError:
+ # The str() is needed around opts.verbose_name because it's a
+ # django.utils.functional.__proxy__ object.
+ ct, created = self.model._default_manager.get_or_create(app_label=key[0],
+ model=key[1], defaults={'name': str(opts.verbose_name)})
+ CONTENT_TYPE_CACHE[key] = ct
+ return ct
+
+ def clear_cache(self):
+ """
+ Clear out the content-type cache. This needs to happen during database
+ flushes to prevent caching of "stale" content type IDs (see
+ django.contrib.contenttypes.management.create_contenttypes for where
+ this gets called).
+ """
+ global CONTENT_TYPE_CACHE
+ CONTENT_TYPE_CACHE = {}
+
+class ContentType(models.Model):
+ name = models.CharField(maxlength=100)
+ app_label = models.CharField(maxlength=100)
+ model = models.CharField(_('python model class name'), maxlength=100)
+ objects = ContentTypeManager()
+ class Meta:
+ verbose_name = _('content type')
+ verbose_name_plural = _('content types')
+ db_table = 'django_content_type'
+ ordering = ('name',)
+ unique_together = (('app_label', 'model'),)
+
+ def __str__(self):
+ return self.name
+
+ def model_class(self):
+ "Returns the Python model class for this type of content."
+ from django.db import models
+ return models.get_model(self.app_label, self.model)
+
+ def get_object_for_this_type(self, **kwargs):
+ """
+ Returns an object of this type for the keyword arguments given.
+ Basically, this is a proxy around this object_type's get_object() model
+ method. The ObjectNotExist exception, if thrown, will not be caught,
+ so code that calls this method should catch it.
+ """
+ return self.model_class()._default_manager.get(**kwargs)
diff --git a/google_appengine/lib/django/django/contrib/csrf/__init__.py b/google_appengine/lib/django/django/contrib/csrf/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/csrf/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/csrf/middleware.py b/google_appengine/lib/django/django/contrib/csrf/middleware.py
new file mode 100755
index 0000000..93a9484
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/csrf/middleware.py
@@ -0,0 +1,92 @@
+"""
+Cross Site Request Forgery Middleware.
+
+This module provides a middleware that implements protection
+against request forgeries from other sites.
+
+"""
+from django.conf import settings
+from django.http import HttpResponseForbidden
+import md5
+import re
+import itertools
+
+_ERROR_MSG = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>'
+
+_POST_FORM_RE = \
+ re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
+
+_HTML_TYPES = ('text/html', 'application/xhtml+xml')
+
+def _make_token(session_id):
+ return md5.new(settings.SECRET_KEY + session_id).hexdigest()
+
+class CsrfMiddleware(object):
+ """Django middleware that adds protection against Cross Site
+ Request Forgeries by adding hidden form fields to POST forms and
+ checking requests for the correct value.
+
+ In the list of middlewares, SessionMiddleware is required, and must come
+ after this middleware. CsrfMiddleWare must come after compression
+ middleware.
+
+ If a session ID cookie is present, it is hashed with the SECRET_KEY
+ setting to create an authentication token. This token is added to all
+ outgoing POST forms and is expected on all incoming POST requests that
+ have a session ID cookie.
+
+ If you are setting cookies directly, instead of using Django's session
+ framework, this middleware will not work.
+ """
+
+ def process_request(self, request):
+ if request.POST:
+ try:
+ session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
+ except KeyError:
+ # No session, no check required
+ return None
+
+ csrf_token = _make_token(session_id)
+ # check incoming token
+ try:
+ request_csrf_token = request.POST['csrfmiddlewaretoken']
+ except KeyError:
+ return HttpResponseForbidden(_ERROR_MSG)
+
+ if request_csrf_token != csrf_token:
+ return HttpResponseForbidden(_ERROR_MSG)
+
+ return None
+
+ def process_response(self, request, response):
+ csrf_token = None
+ try:
+ cookie = response.cookies[settings.SESSION_COOKIE_NAME]
+ csrf_token = _make_token(cookie.value)
+ except KeyError:
+ # No outgoing cookie to set session, but
+ # a session might already exist.
+ try:
+ session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
+ csrf_token = _make_token(session_id)
+ except KeyError:
+ # no incoming or outgoing cookie
+ pass
+
+ if csrf_token is not None and \
+ response['Content-Type'].split(';')[0] in _HTML_TYPES:
+
+ # ensure we don't add the 'id' attribute twice (HTML validity)
+ idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
+ itertools.repeat(''))
+ def add_csrf_field(match):
+ """Returns the matched <form> tag plus the added <input> element"""
+ return match.group() + "<div style='display:none;'>" + \
+ "<input type='hidden' " + idattributes.next() + \
+ " name='csrfmiddlewaretoken' value='" + csrf_token + \
+ "' /></div>"
+
+ # Modify any POST forms
+ response.content = _POST_FORM_RE.sub(add_csrf_field, response.content)
+ return response
diff --git a/google_appengine/lib/django/django/contrib/flatpages/__init__.py b/google_appengine/lib/django/django/contrib/flatpages/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/flatpages/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/flatpages/middleware.py b/google_appengine/lib/django/django/contrib/flatpages/middleware.py
new file mode 100755
index 0000000..231b5fd
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/flatpages/middleware.py
@@ -0,0 +1,18 @@
+from django.contrib.flatpages.views import flatpage
+from django.http import Http404
+from django.conf import settings
+
+class FlatpageFallbackMiddleware(object):
+ def process_response(self, request, response):
+ if response.status_code != 404:
+ return response # No need to check for a flatpage for non-404 responses.
+ try:
+ return flatpage(request, request.path)
+ # Return the original response if any errors happened. Because this
+ # is a middleware, we can't assume the errors will be caught elsewhere.
+ except Http404:
+ return response
+ except:
+ if settings.DEBUG:
+ raise
+ return response
diff --git a/google_appengine/lib/django/django/contrib/flatpages/models.py b/google_appengine/lib/django/django/contrib/flatpages/models.py
new file mode 100755
index 0000000..bc2a392
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/flatpages/models.py
@@ -0,0 +1,33 @@
+from django.core import validators
+from django.db import models
+from django.contrib.sites.models import Site
+from django.utils.translation import gettext_lazy as _
+
+class FlatPage(models.Model):
+ url = models.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
+ help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
+ title = models.CharField(_('title'), maxlength=200)
+ content = models.TextField(_('content'))
+ enable_comments = models.BooleanField(_('enable comments'))
+ template_name = models.CharField(_('template name'), maxlength=70, blank=True,
+ help_text=_("Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'."))
+ registration_required = models.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
+ sites = models.ManyToManyField(Site)
+ class Meta:
+ db_table = 'django_flatpage'
+ verbose_name = _('flat page')
+ verbose_name_plural = _('flat pages')
+ ordering = ('url',)
+ class Admin:
+ fields = (
+ (None, {'fields': ('url', 'title', 'content', 'sites')}),
+ ('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
+ )
+ list_filter = ('sites',)
+ search_fields = ('url', 'title')
+
+ def __str__(self):
+ return "%s -- %s" % (self.url, self.title)
+
+ def get_absolute_url(self):
+ return self.url
diff --git a/google_appengine/lib/django/django/contrib/flatpages/urls.py b/google_appengine/lib/django/django/contrib/flatpages/urls.py
new file mode 100755
index 0000000..4928930
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/flatpages/urls.py
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('django.contrib.flatpages.views',
+ (r'^(?P<url>.*)$', 'flatpage'),
+)
diff --git a/google_appengine/lib/django/django/contrib/flatpages/views.py b/google_appengine/lib/django/django/contrib/flatpages/views.py
new file mode 100755
index 0000000..f386a52
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/flatpages/views.py
@@ -0,0 +1,38 @@
+from django.contrib.flatpages.models import FlatPage
+from django.template import loader, RequestContext
+from django.shortcuts import get_object_or_404
+from django.http import HttpResponse
+from django.conf import settings
+from django.core.xheaders import populate_xheaders
+
+DEFAULT_TEMPLATE = 'flatpages/default.html'
+
+def flatpage(request, url):
+ """
+ Flat page view.
+
+ Models: `flatpages.flatpages`
+ Templates: Uses the template defined by the ``template_name`` field,
+ or `flatpages/default.html` if template_name is not defined.
+ Context:
+ flatpage
+ `flatpages.flatpages` object
+ """
+ if not url.startswith('/'):
+ url = "/" + url
+ f = get_object_or_404(FlatPage, url__exact=url, sites__id__exact=settings.SITE_ID)
+ # If registration is required for accessing this page, and the user isn't
+ # logged in, redirect to the login page.
+ if f.registration_required and not request.user.is_authenticated():
+ from django.contrib.auth.views import redirect_to_login
+ return redirect_to_login(request.path)
+ if f.template_name:
+ t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
+ else:
+ t = loader.get_template(DEFAULT_TEMPLATE)
+ c = RequestContext(request, {
+ 'flatpage': f,
+ })
+ response = HttpResponse(t.render(c))
+ populate_xheaders(request, response, FlatPage, f.id)
+ return response
diff --git a/google_appengine/lib/django/django/contrib/formtools/__init__.py b/google_appengine/lib/django/django/contrib/formtools/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/formtools/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/formtools/preview.py b/google_appengine/lib/django/django/contrib/formtools/preview.py
new file mode 100755
index 0000000..daecba7
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/formtools/preview.py
@@ -0,0 +1,165 @@
+"""
+Formtools Preview application.
+
+This is an abstraction of the following workflow:
+
+ "Display an HTML form, force a preview, then do something with the submission."
+
+Given a django.newforms.Form object that you define, this takes care of the
+following:
+
+ * Displays the form as HTML on a Web page.
+ * Validates the form data once it's submitted via POST.
+ * If it's valid, displays a preview page.
+ * If it's not valid, redisplays the form with error messages.
+ * At the preview page, if the preview confirmation button is pressed, calls
+ a hook that you define -- a done() method.
+
+The framework enforces the required preview by passing a shared-secret hash to
+the preview page. If somebody tweaks the form parameters on the preview page,
+the form submission will fail the hash comparison test.
+
+Usage
+=====
+
+Subclass FormPreview and define a done() method:
+
+ def done(self, request, clean_data):
+ # ...
+
+This method takes an HttpRequest object and a dictionary of the form data after
+it has been validated and cleaned. It should return an HttpResponseRedirect.
+
+Then, just instantiate your FormPreview subclass by passing it a Form class,
+and pass that to your URLconf, like so:
+
+ (r'^post/$', MyFormPreview(MyForm)),
+
+The FormPreview class has a few other hooks. See the docstrings in the source
+code below.
+
+The framework also uses two templates: 'formtools/preview.html' and
+'formtools/form.html'. You can override these by setting 'preview_template' and
+'form_template' attributes on your FormPreview subclass. See
+django/contrib/formtools/templates for the default templates.
+"""
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+import cPickle as pickle
+import md5
+
+AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
+
+class FormPreview(object):
+ preview_template = 'formtools/preview.html'
+ form_template = 'formtools/form.html'
+
+ # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
+
+ def __init__(self, form):
+ # form should be a Form class, not an instance.
+ self.form, self.state = form, {}
+
+ def __call__(self, request, *args, **kwargs):
+ stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview')
+ self.parse_params(*args, **kwargs)
+ try:
+ method = getattr(self, stage + '_' + request.method.lower())
+ except AttributeError:
+ raise Http404
+ return method(request)
+
+ def unused_name(self, name):
+ """
+ Given a first-choice name, adds an underscore to the name until it
+ reaches a name that isn't claimed by any field in the form.
+
+ This is calculated rather than being hard-coded so that no field names
+ are off-limits for use in the form.
+ """
+ while 1:
+ try:
+ f = self.form.fields[name]
+ except KeyError:
+ break # This field name isn't being used by the form.
+ name += '_'
+ return name
+
+ def preview_get(self, request):
+ "Displays the form"
+ f = self.form(auto_id=AUTO_ID)
+ return render_to_response(self.form_template,
+ {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
+ context_instance=RequestContext(request))
+
+ def preview_post(self, request):
+ "Validates the POST data. If valid, displays the preview page. Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}
+ if f.is_valid():
+ context['hash_field'] = self.unused_name('hash')
+ context['hash_value'] = self.security_hash(request, f)
+ return render_to_response(self.preview_template, context, context_instance=RequestContext(request))
+ else:
+ return render_to_response(self.form_template, context, context_instance=RequestContext(request))
+
+ def post_post(self, request):
+ "Validates the POST data. If valid, calls done(). Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ if f.is_valid():
+ if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
+ return self.failed_hash(request) # Security hash failed.
+ return self.done(request, f.clean_data)
+ else:
+ return render_to_response(self.form_template,
+ {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
+ context_instance=RequestContext(request))
+
+ # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
+
+ def parse_params(self, *args, **kwargs):
+ """
+ Given captured args and kwargs from the URLconf, saves something in
+ self.state and/or raises Http404 if necessary.
+
+ For example, this URLconf captures a user_id variable:
+
+ (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)),
+
+ In this case, the kwargs variable in parse_params would be
+ {'user_id': 32} for a request to '/contact/32/'. You can use that
+ user_id to make sure it's a valid user and/or save it for later, for
+ use in done().
+ """
+ pass
+
+ def security_hash(self, request, form):
+ """
+ Calculates the security hash for the given Form instance.
+
+ This creates a list of the form field names/values in a deterministic
+ order, pickles the result with the SECRET_KEY setting and takes an md5
+ hash of that.
+
+ Subclasses may want to take into account request-specific information
+ such as the IP address.
+ """
+ data = [(bf.name, bf.data) for bf in form] + [settings.SECRET_KEY]
+ # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
+ # Python 2.3, but Django requires 2.3 anyway, so that's OK.
+ pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
+ return md5.new(pickled).hexdigest()
+
+ def failed_hash(self, request):
+ "Returns an HttpResponse in the case of an invalid security hash."
+ return self.preview_post(request)
+
+ # METHODS SUBCLASSES MUST OVERRIDE ########################################
+
+ def done(self, request, clean_data):
+ "Does something with the clean_data and returns an HttpResponseRedirect."
+ raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)
diff --git a/google_appengine/lib/django/django/contrib/humanize/__init__.py b/google_appengine/lib/django/django/contrib/humanize/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/humanize/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/humanize/templatetags/__init__.py b/google_appengine/lib/django/django/contrib/humanize/templatetags/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/humanize/templatetags/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/humanize/templatetags/humanize.py b/google_appengine/lib/django/django/contrib/humanize/templatetags/humanize.py
new file mode 100755
index 0000000..a399e7e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/humanize/templatetags/humanize.py
@@ -0,0 +1,69 @@
+from django.utils.translation import ngettext
+from django.utils.translation import gettext_lazy as _
+from django import template
+import re
+
+register = template.Library()
+
+def ordinal(value):
+ """
+ Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd',
+ 3 is '3rd', etc. Works for any integer.
+ """
+ try:
+ value = int(value)
+ except ValueError:
+ return value
+ t = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th'))
+ if value % 100 in (11, 12, 13): # special case
+ return "%d%s" % (value, t[0])
+ return '%d%s' % (value, t[value % 10])
+register.filter(ordinal)
+
+def intcomma(value):
+ """
+ Converts an integer to a string containing commas every three digits.
+ For example, 3000 becomes '3,000' and 45000 becomes '45,000'.
+ """
+ orig = str(value)
+ new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', str(value))
+ if orig == new:
+ return new
+ else:
+ return intcomma(new)
+register.filter(intcomma)
+
+def intword(value):
+ """
+ Converts a large integer to a friendly text representation. Works best for
+ numbers over 1 million. For example, 1000000 becomes '1.0 million', 1200000
+ becomes '1.2 million' and '1200000000' becomes '1.2 billion'.
+ """
+ value = int(value)
+ if value < 1000000:
+ return value
+ if value < 1000000000:
+ new_value = value / 1000000.0
+ return ngettext('%(value).1f million', '%(value).1f million', new_value) % {'value': new_value}
+ if value < 1000000000000:
+ new_value = value / 1000000000.0
+ return ngettext('%(value).1f billion', '%(value).1f billion', new_value) % {'value': new_value}
+ if value < 1000000000000000:
+ new_value = value / 1000000000000.0
+ return ngettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value}
+ return value
+register.filter(intword)
+
+def apnumber(value):
+ """
+ For numbers 1-9, returns the number spelled out. Otherwise, returns the
+ number. This follows Associated Press style.
+ """
+ try:
+ value = int(value)
+ except ValueError:
+ return value
+ if not 0 < value < 10:
+ return value
+ return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
+register.filter(apnumber)
diff --git a/google_appengine/lib/django/django/contrib/localflavor/__init__.py b/google_appengine/lib/django/django/contrib/localflavor/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/localflavor/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/localflavor/uk/__init__.py b/google_appengine/lib/django/django/contrib/localflavor/uk/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/localflavor/uk/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/localflavor/uk/forms.py b/google_appengine/lib/django/django/contrib/localflavor/uk/forms.py
new file mode 100755
index 0000000..ddd033e
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/localflavor/uk/forms.py
@@ -0,0 +1,19 @@
+"""
+UK-specific Form helpers
+"""
+
+from django.newforms.fields import RegexField
+from django.utils.translation import gettext
+
+class UKPostcodeField(RegexField):
+ """
+ A form field that validates its input is a UK postcode.
+
+ The regular expression used is sourced from the schema for British Standard
+ BS7666 address types: http://www.govtalk.gov.uk/gdsc/schemas/bs7666-v2-0.xsd
+ """
+ def __init__(self, *args, **kwargs):
+ super(UKPostcodeField, self).__init__(r'^(GIR 0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HIK-Y][0-9](|[0-9]|[ABEHMNPRVWXY]))|[0-9][A-HJKSTUW]) [0-9][ABD-HJLNP-UW-Z]{2})$',
+ max_length=None, min_length=None,
+ error_message=gettext(u'Enter a postcode. A space is required between the two postcode parts.'),
+ *args, **kwargs)
diff --git a/google_appengine/lib/django/django/contrib/localflavor/usa/__init__.py b/google_appengine/lib/django/django/contrib/localflavor/usa/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/localflavor/usa/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/localflavor/usa/forms.py b/google_appengine/lib/django/django/contrib/localflavor/usa/forms.py
new file mode 100755
index 0000000..9461f4f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/localflavor/usa/forms.py
@@ -0,0 +1,59 @@
+"""
+USA-specific Form helpers
+"""
+
+from django.newforms import ValidationError
+from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
+from django.newforms.util import smart_unicode
+from django.utils.translation import gettext
+import re
+
+phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
+
+class USZipCodeField(RegexField):
+ def __init__(self, *args, **kwargs):
+ super(USZipCodeField, self).__init__(r'^\d{5}(?:-\d{4})?$',
+ max_length=None, min_length=None,
+ error_message=gettext(u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'),
+ *args, **kwargs)
+
+class USPhoneNumberField(Field):
+ def clean(self, value):
+ super(USPhoneNumberField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
+ m = phone_digits_re.search(value)
+ if m:
+ return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
+ raise ValidationError(u'Phone numbers must be in XXX-XXX-XXXX format.')
+
+class USStateField(Field):
+ """
+ A form field that validates its input is a U.S. state name or abbreviation.
+ It normalizes the input to the standard two-leter postal service
+ abbreviation for the given state.
+ """
+ def clean(self, value):
+ from us_states import STATES_NORMALIZED # relative import
+ super(USStateField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ try:
+ value = value.strip().lower()
+ except AttributeError:
+ pass
+ else:
+ try:
+ return STATES_NORMALIZED[value.strip().lower()].decode('ascii')
+ except KeyError:
+ pass
+ raise ValidationError(u'Enter a U.S. state or territory.')
+
+class USStateSelect(Select):
+ """
+ A Select widget that uses a list of U.S. states/territories as its choices.
+ """
+ def __init__(self, attrs=None):
+ from us_states import STATE_CHOICES # relative import
+ super(USStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
diff --git a/google_appengine/lib/django/django/contrib/localflavor/usa/us_states.py b/google_appengine/lib/django/django/contrib/localflavor/usa/us_states.py
new file mode 100755
index 0000000..89124a4
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/localflavor/usa/us_states.py
@@ -0,0 +1,239 @@
+"""
+A mapping of state misspellings/abbreviations to normalized abbreviations, and
+an alphabetical list of states for use as `choices` in a formfield.
+
+This exists in this standalone file so that it's only imported into memory
+when explicitly needed.
+"""
+
+STATE_CHOICES = (
+ ('AL', 'Alabama'),
+ ('AK', 'Alaska'),
+ ('AS', 'American Samoa'),
+ ('AZ', 'Arizona'),
+ ('AR', 'Arkansas'),
+ ('CA', 'California'),
+ ('CO', 'Colorado'),
+ ('CT', 'Connecticut'),
+ ('DE', 'Deleware'),
+ ('DC', 'District of Columbia'),
+ ('FM', 'Federated States of Micronesia'),
+ ('FL', 'Florida'),
+ ('GA', 'Georgia'),
+ ('GU', 'Guam'),
+ ('HI', 'Hawaii'),
+ ('ID', 'Idaho'),
+ ('IL', 'Illinois'),
+ ('IN', 'Indiana'),
+ ('IA', 'Iowa'),
+ ('KS', 'Kansas'),
+ ('KY', 'Kentucky'),
+ ('LA', 'Louisiana'),
+ ('ME', 'Maine'),
+ ('MH', 'Marshall Islands'),
+ ('MD', 'Maryland'),
+ ('MA', 'Massachusetts'),
+ ('MI', 'Michigan'),
+ ('MN', 'Minnesota'),
+ ('MS', 'Mississippi'),
+ ('MO', 'Missouri'),
+ ('MT', 'Montana'),
+ ('NE', 'Nebraska'),
+ ('NV', 'Nevada'),
+ ('NH', 'New Hampshire'),
+ ('NJ', 'New Jersey'),
+ ('NM', 'New Mexico'),
+ ('NY', 'New York'),
+ ('NC', 'North Carolina'),
+ ('ND', 'North Dakota'),
+ ('MP', 'Northern Mariana Islands'),
+ ('OH', 'Ohio'),
+ ('OK', 'Oklahoma'),
+ ('OR', 'Oregon'),
+ ('PW', 'Palau'),
+ ('PA', 'Pennsylvania'),
+ ('PR', 'Puerto Rico'),
+ ('RI', 'Rhode Island'),
+ ('SC', 'South Carolina'),
+ ('SD', 'South Dakota'),
+ ('TN', 'Tennessee'),
+ ('TX', 'Texas'),
+ ('UT', 'Utah'),
+ ('VT', 'Vermont'),
+ ('VI', 'Virgin Islands'),
+ ('VA', 'Virginia'),
+ ('WA', 'Washington'),
+ ('WV', 'West Virginia'),
+ ('WI', 'Wisconsin'),
+ ('WY', 'Wyoming'),
+)
+
+STATES_NORMALIZED = {
+ 'ak': 'AK',
+ 'al': 'AL',
+ 'ala': 'AL',
+ 'alabama': 'AL',
+ 'alaska': 'AK',
+ 'american samao': 'AS',
+ 'american samoa': 'AS',
+ 'ar': 'AR',
+ 'ariz': 'AZ',
+ 'arizona': 'AZ',
+ 'ark': 'AR',
+ 'arkansas': 'AR',
+ 'as': 'AS',
+ 'az': 'AZ',
+ 'ca': 'CA',
+ 'calf': 'CA',
+ 'calif': 'CA',
+ 'california': 'CA',
+ 'co': 'CO',
+ 'colo': 'CO',
+ 'colorado': 'CO',
+ 'conn': 'CT',
+ 'connecticut': 'CT',
+ 'ct': 'CT',
+ 'dc': 'DC',
+ 'de': 'DE',
+ 'del': 'DE',
+ 'delaware': 'DE',
+ 'district of columbia': 'DC',
+ 'federated states of micronesia': 'FM',
+ 'fl': 'FL',
+ 'fla': 'FL',
+ 'florida': 'FL',
+ 'fm': 'FM',
+ 'ga': 'GA',
+ 'georgia': 'GA',
+ 'gu': 'GU',
+ 'guam': 'GU',
+ 'hawaii': 'HI',
+ 'hi': 'HI',
+ 'ia': 'IA',
+ 'id': 'ID',
+ 'idaho': 'ID',
+ 'il': 'IL',
+ 'ill': 'IL',
+ 'illinois': 'IL',
+ 'in': 'IN',
+ 'ind': 'IN',
+ 'indiana': 'IN',
+ 'iowa': 'IA',
+ 'kan': 'KS',
+ 'kans': 'KS',
+ 'kansas': 'KS',
+ 'kentucky': 'KY',
+ 'ks': 'KS',
+ 'ky': 'KY',
+ 'la': 'LA',
+ 'louisiana': 'LA',
+ 'ma': 'MA',
+ 'maine': 'ME',
+ 'marianas islands': 'MP',
+ 'marianas islands of the pacific': 'MP',
+ 'marinas islands of the pacific': 'MP',
+ 'maryland': 'MD',
+ 'mass': 'MA',
+ 'massachusetts': 'MA',
+ 'massachussetts': 'MA',
+ 'md': 'MD',
+ 'me': 'ME',
+ 'mi': 'MI',
+ 'mich': 'MI',
+ 'michigan': 'MI',
+ 'micronesia': 'FM',
+ 'minn': 'MN',
+ 'minnesota': 'MN',
+ 'miss': 'MS',
+ 'mississippi': 'MS',
+ 'missouri': 'MO',
+ 'mn': 'MN',
+ 'mo': 'MO',
+ 'mont': 'MT',
+ 'montana': 'MT',
+ 'mp': 'MP',
+ 'ms': 'MS',
+ 'mt': 'MT',
+ 'n d': 'ND',
+ 'n dak': 'ND',
+ 'n h': 'NH',
+ 'n j': 'NJ',
+ 'n m': 'NM',
+ 'n mex': 'NM',
+ 'nc': 'NC',
+ 'nd': 'ND',
+ 'ne': 'NE',
+ 'neb': 'NE',
+ 'nebr': 'NE',
+ 'nebraska': 'NE',
+ 'nev': 'NV',
+ 'nevada': 'NV',
+ 'new hampshire': 'NH',
+ 'new jersey': 'NJ',
+ 'new mexico': 'NM',
+ 'new york': 'NY',
+ 'nh': 'NH',
+ 'nj': 'NJ',
+ 'nm': 'NM',
+ 'nmex': 'NM',
+ 'north carolina': 'NC',
+ 'north dakota': 'ND',
+ 'northern mariana islands': 'MP',
+ 'nv': 'NV',
+ 'ny': 'NY',
+ 'oh': 'OH',
+ 'ohio': 'OH',
+ 'ok': 'OK',
+ 'okla': 'OK',
+ 'oklahoma': 'OK',
+ 'or': 'OR',
+ 'ore': 'OR',
+ 'oreg': 'OR',
+ 'oregon': 'OR',
+ 'pa': 'PA',
+ 'penn': 'PA',
+ 'pennsylvania': 'PA',
+ 'pr': 'PR',
+ 'puerto rico': 'PR',
+ 'rhode island': 'RI',
+ 'ri': 'RI',
+ 's dak': 'SD',
+ 'sc': 'SC',
+ 'sd': 'SD',
+ 'sdak': 'SD',
+ 'south carolina': 'SC',
+ 'south dakota': 'SD',
+ 'tenn': 'TN',
+ 'tennessee': 'TN',
+ 'territory of hawaii': 'HI',
+ 'tex': 'TX',
+ 'texas': 'TX',
+ 'tn': 'TN',
+ 'tx': 'TX',
+ 'us virgin islands': 'VI',
+ 'usvi': 'VI',
+ 'ut': 'UT',
+ 'utah': 'UT',
+ 'va': 'VA',
+ 'vermont': 'VT',
+ 'vi': 'VI',
+ 'viginia': 'VA',
+ 'virgin islands': 'VI',
+ 'virgina': 'VA',
+ 'virginia': 'VA',
+ 'vt': 'VT',
+ 'w va': 'WV',
+ 'wa': 'WA',
+ 'wash': 'WA',
+ 'washington': 'WA',
+ 'west virginia': 'WV',
+ 'wi': 'WI',
+ 'wis': 'WI',
+ 'wisc': 'WI',
+ 'wisconsin': 'WI',
+ 'wv': 'WV',
+ 'wva': 'WV',
+ 'wy': 'WY',
+ 'wyo': 'WY',
+ 'wyoming': 'WY',
+}
diff --git a/google_appengine/lib/django/django/contrib/markup/__init__.py b/google_appengine/lib/django/django/contrib/markup/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/markup/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/markup/templatetags/__init__.py b/google_appengine/lib/django/django/contrib/markup/templatetags/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/markup/templatetags/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/markup/templatetags/markup.py b/google_appengine/lib/django/django/contrib/markup/templatetags/markup.py
new file mode 100755
index 0000000..4bb135c
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/markup/templatetags/markup.py
@@ -0,0 +1,56 @@
+"""
+Set of "markup" template filters for Django. These filters transform plain text
+markup syntaxes to HTML; currently there is support for:
+
+ * Textile, which requires the PyTextile library available at
+ http://dealmeida.net/projects/textile/
+
+ * Markdown, which requires the Python-markdown library from
+ http://www.freewisdom.org/projects/python-markdown
+
+ * ReStructuredText, which requires docutils from http://docutils.sf.net/
+
+In each case, if the required library is not installed, the filter will
+silently fail and return the un-marked-up text.
+"""
+
+from django import template
+from django.conf import settings
+
+register = template.Library()
+
+def textile(value):
+ try:
+ import textile
+ except ImportError:
+ if settings.DEBUG:
+ raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
+ return value
+ else:
+ return textile.textile(value, encoding=settings.DEFAULT_CHARSET, output=settings.DEFAULT_CHARSET)
+
+def markdown(value):
+ try:
+ import markdown
+ except ImportError:
+ if settings.DEBUG:
+ raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
+ return value
+ else:
+ return markdown.markdown(value)
+
+def restructuredtext(value):
+ try:
+ from docutils.core import publish_parts
+ except ImportError:
+ if settings.DEBUG:
+ raise template.TemplateSyntaxError, "Error in {% restructuredtext %} filter: The Python docutils library isn't installed."
+ return value
+ else:
+ docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
+ parts = publish_parts(source=value, writer_name="html4css1", settings_overrides=docutils_settings)
+ return parts["fragment"]
+
+register.filter(textile)
+register.filter(markdown)
+register.filter(restructuredtext)
diff --git a/google_appengine/lib/django/django/contrib/redirects/__init__.py b/google_appengine/lib/django/django/contrib/redirects/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/redirects/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/redirects/middleware.py b/google_appengine/lib/django/django/contrib/redirects/middleware.py
new file mode 100755
index 0000000..32f2760
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/redirects/middleware.py
@@ -0,0 +1,27 @@
+from django.contrib.redirects.models import Redirect
+from django import http
+from django.conf import settings
+
+class RedirectFallbackMiddleware(object):
+ def process_response(self, request, response):
+ if response.status_code != 404:
+ return response # No need to check for a redirect for non-404 responses.
+ path = request.get_full_path()
+ try:
+ r = Redirect.objects.get(site__id__exact=settings.SITE_ID, old_path=path)
+ except Redirect.DoesNotExist:
+ r = None
+ if r is None and settings.APPEND_SLASH:
+ # Try removing the trailing slash.
+ try:
+ r = Redirect.objects.get(site__id__exact=settings.SITE_ID,
+ old_path=path[:path.rfind('/')]+path[path.rfind('/')+1:])
+ except Redirect.DoesNotExist:
+ pass
+ if r is not None:
+ if r == '':
+ return http.HttpResponseGone()
+ return http.HttpResponsePermanentRedirect(r.new_path)
+
+ # No redirect was found. Return the response.
+ return response
diff --git a/google_appengine/lib/django/django/contrib/redirects/models.py b/google_appengine/lib/django/django/contrib/redirects/models.py
new file mode 100755
index 0000000..60205e2
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/redirects/models.py
@@ -0,0 +1,24 @@
+from django.db import models
+from django.contrib.sites.models import Site
+from django.utils.translation import gettext_lazy as _
+
+class Redirect(models.Model):
+ site = models.ForeignKey(Site, radio_admin=models.VERTICAL)
+ old_path = models.CharField(_('redirect from'), maxlength=200, db_index=True,
+ help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'."))
+ new_path = models.CharField(_('redirect to'), maxlength=200, blank=True,
+ help_text=_("This can be either an absolute path (as above) or a full URL starting with 'http://'."))
+
+ class Meta:
+ verbose_name = _('redirect')
+ verbose_name_plural = _('redirects')
+ db_table = 'django_redirect'
+ unique_together=(('site', 'old_path'),)
+ ordering = ('old_path',)
+
+ class Admin:
+ list_filter = ('site',)
+ search_fields = ('old_path', 'new_path')
+
+ def __str__(self):
+ return "%s ---> %s" % (self.old_path, self.new_path)
diff --git a/google_appengine/lib/django/django/contrib/sessions/__init__.py b/google_appengine/lib/django/django/contrib/sessions/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sessions/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/sessions/middleware.py b/google_appengine/lib/django/django/contrib/sessions/middleware.py
new file mode 100755
index 0000000..1498f3c
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sessions/middleware.py
@@ -0,0 +1,103 @@
+from django.conf import settings
+from django.contrib.sessions.models import Session
+from django.core.exceptions import SuspiciousOperation
+from django.utils.cache import patch_vary_headers
+import datetime
+
+TEST_COOKIE_NAME = 'testcookie'
+TEST_COOKIE_VALUE = 'worked'
+
+class SessionWrapper(object):
+ def __init__(self, session_key):
+ self.session_key = session_key
+ self.accessed = False
+ self.modified = False
+
+ def __contains__(self, key):
+ return key in self._session
+
+ def __getitem__(self, key):
+ return self._session[key]
+
+ def __setitem__(self, key, value):
+ self._session[key] = value
+ self.modified = True
+
+ def __delitem__(self, key):
+ del self._session[key]
+ self.modified = True
+
+ def keys(self):
+ return self._session.keys()
+
+ def items(self):
+ return self._session.items()
+
+ def get(self, key, default=None):
+ return self._session.get(key, default)
+
+ def set_test_cookie(self):
+ self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
+
+ def test_cookie_worked(self):
+ return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
+
+ def delete_test_cookie(self):
+ del self[TEST_COOKIE_NAME]
+
+ def _get_session(self):
+ # Lazily loads session from storage.
+ self.accessed = True
+ try:
+ return self._session_cache
+ except AttributeError:
+ if self.session_key is None:
+ self._session_cache = {}
+ else:
+ try:
+ s = Session.objects.get(session_key=self.session_key,
+ expire_date__gt=datetime.datetime.now())
+ self._session_cache = s.get_decoded()
+ except (Session.DoesNotExist, SuspiciousOperation):
+ self._session_cache = {}
+ # Set the session_key to None to force creation of a new
+ # key, for extra security.
+ self.session_key = None
+ return self._session_cache
+
+ _session = property(_get_session)
+
+class SessionMiddleware(object):
+ def process_request(self, request):
+ request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
+
+ def process_response(self, request, response):
+ # If request.session was modified, or if response.session was set, save
+ # those changes and set a session cookie.
+ try:
+ accessed = request.session.accessed
+ modified = request.session.modified
+ except AttributeError:
+ pass
+ else:
+ if accessed:
+ patch_vary_headers(response, ('Cookie',))
+ if modified or settings.SESSION_SAVE_EVERY_REQUEST:
+ if request.session.session_key:
+ session_key = request.session.session_key
+ else:
+ obj = Session.objects.get_new_session_object()
+ session_key = obj.session_key
+
+ if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
+ max_age = None
+ expires = None
+ else:
+ max_age = settings.SESSION_COOKIE_AGE
+ expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
+ new_session = Session.objects.save(session_key, request.session._session,
+ datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
+ response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
+ max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
+ secure=settings.SESSION_COOKIE_SECURE or None)
+ return response
diff --git a/google_appengine/lib/django/django/contrib/sessions/models.py b/google_appengine/lib/django/django/contrib/sessions/models.py
new file mode 100755
index 0000000..7771840
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sessions/models.py
@@ -0,0 +1,88 @@
+import base64, md5, random, sys, datetime
+import cPickle as pickle
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+from django.conf import settings
+
+class SessionManager(models.Manager):
+ def encode(self, session_dict):
+ "Returns the given session dictionary pickled and encoded as a string."
+ pickled = pickle.dumps(session_dict)
+ pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
+ return base64.encodestring(pickled + pickled_md5)
+
+ def get_new_session_key(self):
+ "Returns session key that isn't being used."
+ # The random module is seeded when this Apache child is created.
+ # Use person_id and SECRET_KEY as added salt.
+ while 1:
+ session_key = md5.new(str(random.randint(0, sys.maxint - 1)) + str(random.randint(0, sys.maxint - 1)) + settings.SECRET_KEY).hexdigest()
+ try:
+ self.get(session_key=session_key)
+ except self.model.DoesNotExist:
+ break
+ return session_key
+
+ def get_new_session_object(self):
+ """
+ Returns a new session object.
+ """
+ # FIXME: There is a *small* chance of collision here, meaning we will
+ # return an existing object. That can be fixed when we add a way to
+ # validate (and guarantee) that non-auto primary keys are unique. For
+ # now, we save immediately in order to reduce the "window of
+ # misfortune" as much as possible.
+ created = False
+ while not created:
+ obj, created = self.get_or_create(session_key=self.get_new_session_key(),
+ expire_date = datetime.datetime.now())
+ # Collision in key generation, so re-seed the generator
+ random.seed()
+ return obj
+
+ def save(self, session_key, session_dict, expire_date):
+ s = self.model(session_key, self.encode(session_dict), expire_date)
+ if session_dict:
+ s.save()
+ else:
+ s.delete() # Clear sessions with no data.
+ return s
+
+class Session(models.Model):
+ """
+ Django provides full support for anonymous sessions. The session
+ framework lets you store and retrieve arbitrary data on a
+ per-site-visitor basis. It stores data on the server side and
+ abstracts the sending and receiving of cookies. Cookies contain a
+ session ID -- not the data itself.
+
+ The Django sessions framework is entirely cookie-based. It does
+ not fall back to putting session IDs in URLs. This is an intentional
+ design decision. Not only does that behavior make URLs ugly, it makes
+ your site vulnerable to session-ID theft via the "Referer" header.
+
+ For complete documentation on using Sessions in your code, consult
+ the sessions documentation that is shipped with Django (also available
+ on the Django website).
+ """
+ session_key = models.CharField(_('session key'), maxlength=40, primary_key=True)
+ session_data = models.TextField(_('session data'))
+ expire_date = models.DateTimeField(_('expire date'))
+ objects = SessionManager()
+ class Meta:
+ db_table = 'django_session'
+ verbose_name = _('session')
+ verbose_name_plural = _('sessions')
+
+ def get_decoded(self):
+ encoded_data = base64.decodestring(self.session_data)
+ pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
+ if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
+ from django.core.exceptions import SuspiciousOperation
+ raise SuspiciousOperation, "User tampered with session cookie."
+ try:
+ return pickle.loads(pickled)
+ # Unpickling can cause a variety of exceptions. If something happens,
+ # just return an empty dictionary (an empty session).
+ except:
+ return {}
diff --git a/google_appengine/lib/django/django/contrib/sitemaps/__init__.py b/google_appengine/lib/django/django/contrib/sitemaps/__init__.py
new file mode 100755
index 0000000..44ede44
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sitemaps/__init__.py
@@ -0,0 +1,90 @@
+from django.core import urlresolvers
+import urllib
+
+PING_URL = "http://www.google.com/webmasters/sitemaps/ping"
+
+class SitemapNotFound(Exception):
+ pass
+
+def ping_google(sitemap_url=None, ping_url=PING_URL):
+ """
+ Alerts Google that the sitemap for the current site has been updated.
+ If sitemap_url is provided, it should be an absolute path to the sitemap
+ for this site -- e.g., '/sitemap.xml'. If sitemap_url is not provided, this
+ function will attempt to deduce it by using urlresolvers.reverse().
+ """
+ if sitemap_url is None:
+ try:
+ # First, try to get the "index" sitemap URL.
+ sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.index')
+ except urlresolvers.NoReverseMatch:
+ try:
+ # Next, try for the "global" sitemap URL.
+ sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap')
+ except urlresolvers.NoReverseMatch:
+ pass
+
+ if sitemap_url is None:
+ raise SitemapNotFound("You didn't provide a sitemap_url, and the sitemap URL couldn't be auto-detected.")
+
+ from django.contrib.sites.models import Site
+ current_site = Site.objects.get_current()
+ url = "%s%s" % (current_site.domain, sitemap_url)
+ params = urllib.urlencode({'sitemap':url})
+ urllib.urlopen("%s?%s" % (ping_url, params))
+
+class Sitemap:
+ def __get(self, name, obj, default=None):
+ try:
+ attr = getattr(self, name)
+ except AttributeError:
+ return default
+ if callable(attr):
+ return attr(obj)
+ return attr
+
+ def items(self):
+ return []
+
+ def location(self, obj):
+ return obj.get_absolute_url()
+
+ def get_urls(self):
+ from django.contrib.sites.models import Site
+ current_site = Site.objects.get_current()
+ urls = []
+ for item in self.items():
+ loc = "http://%s%s" % (current_site.domain, self.__get('location', item))
+ url_info = {
+ 'location': loc,
+ 'lastmod': self.__get('lastmod', item, None),
+ 'changefreq': self.__get('changefreq', item, None),
+ 'priority': self.__get('priority', item, None)
+ }
+ urls.append(url_info)
+ return urls
+
+class FlatPageSitemap(Sitemap):
+ def items(self):
+ from django.contrib.sites.models import Site
+ current_site = Site.objects.get_current()
+ return current_site.flatpage_set.all()
+
+class GenericSitemap(Sitemap):
+ priority = None
+ changefreq = None
+
+ def __init__(self, info_dict, priority=None, changefreq=None):
+ self.queryset = info_dict['queryset']
+ self.date_field = info_dict.get('date_field', None)
+ self.priority = priority
+ self.changefreq = changefreq
+
+ def items(self):
+ # Make sure to return a clone; we don't want premature evaluation.
+ return self.queryset.filter()
+
+ def lastmod(self, item):
+ if self.date_field is not None:
+ return getattr(item, self.date_field)
+ return None
diff --git a/google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap.xml b/google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap.xml
new file mode 100644
index 0000000..16d9a0b
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+{% spaceless %}
+{% for url in urlset %}
+ <url>
+ <loc>{{ url.location|escape }}</loc>
+ {% if url.lastmod %}<lastmod>{{ url.lastmod|date:"Y-m-d" }}</lastmod>{% endif %}
+ {% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
+ {% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
+ </url>
+{% endfor %}
+{% endspaceless %}
+</urlset>
diff --git a/google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap_index.xml b/google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap_index.xml
new file mode 100644
index 0000000..a2bcce8
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap_index.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
+</sitemapindex>
diff --git a/google_appengine/lib/django/django/contrib/sitemaps/views.py b/google_appengine/lib/django/django/contrib/sitemaps/views.py
new file mode 100755
index 0000000..576e3d0
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sitemaps/views.py
@@ -0,0 +1,30 @@
+from django.http import HttpResponse, Http404
+from django.template import loader
+from django.contrib.sites.models import Site
+from django.core import urlresolvers
+
+def index(request, sitemaps):
+ current_site = Site.objects.get_current()
+ sites = []
+ protocol = request.is_secure() and 'https' or 'http'
+ for section in sitemaps.keys():
+ sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': section})
+ sites.append('%s://%s%s' % (protocol, current_site.domain, sitemap_url))
+ xml = loader.render_to_string('sitemap_index.xml', {'sitemaps': sites})
+ return HttpResponse(xml, mimetype='application/xml')
+
+def sitemap(request, sitemaps, section=None):
+ maps, urls = [], []
+ if section is not None:
+ if not sitemaps.has_key(section):
+ raise Http404("No sitemap available for section: %r" % section)
+ maps.append(sitemaps[section])
+ else:
+ maps = sitemaps.values()
+ for site in maps:
+ if callable(site):
+ urls.extend(site().get_urls())
+ else:
+ urls.extend(site.get_urls())
+ xml = loader.render_to_string('sitemap.xml', {'urlset': urls})
+ return HttpResponse(xml, mimetype='application/xml')
diff --git a/google_appengine/lib/django/django/contrib/sites/__init__.py b/google_appengine/lib/django/django/contrib/sites/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sites/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/sites/management.py b/google_appengine/lib/django/django/contrib/sites/management.py
new file mode 100755
index 0000000..6831cab
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sites/management.py
@@ -0,0 +1,17 @@
+"""
+Creates the default Site object.
+"""
+
+from django.dispatch import dispatcher
+from django.db.models import signals
+from django.contrib.sites.models import Site
+from django.contrib.sites import models as site_app
+
+def create_default_site(app, created_models, verbosity):
+ if Site in created_models:
+ if verbosity >= 2:
+ print "Creating example.com Site object"
+ s = Site(domain="example.com", name="example.com")
+ s.save()
+
+dispatcher.connect(create_default_site, sender=site_app, signal=signals.post_syncdb)
diff --git a/google_appengine/lib/django/django/contrib/sites/managers.py b/google_appengine/lib/django/django/contrib/sites/managers.py
new file mode 100755
index 0000000..d49244f
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sites/managers.py
@@ -0,0 +1,20 @@
+from django.conf import settings
+from django.db import models
+from django.db.models.fields import FieldDoesNotExist
+
+class CurrentSiteManager(models.Manager):
+ "Use this to limit objects to those associated with the current site."
+ def __init__(self, field_name='site'):
+ super(CurrentSiteManager, self).__init__()
+ self.__field_name = field_name
+ self.__is_validated = False
+
+ def get_query_set(self):
+ if not self.__is_validated:
+ try:
+ self.model._meta.get_field(self.__field_name)
+ except FieldDoesNotExist:
+ raise ValueError, "%s couldn't find a field named %s in %s." % \
+ (self.__class__.__name__, self.__field_name, self.model._meta.object_name)
+ self.__is_validated = True
+ return super(CurrentSiteManager, self).get_query_set().filter(**{self.__field_name + '__id__exact': settings.SITE_ID})
diff --git a/google_appengine/lib/django/django/contrib/sites/models.py b/google_appengine/lib/django/django/contrib/sites/models.py
new file mode 100755
index 0000000..df93c54
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/sites/models.py
@@ -0,0 +1,23 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+class SiteManager(models.Manager):
+ def get_current(self):
+ from django.conf import settings
+ return self.get(pk=settings.SITE_ID)
+
+class Site(models.Model):
+ domain = models.CharField(_('domain name'), maxlength=100)
+ name = models.CharField(_('display name'), maxlength=50)
+ objects = SiteManager()
+ class Meta:
+ db_table = 'django_site'
+ verbose_name = _('site')
+ verbose_name_plural = _('sites')
+ ordering = ('domain',)
+ class Admin:
+ list_display = ('domain', 'name')
+ search_fields = ('domain', 'name')
+
+ def __str__(self):
+ return self.domain
diff --git a/google_appengine/lib/django/django/contrib/syndication/__init__.py b/google_appengine/lib/django/django/contrib/syndication/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/syndication/__init__.py
diff --git a/google_appengine/lib/django/django/contrib/syndication/feeds.py b/google_appengine/lib/django/django/contrib/syndication/feeds.py
new file mode 100755
index 0000000..cdb4e81
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/syndication/feeds.py
@@ -0,0 +1,122 @@
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
+from django.template import Context, loader, Template, TemplateDoesNotExist
+from django.contrib.sites.models import Site
+from django.utils import feedgenerator
+from django.conf import settings
+
+def add_domain(domain, url):
+ if not url.startswith('http://'):
+ url = u'http://%s%s' % (domain, url)
+ return url
+
+class FeedDoesNotExist(ObjectDoesNotExist):
+ pass
+
+class Feed(object):
+ item_pubdate = None
+ item_enclosure_url = None
+ feed_type = feedgenerator.DefaultFeed
+ title_template = None
+ description_template = None
+
+ def __init__(self, slug, feed_url):
+ self.slug = slug
+ self.feed_url = feed_url
+ self.title_template_name = self.title_template or ('feeds/%s_title.html' % slug)
+ self.description_template_name = self.description_template or ('feeds/%s_description.html' % slug)
+
+ def item_link(self, item):
+ try:
+ return item.get_absolute_url()
+ except AttributeError:
+ raise ImproperlyConfigured, "Give your %s class a get_absolute_url() method, or define an item_link() method in your Feed class." % item.__class__.__name__
+
+ def __get_dynamic_attr(self, attname, obj, default=None):
+ try:
+ attr = getattr(self, attname)
+ except AttributeError:
+ return default
+ if callable(attr):
+ # Check func_code.co_argcount rather than try/excepting the
+ # function and catching the TypeError, because something inside
+ # the function may raise the TypeError. This technique is more
+ # accurate.
+ if hasattr(attr, 'func_code'):
+ argcount = attr.func_code.co_argcount
+ else:
+ argcount = attr.__call__.func_code.co_argcount
+ if argcount == 2: # one argument is 'self'
+ return attr(obj)
+ else:
+ return attr()
+ return attr
+
+ def get_feed(self, url=None):
+ """
+ Returns a feedgenerator.DefaultFeed object, fully populated, for
+ this feed. Raises FeedDoesNotExist for invalid parameters.
+ """
+ if url:
+ try:
+ obj = self.get_object(url.split('/'))
+ except (AttributeError, ObjectDoesNotExist):
+ raise FeedDoesNotExist
+ else:
+ obj = None
+
+ current_site = Site.objects.get_current()
+ link = self.__get_dynamic_attr('link', obj)
+ link = add_domain(current_site.domain, link)
+
+ feed = self.feed_type(
+ title = self.__get_dynamic_attr('title', obj),
+ link = link,
+ description = self.__get_dynamic_attr('description', obj),
+ language = settings.LANGUAGE_CODE.decode(),
+ feed_url = add_domain(current_site, self.__get_dynamic_attr('feed_url', obj)),
+ author_name = self.__get_dynamic_attr('author_name', obj),
+ author_link = self.__get_dynamic_attr('author_link', obj),
+ author_email = self.__get_dynamic_attr('author_email', obj),
+ categories = self.__get_dynamic_attr('categories', obj),
+ feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
+ )
+
+ try:
+ title_tmp = loader.get_template(self.title_template_name)
+ except TemplateDoesNotExist:
+ title_tmp = Template('{{ obj }}')
+ try:
+ description_tmp = loader.get_template(self.description_template_name)
+ except TemplateDoesNotExist:
+ description_tmp = Template('{{ obj }}')
+
+ for item in self.__get_dynamic_attr('items', obj):
+ link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item))
+ enc = None
+ enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
+ if enc_url:
+ enc = feedgenerator.Enclosure(
+ url = enc_url.decode('utf-8'),
+ length = str(self.__get_dynamic_attr('item_enclosure_length', item)).decode('utf-8'),
+ mime_type = self.__get_dynamic_attr('item_enclosure_mime_type', item).decode('utf-8'),
+ )
+ author_name = self.__get_dynamic_attr('item_author_name', item)
+ if author_name is not None:
+ author_email = self.__get_dynamic_attr('item_author_email', item)
+ author_link = self.__get_dynamic_attr('item_author_link', item)
+ else:
+ author_email = author_link = None
+ feed.add_item(
+ title = title_tmp.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
+ link = link,
+ description = description_tmp.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
+ unique_id = link,
+ enclosure = enc,
+ pubdate = self.__get_dynamic_attr('item_pubdate', item),
+ author_name = author_name,
+ author_email = author_email,
+ author_link = author_link,
+ categories = self.__get_dynamic_attr('item_categories', item),
+ item_copyright = self.__get_dynamic_attr('item_copyright', item),
+ )
+ return feed
diff --git a/google_appengine/lib/django/django/contrib/syndication/views.py b/google_appengine/lib/django/django/contrib/syndication/views.py
new file mode 100755
index 0000000..621665d
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/syndication/views.py
@@ -0,0 +1,25 @@
+from django.contrib.syndication import feeds
+from django.http import HttpResponse, Http404
+
+def feed(request, url, feed_dict=None):
+ if not feed_dict:
+ raise Http404, "No feeds are registered."
+
+ try:
+ slug, param = url.split('/', 1)
+ except ValueError:
+ slug, param = url, ''
+
+ try:
+ f = feed_dict[slug]
+ except KeyError:
+ raise Http404, "Slug %r isn't registered." % slug
+
+ try:
+ feedgen = f(slug, request.path).get_feed(param)
+ except feeds.FeedDoesNotExist:
+ raise Http404, "Invalid feed parameters. Slug %r is valid, but other parameters, or lack thereof, are not." % slug
+
+ response = HttpResponse(mimetype=feedgen.mime_type)
+ feedgen.write(response, 'utf-8')
+ return response
diff --git a/google_appengine/lib/django/django/core/__init__.py b/google_appengine/lib/django/django/core/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/core/__init__.py
diff --git a/google_appengine/lib/django/django/core/__init__.pyc b/google_appengine/lib/django/django/core/__init__.pyc
new file mode 100644
index 0000000..0131802
--- /dev/null
+++ b/google_appengine/lib/django/django/core/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/core/cache/__init__.py b/google_appengine/lib/django/django/core/cache/__init__.py
new file mode 100755
index 0000000..6da8e88
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/__init__.py
@@ -0,0 +1,54 @@
+"""
+Caching framework.
+
+This package defines set of cache backends that all conform to a simple API.
+In a nutshell, a cache is a set of values -- which can be any object that
+may be pickled -- identified by string keys. For the complete API, see
+the abstract BaseCache class in django.core.cache.backends.base.
+
+Client code should not access a cache backend directly; instead it should
+either use the "cache" variable made available here, or it should use the
+get_cache() function made available here. get_cache() takes a backend URI
+(e.g. "memcached://127.0.0.1:11211/") and returns an instance of a backend
+cache class.
+
+See docs/cache.txt for information on the public API.
+"""
+
+from cgi import parse_qsl
+from django.conf import settings
+from django.core.cache.backends.base import InvalidCacheBackendError
+
+BACKENDS = {
+ # name for use in settings file --> name of module in "backends" directory
+ 'memcached': 'memcached',
+ 'simple': 'simple',
+ 'locmem': 'locmem',
+ 'file': 'filebased',
+ 'db': 'db',
+ 'dummy': 'dummy',
+}
+
+def get_cache(backend_uri):
+ if backend_uri.find(':') == -1:
+ raise InvalidCacheBackendError, "Backend URI must start with scheme://"
+ scheme, rest = backend_uri.split(':', 1)
+ if not rest.startswith('//'):
+ raise InvalidCacheBackendError, "Backend URI must start with scheme://"
+ if scheme not in BACKENDS:
+ raise InvalidCacheBackendError, "%r is not a valid cache backend" % scheme
+
+ host = rest[2:]
+ qpos = rest.find('?')
+ if qpos != -1:
+ params = dict(parse_qsl(rest[qpos+1:]))
+ host = rest[2:qpos]
+ else:
+ params = {}
+ if host.endswith('/'):
+ host = host[:-1]
+
+ cache_class = getattr(__import__('django.core.cache.backends.%s' % BACKENDS[scheme], {}, {}, ['']), 'CacheClass')
+ return cache_class(host, params)
+
+cache = get_cache(settings.CACHE_BACKEND)
diff --git a/google_appengine/lib/django/django/core/cache/backends/__init__.py b/google_appengine/lib/django/django/core/cache/backends/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/__init__.py
diff --git a/google_appengine/lib/django/django/core/cache/backends/base.py b/google_appengine/lib/django/django/core/cache/backends/base.py
new file mode 100755
index 0000000..ef5f6a6
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/base.py
@@ -0,0 +1,56 @@
+"Base Cache class."
+
+from django.core.exceptions import ImproperlyConfigured
+
+class InvalidCacheBackendError(ImproperlyConfigured):
+ pass
+
+class BaseCache(object):
+ def __init__(self, params):
+ timeout = params.get('timeout', 300)
+ try:
+ timeout = int(timeout)
+ except (ValueError, TypeError):
+ timeout = 300
+ self.default_timeout = timeout
+
+ def get(self, key, default=None):
+ """
+ Fetch a given key from the cache. If the key does not exist, return
+ default, which itself defaults to None.
+ """
+ raise NotImplementedError
+
+ def set(self, key, value, timeout=None):
+ """
+ Set a value in the cache. If timeout is given, that timeout will be
+ used for the key; otherwise the default cache timeout will be used.
+ """
+ raise NotImplementedError
+
+ def delete(self, key):
+ """
+ Delete a key from the cache, failing silently.
+ """
+ raise NotImplementedError
+
+ def get_many(self, keys):
+ """
+ Fetch a bunch of keys from the cache. For certain backends (memcached,
+ pgsql) this can be *much* faster when fetching multiple values.
+
+ Returns a dict mapping each key in keys to its value. If the given
+ key is missing, it will be missing from the response dict.
+ """
+ d = {}
+ for k in keys:
+ val = self.get(k)
+ if val is not None:
+ d[k] = val
+ return d
+
+ def has_key(self, key):
+ """
+ Returns True if the key is in the cache and has not expired.
+ """
+ return self.get(key) is not None
diff --git a/google_appengine/lib/django/django/core/cache/backends/db.py b/google_appengine/lib/django/django/core/cache/backends/db.py
new file mode 100755
index 0000000..4a0d44a
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/db.py
@@ -0,0 +1,82 @@
+"Database cache backend."
+
+from django.core.cache.backends.base import BaseCache
+from django.db import connection, transaction, DatabaseError
+import base64, time
+from datetime import datetime
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+class CacheClass(BaseCache):
+ def __init__(self, table, params):
+ BaseCache.__init__(self, params)
+ self._table = table
+ max_entries = params.get('max_entries', 300)
+ try:
+ self._max_entries = int(max_entries)
+ except (ValueError, TypeError):
+ self._max_entries = 300
+ cull_frequency = params.get('cull_frequency', 3)
+ try:
+ self._cull_frequency = int(cull_frequency)
+ except (ValueError, TypeError):
+ self._cull_frequency = 3
+
+ def get(self, key, default=None):
+ cursor = connection.cursor()
+ cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
+ row = cursor.fetchone()
+ if row is None:
+ return default
+ now = datetime.now()
+ if row[2] < now:
+ cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
+ transaction.commit_unless_managed()
+ return default
+ return pickle.loads(base64.decodestring(row[1]))
+
+ def set(self, key, value, timeout=None):
+ if timeout is None:
+ timeout = self.default_timeout
+ cursor = connection.cursor()
+ cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
+ num = cursor.fetchone()[0]
+ now = datetime.now().replace(microsecond=0)
+ exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
+ if num > self._max_entries:
+ self._cull(cursor, now)
+ encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
+ cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
+ try:
+ if cursor.fetchone():
+ cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key])
+ else:
+ cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)])
+ except DatabaseError:
+ # To be threadsafe, updates/inserts are allowed to fail silently
+ pass
+ else:
+ transaction.commit_unless_managed()
+
+ def delete(self, key):
+ cursor = connection.cursor()
+ cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
+ transaction.commit_unless_managed()
+
+ def has_key(self, key):
+ cursor = connection.cursor()
+ cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
+ return cursor.fetchone() is not None
+
+ def _cull(self, cursor, now):
+ if self._cull_frequency == 0:
+ cursor.execute("DELETE FROM %s" % self._table)
+ else:
+ cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, [str(now)])
+ cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
+ num = cursor.fetchone()[0]
+ if num > self._max_entries:
+ cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency])
+ cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % self._table, [cursor.fetchone()[0]])
diff --git a/google_appengine/lib/django/django/core/cache/backends/dummy.py b/google_appengine/lib/django/django/core/cache/backends/dummy.py
new file mode 100755
index 0000000..4c64161
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/dummy.py
@@ -0,0 +1,22 @@
+"Dummy cache backend"
+
+from django.core.cache.backends.base import BaseCache
+
+class CacheClass(BaseCache):
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def get(self, key, default=None):
+ return default
+
+ def set(self, *args, **kwargs):
+ pass
+
+ def delete(self, *args, **kwargs):
+ pass
+
+ def get_many(self, *args, **kwargs):
+ return {}
+
+ def has_key(self, *args, **kwargs):
+ return False
diff --git a/google_appengine/lib/django/django/core/cache/backends/filebased.py b/google_appengine/lib/django/django/core/cache/backends/filebased.py
new file mode 100755
index 0000000..faaf891
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/filebased.py
@@ -0,0 +1,80 @@
+"File-based cache backend"
+
+from django.core.cache.backends.simple import CacheClass as SimpleCacheClass
+import os, time, urllib
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+class CacheClass(SimpleCacheClass):
+ def __init__(self, dir, params):
+ self._dir = dir
+ if not os.path.exists(self._dir):
+ self._createdir()
+ SimpleCacheClass.__init__(self, dir, params)
+ del self._cache
+ del self._expire_info
+
+ def get(self, key, default=None):
+ fname = self._key_to_file(key)
+ try:
+ f = open(fname, 'rb')
+ exp = pickle.load(f)
+ now = time.time()
+ if exp < now:
+ f.close()
+ os.remove(fname)
+ else:
+ return pickle.load(f)
+ except (IOError, OSError, EOFError, pickle.PickleError):
+ pass
+ return default
+
+ def set(self, key, value, timeout=None):
+ fname = self._key_to_file(key)
+ if timeout is None:
+ timeout = self.default_timeout
+ try:
+ filelist = os.listdir(self._dir)
+ except (IOError, OSError):
+ self._createdir()
+ filelist = []
+ if len(filelist) > self._max_entries:
+ self._cull(filelist)
+ try:
+ f = open(fname, 'wb')
+ now = time.time()
+ pickle.dump(now + timeout, f, 2)
+ pickle.dump(value, f, 2)
+ except (IOError, OSError):
+ pass
+
+ def delete(self, key):
+ try:
+ os.remove(self._key_to_file(key))
+ except (IOError, OSError):
+ pass
+
+ def has_key(self, key):
+ return os.path.exists(self._key_to_file(key))
+
+ def _cull(self, filelist):
+ if self._cull_frequency == 0:
+ doomed = filelist
+ else:
+ doomed = [k for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
+ for fname in doomed:
+ try:
+ os.remove(os.path.join(self._dir, fname))
+ except (IOError, OSError):
+ pass
+
+ def _createdir(self):
+ try:
+ os.makedirs(self._dir)
+ except OSError:
+ raise EnvironmentError, "Cache directory '%s' does not exist and could not be created'" % self._dir
+
+ def _key_to_file(self, key):
+ return os.path.join(self._dir, urllib.quote_plus(key))
diff --git a/google_appengine/lib/django/django/core/cache/backends/locmem.py b/google_appengine/lib/django/django/core/cache/backends/locmem.py
new file mode 100755
index 0000000..0e21b80
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/locmem.py
@@ -0,0 +1,47 @@
+"Thread-safe in-memory cache backend."
+
+from django.core.cache.backends.simple import CacheClass as SimpleCacheClass
+from django.utils.synch import RWLock
+import copy, time
+
+class CacheClass(SimpleCacheClass):
+ def __init__(self, host, params):
+ SimpleCacheClass.__init__(self, host, params)
+ self._lock = RWLock()
+
+ def get(self, key, default=None):
+ should_delete = False
+ self._lock.reader_enters()
+ try:
+ now = time.time()
+ exp = self._expire_info.get(key)
+ if exp is None:
+ return default
+ elif exp < now:
+ should_delete = True
+ else:
+ return copy.deepcopy(self._cache[key])
+ finally:
+ self._lock.reader_leaves()
+ if should_delete:
+ self._lock.writer_enters()
+ try:
+ del self._cache[key]
+ del self._expire_info[key]
+ return default
+ finally:
+ self._lock.writer_leaves()
+
+ def set(self, key, value, timeout=None):
+ self._lock.writer_enters()
+ try:
+ SimpleCacheClass.set(self, key, value, timeout)
+ finally:
+ self._lock.writer_leaves()
+
+ def delete(self, key):
+ self._lock.writer_enters()
+ try:
+ SimpleCacheClass.delete(self, key)
+ finally:
+ self._lock.writer_leaves()
diff --git a/google_appengine/lib/django/django/core/cache/backends/memcached.py b/google_appengine/lib/django/django/core/cache/backends/memcached.py
new file mode 100755
index 0000000..180f95d
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/memcached.py
@@ -0,0 +1,29 @@
+"Memcached cache backend"
+
+from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
+
+try:
+ import memcache
+except ImportError:
+ raise InvalidCacheBackendError, "Memcached cache backend requires the 'memcache' library"
+
+class CacheClass(BaseCache):
+ def __init__(self, server, params):
+ BaseCache.__init__(self, params)
+ self._cache = memcache.Client(server.split(';'))
+
+ def get(self, key, default=None):
+ val = self._cache.get(key)
+ if val is None:
+ return default
+ else:
+ return val
+
+ def set(self, key, value, timeout=0):
+ self._cache.set(key, value, timeout or self.default_timeout)
+
+ def delete(self, key):
+ self._cache.delete(key)
+
+ def get_many(self, keys):
+ return self._cache.get_multi(keys)
diff --git a/google_appengine/lib/django/django/core/cache/backends/simple.py b/google_appengine/lib/django/django/core/cache/backends/simple.py
new file mode 100755
index 0000000..175944a
--- /dev/null
+++ b/google_appengine/lib/django/django/core/cache/backends/simple.py
@@ -0,0 +1,64 @@
+"Single-process in-memory cache backend."
+
+from django.core.cache.backends.base import BaseCache
+import time
+
+class CacheClass(BaseCache):
+ def __init__(self, host, params):
+ BaseCache.__init__(self, params)
+ self._cache = {}
+ self._expire_info = {}
+
+ max_entries = params.get('max_entries', 300)
+ try:
+ self._max_entries = int(max_entries)
+ except (ValueError, TypeError):
+ self._max_entries = 300
+
+ cull_frequency = params.get('cull_frequency', 3)
+ try:
+ self._cull_frequency = int(cull_frequency)
+ except (ValueError, TypeError):
+ self._cull_frequency = 3
+
+ def get(self, key, default=None):
+ now = time.time()
+ exp = self._expire_info.get(key)
+ if exp is None:
+ return default
+ elif exp < now:
+ del self._cache[key]
+ del self._expire_info[key]
+ return default
+ else:
+ return self._cache[key]
+
+ def set(self, key, value, timeout=None):
+ if len(self._cache) >= self._max_entries:
+ self._cull()
+ if timeout is None:
+ timeout = self.default_timeout
+ self._cache[key] = value
+ self._expire_info[key] = time.time() + timeout
+
+ def delete(self, key):
+ try:
+ del self._cache[key]
+ except KeyError:
+ pass
+ try:
+ del self._expire_info[key]
+ except KeyError:
+ pass
+
+ def has_key(self, key):
+ return self._cache.has_key(key)
+
+ def _cull(self):
+ if self._cull_frequency == 0:
+ self._cache.clear()
+ self._expire_info.clear()
+ else:
+ doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
+ for k in doomed:
+ self.delete(k)
diff --git a/google_appengine/lib/django/django/core/context_processors.py b/google_appengine/lib/django/django/core/context_processors.py
new file mode 100755
index 0000000..f4b288d
--- /dev/null
+++ b/google_appengine/lib/django/django/core/context_processors.py
@@ -0,0 +1,69 @@
+"""
+A set of request processors that return dictionaries to be merged into a
+template context. Each function takes the request object as its only parameter
+and returns a dictionary to add to the context.
+
+These are referenced from the setting TEMPLATE_CONTEXT_PROCESSORS and used by
+RequestContext.
+"""
+
+from django.conf import settings
+
+def auth(request):
+ """
+ Returns context variables required by apps that use Django's authentication
+ system.
+ """
+ return {
+ 'user': request.user,
+ 'messages': request.user.get_and_delete_messages(),
+ 'perms': PermWrapper(request.user),
+ }
+
+def debug(request):
+ "Returns context variables helpful for debugging."
+ context_extras = {}
+ if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
+ context_extras['debug'] = True
+ from django.db import connection
+ context_extras['sql_queries'] = connection.queries
+ return context_extras
+
+def i18n(request):
+ context_extras = {}
+ context_extras['LANGUAGES'] = settings.LANGUAGES
+ if hasattr(request, 'LANGUAGE_CODE'):
+ context_extras['LANGUAGE_CODE'] = request.LANGUAGE_CODE
+ else:
+ context_extras['LANGUAGE_CODE'] = settings.LANGUAGE_CODE
+
+ from django.utils import translation
+ context_extras['LANGUAGE_BIDI'] = translation.get_language_bidi()
+
+ return context_extras
+
+def request(request):
+ return {'request': request}
+
+# PermWrapper and PermLookupDict proxy the permissions system into objects that
+# the template system can understand.
+
+class PermLookupDict(object):
+ def __init__(self, user, module_name):
+ self.user, self.module_name = user, module_name
+
+ def __repr__(self):
+ return str(self.user.get_all_permissions())
+
+ def __getitem__(self, perm_name):
+ return self.user.has_perm("%s.%s" % (self.module_name, perm_name))
+
+ def __nonzero__(self):
+ return self.user.has_module_perms(self.module_name)
+
+class PermWrapper(object):
+ def __init__(self, user):
+ self.user = user
+
+ def __getitem__(self, module_name):
+ return PermLookupDict(self.user, module_name)
diff --git a/google_appengine/lib/django/django/core/exceptions.py b/google_appengine/lib/django/django/core/exceptions.py
new file mode 100755
index 0000000..f22f67c
--- /dev/null
+++ b/google_appengine/lib/django/django/core/exceptions.py
@@ -0,0 +1,25 @@
+"Global Django exceptions"
+
+class ObjectDoesNotExist(Exception):
+ "The requested object does not exist"
+ silent_variable_failure = True
+
+class SuspiciousOperation(Exception):
+ "The user did something suspicious"
+ pass
+
+class PermissionDenied(Exception):
+ "The user did not have permission to do that"
+ pass
+
+class ViewDoesNotExist(Exception):
+ "The requested view does not exist"
+ pass
+
+class MiddlewareNotUsed(Exception):
+ "This middleware is not used in this server configuration"
+ pass
+
+class ImproperlyConfigured(Exception):
+ "Django is somehow improperly configured"
+ pass
diff --git a/google_appengine/lib/django/django/core/exceptions.pyc b/google_appengine/lib/django/django/core/exceptions.pyc
new file mode 100644
index 0000000..52f0758
--- /dev/null
+++ b/google_appengine/lib/django/django/core/exceptions.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/core/handler.py b/google_appengine/lib/django/django/core/handler.py
new file mode 100755
index 0000000..0394067
--- /dev/null
+++ b/google_appengine/lib/django/django/core/handler.py
@@ -0,0 +1,11 @@
+# This module is DEPRECATED!
+#
+# You should no longer be pointing your mod_python configuration
+# at "django.core.handler".
+#
+# Use "django.core.handlers.modpython" instead.
+
+from django.core.handlers.modpython import ModPythonHandler
+
+def handler(req):
+ return ModPythonHandler()(req)
diff --git a/google_appengine/lib/django/django/core/handlers/__init__.py b/google_appengine/lib/django/django/core/handlers/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/core/handlers/__init__.py
diff --git a/google_appengine/lib/django/django/core/handlers/base.py b/google_appengine/lib/django/django/core/handlers/base.py
new file mode 100755
index 0000000..ca48b30
--- /dev/null
+++ b/google_appengine/lib/django/django/core/handlers/base.py
@@ -0,0 +1,131 @@
+from django.core import signals
+from django.dispatch import dispatcher
+from django import http
+import sys
+
+class BaseHandler(object):
+ def __init__(self):
+ self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
+
+ def load_middleware(self):
+ """
+ Populate middleware lists from settings.MIDDLEWARE_CLASSES.
+
+ Must be called after the environment is fixed (see __call__).
+ """
+ from django.conf import settings
+ from django.core import exceptions
+ self._request_middleware = []
+ self._view_middleware = []
+ self._response_middleware = []
+ self._exception_middleware = []
+ for middleware_path in settings.MIDDLEWARE_CLASSES:
+ try:
+ dot = middleware_path.rindex('.')
+ except ValueError:
+ raise exceptions.ImproperlyConfigured, '%s isn\'t a middleware module' % middleware_path
+ mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
+ try:
+ mod = __import__(mw_module, {}, {}, [''])
+ except ImportError, e:
+ raise exceptions.ImproperlyConfigured, 'Error importing middleware %s: "%s"' % (mw_module, e)
+ try:
+ mw_class = getattr(mod, mw_classname)
+ except AttributeError:
+ raise exceptions.ImproperlyConfigured, 'Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)
+
+ try:
+ mw_instance = mw_class()
+ except exceptions.MiddlewareNotUsed:
+ continue
+
+ if hasattr(mw_instance, 'process_request'):
+ self._request_middleware.append(mw_instance.process_request)
+ if hasattr(mw_instance, 'process_view'):
+ self._view_middleware.append(mw_instance.process_view)
+ if hasattr(mw_instance, 'process_response'):
+ self._response_middleware.insert(0, mw_instance.process_response)
+ if hasattr(mw_instance, 'process_exception'):
+ self._exception_middleware.insert(0, mw_instance.process_exception)
+
+ def get_response(self, request):
+ "Returns an HttpResponse object for the given HttpRequest"
+ from django.core import exceptions, urlresolvers
+ from django.core.mail import mail_admins
+ from django.conf import settings
+
+ # Apply request middleware
+ for middleware_method in self._request_middleware:
+ response = middleware_method(request)
+ if response:
+ return response
+
+ # Get urlconf from request object, if available. Otherwise use default.
+ urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
+
+ resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
+ try:
+ callback, callback_args, callback_kwargs = resolver.resolve(request.path)
+
+ # Apply view middleware
+ for middleware_method in self._view_middleware:
+ response = middleware_method(request, callback, callback_args, callback_kwargs)
+ if response:
+ return response
+
+ try:
+ response = callback(request, *callback_args, **callback_kwargs)
+ except Exception, e:
+ # If the view raised an exception, run it through exception
+ # middleware, and if the exception middleware returns a
+ # response, use that. Otherwise, reraise the exception.
+ for middleware_method in self._exception_middleware:
+ response = middleware_method(request, e)
+ if response:
+ return response
+ raise
+
+ # Complain if the view returned None (a common error).
+ if response is None:
+ try:
+ view_name = callback.func_name # If it's a function
+ except AttributeError:
+ view_name = callback.__class__.__name__ + '.__call__' # If it's a class
+ raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
+
+ return response
+ except http.Http404, e:
+ if settings.DEBUG:
+ from django.views import debug
+ return debug.technical_404_response(request, e)
+ else:
+ callback, param_dict = resolver.resolve404()
+ return callback(request, **param_dict)
+ except exceptions.PermissionDenied:
+ return http.HttpResponseForbidden('<h1>Permission denied</h1>')
+ except SystemExit:
+ pass # See http://code.djangoproject.com/ticket/1023
+ except: # Handle everything else, including SuspiciousOperation, etc.
+ if settings.DEBUG:
+ from django.views import debug
+ return debug.technical_500_response(request, *sys.exc_info())
+ else:
+ # Get the exception info now, in case another exception is thrown later.
+ exc_info = sys.exc_info()
+ receivers = dispatcher.send(signal=signals.got_request_exception)
+ # When DEBUG is False, send an error message to the admins.
+ subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
+ try:
+ request_repr = repr(request)
+ except:
+ request_repr = "Request repr() unavailable"
+ message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr)
+ mail_admins(subject, message, fail_silently=True)
+ # Return an HttpResponse that displays a friendly error message.
+ callback, param_dict = resolver.resolve500()
+ return callback(request, **param_dict)
+
+ def _get_traceback(self, exc_info=None):
+ "Helper function to return the traceback as a string"
+ import traceback
+ return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
diff --git a/google_appengine/lib/django/django/core/handlers/modpython.py b/google_appengine/lib/django/django/core/handlers/modpython.py
new file mode 100755
index 0000000..5fc41a0
--- /dev/null
+++ b/google_appengine/lib/django/django/core/handlers/modpython.py
@@ -0,0 +1,177 @@
+from django.core.handlers.base import BaseHandler
+from django.core import signals
+from django.dispatch import dispatcher
+from django.utils import datastructures
+from django import http
+from pprint import pformat
+import os
+
+# NOTE: do *not* import settings (or any module which eventually imports
+# settings) until after ModPythonHandler has been called; otherwise os.environ
+# won't be set up correctly (with respect to settings).
+
+class ModPythonRequest(http.HttpRequest):
+ def __init__(self, req):
+ self._req = req
+ self.path = req.uri
+
+ def __repr__(self):
+ # Since this is called as part of error handling, we need to be very
+ # robust against potentially malformed input.
+ try:
+ get = pformat(self.GET)
+ except:
+ get = '<could not parse>'
+ try:
+ post = pformat(self.POST)
+ except:
+ post = '<could not parse>'
+ try:
+ cookies = pformat(self.COOKIES)
+ except:
+ cookies = '<could not parse>'
+ try:
+ meta = pformat(self.META)
+ except:
+ meta = '<could not parse>'
+ return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
+ (self.path, get, post, cookies, meta)
+
+ def get_full_path(self):
+ return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
+
+ def is_secure(self):
+ # Note: modpython 3.2.10+ has req.is_https(), but we need to support previous versions
+ return self._req.subprocess_env.has_key('HTTPS') and self._req.subprocess_env['HTTPS'] == 'on'
+
+ def _load_post_and_files(self):
+ "Populates self._post and self._files"
+ if self._req.headers_in.has_key('content-type') and self._req.headers_in['content-type'].startswith('multipart'):
+ self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
+ else:
+ self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
+
+ def _get_request(self):
+ if not hasattr(self, '_request'):
+ self._request = datastructures.MergeDict(self.POST, self.GET)
+ return self._request
+
+ def _get_get(self):
+ if not hasattr(self, '_get'):
+ self._get = http.QueryDict(self._req.args)
+ return self._get
+
+ def _set_get(self, get):
+ self._get = get
+
+ def _get_post(self):
+ if not hasattr(self, '_post'):
+ self._load_post_and_files()
+ return self._post
+
+ def _set_post(self, post):
+ self._post = post
+
+ def _get_cookies(self):
+ if not hasattr(self, '_cookies'):
+ self._cookies = http.parse_cookie(self._req.headers_in.get('cookie', ''))
+ return self._cookies
+
+ def _set_cookies(self, cookies):
+ self._cookies = cookies
+
+ def _get_files(self):
+ if not hasattr(self, '_files'):
+ self._load_post_and_files()
+ return self._files
+
+ def _get_meta(self):
+ "Lazy loader that returns self.META dictionary"
+ if not hasattr(self, '_meta'):
+ self._meta = {
+ 'AUTH_TYPE': self._req.ap_auth_type,
+ 'CONTENT_LENGTH': self._req.clength, # This may be wrong
+ 'CONTENT_TYPE': self._req.content_type, # This may be wrong
+ 'GATEWAY_INTERFACE': 'CGI/1.1',
+ 'PATH_INFO': self._req.path_info,
+ 'PATH_TRANSLATED': None, # Not supported
+ 'QUERY_STRING': self._req.args,
+ 'REMOTE_ADDR': self._req.connection.remote_ip,
+ 'REMOTE_HOST': None, # DNS lookups not supported
+ 'REMOTE_IDENT': self._req.connection.remote_logname,
+ 'REMOTE_USER': self._req.user,
+ 'REQUEST_METHOD': self._req.method,
+ 'SCRIPT_NAME': None, # Not supported
+ 'SERVER_NAME': self._req.server.server_hostname,
+ 'SERVER_PORT': self._req.server.port,
+ 'SERVER_PROTOCOL': self._req.protocol,
+ 'SERVER_SOFTWARE': 'mod_python'
+ }
+ for key, value in self._req.headers_in.items():
+ key = 'HTTP_' + key.upper().replace('-', '_')
+ self._meta[key] = value
+ return self._meta
+
+ def _get_raw_post_data(self):
+ try:
+ return self._raw_post_data
+ except AttributeError:
+ self._raw_post_data = self._req.read()
+ return self._raw_post_data
+
+ def _get_method(self):
+ return self.META['REQUEST_METHOD'].upper()
+
+ GET = property(_get_get, _set_get)
+ POST = property(_get_post, _set_post)
+ COOKIES = property(_get_cookies, _set_cookies)
+ FILES = property(_get_files)
+ META = property(_get_meta)
+ REQUEST = property(_get_request)
+ raw_post_data = property(_get_raw_post_data)
+ method = property(_get_method)
+
+class ModPythonHandler(BaseHandler):
+ def __call__(self, req):
+ # mod_python fakes the environ, and thus doesn't process SetEnv. This fixes that
+ os.environ.update(req.subprocess_env)
+
+ # now that the environ works we can see the correct settings, so imports
+ # that use settings now can work
+ from django.conf import settings
+
+ # if we need to set up middleware, now that settings works we can do it now.
+ if self._request_middleware is None:
+ self.load_middleware()
+
+ dispatcher.send(signal=signals.request_started)
+ try:
+ request = ModPythonRequest(req)
+ response = self.get_response(request)
+
+ # Apply response middleware
+ for middleware_method in self._response_middleware:
+ response = middleware_method(request, response)
+
+ finally:
+ dispatcher.send(signal=signals.request_finished)
+
+ # Convert our custom HttpResponse object back into the mod_python req.
+ req.content_type = response['Content-Type']
+ for key, value in response.headers.items():
+ if key != 'Content-Type':
+ req.headers_out[key] = value
+ for c in response.cookies.values():
+ req.headers_out.add('Set-Cookie', c.output(header=''))
+ req.status = response.status_code
+ try:
+ for chunk in response:
+ req.write(chunk)
+ finally:
+ response.close()
+
+ return 0 # mod_python.apache.OK
+
+def handler(req):
+ # mod_python hooks into this function.
+ return ModPythonHandler()(req)
diff --git a/google_appengine/lib/django/django/core/handlers/profiler-hotshot.py b/google_appengine/lib/django/django/core/handlers/profiler-hotshot.py
new file mode 100755
index 0000000..6cf94b0
--- /dev/null
+++ b/google_appengine/lib/django/django/core/handlers/profiler-hotshot.py
@@ -0,0 +1,22 @@
+import hotshot, time, os
+from django.core.handlers.modpython import ModPythonHandler
+
+PROFILE_DATA_DIR = "/var/log/cmsprofile"
+
+def handler(req):
+ '''
+ Handler that uses hotshot to store profile data.
+
+ Stores profile data in PROFILE_DATA_DIR. Since hotshot has no way (that I
+ know of) to append profile data to a single file, each request gets its own
+ profile. The file names are in the format <url>.<n>.prof where <url> is
+ the request path with "/" replaced by ".", and <n> is a timestamp with
+ microseconds to prevent overwriting files.
+
+ Use the gather_profile_stats.py script to gather these individual request
+ profiles into aggregated profiles by request path.
+ '''
+ profname = "%s.%.3f.prof" % (req.uri.strip("/").replace('/', '.'), time.time())
+ profname = os.path.join(PROFILE_DATA_DIR, profname)
+ prof = hotshot.Profile(profname)
+ return prof.runcall(ModPythonHandler(), req)
diff --git a/google_appengine/lib/django/django/core/handlers/wsgi.py b/google_appengine/lib/django/django/core/handlers/wsgi.py
new file mode 100755
index 0000000..71cfecd
--- /dev/null
+++ b/google_appengine/lib/django/django/core/handlers/wsgi.py
@@ -0,0 +1,207 @@
+from django.core.handlers.base import BaseHandler
+from django.core import signals
+from django.dispatch import dispatcher
+from django.utils import datastructures
+from django import http
+from pprint import pformat
+from shutil import copyfileobj
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+STATUS_CODE_TEXT = {
+ 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: 'RESERVED',
+ 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',
+}
+
+def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):
+ """
+ A version of shutil.copyfileobj that will not read more than 'size' bytes.
+ This makes it safe from clients sending more than CONTENT_LENGTH bytes of
+ data in the body.
+ """
+ if not size:
+ return
+ while size > 0:
+ buf = fsrc.read(min(length, size))
+ if not buf:
+ break
+ fdst.write(buf)
+ size -= len(buf)
+
+class WSGIRequest(http.HttpRequest):
+ def __init__(self, environ):
+ self.environ = environ
+ self.path = environ['PATH_INFO']
+ self.META = environ
+ self.method = environ['REQUEST_METHOD'].upper()
+
+ def __repr__(self):
+ # Since this is called as part of error handling, we need to be very
+ # robust against potentially malformed input.
+ try:
+ get = pformat(self.GET)
+ except:
+ get = '<could not parse>'
+ try:
+ post = pformat(self.POST)
+ except:
+ post = '<could not parse>'
+ try:
+ cookies = pformat(self.COOKIES)
+ except:
+ cookies = '<could not parse>'
+ try:
+ meta = pformat(self.META)
+ except:
+ meta = '<could not parse>'
+ return '<WSGIRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
+ (get, post, cookies, meta)
+
+ def get_full_path(self):
+ return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
+
+ def is_secure(self):
+ return self.environ.has_key('HTTPS') and self.environ['HTTPS'] == 'on'
+
+ def _load_post_and_files(self):
+ # Populates self._post and self._files
+ if self.method == 'POST':
+ if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
+ header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
+ header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
+ self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
+ else:
+ self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
+ else:
+ self._post, self._files = http.QueryDict(''), datastructures.MultiValueDict()
+
+ def _get_request(self):
+ if not hasattr(self, '_request'):
+ self._request = datastructures.MergeDict(self.POST, self.GET)
+ return self._request
+
+ def _get_get(self):
+ if not hasattr(self, '_get'):
+ # The WSGI spec says 'QUERY_STRING' may be absent.
+ self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''))
+ return self._get
+
+ def _set_get(self, get):
+ self._get = get
+
+ def _get_post(self):
+ if not hasattr(self, '_post'):
+ self._load_post_and_files()
+ return self._post
+
+ def _set_post(self, post):
+ self._post = post
+
+ def _get_cookies(self):
+ if not hasattr(self, '_cookies'):
+ self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
+ return self._cookies
+
+ def _set_cookies(self, cookies):
+ self._cookies = cookies
+
+ def _get_files(self):
+ if not hasattr(self, '_files'):
+ self._load_post_and_files()
+ return self._files
+
+ def _get_raw_post_data(self):
+ try:
+ return self._raw_post_data
+ except AttributeError:
+ buf = StringIO()
+ try:
+ # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
+ content_length = int(self.environ.get('CONTENT_LENGTH', 0))
+ except ValueError: # if CONTENT_LENGTH was empty string or not an integer
+ content_length = 0
+ safe_copyfileobj(self.environ['wsgi.input'], buf, size=content_length)
+ self._raw_post_data = buf.getvalue()
+ buf.close()
+ return self._raw_post_data
+
+ GET = property(_get_get, _set_get)
+ POST = property(_get_post, _set_post)
+ COOKIES = property(_get_cookies, _set_cookies)
+ FILES = property(_get_files)
+ REQUEST = property(_get_request)
+ raw_post_data = property(_get_raw_post_data)
+
+class WSGIHandler(BaseHandler):
+ def __call__(self, environ, start_response):
+ from django.conf import settings
+
+ # Set up middleware if needed. We couldn't do this earlier, because
+ # settings weren't available.
+ if self._request_middleware is None:
+ self.load_middleware()
+
+ dispatcher.send(signal=signals.request_started)
+ try:
+ request = WSGIRequest(environ)
+ response = self.get_response(request)
+
+ # Apply response middleware
+ for middleware_method in self._response_middleware:
+ response = middleware_method(request, response)
+
+ finally:
+ dispatcher.send(signal=signals.request_finished)
+
+ try:
+ status_text = STATUS_CODE_TEXT[response.status_code]
+ except KeyError:
+ status_text = 'UNKNOWN STATUS CODE'
+ status = '%s %s' % (response.status_code, status_text)
+ response_headers = response.headers.items()
+ for c in response.cookies.values():
+ response_headers.append(('Set-Cookie', c.output(header='')))
+ start_response(status, response_headers)
+ return response
diff --git a/google_appengine/lib/django/django/core/mail.py b/google_appengine/lib/django/django/core/mail.py
new file mode 100755
index 0000000..b9966c2
--- /dev/null
+++ b/google_appengine/lib/django/django/core/mail.py
@@ -0,0 +1,108 @@
+# Use this module for e-mailing.
+
+from django.conf import settings
+from email.MIMEText import MIMEText
+from email.Header import Header
+from email.Utils import formatdate
+import smtplib
+import socket
+import time
+import random
+
+# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
+# seconds, which slows down the restart of the server.
+class CachedDnsName(object):
+ def __str__(self):
+ return self.get_fqdn()
+
+ def get_fqdn(self):
+ if not hasattr(self, '_fqdn'):
+ self._fqdn = socket.getfqdn()
+ return self._fqdn
+
+DNS_NAME = CachedDnsName()
+
+class BadHeaderError(ValueError):
+ pass
+
+class SafeMIMEText(MIMEText):
+ def __setitem__(self, name, val):
+ "Forbids multi-line headers, to prevent header injection."
+ if '\n' in val or '\r' in val:
+ raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
+ if name == "Subject":
+ val = Header(val, settings.DEFAULT_CHARSET)
+ MIMEText.__setitem__(self, name, val)
+
+def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
+ """
+ Easy wrapper for sending a single message to a recipient list. All members
+ of the recipient list will see the other recipients in the 'To' field.
+
+ If auth_user is None, the EMAIL_HOST_USER setting is used.
+ If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+ """
+ if auth_user is None:
+ auth_user = settings.EMAIL_HOST_USER
+ if auth_password is None:
+ auth_password = settings.EMAIL_HOST_PASSWORD
+ return send_mass_mail([[subject, message, from_email, recipient_list]], fail_silently, auth_user, auth_password)
+
+def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None):
+ """
+ Given a datatuple of (subject, message, from_email, recipient_list), sends
+ each message to each recipient list. Returns the number of e-mails sent.
+
+ If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
+ If auth_user and auth_password are set, they're used to log in.
+ If auth_user is None, the EMAIL_HOST_USER setting is used.
+ If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+ """
+ if auth_user is None:
+ auth_user = settings.EMAIL_HOST_USER
+ if auth_password is None:
+ auth_password = settings.EMAIL_HOST_PASSWORD
+ try:
+ server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT)
+ if auth_user and auth_password:
+ server.login(auth_user, auth_password)
+ except:
+ if fail_silently:
+ return
+ raise
+ num_sent = 0
+ for subject, message, from_email, recipient_list in datatuple:
+ if not recipient_list:
+ continue
+ from_email = from_email or settings.DEFAULT_FROM_EMAIL
+ msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET)
+ msg['Subject'] = subject
+ msg['From'] = from_email
+ msg['To'] = ', '.join(recipient_list)
+ msg['Date'] = formatdate()
+ try:
+ random_bits = str(random.getrandbits(64))
+ except AttributeError: # Python 2.3 doesn't have random.getrandbits().
+ random_bits = ''.join([random.choice('1234567890') for i in range(19)])
+ msg['Message-ID'] = "<%d.%s@%s>" % (time.time(), random_bits, DNS_NAME)
+ try:
+ server.sendmail(from_email, recipient_list, msg.as_string())
+ num_sent += 1
+ except:
+ if not fail_silently:
+ raise
+ try:
+ server.quit()
+ except:
+ if fail_silently:
+ return
+ raise
+ return num_sent
+
+def mail_admins(subject, message, fail_silently=False):
+ "Sends a message to the admins, as defined by the ADMINS setting."
+ send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], fail_silently)
+
+def mail_managers(subject, message, fail_silently=False):
+ "Sends a message to the managers, as defined by the MANAGERS setting."
+ send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], fail_silently)
diff --git a/google_appengine/lib/django/django/core/management.py b/google_appengine/lib/django/django/core/management.py
new file mode 100755
index 0000000..68c85c7
--- /dev/null
+++ b/google_appengine/lib/django/django/core/management.py
@@ -0,0 +1,1670 @@
+# Django management-related functions, including "CREATE TABLE" generation and
+# development-server initialization.
+
+import django
+from django.core.exceptions import ImproperlyConfigured
+import os, re, shutil, sys, textwrap
+from optparse import OptionParser
+from django.utils import termcolors
+
+# For Python 2.3
+if not hasattr(__builtins__, 'set'):
+ from sets import Set as set
+
+MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%}
+ <tr>
+ <th>{%% if perms.%(app)s.%(changeperm)s %%}<a href="%(app)s/%(mod)s/">{%% endif %%}%(name)s{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</th>
+ <td class="x50">{%% if perms.%(app)s.%(addperm)s %%}<a href="%(app)s/%(mod)s/add/" class="addlink">{%% endif %%}Add{%% if perms.%(app)s.%(addperm)s %%}</a>{%% endif %%}</td>
+ <td class="x75">{%% if perms.%(app)s.%(changeperm)s %%}<a href="%(app)s/%(mod)s/" class="changelink">{%% endif %%}Change{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</td>
+ </tr>
+ {%% endif %%}'''
+
+APP_ARGS = '[appname ...]'
+
+# Use django.__path__[0] because we don't know which directory django into
+# which has been installed.
+PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template')
+
+INVALID_PROJECT_NAMES = ('django', 'site', 'test')
+
+# Set up the terminal color scheme.
+class dummy: pass
+style = dummy()
+style.ERROR = termcolors.make_style(fg='red', opts=('bold',))
+style.ERROR_OUTPUT = termcolors.make_style(fg='red', opts=('bold',))
+style.NOTICE = termcolors.make_style(fg='red')
+style.SQL_FIELD = termcolors.make_style(fg='green', opts=('bold',))
+style.SQL_COLTYPE = termcolors.make_style(fg='green')
+style.SQL_KEYWORD = termcolors.make_style(fg='yellow')
+style.SQL_TABLE = termcolors.make_style(opts=('bold',))
+del dummy
+
+def disable_termcolors():
+ class dummy:
+ def __getattr__(self, attr):
+ return lambda x: x
+ global style
+ style = dummy()
+
+# Disable terminal coloring on Windows, Pocket PC, or if somebody's piping the output.
+if sys.platform == 'win32' or sys.platform == 'Pocket PC' or not sys.stdout.isatty():
+ disable_termcolors()
+
+def _is_valid_dir_name(s):
+ return bool(re.search(r'^\w+$', s))
+
+def _get_installed_models(table_list):
+ "Gets a set of all models that are installed, given a list of existing tables"
+ from django.db import models
+ all_models = []
+ for app in models.get_apps():
+ for model in models.get_models(app):
+ all_models.append(model)
+ return set([m for m in all_models if m._meta.db_table in table_list])
+
+def _get_table_list():
+ "Gets a list of all db tables that are physically installed."
+ from django.db import connection, get_introspection_module
+ cursor = connection.cursor()
+ return get_introspection_module().get_table_list(cursor)
+
+def _get_sequence_list():
+ "Returns a list of information about all DB sequences for all models in all apps"
+ from django.db import models
+
+ apps = models.get_apps()
+ sequence_list = []
+
+ for app in apps:
+ for model in models.get_models(app):
+ for f in model._meta.fields:
+ if isinstance(f, models.AutoField):
+ sequence_list.append({'table':model._meta.db_table,'column':f.column,})
+ break # Only one AutoField is allowed per model, so don't bother continuing.
+
+ for f in model._meta.many_to_many:
+ sequence_list.append({'table':f.m2m_db_table(),'column':None,})
+
+ return sequence_list
+
+# If the foreign key points to an AutoField, a PositiveIntegerField or a
+# PositiveSmallIntegerField, the foreign key should be an IntegerField, not the
+# referred field type. Otherwise, the foreign key should be the same type of
+# field as the field to which it points.
+get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField')) and 'IntegerField' or f.get_internal_type()
+
+def get_version():
+ "Returns the version as a human-format string."
+ from django import VERSION
+ v = '.'.join([str(i) for i in VERSION[:-1]])
+ if VERSION[-1]:
+ v += '-' + VERSION[-1]
+ return v
+
+def get_sql_create(app):
+ "Returns a list of the CREATE TABLE SQL statements for the given app."
+ from django.db import get_creation_module, models
+ data_types = get_creation_module().DATA_TYPES
+
+ if not data_types:
+ # This must be the "dummy" database backend, which means the user
+ # hasn't set DATABASE_ENGINE.
+ sys.stderr.write(style.ERROR("Error: Django doesn't know which syntax to use for your SQL statements,\n" +
+ "because you haven't specified the DATABASE_ENGINE setting.\n" +
+ "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.\n"))
+ sys.exit(1)
+
+ # Get installed models, so we generate REFERENCES right.
+ # We trim models from the current app so that the sqlreset command does not
+ # generate invalid SQL (leaving models out of known_models is harmless, so
+ # we can be conservative).
+ app_models = models.get_models(app)
+ final_output = []
+ known_models = set([model for model in _get_installed_models(_get_table_list()) if model not in app_models])
+ pending_references = {}
+
+ for model in app_models:
+ output, references = _get_sql_model_create(model, known_models)
+ final_output.extend(output)
+ for refto, refs in references.items():
+ pending_references.setdefault(refto,[]).extend(refs)
+ final_output.extend(_get_sql_for_pending_references(model, pending_references))
+ # Keep track of the fact that we've created the table for this model.
+ known_models.add(model)
+
+ # Create the many-to-many join tables.
+ for model in app_models:
+ final_output.extend(_get_many_to_many_sql_for_model(model))
+
+ # Handle references to tables that are from other apps
+ # but don't exist physically
+ not_installed_models = set(pending_references.keys())
+ if not_installed_models:
+ alter_sql = []
+ for model in not_installed_models:
+ alter_sql.extend(['-- ' + sql for sql in
+ _get_sql_for_pending_references(model, pending_references)])
+ if alter_sql:
+ final_output.append('-- The following references should be added but depend on non-existent tables:')
+ final_output.extend(alter_sql)
+
+ return final_output
+get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given app name(s)."
+get_sql_create.args = APP_ARGS
+
+def _get_sql_model_create(model, known_models=set()):
+ """
+ Get the SQL required to create a single model.
+
+ Returns list_of_sql, pending_references_dict
+ """
+ from django.db import backend, get_creation_module, models
+ data_types = get_creation_module().DATA_TYPES
+
+ opts = model._meta
+ final_output = []
+ table_output = []
+ pending_references = {}
+ for f in opts.fields:
+ if isinstance(f, (models.ForeignKey, models.OneToOneField)):
+ rel_field = f.rel.get_related_field()
+ data_type = get_rel_data_type(rel_field)
+ else:
+ rel_field = f
+ data_type = f.get_internal_type()
+ col_type = data_types[data_type]
+ if col_type is not None:
+ # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
+ field_output = [style.SQL_FIELD(backend.quote_name(f.column)),
+ style.SQL_COLTYPE(col_type % rel_field.__dict__)]
+ field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
+ if f.unique:
+ field_output.append(style.SQL_KEYWORD('UNIQUE'))
+ if f.primary_key:
+ field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
+ if f.rel:
+ if f.rel.to in known_models:
+ field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
+ style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \
+ style.SQL_FIELD(backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' +
+ backend.get_deferrable_sql()
+ )
+ else:
+ # We haven't yet created the table to which this field
+ # is related, so save it for later.
+ pr = pending_references.setdefault(f.rel.to, []).append((model, f))
+ table_output.append(' '.join(field_output))
+ if opts.order_with_respect_to:
+ table_output.append(style.SQL_FIELD(backend.quote_name('_order')) + ' ' + \
+ style.SQL_COLTYPE(data_types['IntegerField']) + ' ' + \
+ style.SQL_KEYWORD('NULL'))
+ for field_constraints in opts.unique_together:
+ table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
+ ", ".join([backend.quote_name(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints]))
+
+ full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' (']
+ for i, line in enumerate(table_output): # Combine and add commas.
+ full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
+ full_statement.append(');')
+ final_output.append('\n'.join(full_statement))
+
+ return final_output, pending_references
+
+def _get_sql_for_pending_references(model, pending_references):
+ """
+ Get any ALTER TABLE statements to add constraints after the fact.
+ """
+ from django.db import backend, get_creation_module
+ data_types = get_creation_module().DATA_TYPES
+
+ final_output = []
+ if backend.supports_constraints:
+ opts = model._meta
+ if model in pending_references:
+ for rel_class, f in pending_references[model]:
+ rel_opts = rel_class._meta
+ r_table = rel_opts.db_table
+ r_col = f.column
+ table = opts.db_table
+ col = opts.get_field(f.rel.field_name).column
+ # For MySQL, r_name must be unique in the first 64 characters.
+ # So we are careful with character usage here.
+ r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
+ final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
+ (backend.quote_name(r_table), r_name,
+ backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col),
+ backend.get_deferrable_sql()))
+ del pending_references[model]
+ return final_output
+
+def _get_many_to_many_sql_for_model(model):
+ from django.db import backend, get_creation_module
+ from django.db.models import GenericRel
+
+ data_types = get_creation_module().DATA_TYPES
+
+ opts = model._meta
+ final_output = []
+ for f in opts.many_to_many:
+ if not isinstance(f.rel, GenericRel):
+ table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
+ style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
+ table_output.append(' %s %s %s,' % \
+ (style.SQL_FIELD(backend.quote_name('id')),
+ style.SQL_COLTYPE(data_types['AutoField']),
+ style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
+ table_output.append(' %s %s %s %s (%s)%s,' % \
+ (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
+ style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__),
+ style.SQL_KEYWORD('NOT NULL REFERENCES'),
+ style.SQL_TABLE(backend.quote_name(opts.db_table)),
+ style.SQL_FIELD(backend.quote_name(opts.pk.column)),
+ backend.get_deferrable_sql()))
+ table_output.append(' %s %s %s %s (%s)%s,' % \
+ (style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())),
+ style.SQL_COLTYPE(data_types[get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__),
+ style.SQL_KEYWORD('NOT NULL REFERENCES'),
+ style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)),
+ style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)),
+ backend.get_deferrable_sql()))
+ table_output.append(' %s (%s, %s)' % \
+ (style.SQL_KEYWORD('UNIQUE'),
+ style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
+ style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name()))))
+ table_output.append(');')
+ final_output.append('\n'.join(table_output))
+ return final_output
+
+def get_sql_delete(app):
+ "Returns a list of the DROP TABLE SQL statements for the given app."
+ from django.db import backend, connection, models, get_introspection_module
+ introspection = get_introspection_module()
+
+ # This should work even if a connection isn't available
+ try:
+ cursor = connection.cursor()
+ except:
+ cursor = None
+
+ # Figure out which tables already exist
+ if cursor:
+ table_names = introspection.get_table_list(cursor)
+ else:
+ table_names = []
+
+ output = []
+
+ # Output DROP TABLE statements for standard application tables.
+ to_delete = set()
+
+ references_to_delete = {}
+ app_models = models.get_models(app)
+ for model in app_models:
+ if cursor and model._meta.db_table in table_names:
+ # The table exists, so it needs to be dropped
+ opts = model._meta
+ for f in opts.fields:
+ if f.rel and f.rel.to not in to_delete:
+ references_to_delete.setdefault(f.rel.to, []).append( (model, f) )
+
+ to_delete.add(model)
+
+ for model in app_models:
+ if cursor and model._meta.db_table in table_names:
+ # Drop the table now
+ output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
+ style.SQL_TABLE(backend.quote_name(model._meta.db_table))))
+ if backend.supports_constraints and references_to_delete.has_key(model):
+ for rel_class, f in references_to_delete[model]:
+ table = rel_class._meta.db_table
+ col = f.column
+ r_table = model._meta.db_table
+ r_col = model._meta.get_field(f.rel.field_name).column
+ output.append('%s %s %s %s;' % \
+ (style.SQL_KEYWORD('ALTER TABLE'),
+ style.SQL_TABLE(backend.quote_name(table)),
+ style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()),
+ style.SQL_FIELD(backend.quote_name('%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))))))
+ del references_to_delete[model]
+
+ # Output DROP TABLE statements for many-to-many tables.
+ for model in app_models:
+ opts = model._meta
+ for f in opts.many_to_many:
+ if cursor and f.m2m_db_table() in table_names:
+ output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
+ style.SQL_TABLE(backend.quote_name(f.m2m_db_table()))))
+
+ app_label = app_models[0]._meta.app_label
+
+ # Close database connection explicitly, in case this output is being piped
+ # directly into a database client, to avoid locking issues.
+ if cursor:
+ cursor.close()
+ connection.close()
+
+ return output[::-1] # Reverse it, to deal with table dependencies.
+get_sql_delete.help_doc = "Prints the DROP TABLE SQL statements for the given app name(s)."
+get_sql_delete.args = APP_ARGS
+
+def get_sql_reset(app):
+ "Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module."
+ return get_sql_delete(app) + get_sql_all(app)
+get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s)."
+get_sql_reset.args = APP_ARGS
+
+def get_sql_flush():
+ "Returns a list of the SQL statements used to flush the database"
+ from django.db import backend
+ statements = backend.get_sql_flush(style, _get_table_list(), _get_sequence_list())
+ return statements
+get_sql_flush.help_doc = "Returns a list of the SQL statements required to return all tables in the database to the state they were in just after they were installed."
+get_sql_flush.args = ''
+
+def get_custom_sql_for_model(model):
+ from django.db import models
+ from django.conf import settings
+
+ opts = model._meta
+ app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
+ output = []
+
+ # Some backends can't execute more than one SQL statement at a time,
+ # so split into separate statements.
+ statements = re.compile(r";[ \t]*$", re.M)
+
+ # Find custom SQL, if it's available.
+ sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), settings.DATABASE_ENGINE)),
+ os.path.join(app_dir, "%s.sql" % opts.object_name.lower())]
+ for sql_file in sql_files:
+ if os.path.exists(sql_file):
+ fp = open(sql_file, 'U')
+ for statement in statements.split(fp.read()):
+ # Remove any comments from the file
+ statement = re.sub(r"--.*[\n\Z]", "", statement)
+ if statement.strip():
+ output.append(statement + ";")
+ fp.close()
+
+ return output
+
+def get_custom_sql(app):
+ "Returns a list of the custom table modifying SQL statements for the given app."
+ from django.db.models import get_models
+ output = []
+
+ app_models = get_models(app)
+ app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql'))
+
+ for model in app_models:
+ output.extend(get_custom_sql_for_model(model))
+
+ return output
+get_custom_sql.help_doc = "Prints the custom table modifying SQL statements for the given app name(s)."
+get_custom_sql.args = APP_ARGS
+
+def get_sql_initial_data(apps):
+ "Returns a list of the initial INSERT SQL statements for the given app."
+ return style.ERROR("This action has been renamed. Try './manage.py sqlcustom %s'." % ' '.join(apps and apps or ['app1', 'app2']))
+get_sql_initial_data.help_doc = "RENAMED: see 'sqlcustom'"
+get_sql_initial_data.args = ''
+
+def get_sql_sequence_reset(app):
+ "Returns a list of the SQL statements to reset PostgreSQL sequences for the given app."
+ from django.db import backend, models
+ output = []
+ for model in models.get_models(app):
+ for f in model._meta.fields:
+ if isinstance(f, models.AutoField):
+ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)),
+ style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(backend.quote_name(f.column)),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(backend.quote_name(model._meta.db_table))))
+ break # Only one AutoField is allowed per model, so don't bother continuing.
+ for f in model._meta.many_to_many:
+ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()),
+ style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(backend.quote_name('id')),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(f.m2m_db_table())))
+ return output
+get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app name(s)."
+get_sql_sequence_reset.args = APP_ARGS
+
+def get_sql_indexes(app):
+ "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
+ from django.db import models
+ output = []
+ for model in models.get_models(app):
+ output.extend(get_sql_indexes_for_model(model))
+ return output
+get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given model module name(s)."
+get_sql_indexes.args = APP_ARGS
+
+def get_sql_indexes_for_model(model):
+ "Returns the CREATE INDEX SQL statements for a single model"
+ from django.db import backend
+ output = []
+
+ for f in model._meta.fields:
+ if f.db_index:
+ unique = f.unique and 'UNIQUE ' or ''
+ output.append(
+ style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
+ style.SQL_TABLE('%s_%s' % (model._meta.db_table, f.column)) + ' ' + \
+ style.SQL_KEYWORD('ON') + ' ' + \
+ style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \
+ "(%s);" % style.SQL_FIELD(backend.quote_name(f.column))
+ )
+ return output
+
+def get_sql_all(app):
+ "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
+ return get_sql_create(app) + get_custom_sql(app) + get_sql_indexes(app)
+get_sql_all.help_doc = "Prints the CREATE TABLE, initial-data and CREATE INDEX SQL statements for the given model module name(s)."
+get_sql_all.args = APP_ARGS
+
+def _emit_post_sync_signal(created_models, verbosity, interactive):
+ from django.db import models
+ from django.dispatch import dispatcher
+ # Emit the post_sync signal for every application.
+ for app in models.get_apps():
+ app_name = app.__name__.split('.')[-2]
+ if verbosity >= 2:
+ print "Running post-sync handlers for application", app_name
+ dispatcher.send(signal=models.signals.post_syncdb, sender=app,
+ app=app, created_models=created_models,
+ verbosity=verbosity, interactive=interactive)
+
+def syncdb(verbosity=1, interactive=True):
+ "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
+ from django.db import connection, transaction, models, get_creation_module
+ from django.conf import settings
+
+ disable_termcolors()
+
+ # First, try validating the models.
+ _check_for_validation_errors()
+
+ # Import the 'management' module within each installed app, to register
+ # dispatcher events.
+ for app_name in settings.INSTALLED_APPS:
+ try:
+ __import__(app_name + '.management', {}, {}, [''])
+ except ImportError:
+ pass
+
+ data_types = get_creation_module().DATA_TYPES
+
+ cursor = connection.cursor()
+
+ # Get a list of all existing database tables,
+ # so we know what needs to be added.
+ table_list = _get_table_list()
+
+ # Get a list of already installed *models* so that references work right.
+ seen_models = _get_installed_models(table_list)
+ created_models = set()
+ pending_references = {}
+
+ # Create the tables for each model
+ for app in models.get_apps():
+ app_name = app.__name__.split('.')[-2]
+ model_list = models.get_models(app)
+ for model in model_list:
+ # Create the model's database table, if it doesn't already exist.
+ if verbosity >= 2:
+ print "Processing %s.%s model" % (app_name, model._meta.object_name)
+ if model._meta.db_table in table_list:
+ continue
+ sql, references = _get_sql_model_create(model, seen_models)
+ seen_models.add(model)
+ created_models.add(model)
+ for refto, refs in references.items():
+ pending_references.setdefault(refto, []).extend(refs)
+ sql.extend(_get_sql_for_pending_references(model, pending_references))
+ if verbosity >= 1:
+ print "Creating table %s" % model._meta.db_table
+ for statement in sql:
+ cursor.execute(statement)
+ table_list.append(model._meta.db_table)
+
+ # Create the m2m tables. This must be done after all tables have been created
+ # to ensure that all referred tables will exist.
+ for app in models.get_apps():
+ app_name = app.__name__.split('.')[-2]
+ model_list = models.get_models(app)
+ for model in model_list:
+ if model in created_models:
+ sql = _get_many_to_many_sql_for_model(model)
+ if sql:
+ if verbosity >= 2:
+ print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name)
+ for statement in sql:
+ cursor.execute(statement)
+
+ transaction.commit_unless_managed()
+
+ # Send the post_syncdb signal, so individual apps can do whatever they need
+ # to do at this point.
+ _emit_post_sync_signal(created_models, verbosity, interactive)
+
+ # Install custom SQL for the app (but only if this
+ # is a model we've just created)
+ for app in models.get_apps():
+ for model in models.get_models(app):
+ if model in created_models:
+ custom_sql = get_custom_sql_for_model(model)
+ if custom_sql:
+ if verbosity >= 1:
+ print "Installing custom SQL for %s.%s model" % (app_name, model._meta.object_name)
+ try:
+ for sql in custom_sql:
+ cursor.execute(sql)
+ except Exception, e:
+ sys.stderr.write("Failed to install custom SQL for %s.%s model: %s" % \
+ (app_name, model._meta.object_name, e))
+ transaction.rollback_unless_managed()
+ else:
+ transaction.commit_unless_managed()
+
+ # Install SQL indicies for all newly created models
+ for app in models.get_apps():
+ app_name = app.__name__.split('.')[-2]
+ for model in models.get_models(app):
+ if model in created_models:
+ index_sql = get_sql_indexes_for_model(model)
+ if index_sql:
+ if verbosity >= 1:
+ print "Installing index for %s.%s model" % (app_name, model._meta.object_name)
+ try:
+ for sql in index_sql:
+ cursor.execute(sql)
+ except Exception, e:
+ sys.stderr.write("Failed to install index for %s.%s model: %s" % \
+ (app_name, model._meta.object_name, e))
+ transaction.rollback_unless_managed()
+ else:
+ transaction.commit_unless_managed()
+
+ # Install the 'initialdata' fixture, using format discovery
+ load_data(['initial_data'], verbosity=verbosity)
+syncdb.help_doc = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
+syncdb.args = '[--verbosity] [--interactive]'
+
+def get_admin_index(app):
+ "Returns admin-index template snippet (in list form) for the given app."
+ from django.utils.text import capfirst
+ from django.db.models import get_models
+ output = []
+ app_models = get_models(app)
+ app_label = app_models[0]._meta.app_label
+ output.append('{%% if perms.%s %%}' % app_label)
+ output.append('<div class="module"><h2>%s</h2><table>' % app_label.title())
+ for model in app_models:
+ if model._meta.admin:
+ output.append(MODULE_TEMPLATE % {
+ 'app': app_label,
+ 'mod': model._meta.module_name,
+ 'name': capfirst(model._meta.verbose_name_plural),
+ 'addperm': model._meta.get_add_permission(),
+ 'changeperm': model._meta.get_change_permission(),
+ })
+ output.append('</table></div>')
+ output.append('{% endif %}')
+ return output
+get_admin_index.help_doc = "Prints the admin-index template snippet for the given app name(s)."
+get_admin_index.args = APP_ARGS
+
+def _module_to_dict(module, omittable=lambda k: k.startswith('_')):
+ "Converts a module namespace to a Python dictionary. Used by get_settings_diff."
+ return dict([(k, repr(v)) for k, v in module.__dict__.items() if not omittable(k)])
+
+def diffsettings():
+ """
+ Displays differences between the current settings.py and Django's
+ default settings. Settings that don't appear in the defaults are
+ followed by "###".
+ """
+ # Inspired by Postfix's "postconf -n".
+ from django.conf import settings, global_settings
+
+ user_settings = _module_to_dict(settings._target)
+ default_settings = _module_to_dict(global_settings)
+
+ output = []
+ keys = user_settings.keys()
+ keys.sort()
+ for key in keys:
+ if key not in default_settings:
+ output.append("%s = %s ###" % (key, user_settings[key]))
+ elif user_settings[key] != default_settings[key]:
+ output.append("%s = %s" % (key, user_settings[key]))
+ print '\n'.join(output)
+diffsettings.args = ""
+
+def reset(app, interactive=True):
+ "Executes the equivalent of 'get_sql_reset' in the current database."
+ from django.db import connection, transaction
+ from django.conf import settings
+ app_name = app.__name__.split('.')[-2]
+
+ disable_termcolors()
+
+ # First, try validating the models.
+ _check_for_validation_errors(app)
+ sql_list = get_sql_reset(app)
+
+ if interactive:
+ confirm = raw_input("""
+You have requested a database reset.
+This will IRREVERSIBLY DESTROY any data for
+the "%s" application in the database "%s".
+Are you sure you want to do this?
+
+Type 'yes' to continue, or 'no' to cancel: """ % (app_name, settings.DATABASE_NAME))
+ else:
+ confirm = 'yes'
+
+ if confirm == 'yes':
+ try:
+ cursor = connection.cursor()
+ for sql in sql_list:
+ cursor.execute(sql)
+ except Exception, e:
+ sys.stderr.write(style.ERROR("""Error: %s couldn't be reset. Possible reasons:
+ * The database isn't running or isn't configured correctly.
+ * At least one of the database tables doesn't exist.
+ * The SQL was invalid.
+Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run.
+The full error: """ % (app_name, app_name)) + style.ERROR_OUTPUT(str(e)) + '\n')
+ transaction.rollback_unless_managed()
+ sys.exit(1)
+ transaction.commit_unless_managed()
+ else:
+ print "Reset cancelled."
+reset.help_doc = "Executes ``sqlreset`` for the given app(s) in the current database."
+reset.args = '[--interactive]' + APP_ARGS
+
+def flush(verbosity=1, interactive=True):
+ "Returns all tables in the database to the same state they were in immediately after syncdb."
+ from django.conf import settings
+ from django.db import connection, transaction, models
+ from django.dispatch import dispatcher
+
+ disable_termcolors()
+
+ # First, try validating the models.
+ _check_for_validation_errors()
+
+ # Import the 'management' module within each installed app, to register
+ # dispatcher events.
+ for app_name in settings.INSTALLED_APPS:
+ try:
+ __import__(app_name + '.management', {}, {}, [''])
+ except ImportError:
+ pass
+
+ sql_list = get_sql_flush()
+
+ if interactive:
+ confirm = raw_input("""
+You have requested a flush of the database.
+This will IRREVERSIBLY DESTROY all data currently in the database,
+and return each table to the state it was in after syncdb.
+Are you sure you want to do this?
+
+Type 'yes' to continue, or 'no' to cancel: """)
+ else:
+ confirm = 'yes'
+
+ if confirm == 'yes':
+ try:
+ cursor = connection.cursor()
+ for sql in sql_list:
+ cursor.execute(sql)
+ except Exception, e:
+ sys.stderr.write(style.ERROR("""Error: Database %s couldn't be flushed. Possible reasons:
+ * The database isn't running or isn't configured correctly.
+ * At least one of the expected database tables doesn't exist.
+ * The SQL was invalid.
+Hint: Look at the output of 'django-admin.py sqlflush'. That's the SQL this command wasn't able to run.
+The full error: """ % settings.DATABASE_NAME + style.ERROR_OUTPUT(str(e)) + '\n'))
+ transaction.rollback_unless_managed()
+ sys.exit(1)
+ transaction.commit_unless_managed()
+
+ # Emit the post sync signal. This allows individual
+ # applications to respond as if the database had been
+ # sync'd from scratch.
+ _emit_post_sync_signal(models.get_models(), verbosity, interactive)
+
+ # Reinstall the initial_data fixture
+ load_data(['initial_data'], verbosity=verbosity)
+
+ else:
+ print "Flush cancelled."
+flush.help_doc = "Executes ``sqlflush`` on the current database."
+flush.args = '[--verbosity] [--interactive]'
+
+def _start_helper(app_or_project, name, directory, other_name=''):
+ other = {'project': 'app', 'app': 'project'}[app_or_project]
+ if not _is_valid_dir_name(name):
+ sys.stderr.write(style.ERROR("Error: %r is not a valid %s name. Please use only numbers, letters and underscores.\n" % (name, app_or_project)))
+ sys.exit(1)
+ top_dir = os.path.join(directory, name)
+ try:
+ os.mkdir(top_dir)
+ except OSError, e:
+ sys.stderr.write(style.ERROR("Error: %s\n" % e))
+ sys.exit(1)
+ template_dir = PROJECT_TEMPLATE_DIR % app_or_project
+ for d, subdirs, files in os.walk(template_dir):
+ relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name)
+ if relative_dir:
+ os.mkdir(os.path.join(top_dir, relative_dir))
+ for i, subdir in enumerate(subdirs):
+ if subdir.startswith('.'):
+ del subdirs[i]
+ for f in files:
+ if f.endswith('.pyc'):
+ continue
+ path_old = os.path.join(d, f)
+ path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name))
+ fp_old = open(path_old, 'r')
+ fp_new = open(path_new, 'w')
+ fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
+ fp_old.close()
+ fp_new.close()
+ try:
+ shutil.copymode(path_old, path_new)
+ except OSError:
+ sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
+
+def startproject(project_name, directory):
+ "Creates a Django project for the given project_name in the given directory."
+ from random import choice
+ if project_name in INVALID_PROJECT_NAMES:
+ sys.stderr.write(style.ERROR("Error: '%r' conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name.\n" % project_name))
+ sys.exit(1)
+ _start_helper('project', project_name, directory)
+ # Create a random SECRET_KEY hash, and put it in the main settings.
+ main_settings_file = os.path.join(directory, project_name, 'settings.py')
+ settings_contents = open(main_settings_file, 'r').read()
+ fp = open(main_settings_file, 'w')
+ secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
+ settings_contents = re.sub(r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents)
+ fp.write(settings_contents)
+ fp.close()
+startproject.help_doc = "Creates a Django project directory structure for the given project name in the current directory."
+startproject.args = "[projectname]"
+
+def startapp(app_name, directory):
+ "Creates a Django app for the given app_name in the given directory."
+ # Determine the project_name a bit naively -- by looking at the name of
+ # the parent directory.
+ project_dir = os.path.normpath(os.path.join(directory, '..'))
+ project_name = os.path.basename(project_dir)
+ if app_name == os.path.basename(directory):
+ sys.stderr.write(style.ERROR("Error: You cannot create an app with the same name (%r) as your project.\n" % app_name))
+ sys.exit(1)
+ _start_helper('app', app_name, directory, project_name)
+startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."
+startapp.args = "[appname]"
+
+def inspectdb():
+ "Generator that introspects the tables in the given database name and returns a Django model, one line at a time."
+ from django.db import connection, get_introspection_module
+ import keyword
+
+ introspection_module = get_introspection_module()
+
+ table2model = lambda table_name: table_name.title().replace('_', '')
+
+ cursor = connection.cursor()
+ yield "# This is an auto-generated Django model module."
+ yield "# You'll have to do the following manually to clean this up:"
+ yield "# * Rearrange models' order"
+ yield "# * Make sure each model has one field with primary_key=True"
+ yield "# Feel free to rename the models, but don't rename db_table values or field names."
+ yield "#"
+ yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
+ yield "# into your database."
+ yield ''
+ yield 'from django.db import models'
+ yield ''
+ for table_name in introspection_module.get_table_list(cursor):
+ yield 'class %s(models.Model):' % table2model(table_name)
+ try:
+ relations = introspection_module.get_relations(cursor, table_name)
+ except NotImplementedError:
+ relations = {}
+ try:
+ indexes = introspection_module.get_indexes(cursor, table_name)
+ except NotImplementedError:
+ indexes = {}
+ for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)):
+ att_name = row[0]
+ comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
+ extra_params = {} # Holds Field parameters such as 'db_column'.
+
+ if ' ' in att_name:
+ extra_params['db_column'] = att_name
+ att_name = att_name.replace(' ', '')
+ comment_notes.append('Field renamed to remove spaces.')
+ if keyword.iskeyword(att_name):
+ extra_params['db_column'] = att_name
+ att_name += '_field'
+ comment_notes.append('Field renamed because it was a Python reserved word.')
+
+ if relations.has_key(i):
+ rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
+ field_type = 'ForeignKey(%s' % rel_to
+ if att_name.endswith('_id'):
+ att_name = att_name[:-3]
+ else:
+ extra_params['db_column'] = att_name
+ else:
+ try:
+ field_type = introspection_module.DATA_TYPES_REVERSE[row[1]]
+ except KeyError:
+ field_type = 'TextField'
+ comment_notes.append('This field type is a guess.')
+
+ # This is a hook for DATA_TYPES_REVERSE to return a tuple of
+ # (field_type, extra_params_dict).
+ if type(field_type) is tuple:
+ field_type, new_params = field_type
+ extra_params.update(new_params)
+
+ # Add maxlength for all CharFields.
+ if field_type == 'CharField' and row[3]:
+ extra_params['maxlength'] = row[3]
+
+ if field_type == 'FloatField':
+ extra_params['max_digits'] = row[4]
+ extra_params['decimal_places'] = row[5]
+
+ # Add primary_key and unique, if necessary.
+ column_name = extra_params.get('db_column', att_name)
+ if column_name in indexes:
+ if indexes[column_name]['primary_key']:
+ extra_params['primary_key'] = True
+ elif indexes[column_name]['unique']:
+ extra_params['unique'] = True
+
+ field_type += '('
+
+ # Don't output 'id = meta.AutoField(primary_key=True)', because
+ # that's assumed if it doesn't exist.
+ if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
+ continue
+
+ # Add 'null' and 'blank', if the 'null_ok' flag was present in the
+ # table description.
+ if row[6]: # If it's NULL...
+ extra_params['blank'] = True
+ if not field_type in ('TextField(', 'CharField('):
+ extra_params['null'] = True
+
+ field_desc = '%s = models.%s' % (att_name, field_type)
+ if extra_params:
+ if not field_desc.endswith('('):
+ field_desc += ', '
+ field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()])
+ field_desc += ')'
+ if comment_notes:
+ field_desc += ' # ' + ' '.join(comment_notes)
+ yield ' %s' % field_desc
+ yield ' class Meta:'
+ yield ' db_table = %r' % table_name
+ yield ''
+inspectdb.help_doc = "Introspects the database tables in the given database and outputs a Django model module."
+inspectdb.args = ""
+
+class ModelErrorCollection:
+ def __init__(self, outfile=sys.stdout):
+ self.errors = []
+ self.outfile = outfile
+
+ def add(self, context, error):
+ self.errors.append((context, error))
+ self.outfile.write(style.ERROR("%s: %s\n" % (context, error)))
+
+def get_validation_errors(outfile, app=None):
+ """
+ Validates all models that are part of the specified app. If no app name is provided,
+ validates all models of all installed apps. Writes errors, if any, to outfile.
+ Returns number of errors.
+ """
+ from django.conf import settings
+ from django.db import models, connection
+ from django.db.models.loading import get_app_errors
+ from django.db.models.fields.related import RelatedObject
+
+ e = ModelErrorCollection(outfile)
+
+ for (app_name, error) in get_app_errors().items():
+ e.add(app_name, error)
+
+ for cls in models.get_models(app):
+ opts = cls._meta
+
+ # Do field-specific validation.
+ for f in opts.fields:
+ if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
+ e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name)
+ if isinstance(f, models.CharField) and f.maxlength in (None, 0):
+ e.add(opts, '"%s": CharFields require a "maxlength" attribute.' % f.name)
+ if isinstance(f, models.FloatField):
+ if f.decimal_places is None:
+ e.add(opts, '"%s": FloatFields require a "decimal_places" attribute.' % f.name)
+ if f.max_digits is None:
+ e.add(opts, '"%s": FloatFields require a "max_digits" attribute.' % f.name)
+ if isinstance(f, models.FileField) and not f.upload_to:
+ e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
+ if isinstance(f, models.ImageField):
+ try:
+ from PIL import Image
+ except ImportError:
+ e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name)
+ if f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple):
+ e.add(opts, '"%s": prepopulate_from should be a list or tuple.' % f.name)
+ if f.choices:
+ if not hasattr(f.choices, '__iter__'):
+ e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
+ else:
+ for c in f.choices:
+ if not type(c) in (tuple, list) or len(c) != 2:
+ e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
+ if f.db_index not in (None, True, False):
+ e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
+
+ # Check that maxlength <= 255 if using older MySQL versions.
+ if settings.DATABASE_ENGINE == 'mysql':
+ db_version = connection.get_server_version()
+ if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.maxlength > 255:
+ e.add(opts, '"%s": %s cannot have a "maxlength" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]])))
+
+ # Check to see if the related field will clash with any
+ # existing fields, m2m fields, m2m related objects or related objects
+ if f.rel:
+ rel_opts = f.rel.to._meta
+ if f.rel.to not in models.get_models():
+ e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))
+
+ rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
+ rel_query_name = f.related_query_name()
+ for r in rel_opts.fields:
+ if r.name == rel_name:
+ e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ if r.name == rel_query_name:
+ e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ for r in rel_opts.many_to_many:
+ if r.name == rel_name:
+ e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ if r.name == rel_query_name:
+ e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ for r in rel_opts.get_all_related_many_to_many_objects():
+ if r.get_accessor_name() == rel_name:
+ e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ if r.get_accessor_name() == rel_query_name:
+ e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ for r in rel_opts.get_all_related_objects():
+ if r.field is not f:
+ if r.get_accessor_name() == rel_name:
+ e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ if r.get_accessor_name() == rel_query_name:
+ e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+
+
+ for i, f in enumerate(opts.many_to_many):
+ # Check to see if the related m2m field will clash with any
+ # existing fields, m2m fields, m2m related objects or related objects
+ rel_opts = f.rel.to._meta
+ if f.rel.to not in models.get_models():
+ e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))
+
+ rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
+ rel_query_name = f.related_query_name()
+ # If rel_name is none, there is no reverse accessor.
+ # (This only occurs for symmetrical m2m relations to self).
+ # If this is the case, there are no clashes to check for this field, as
+ # there are no reverse descriptors for this field.
+ if rel_name is not None:
+ for r in rel_opts.fields:
+ if r.name == rel_name:
+ e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ if r.name == rel_query_name:
+ e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ for r in rel_opts.many_to_many:
+ if r.name == rel_name:
+ e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ if r.name == rel_query_name:
+ e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ for r in rel_opts.get_all_related_many_to_many_objects():
+ if r.field is not f:
+ if r.get_accessor_name() == rel_name:
+ e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ if r.get_accessor_name() == rel_query_name:
+ e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ for r in rel_opts.get_all_related_objects():
+ if r.get_accessor_name() == rel_name:
+ e.add(opts, "Accessor for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ if r.get_accessor_name() == rel_query_name:
+ e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+
+ # Check admin attribute.
+ if opts.admin is not None:
+ if not isinstance(opts.admin, models.AdminOptions):
+ e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.')
+ else:
+ # list_display
+ if not isinstance(opts.admin.list_display, (list, tuple)):
+ e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.')
+ else:
+ for fn in opts.admin.list_display:
+ try:
+ f = opts.get_field(fn)
+ except models.FieldDoesNotExist:
+ if not hasattr(cls, fn):
+ e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn)
+ else:
+ if isinstance(f, models.ManyToManyField):
+ e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn)
+ # list_display_links
+ if opts.admin.list_display_links and not opts.admin.list_display:
+ e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.')
+ if not isinstance(opts.admin.list_display_links, (list, tuple)):
+ e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.')
+ else:
+ for fn in opts.admin.list_display_links:
+ try:
+ f = opts.get_field(fn)
+ except models.FieldDoesNotExist:
+ if not hasattr(cls, fn):
+ e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn)
+ if fn not in opts.admin.list_display:
+ e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn)
+ # list_filter
+ if not isinstance(opts.admin.list_filter, (list, tuple)):
+ e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.')
+ else:
+ for fn in opts.admin.list_filter:
+ try:
+ f = opts.get_field(fn)
+ except models.FieldDoesNotExist:
+ e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn)
+ # date_hierarchy
+ if opts.admin.date_hierarchy:
+ try:
+ f = opts.get_field(opts.admin.date_hierarchy)
+ except models.FieldDoesNotExist:
+ e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy)
+
+ # Check ordering attribute.
+ if opts.ordering:
+ for field_name in opts.ordering:
+ if field_name == '?': continue
+ if field_name.startswith('-'):
+ field_name = field_name[1:]
+ if opts.order_with_respect_to and field_name == '_order':
+ continue
+ if '.' in field_name: continue # Skip ordering in the format 'table.field'.
+ try:
+ opts.get_field(field_name, many_to_many=False)
+ except models.FieldDoesNotExist:
+ e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
+
+ # Check core=True, if needed.
+ for related in opts.get_followed_related_objects():
+ if not related.edit_inline:
+ continue
+ try:
+ for f in related.opts.fields:
+ if f.core:
+ raise StopIteration
+ e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
+ except StopIteration:
+ pass
+
+ # Check unique_together.
+ for ut in opts.unique_together:
+ for field_name in ut:
+ try:
+ f = opts.get_field(field_name, many_to_many=True)
+ except models.FieldDoesNotExist:
+ e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name)
+ else:
+ if isinstance(f.rel, models.ManyToManyRel):
+ e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name)
+
+ return len(e.errors)
+
+def validate(outfile=sys.stdout, silent_success=False):
+ "Validates all installed models."
+ try:
+ num_errors = get_validation_errors(outfile)
+ if silent_success and num_errors == 0:
+ return
+ outfile.write('%s error%s found.\n' % (num_errors, num_errors != 1 and 's' or ''))
+ except ImproperlyConfigured:
+ outfile.write("Skipping validation because things aren't configured properly.")
+validate.args = ''
+
+def _check_for_validation_errors(app=None):
+ """Check that an app has no validation errors, and exit with errors if it does."""
+ try:
+ from cStringIO import StringIO
+ except ImportError:
+ from StringIO import StringIO
+ s = StringIO()
+ num_errors = get_validation_errors(s, app)
+ if num_errors:
+ if app:
+ sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app))
+ else:
+ sys.stderr.write(style.ERROR("Error: Couldn't install apps, because there were errors in one or more models:\n"))
+ s.seek(0)
+ sys.stderr.write(s.read())
+ sys.exit(1)
+
+def runserver(addr, port, use_reloader=True, admin_media_dir=''):
+ "Starts a lightweight Web server for development."
+ from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
+ from django.core.handlers.wsgi import WSGIHandler
+ if not addr:
+ addr = '127.0.0.1'
+ if not port.isdigit():
+ sys.stderr.write(style.ERROR("Error: %r is not a valid port number.\n" % port))
+ sys.exit(1)
+ quit_command = sys.platform == 'win32' and 'CTRL-BREAK' or 'CONTROL-C'
+ def inner_run():
+ from django.conf import settings
+ print "Validating models..."
+ validate()
+ print "\nDjango version %s, using settings %r" % (get_version(), settings.SETTINGS_MODULE)
+ print "Development server is running at http://%s:%s/" % (addr, port)
+ print "Quit the server with %s." % quit_command
+ try:
+ handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
+ run(addr, int(port), handler)
+ except WSGIServerException, e:
+ # Use helpful error messages instead of ugly tracebacks.
+ ERRORS = {
+ 13: "You don't have permission to access that port.",
+ 98: "That port is already in use.",
+ 99: "That IP address can't be assigned-to.",
+ }
+ try:
+ error_text = ERRORS[e.args[0].args[0]]
+ except (AttributeError, KeyError):
+ error_text = str(e)
+ sys.stderr.write(style.ERROR("Error: %s" % error_text) + '\n')
+ sys.exit(1)
+ except KeyboardInterrupt:
+ sys.exit(0)
+ if use_reloader:
+ from django.utils import autoreload
+ autoreload.main(inner_run)
+ else:
+ inner_run()
+runserver.args = '[--noreload] [--adminmedia=ADMIN_MEDIA_PATH] [optional port number, or ipaddr:port]'
+
+def createcachetable(tablename):
+ "Creates the table needed to use the SQL cache backend"
+ from django.db import backend, connection, transaction, get_creation_module, models
+ data_types = get_creation_module().DATA_TYPES
+ fields = (
+ # "key" is a reserved word in MySQL, so use "cache_key" instead.
+ models.CharField(name='cache_key', maxlength=255, unique=True, primary_key=True),
+ models.TextField(name='value'),
+ models.DateTimeField(name='expires', db_index=True),
+ )
+ table_output = []
+ index_output = []
+ for f in fields:
+ field_output = [backend.quote_name(f.name), data_types[f.get_internal_type()] % f.__dict__]
+ field_output.append("%sNULL" % (not f.null and "NOT " or ""))
+ if f.unique:
+ field_output.append("UNIQUE")
+ if f.primary_key:
+ field_output.append("PRIMARY KEY")
+ if f.db_index:
+ unique = f.unique and "UNIQUE " or ""
+ index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
+ (unique, tablename, f.name, backend.quote_name(tablename),
+ backend.quote_name(f.name)))
+ table_output.append(" ".join(field_output))
+ full_statement = ["CREATE TABLE %s (" % backend.quote_name(tablename)]
+ for i, line in enumerate(table_output):
+ full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
+ full_statement.append(');')
+ curs = connection.cursor()
+ curs.execute("\n".join(full_statement))
+ for statement in index_output:
+ curs.execute(statement)
+ transaction.commit_unless_managed()
+createcachetable.args = "[tablename]"
+
+def run_shell(use_plain=False):
+ "Runs a Python interactive interpreter. Tries to use IPython, if it's available."
+ # XXX: (Temporary) workaround for ticket #1796: force early loading of all
+ # models from installed apps.
+ from django.db.models.loading import get_models
+ loaded_models = get_models()
+
+ try:
+ if use_plain:
+ # Don't bother loading IPython, because the user wants plain Python.
+ raise ImportError
+ import IPython
+ # Explicitly pass an empty list as arguments, because otherwise IPython
+ # would use sys.argv from this script.
+ shell = IPython.Shell.IPShell(argv=[])
+ shell.mainloop()
+ except ImportError:
+ import code
+ try: # Try activating rlcompleter, because it's handy.
+ import readline
+ except ImportError:
+ pass
+ else:
+ # We don't have to wrap the following import in a 'try', because
+ # we already know 'readline' was imported successfully.
+ import rlcompleter
+ readline.parse_and_bind("tab:complete")
+ code.interact()
+run_shell.args = '[--plain]'
+
+def dbshell():
+ "Runs the command-line client for the current DATABASE_ENGINE."
+ from django.db import runshell
+ runshell()
+dbshell.args = ""
+
+def runfcgi(args):
+ "Runs this project as a FastCGI application. Requires flup."
+ from django.conf import settings
+ from django.utils import translation
+ # Activate the current language, because it won't get activated later.
+ try:
+ translation.activate(settings.LANGUAGE_CODE)
+ except AttributeError:
+ pass
+ from django.core.servers.fastcgi import runfastcgi
+ runfastcgi(args)
+runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]'
+
+def test(app_labels, verbosity=1):
+ "Runs the test suite for the specified applications"
+ from django.conf import settings
+ from django.db.models import get_app, get_apps
+
+ if len(app_labels) == 0:
+ app_list = get_apps()
+ else:
+ app_list = [get_app(app_label) for app_label in app_labels]
+
+ test_path = settings.TEST_RUNNER.split('.')
+ # Allow for Python 2.5 relative paths
+ if len(test_path) > 1:
+ test_module_name = '.'.join(test_path[:-1])
+ else:
+ test_module_name = '.'
+ test_module = __import__(test_module_name, {}, {}, test_path[-1])
+ test_runner = getattr(test_module, test_path[-1])
+
+ failures = test_runner(app_list, verbosity)
+ if failures:
+ sys.exit(failures)
+
+test.help_doc = 'Runs the test suite for the specified applications, or the entire site if no apps are specified'
+test.args = '[--verbosity] ' + APP_ARGS
+
+def load_data(fixture_labels, verbosity=1):
+ "Installs the provided fixture file(s) as data in the database."
+ from django.db.models import get_apps
+ from django.core import serializers
+ from django.db import connection, transaction
+ from django.conf import settings
+ import sys
+
+ # Keep a count of the installed objects and fixtures
+ count = [0,0]
+
+ humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
+
+ # Get a cursor (even though we don't need one yet). This has
+ # the side effect of initializing the test database (if
+ # it isn't already initialized).
+ cursor = connection.cursor()
+
+ # Start transaction management. All fixtures are installed in a
+ # single transaction to ensure that all references are resolved.
+ transaction.commit_unless_managed()
+ transaction.enter_transaction_management()
+ transaction.managed(True)
+
+ app_fixtures = [os.path.join(os.path.dirname(app.__file__),'fixtures') for app in get_apps()]
+ for fixture_label in fixture_labels:
+ if verbosity > 0:
+ print "Loading '%s' fixtures..." % fixture_label
+ for fixture_dir in app_fixtures + list(settings.FIXTURE_DIRS) + ['']:
+ if verbosity > 1:
+ print "Checking %s for fixtures..." % humanize(fixture_dir)
+ parts = fixture_label.split('.')
+ if len(parts) == 1:
+ fixture_name = fixture_label
+ formats = serializers.get_serializer_formats()
+ else:
+ fixture_name, format = '.'.join(parts[:-1]), parts[-1]
+ formats = [format]
+
+ label_found = False
+ for format in formats:
+ serializer = serializers.get_serializer(format)
+ if verbosity > 1:
+ print "Trying %s for %s fixture '%s'..." % \
+ (humanize(fixture_dir), format, fixture_name)
+ try:
+ full_path = os.path.join(fixture_dir, '.'.join([fixture_name, format]))
+ fixture = open(full_path, 'r')
+ if label_found:
+ fixture.close()
+ print style.ERROR("Multiple fixtures named '%s' in %s. Aborting." %
+ (fixture_name, humanize(fixture_dir)))
+ transaction.rollback()
+ transaction.leave_transaction_management()
+ return
+ else:
+ count[1] += 1
+ if verbosity > 0:
+ print "Installing %s fixture '%s' from %s." % \
+ (format, fixture_name, humanize(fixture_dir))
+ try:
+ objects = serializers.deserialize(format, fixture)
+ for obj in objects:
+ count[0] += 1
+ obj.save()
+ label_found = True
+ except Exception, e:
+ fixture.close()
+ sys.stderr.write(
+ style.ERROR("Problem installing fixture '%s': %s\n" %
+ (full_path, str(e))))
+ transaction.rollback()
+ transaction.leave_transaction_management()
+ return
+ fixture.close()
+ except:
+ if verbosity > 1:
+ print "No %s fixture '%s' in %s." % \
+ (format, fixture_name, humanize(fixture_dir))
+ if count[0] == 0:
+ if verbosity > 0:
+ print "No fixtures found."
+ else:
+ if verbosity > 0:
+ print "Installed %d object(s) from %d fixture(s)" % tuple(count)
+ transaction.commit()
+ transaction.leave_transaction_management()
+
+load_data.help_doc = 'Installs the named fixture(s) in the database'
+load_data.args = "[--verbosity] fixture, fixture, ..."
+
+def dump_data(app_labels, format='json', indent=None):
+ "Output the current contents of the database as a fixture of the given format"
+ from django.db.models import get_app, get_apps, get_models
+ from django.core import serializers
+
+ if len(app_labels) == 0:
+ app_list = get_apps()
+ else:
+ app_list = [get_app(app_label) for app_label in app_labels]
+
+ # Check that the serialization format exists; this is a shortcut to
+ # avoid collating all the objects and _then_ failing.
+ try:
+ serializers.get_serializer(format)
+ except KeyError:
+ sys.stderr.write(style.ERROR("Unknown serialization format: %s\n" % format))
+
+ objects = []
+ for app in app_list:
+ for model in get_models(app):
+ objects.extend(model.objects.all())
+ try:
+ return serializers.serialize(format, objects, indent=indent)
+ except Exception, e:
+ sys.stderr.write(style.ERROR("Unable to serialize database: %s\n" % e))
+dump_data.help_doc = 'Output the contents of the database as a fixture of the given format'
+dump_data.args = '[--format]' + APP_ARGS
+
+# Utilities for command-line script
+
+DEFAULT_ACTION_MAPPING = {
+ 'adminindex': get_admin_index,
+ 'createcachetable' : createcachetable,
+ 'dbshell': dbshell,
+ 'diffsettings': diffsettings,
+ 'dumpdata': dump_data,
+ 'flush': flush,
+ 'inspectdb': inspectdb,
+ 'loaddata': load_data,
+ 'reset': reset,
+ 'runfcgi': runfcgi,
+ 'runserver': runserver,
+ 'shell': run_shell,
+ 'sql': get_sql_create,
+ 'sqlall': get_sql_all,
+ 'sqlclear': get_sql_delete,
+ 'sqlcustom': get_custom_sql,
+ 'sqlflush': get_sql_flush,
+ 'sqlindexes': get_sql_indexes,
+ 'sqlinitialdata': get_sql_initial_data,
+ 'sqlreset': get_sql_reset,
+ 'sqlsequencereset': get_sql_sequence_reset,
+ 'startapp': startapp,
+ 'startproject': startproject,
+ 'syncdb': syncdb,
+ 'validate': validate,
+ 'test':test,
+}
+
+NO_SQL_TRANSACTION = (
+ 'adminindex',
+ 'createcachetable',
+ 'dbshell',
+ 'diffsettings',
+ 'reset',
+ 'sqlindexes',
+ 'syncdb',
+)
+
+class DjangoOptionParser(OptionParser):
+ def print_usage_and_exit(self):
+ self.print_help(sys.stderr)
+ sys.exit(1)
+
+def get_usage(action_mapping):
+ """
+ Returns a usage string. Doesn't do the options stuff, because optparse
+ takes care of that.
+ """
+ usage = ["%prog action [options]\nactions:"]
+ available_actions = action_mapping.keys()
+ available_actions.sort()
+ for a in available_actions:
+ func = action_mapping[a]
+ usage.append(" %s %s" % (a, func.args))
+ usage.extend(textwrap.wrap(getattr(func, 'help_doc', textwrap.dedent(func.__doc__.strip())), initial_indent=' ', subsequent_indent=' '))
+ usage.append("")
+ return '\n'.join(usage[:-1]) # Cut off last list element, an empty space.
+
+def print_error(msg, cmd):
+ sys.stderr.write(style.ERROR('Error: %s' % msg) + '\nRun "%s --help" for help.\n' % cmd)
+ sys.exit(1)
+
+def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
+ # Use sys.argv if we've not passed in a custom argv
+ if argv is None:
+ argv = sys.argv
+
+ # Parse the command-line arguments. optparse handles the dirty work.
+ parser = DjangoOptionParser(usage=get_usage(action_mapping), version=get_version())
+ parser.add_option('--settings',
+ help='Python path to settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
+ parser.add_option('--pythonpath',
+ help='Lets you manually add a directory the Python path, e.g. "/home/djangoprojects/myproject".')
+ parser.add_option('--plain', action='store_true', dest='plain',
+ help='Tells Django to use plain Python, not IPython, for "shell" command.')
+ parser.add_option('--noinput', action='store_false', dest='interactive', default=True,
+ help='Tells Django to NOT prompt the user for input of any kind.')
+ parser.add_option('--noreload', action='store_false', dest='use_reloader', default=True,
+ help='Tells Django to NOT use the auto-reloader when running the development server.')
+ parser.add_option('--format', default='json', dest='format',
+ help='Specifies the output serialization format for fixtures')
+ parser.add_option('--indent', default=None, dest='indent',
+ type='int', help='Specifies the indent level to use when pretty-printing output')
+ parser.add_option('--verbosity', action='store', dest='verbosity', default='1',
+ type='choice', choices=['0', '1', '2'],
+ help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
+ parser.add_option('--adminmedia', dest='admin_media_path', default='', help='Specifies the directory from which to serve admin media for runserver.'),
+
+ options, args = parser.parse_args(argv[1:])
+
+ # Take care of options.
+ if options.settings:
+ os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
+ if options.pythonpath:
+ sys.path.insert(0, options.pythonpath)
+
+ # Run the appropriate action. Unfortunately, optparse can't handle
+ # positional arguments, so this has to parse/validate them.
+ try:
+ action = args[0]
+ except IndexError:
+ parser.print_usage_and_exit()
+ if not action_mapping.has_key(action):
+ print_error("Your action, %r, was invalid." % action, argv[0])
+
+ # Switch to English, because django-admin.py creates database content
+ # like permissions, and those shouldn't contain any translations.
+ # But only do this if we should have a working settings file.
+ if action not in ('startproject', 'startapp'):
+ from django.utils import translation
+ translation.activate('en-us')
+
+ if action == 'shell':
+ action_mapping[action](options.plain is True)
+ elif action in ('validate', 'diffsettings', 'dbshell'):
+ action_mapping[action]()
+ elif action in ('flush', 'syncdb'):
+ action_mapping[action](int(options.verbosity), options.interactive)
+ elif action == 'inspectdb':
+ try:
+ for line in action_mapping[action]():
+ print line
+ except NotImplementedError:
+ sys.stderr.write(style.ERROR("Error: %r isn't supported for the currently selected database backend.\n" % action))
+ sys.exit(1)
+ elif action == 'createcachetable':
+ try:
+ action_mapping[action](args[1])
+ except IndexError:
+ parser.print_usage_and_exit()
+ elif action in ('test', 'loaddata'):
+ try:
+ action_mapping[action](args[1:], int(options.verbosity))
+ except IndexError:
+ parser.print_usage_and_exit()
+ elif action == 'dumpdata':
+ try:
+ print action_mapping[action](args[1:], options.format, options.indent)
+ except IndexError:
+ parser.print_usage_and_exit()
+ elif action in ('startapp', 'startproject'):
+ try:
+ name = args[1]
+ except IndexError:
+ parser.print_usage_and_exit()
+ action_mapping[action](name, os.getcwd())
+ elif action == 'runserver':
+ if len(args) < 2:
+ addr = ''
+ port = '8000'
+ else:
+ try:
+ addr, port = args[1].split(':')
+ except ValueError:
+ addr, port = '', args[1]
+ action_mapping[action](addr, port, options.use_reloader, options.admin_media_path)
+ elif action == 'runfcgi':
+ action_mapping[action](args[1:])
+ elif action == 'sqlinitialdata':
+ print action_mapping[action](args[1:])
+ elif action == 'sqlflush':
+ print '\n'.join(action_mapping[action]())
+ else:
+ from django.db import models
+ validate(silent_success=True)
+ try:
+ mod_list = [models.get_app(app_label) for app_label in args[1:]]
+ except ImportError, e:
+ sys.stderr.write(style.ERROR("Error: %s. Are you sure your INSTALLED_APPS setting is correct?\n" % e))
+ sys.exit(1)
+ if not mod_list:
+ parser.print_usage_and_exit()
+ if action not in NO_SQL_TRANSACTION:
+ print style.SQL_KEYWORD("BEGIN;")
+ for mod in mod_list:
+ if action == 'reset':
+ output = action_mapping[action](mod, options.interactive)
+ else:
+ output = action_mapping[action](mod)
+ if output:
+ print '\n'.join(output)
+ if action not in NO_SQL_TRANSACTION:
+ print style.SQL_KEYWORD("COMMIT;")
+
+def setup_environ(settings_mod):
+ """
+ Configure the runtime environment. This can also be used by external
+ scripts wanting to set up a similar environment to manage.py.
+ """
+ # Add this project to sys.path so that it's importable in the conventional
+ # way. For example, if this file (manage.py) lives in a directory
+ # "myproject", this code would add "/path/to/myproject" to sys.path.
+ project_directory = os.path.dirname(settings_mod.__file__)
+ project_name = os.path.basename(project_directory)
+ sys.path.append(os.path.join(project_directory, '..'))
+ project_module = __import__(project_name, {}, {}, [''])
+ sys.path.pop()
+
+ # Set DJANGO_SETTINGS_MODULE appropriately.
+ os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name
+ return project_directory
+
+def execute_manager(settings_mod, argv=None):
+ project_directory = setup_environ(settings_mod)
+ action_mapping = DEFAULT_ACTION_MAPPING.copy()
+
+ # Remove the "startproject" command from the action_mapping, because that's
+ # a django-admin.py command, not a manage.py command.
+ del action_mapping['startproject']
+
+ # Override the startapp handler so that it always uses the
+ # project_directory, not the current working directory (which is default).
+ action_mapping['startapp'] = lambda app_name, directory: startapp(app_name, project_directory)
+ action_mapping['startapp'].__doc__ = startapp.__doc__
+ action_mapping['startapp'].help_doc = startapp.help_doc
+ action_mapping['startapp'].args = startapp.args
+
+ # Run the django-admin.py command.
+ execute_from_command_line(action_mapping, argv)
diff --git a/google_appengine/lib/django/django/core/paginator.py b/google_appengine/lib/django/django/core/paginator.py
new file mode 100755
index 0000000..380808a
--- /dev/null
+++ b/google_appengine/lib/django/django/core/paginator.py
@@ -0,0 +1,88 @@
+class InvalidPage(Exception):
+ pass
+
+class ObjectPaginator(object):
+ """
+ This class makes pagination easy. Feed it a QuerySet or list, plus the number
+ of objects you want on each page. Then read the hits and pages properties to
+ see how many pages it involves. Call get_page with a page number (starting
+ at 0) to get back a list of objects for that page.
+
+ Finally, check if a page number has a next/prev page using
+ has_next_page(page_number) and has_previous_page(page_number).
+
+ Use orphans to avoid small final pages. For example:
+ 13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10
+ 12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12
+ """
+ def __init__(self, query_set, num_per_page, orphans=0):
+ self.query_set = query_set
+ self.num_per_page = num_per_page
+ self.orphans = orphans
+ self._hits = self._pages = None
+
+ def validate_page_number(self, page_number):
+ try:
+ page_number = int(page_number)
+ except ValueError:
+ raise InvalidPage
+ if page_number < 0 or page_number > self.pages - 1:
+ raise InvalidPage
+ return page_number
+
+ def get_page(self, page_number):
+ page_number = self.validate_page_number(page_number)
+ bottom = page_number * self.num_per_page
+ top = bottom + self.num_per_page
+ if top + self.orphans >= self.hits:
+ top = self.hits
+ return self.query_set[bottom:top]
+
+ def has_next_page(self, page_number):
+ "Does page $page_number have a 'next' page?"
+ return page_number < self.pages - 1
+
+ def has_previous_page(self, page_number):
+ return page_number > 0
+
+ def first_on_page(self, page_number):
+ """
+ Returns the 1-based index of the first object on the given page,
+ relative to total objects found (hits).
+ """
+ page_number = self.validate_page_number(page_number)
+ return (self.num_per_page * page_number) + 1
+
+ def last_on_page(self, page_number):
+ """
+ Returns the 1-based index of the last object on the given page,
+ relative to total objects found (hits).
+ """
+ page_number = self.validate_page_number(page_number)
+ page_number += 1 # 1-base
+ if page_number == self.pages:
+ return self.hits
+ return page_number * self.num_per_page
+
+ def _get_hits(self):
+ if self._hits is None:
+ # Try .count() or fall back to len().
+ try:
+ self._hits = int(self.query_set.count())
+ except (AttributeError, TypeError, ValueError):
+ # AttributeError if query_set has no object count.
+ # TypeError if query_set.count() required arguments.
+ # ValueError if int() fails.
+ self._hits = len(self.query_set)
+ return self._hits
+
+ def _get_pages(self):
+ if self._pages is None:
+ hits = (self.hits - 1 - self.orphans)
+ if hits < 1:
+ hits = 0
+ self._pages = hits // self.num_per_page + 1
+ return self._pages
+
+ hits = property(_get_hits)
+ pages = property(_get_pages)
diff --git a/google_appengine/lib/django/django/core/serializers/__init__.py b/google_appengine/lib/django/django/core/serializers/__init__.py
new file mode 100755
index 0000000..494393f
--- /dev/null
+++ b/google_appengine/lib/django/django/core/serializers/__init__.py
@@ -0,0 +1,90 @@
+"""
+Interfaces for serializing Django objects.
+
+Usage::
+
+ >>> from django.core import serializers
+ >>> json = serializers.serialize("json", some_query_set)
+ >>> objects = list(serializers.deserialize("json", json))
+
+To add your own serializers, use the SERIALIZATION_MODULES setting::
+
+ SERIALIZATION_MODULES = {
+ "csv" : "path.to.csv.serializer",
+ "txt" : "path.to.txt.serializer",
+ }
+
+"""
+
+from django.conf import settings
+
+# Built-in serializers
+BUILTIN_SERIALIZERS = {
+ "xml" : "django.core.serializers.xml_serializer",
+ "python" : "django.core.serializers.python",
+ "json" : "django.core.serializers.json",
+}
+
+# Check for PyYaml and register the serializer if it's available.
+try:
+ import yaml
+ BUILTIN_SERIALIZERS["yaml"] = "django.core.serializers.pyyaml"
+except ImportError:
+ pass
+
+_serializers = {}
+
+def register_serializer(format, serializer_module):
+ """Register a new serializer by passing in a module name."""
+ module = __import__(serializer_module, {}, {}, [''])
+ _serializers[format] = module
+
+def unregister_serializer(format):
+ """Unregister a given serializer"""
+ del _serializers[format]
+
+def get_serializer(format):
+ if not _serializers:
+ _load_serializers()
+ return _serializers[format].Serializer
+
+def get_serializer_formats():
+ if not _serializers:
+ _load_serializers()
+ return _serializers.keys()
+
+def get_deserializer(format):
+ if not _serializers:
+ _load_serializers()
+ return _serializers[format].Deserializer
+
+def serialize(format, queryset, **options):
+ """
+ Serialize a queryset (or any iterator that returns database objects) using
+ a certain serializer.
+ """
+ s = get_serializer(format)()
+ s.serialize(queryset, **options)
+ return s.getvalue()
+
+def deserialize(format, stream_or_string):
+ """
+ Deserialize a stream or a string. Returns an iterator that yields ``(obj,
+ m2m_relation_dict)``, where ``obj`` is a instantiated -- but *unsaved* --
+ object, and ``m2m_relation_dict`` is a dictionary of ``{m2m_field_name :
+ list_of_related_objects}``.
+ """
+ d = get_deserializer(format)
+ return d(stream_or_string)
+
+def _load_serializers():
+ """
+ Register built-in and settings-defined serializers. This is done lazily so
+ that user code has a chance to (e.g.) set up custom settings without
+ needing to be careful of import order.
+ """
+ for format in BUILTIN_SERIALIZERS:
+ register_serializer(format, BUILTIN_SERIALIZERS[format])
+ if hasattr(settings, "SERIALIZATION_MODULES"):
+ for format in settings.SERIALIZATION_MODULES:
+ register_serializer(format, settings.SERIALIZATION_MODULES[format]) \ No newline at end of file
diff --git a/google_appengine/lib/django/django/core/serializers/base.py b/google_appengine/lib/django/django/core/serializers/base.py
new file mode 100755
index 0000000..8e610ad
--- /dev/null
+++ b/google_appengine/lib/django/django/core/serializers/base.py
@@ -0,0 +1,165 @@
+"""
+Module for abstract serializer/unserializer base classes.
+"""
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+from django.db import models
+
+class SerializationError(Exception):
+ """Something bad happened during serialization."""
+ pass
+
+class DeserializationError(Exception):
+ """Something bad happened during deserialization."""
+ pass
+
+class Serializer(object):
+ """
+ Abstract serializer base class.
+ """
+
+ def serialize(self, queryset, **options):
+ """
+ Serialize a queryset.
+ """
+ self.options = options
+
+ self.stream = options.get("stream", StringIO())
+ self.selected_fields = options.get("fields")
+
+ self.start_serialization()
+ for obj in queryset:
+ self.start_object(obj)
+ for field in obj._meta.fields:
+ if field.serialize:
+ if field.rel is None:
+ if self.selected_fields is None or field.attname in self.selected_fields:
+ self.handle_field(obj, field)
+ else:
+ if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
+ self.handle_fk_field(obj, field)
+ for field in obj._meta.many_to_many:
+ if field.serialize:
+ if self.selected_fields is None or field.attname in self.selected_fields:
+ self.handle_m2m_field(obj, field)
+ self.end_object(obj)
+ self.end_serialization()
+ return self.getvalue()
+
+ def get_string_value(self, obj, field):
+ """
+ Convert a field's value to a string.
+ """
+ if isinstance(field, models.DateTimeField):
+ value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S")
+ elif isinstance(field, models.FileField):
+ value = getattr(obj, "get_%s_url" % field.name, lambda: None)()
+ else:
+ value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
+ return str(value)
+
+ def start_serialization(self):
+ """
+ Called when serializing of the queryset starts.
+ """
+ raise NotImplementedError
+
+ def end_serialization(self):
+ """
+ Called when serializing of the queryset ends.
+ """
+ pass
+
+ def start_object(self, obj):
+ """
+ Called when serializing of an object starts.
+ """
+ raise NotImplementedError
+
+ def end_object(self, obj):
+ """
+ Called when serializing of an object ends.
+ """
+ pass
+
+ def handle_field(self, obj, field):
+ """
+ Called to handle each individual (non-relational) field on an object.
+ """
+ raise NotImplementedError
+
+ def handle_fk_field(self, obj, field):
+ """
+ Called to handle a ForeignKey field.
+ """
+ raise NotImplementedError
+
+ def handle_m2m_field(self, obj, field):
+ """
+ Called to handle a ManyToManyField.
+ """
+ raise NotImplementedError
+
+ def getvalue(self):
+ """
+ Return the fully serialized queryset.
+ """
+ return self.stream.getvalue()
+
+class Deserializer(object):
+ """
+ Abstract base deserializer class.
+ """
+
+ def __init__(self, stream_or_string, **options):
+ """
+ Init this serializer given a stream or a string
+ """
+ self.options = options
+ if isinstance(stream_or_string, basestring):
+ self.stream = StringIO(stream_or_string)
+ else:
+ self.stream = stream_or_string
+ # hack to make sure that the models have all been loaded before
+ # deserialization starts (otherwise subclass calls to get_model()
+ # and friends might fail...)
+ models.get_apps()
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ """Iteration iterface -- return the next item in the stream"""
+ raise NotImplementedError
+
+class DeserializedObject(object):
+ """
+ A deserialized model.
+
+ Basically a container for holding the pre-saved deserialized data along
+ with the many-to-many data saved with the object.
+
+ Call ``save()`` to save the object (with the many-to-many data) to the
+ database; call ``save(save_m2m=False)`` to save just the object fields
+ (and not touch the many-to-many stuff.)
+ """
+
+ def __init__(self, obj, m2m_data=None):
+ self.object = obj
+ self.m2m_data = m2m_data
+
+ def __repr__(self):
+ return "<DeserializedObject: %s>" % str(self.object)
+
+ def save(self, save_m2m=True):
+ self.object.save()
+ if self.m2m_data and save_m2m:
+ for accessor_name, object_list in self.m2m_data.items():
+ setattr(self.object, accessor_name, object_list)
+
+ # prevent a second (possibly accidental) call to save() from saving
+ # the m2m data twice.
+ self.m2m_data = None
diff --git a/google_appengine/lib/django/django/core/serializers/json.py b/google_appengine/lib/django/django/core/serializers/json.py
new file mode 100755
index 0000000..15770f1
--- /dev/null
+++ b/google_appengine/lib/django/django/core/serializers/json.py
@@ -0,0 +1,51 @@
+"""
+Serialize data to/from JSON
+"""
+
+import datetime
+from django.utils import simplejson
+from django.core.serializers.python import Serializer as PythonSerializer
+from django.core.serializers.python import Deserializer as PythonDeserializer
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+class Serializer(PythonSerializer):
+ """
+ Convert a queryset to JSON.
+ """
+ def end_serialization(self):
+ simplejson.dump(self.objects, self.stream, cls=DateTimeAwareJSONEncoder, **self.options)
+
+ def getvalue(self):
+ return self.stream.getvalue()
+
+def Deserializer(stream_or_string, **options):
+ """
+ Deserialize a stream or string of JSON data.
+ """
+ if isinstance(stream_or_string, basestring):
+ stream = StringIO(stream_or_string)
+ else:
+ stream = stream_or_string
+ for obj in PythonDeserializer(simplejson.load(stream)):
+ yield obj
+
+class DateTimeAwareJSONEncoder(simplejson.JSONEncoder):
+ """
+ JSONEncoder subclass that knows how to encode date/time types
+ """
+
+ DATE_FORMAT = "%Y-%m-%d"
+ TIME_FORMAT = "%H:%M:%S"
+
+ def default(self, o):
+ if isinstance(o, datetime.datetime):
+ return o.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
+ elif isinstance(o, datetime.date):
+ return o.strftime(self.DATE_FORMAT)
+ elif isinstance(o, datetime.time):
+ return o.strftime(self.TIME_FORMAT)
+ else:
+ return super(DateTimeAwareJSONEncoder, self).default(o)
diff --git a/google_appengine/lib/django/django/core/serializers/python.py b/google_appengine/lib/django/django/core/serializers/python.py
new file mode 100755
index 0000000..29ce6bf
--- /dev/null
+++ b/google_appengine/lib/django/django/core/serializers/python.py
@@ -0,0 +1,101 @@
+"""
+A Python "serializer". Doesn't do much serializing per se -- just converts to
+and from basic Python data types (lists, dicts, strings, etc.). Useful as a basis for
+other serializers.
+"""
+
+from django.conf import settings
+from django.core.serializers import base
+from django.db import models
+
+class Serializer(base.Serializer):
+ """
+ Serializes a QuerySet to basic Python objects.
+ """
+
+ def start_serialization(self):
+ self._current = None
+ self.objects = []
+
+ def end_serialization(self):
+ pass
+
+ def start_object(self, obj):
+ self._current = {}
+
+ def end_object(self, obj):
+ self.objects.append({
+ "model" : str(obj._meta),
+ "pk" : str(obj._get_pk_val()),
+ "fields" : self._current
+ })
+ self._current = None
+
+ def handle_field(self, obj, field):
+ self._current[field.name] = getattr(obj, field.name)
+
+ def handle_fk_field(self, obj, field):
+ related = getattr(obj, field.name)
+ if related is not None:
+ related = related._get_pk_val()
+ self._current[field.name] = related
+
+ def handle_m2m_field(self, obj, field):
+ self._current[field.name] = [related._get_pk_val() for related in getattr(obj, field.name).iterator()]
+
+ def getvalue(self):
+ return self.objects
+
+def Deserializer(object_list, **options):
+ """
+ Deserialize simple Python objects back into Django ORM instances.
+
+ It's expected that you pass the Python objects themselves (instead of a
+ stream or a string) to the constructor
+ """
+ models.get_apps()
+ for d in object_list:
+ # Look up the model and starting build a dict of data for it.
+ Model = _get_model(d["model"])
+ data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
+ m2m_data = {}
+
+ # Handle each field
+ for (field_name, field_value) in d["fields"].iteritems():
+ if isinstance(field_value, unicode):
+ field_value = field_value.encode(options.get("encoding", settings.DEFAULT_CHARSET))
+
+ field = Model._meta.get_field(field_name)
+
+ # Handle M2M relations
+ if field.rel and isinstance(field.rel, models.ManyToManyRel):
+ pks = []
+ m2m_convert = field.rel.to._meta.pk.to_python
+ for pk in field_value:
+ if isinstance(pk, unicode):
+ pks.append(m2m_convert(pk.encode(options.get("encoding", settings.DEFAULT_CHARSET))))
+ else:
+ pks.append(m2m_convert(pk))
+ m2m_data[field.name] = pks
+
+ # Handle FK fields
+ elif field.rel and isinstance(field.rel, models.ManyToOneRel):
+ data[field.attname] = field.rel.to._meta.pk.to_python(field_value)
+
+ # Handle all other fields
+ else:
+ data[field.name] = field.to_python(field_value)
+
+ yield base.DeserializedObject(Model(**data), m2m_data)
+
+def _get_model(model_identifier):
+ """
+ Helper to look up a model from an "app_label.module_name" string.
+ """
+ try:
+ Model = models.get_model(*model_identifier.split("."))
+ except TypeError:
+ Model = None
+ if Model is None:
+ raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)
+ return Model
diff --git a/google_appengine/lib/django/django/core/serializers/pyyaml.py b/google_appengine/lib/django/django/core/serializers/pyyaml.py
new file mode 100755
index 0000000..fa3dec9
--- /dev/null
+++ b/google_appengine/lib/django/django/core/serializers/pyyaml.py
@@ -0,0 +1,36 @@
+"""
+YAML serializer.
+
+Requires PyYaml (http://pyyaml.org/), but that's checked for in __init__.
+"""
+
+import datetime
+from django.core.serializers.python import Serializer as PythonSerializer
+from django.core.serializers.python import Deserializer as PythonDeserializer
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+import yaml
+
+class Serializer(PythonSerializer):
+ """
+ Convert a queryset to YAML.
+ """
+ def end_serialization(self):
+ yaml.dump(self.objects, self.stream, **self.options)
+
+ def getvalue(self):
+ return self.stream.getvalue()
+
+def Deserializer(stream_or_string, **options):
+ """
+ Deserialize a stream or string of YAML data.
+ """
+ if isinstance(stream_or_string, basestring):
+ stream = StringIO(stream_or_string)
+ else:
+ stream = stream_or_string
+ for obj in PythonDeserializer(yaml.load(stream)):
+ yield obj
+
diff --git a/google_appengine/lib/django/django/core/serializers/xml_serializer.py b/google_appengine/lib/django/django/core/serializers/xml_serializer.py
new file mode 100755
index 0000000..3a0fdb5
--- /dev/null
+++ b/google_appengine/lib/django/django/core/serializers/xml_serializer.py
@@ -0,0 +1,229 @@
+"""
+XML serializer.
+"""
+
+from django.conf import settings
+from django.core.serializers import base
+from django.db import models
+from django.utils.xmlutils import SimplerXMLGenerator
+from xml.dom import pulldom
+
+class Serializer(base.Serializer):
+ """
+ Serializes a QuerySet to XML.
+ """
+
+ def indent(self, level):
+ if self.options.get('indent', None) is not None:
+ self.xml.ignorableWhitespace('\n' + ' ' * self.options.get('indent', None) * level)
+
+ def start_serialization(self):
+ """
+ Start serialization -- open the XML document and the root element.
+ """
+ self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", settings.DEFAULT_CHARSET))
+ self.xml.startDocument()
+ self.xml.startElement("django-objects", {"version" : "1.0"})
+
+ def end_serialization(self):
+ """
+ End serialization -- end the document.
+ """
+ self.indent(0)
+ self.xml.endElement("django-objects")
+ self.xml.endDocument()
+
+ def start_object(self, obj):
+ """
+ Called as each object is handled.
+ """
+ if not hasattr(obj, "_meta"):
+ raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
+
+ self.indent(1)
+ self.xml.startElement("object", {
+ "pk" : str(obj._get_pk_val()),
+ "model" : str(obj._meta),
+ })
+
+ def end_object(self, obj):
+ """
+ Called after handling all fields for an object.
+ """
+ self.indent(1)
+ self.xml.endElement("object")
+
+ def handle_field(self, obj, field):
+ """
+ Called to handle each field on an object (except for ForeignKeys and
+ ManyToManyFields)
+ """
+ self.indent(2)
+ self.xml.startElement("field", {
+ "name" : field.name,
+ "type" : field.get_internal_type()
+ })
+
+ # Get a "string version" of the object's data (this is handled by the
+ # serializer base class).
+ if getattr(obj, field.name) is not None:
+ value = self.get_string_value(obj, field)
+ self.xml.characters(str(value))
+ else:
+ self.xml.addQuickElement("None")
+
+ self.xml.endElement("field")
+
+ def handle_fk_field(self, obj, field):
+ """
+ Called to handle a ForeignKey (we need to treat them slightly
+ differently from regular fields).
+ """
+ self._start_relational_field(field)
+ related = getattr(obj, field.name)
+ if related is not None:
+ self.xml.characters(str(related._get_pk_val()))
+ else:
+ self.xml.addQuickElement("None")
+ self.xml.endElement("field")
+
+ def handle_m2m_field(self, obj, field):
+ """
+ Called to handle a ManyToManyField. Related objects are only
+ serialized as references to the object's PK (i.e. the related *data*
+ is not dumped, just the relation).
+ """
+ self._start_relational_field(field)
+ for relobj in getattr(obj, field.name).iterator():
+ self.xml.addQuickElement("object", attrs={"pk" : str(relobj._get_pk_val())})
+ self.xml.endElement("field")
+
+ def _start_relational_field(self, field):
+ """
+ Helper to output the <field> element for relational fields
+ """
+ self.indent(2)
+ self.xml.startElement("field", {
+ "name" : field.name,
+ "rel" : field.rel.__class__.__name__,
+ "to" : str(field.rel.to._meta),
+ })
+
+class Deserializer(base.Deserializer):
+ """
+ Deserialize XML.
+ """
+
+ def __init__(self, stream_or_string, **options):
+ super(Deserializer, self).__init__(stream_or_string, **options)
+ self.encoding = self.options.get("encoding", settings.DEFAULT_CHARSET)
+ self.event_stream = pulldom.parse(self.stream)
+
+ def next(self):
+ for event, node in self.event_stream:
+ if event == "START_ELEMENT" and node.nodeName == "object":
+ self.event_stream.expandNode(node)
+ return self._handle_object(node)
+ raise StopIteration
+
+ def _handle_object(self, node):
+ """
+ Convert an <object> node to a DeserializedObject.
+ """
+ # Look up the model using the model loading mechanism. If this fails, bail.
+ Model = self._get_model_from_node(node, "model")
+
+ # Start building a data dictionary from the object. If the node is
+ # missing the pk attribute, bail.
+ pk = node.getAttribute("pk")
+ if not pk:
+ raise base.DeserializationError("<object> node is missing the 'pk' attribute")
+
+ data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
+
+ # Also start building a dict of m2m data (this is saved as
+ # {m2m_accessor_attribute : [list_of_related_objects]})
+ m2m_data = {}
+
+ # Deseralize each field.
+ for field_node in node.getElementsByTagName("field"):
+ # If the field is missing the name attribute, bail (are you
+ # sensing a pattern here?)
+ field_name = field_node.getAttribute("name")
+ if not field_name:
+ raise base.DeserializationError("<field> node is missing the 'name' attribute")
+
+ # Get the field from the Model. This will raise a
+ # FieldDoesNotExist if, well, the field doesn't exist, which will
+ # be propagated correctly.
+ field = Model._meta.get_field(field_name)
+
+ # As is usually the case, relation fields get the special treatment.
+ if field.rel and isinstance(field.rel, models.ManyToManyRel):
+ m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
+ elif field.rel and isinstance(field.rel, models.ManyToOneRel):
+ data[field.attname] = self._handle_fk_field_node(field_node, field)
+ else:
+ if len(field_node.childNodes) == 1 and field_node.childNodes[0].nodeName == 'None':
+ value = None
+ else:
+ value = field.to_python(getInnerText(field_node).strip().encode(self.encoding))
+ data[field.name] = value
+
+ # Return a DeserializedObject so that the m2m data has a place to live.
+ return base.DeserializedObject(Model(**data), m2m_data)
+
+ def _handle_fk_field_node(self, node, field):
+ """
+ Handle a <field> node for a ForeignKey
+ """
+ # Check if there is a child node named 'None', returning None if so.
+ if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None':
+ return None
+ else:
+ return field.rel.to._meta.pk.to_python(
+ getInnerText(node).strip().encode(self.encoding))
+
+ def _handle_m2m_field_node(self, node, field):
+ """
+ Handle a <field> node for a ManyToManyField
+ """
+ return [field.rel.to._meta.pk.to_python(
+ c.getAttribute("pk").encode(self.encoding))
+ for c in node.getElementsByTagName("object")]
+
+ def _get_model_from_node(self, node, attr):
+ """
+ Helper to look up a model from a <object model=...> or a <field
+ rel=... to=...> node.
+ """
+ model_identifier = node.getAttribute(attr)
+ if not model_identifier:
+ raise base.DeserializationError(
+ "<%s> node is missing the required '%s' attribute" \
+ % (node.nodeName, attr))
+ try:
+ Model = models.get_model(*model_identifier.split("."))
+ except TypeError:
+ Model = None
+ if Model is None:
+ raise base.DeserializationError(
+ "<%s> node has invalid model identifier: '%s'" % \
+ (node.nodeName, model_identifier))
+ return Model
+
+
+def getInnerText(node):
+ """
+ Get all the inner text of a DOM node (recursively).
+ """
+ # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html
+ inner_text = []
+ for child in node.childNodes:
+ if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE:
+ inner_text.append(child.data)
+ elif child.nodeType == child.ELEMENT_NODE:
+ inner_text.extend(getInnerText(child))
+ else:
+ pass
+ return "".join(inner_text) \ No newline at end of file
diff --git a/google_appengine/lib/django/django/core/servers/__init__.py b/google_appengine/lib/django/django/core/servers/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/core/servers/__init__.py
diff --git a/google_appengine/lib/django/django/core/servers/basehttp.py b/google_appengine/lib/django/django/core/servers/basehttp.py
new file mode 100755
index 0000000..27051d4
--- /dev/null
+++ b/google_appengine/lib/django/django/core/servers/basehttp.py
@@ -0,0 +1,664 @@
+"""
+BaseHTTPServer that implements the Python WSGI protocol (PEP 333, rev 1.21).
+
+Adapted from wsgiref.simple_server: http://svn.eby-sarna.com/wsgiref/
+
+This is a simple server for use in testing or debugging Django apps. It hasn't
+been reviewed for security issues. Don't use it for production use.
+"""
+
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+from types import ListType, StringType
+import os, re, sys, time, urllib
+
+from django.utils._os import safe_join
+
+__version__ = "0.1"
+__all__ = ['WSGIServer','WSGIRequestHandler','demo_app']
+
+server_version = "WSGIServer/" + __version__
+sys_version = "Python/" + sys.version.split()[0]
+software_version = server_version + ' ' + sys_version
+
+class WSGIServerException(Exception):
+ pass
+
+class FileWrapper(object):
+ """Wrapper to convert file-like objects to iterables"""
+
+ def __init__(self, filelike, blksize=8192):
+ self.filelike = filelike
+ self.blksize = blksize
+ if hasattr(filelike,'close'):
+ self.close = filelike.close
+
+ def __getitem__(self,key):
+ data = self.filelike.read(self.blksize)
+ if data:
+ return data
+ raise IndexError
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ data = self.filelike.read(self.blksize)
+ if data:
+ return data
+ raise StopIteration
+
+# Regular expression that matches `special' characters in parameters, the
+# existence of which force quoting of the parameter value.
+tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
+
+def _formatparam(param, value=None, quote=1):
+ """Convenience function to format and return a key=value pair.
+
+ This will quote the value if needed or if quote is true.
+ """
+ if value is not None and len(value) > 0:
+ if quote or tspecials.search(value):
+ value = value.replace('\\', '\\\\').replace('"', r'\"')
+ return '%s="%s"' % (param, value)
+ else:
+ return '%s=%s' % (param, value)
+ else:
+ return param
+
+class Headers(object):
+ """Manage a collection of HTTP response headers"""
+ def __init__(self,headers):
+ if type(headers) is not ListType:
+ raise TypeError("Headers must be a list of name/value tuples")
+ self._headers = headers
+
+ def __len__(self):
+ """Return the total number of headers, including duplicates."""
+ return len(self._headers)
+
+ def __setitem__(self, name, val):
+ """Set the value of a header."""
+ del self[name]
+ self._headers.append((name, val))
+
+ def __delitem__(self,name):
+ """Delete all occurrences of a header, if present.
+
+ Does *not* raise an exception if the header is missing.
+ """
+ name = name.lower()
+ self._headers[:] = [kv for kv in self._headers if kv[0].lower()<>name]
+
+ def __getitem__(self,name):
+ """Get the first header value for 'name'
+
+ Return None if the header is missing instead of raising an exception.
+
+ Note that if the header appeared multiple times, the first exactly which
+ occurrance gets returned is undefined. Use getall() to get all
+ the values matching a header field name.
+ """
+ return self.get(name)
+
+ def has_key(self, name):
+ """Return true if the message contains the header."""
+ return self.get(name) is not None
+
+ __contains__ = has_key
+
+ def get_all(self, name):
+ """Return a list of all the values for the named field.
+
+ These will be sorted in the order they appeared in the original header
+ list or were added to this instance, and may contain duplicates. Any
+ fields deleted and re-inserted are always appended to the header list.
+ If no fields exist with the given name, returns an empty list.
+ """
+ name = name.lower()
+ return [kv[1] for kv in self._headers if kv[0].lower()==name]
+
+
+ def get(self,name,default=None):
+ """Get the first header value for 'name', or return 'default'"""
+ name = name.lower()
+ for k,v in self._headers:
+ if k.lower()==name:
+ return v
+ return default
+
+ def keys(self):
+ """Return a list of all the header field names.
+
+ These will be sorted in the order they appeared in the original header
+ list, or were added to this instance, and may contain duplicates.
+ Any fields deleted and re-inserted are always appended to the header
+ list.
+ """
+ return [k for k, v in self._headers]
+
+ def values(self):
+ """Return a list of all header values.
+
+ These will be sorted in the order they appeared in the original header
+ list, or were added to this instance, and may contain duplicates.
+ Any fields deleted and re-inserted are always appended to the header
+ list.
+ """
+ return [v for k, v in self._headers]
+
+ def items(self):
+ """Get all the header fields and values.
+
+ These will be sorted in the order they were in the original header
+ list, or were added to this instance, and may contain duplicates.
+ Any fields deleted and re-inserted are always appended to the header
+ list.
+ """
+ return self._headers[:]
+
+ def __repr__(self):
+ return "Headers(%s)" % `self._headers`
+
+ def __str__(self):
+ """str() returns the formatted headers, complete with end line,
+ suitable for direct HTTP transmission."""
+ return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['',''])
+
+ def setdefault(self,name,value):
+ """Return first matching header value for 'name', or 'value'
+
+ If there is no header named 'name', add a new header with name 'name'
+ and value 'value'."""
+ result = self.get(name)
+ if result is None:
+ self._headers.append((name,value))
+ return value
+ else:
+ return result
+
+ def add_header(self, _name, _value, **_params):
+ """Extended header setting.
+
+ _name is the header field to add. keyword arguments can be used to set
+ additional parameters for the header field, with underscores converted
+ to dashes. Normally the parameter will be added as key="value" unless
+ value is None, in which case only the key will be added.
+
+ Example:
+
+ h.add_header('content-disposition', 'attachment', filename='bud.gif')
+
+ Note that unlike the corresponding 'email.Message' method, this does
+ *not* handle '(charset, language, value)' tuples: all values must be
+ strings or None.
+ """
+ parts = []
+ if _value is not None:
+ parts.append(_value)
+ for k, v in _params.items():
+ if v is None:
+ parts.append(k.replace('_', '-'))
+ else:
+ parts.append(_formatparam(k.replace('_', '-'), v))
+ self._headers.append((_name, "; ".join(parts)))
+
+def guess_scheme(environ):
+ """Return a guess for whether 'wsgi.url_scheme' should be 'http' or 'https'
+ """
+ if environ.get("HTTPS") in ('yes','on','1'):
+ return 'https'
+ else:
+ return 'http'
+
+_hoppish = {
+ 'connection':1, 'keep-alive':1, 'proxy-authenticate':1,
+ 'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1,
+ 'upgrade':1
+}.has_key
+
+def is_hop_by_hop(header_name):
+ """Return true if 'header_name' is an HTTP/1.1 "Hop-by-Hop" header"""
+ return _hoppish(header_name.lower())
+
+class ServerHandler(object):
+ """Manage the invocation of a WSGI application"""
+
+ # Configuration parameters; can override per-subclass or per-instance
+ wsgi_version = (1,0)
+ wsgi_multithread = True
+ wsgi_multiprocess = True
+ wsgi_run_once = False
+
+ origin_server = True # We are transmitting direct to client
+ http_version = "1.0" # Version that should be used for response
+ server_software = software_version
+
+ # os_environ is used to supply configuration from the OS environment:
+ # by default it's a copy of 'os.environ' as of import time, but you can
+ # override this in e.g. your __init__ method.
+ os_environ = dict(os.environ.items())
+
+ # Collaborator classes
+ wsgi_file_wrapper = FileWrapper # set to None to disable
+ headers_class = Headers # must be a Headers-like class
+
+ # Error handling (also per-subclass or per-instance)
+ traceback_limit = None # Print entire traceback to self.get_stderr()
+ error_status = "500 INTERNAL SERVER ERROR"
+ error_headers = [('Content-Type','text/plain')]
+
+ # State variables (don't mess with these)
+ status = result = None
+ headers_sent = False
+ headers = None
+ bytes_sent = 0
+
+ def __init__(self, stdin, stdout, stderr, environ, multithread=True,
+ multiprocess=False):
+ self.stdin = stdin
+ self.stdout = stdout
+ self.stderr = stderr
+ self.base_env = environ
+ self.wsgi_multithread = multithread
+ self.wsgi_multiprocess = multiprocess
+
+ def run(self, application):
+ """Invoke the application"""
+ # Note to self: don't move the close()! Asynchronous servers shouldn't
+ # call close() from finish_response(), so if you close() anywhere but
+ # the double-error branch here, you'll break asynchronous servers by
+ # prematurely closing. Async servers must return from 'run()' without
+ # closing if there might still be output to iterate over.
+ try:
+ self.setup_environ()
+ self.result = application(self.environ, self.start_response)
+ self.finish_response()
+ except:
+ try:
+ self.handle_error()
+ except:
+ # If we get an error handling an error, just give up already!
+ self.close()
+ raise # ...and let the actual server figure it out.
+
+ def setup_environ(self):
+ """Set up the environment for one request"""
+
+ env = self.environ = self.os_environ.copy()
+ self.add_cgi_vars()
+
+ env['wsgi.input'] = self.get_stdin()
+ env['wsgi.errors'] = self.get_stderr()
+ env['wsgi.version'] = self.wsgi_version
+ env['wsgi.run_once'] = self.wsgi_run_once
+ env['wsgi.url_scheme'] = self.get_scheme()
+ env['wsgi.multithread'] = self.wsgi_multithread
+ env['wsgi.multiprocess'] = self.wsgi_multiprocess
+
+ if self.wsgi_file_wrapper is not None:
+ env['wsgi.file_wrapper'] = self.wsgi_file_wrapper
+
+ if self.origin_server and self.server_software:
+ env.setdefault('SERVER_SOFTWARE',self.server_software)
+
+ def finish_response(self):
+ """Send any iterable data, then close self and the iterable
+
+ Subclasses intended for use in asynchronous servers will
+ want to redefine this method, such that it sets up callbacks
+ in the event loop to iterate over the data, and to call
+ 'self.close()' once the response is finished.
+ """
+ if not self.result_is_file() and not self.sendfile():
+ for data in self.result:
+ self.write(data)
+ self.finish_content()
+ self.close()
+
+ def get_scheme(self):
+ """Return the URL scheme being used"""
+ return guess_scheme(self.environ)
+
+ def set_content_length(self):
+ """Compute Content-Length or switch to chunked encoding if possible"""
+ try:
+ blocks = len(self.result)
+ except (TypeError,AttributeError,NotImplementedError):
+ pass
+ else:
+ if blocks==1:
+ self.headers['Content-Length'] = str(self.bytes_sent)
+ return
+ # XXX Try for chunked encoding if origin server and client is 1.1
+
+ def cleanup_headers(self):
+ """Make any necessary header changes or defaults
+
+ Subclasses can extend this to add other defaults.
+ """
+ if not self.headers.has_key('Content-Length'):
+ self.set_content_length()
+
+ def start_response(self, status, headers,exc_info=None):
+ """'start_response()' callable as specified by PEP 333"""
+
+ if exc_info:
+ try:
+ if self.headers_sent:
+ # Re-raise original exception if headers sent
+ raise exc_info[0], exc_info[1], exc_info[2]
+ finally:
+ exc_info = None # avoid dangling circular ref
+ elif self.headers is not None:
+ raise AssertionError("Headers already set!")
+
+ assert type(status) is StringType,"Status must be a string"
+ assert len(status)>=4,"Status must be at least 4 characters"
+ assert int(status[:3]),"Status message must begin w/3-digit code"
+ assert status[3]==" ", "Status message must have a space after code"
+ if __debug__:
+ for name,val in headers:
+ assert type(name) is StringType,"Header names must be strings"
+ assert type(val) is StringType,"Header values must be strings"
+ assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
+ self.status = status
+ self.headers = self.headers_class(headers)
+ return self.write
+
+ def send_preamble(self):
+ """Transmit version/status/date/server, via self._write()"""
+ if self.origin_server:
+ if self.client_is_modern():
+ self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
+ if not self.headers.has_key('Date'):
+ self._write(
+ 'Date: %s\r\n' % time.asctime(time.gmtime(time.time()))
+ )
+ if self.server_software and not self.headers.has_key('Server'):
+ self._write('Server: %s\r\n' % self.server_software)
+ else:
+ self._write('Status: %s\r\n' % self.status)
+
+ def write(self, data):
+ """'write()' callable as specified by PEP 333"""
+
+ assert type(data) is StringType,"write() argument must be string"
+
+ if not self.status:
+ raise AssertionError("write() before start_response()")
+
+ elif not self.headers_sent:
+ # Before the first output, send the stored headers
+ self.bytes_sent = len(data) # make sure we know content-length
+ self.send_headers()
+ else:
+ self.bytes_sent += len(data)
+
+ # XXX check Content-Length and truncate if too many bytes written?
+ self._write(data)
+ self._flush()
+
+ def sendfile(self):
+ """Platform-specific file transmission
+
+ Override this method in subclasses to support platform-specific
+ file transmission. It is only called if the application's
+ return iterable ('self.result') is an instance of
+ 'self.wsgi_file_wrapper'.
+
+ This method should return a true value if it was able to actually
+ transmit the wrapped file-like object using a platform-specific
+ approach. It should return a false value if normal iteration
+ should be used instead. An exception can be raised to indicate
+ that transmission was attempted, but failed.
+
+ NOTE: this method should call 'self.send_headers()' if
+ 'self.headers_sent' is false and it is going to attempt direct
+ transmission of the file1.
+ """
+ return False # No platform-specific transmission by default
+
+ def finish_content(self):
+ """Ensure headers and content have both been sent"""
+ if not self.headers_sent:
+ self.headers['Content-Length'] = "0"
+ self.send_headers()
+ else:
+ pass # XXX check if content-length was too short?
+
+ def close(self):
+ try:
+ self.request_handler.log_request(self.status.split(' ',1)[0], self.bytes_sent)
+ finally:
+ try:
+ if hasattr(self.result,'close'):
+ self.result.close()
+ finally:
+ self.result = self.headers = self.status = self.environ = None
+ self.bytes_sent = 0; self.headers_sent = False
+
+ def send_headers(self):
+ """Transmit headers to the client, via self._write()"""
+ self.cleanup_headers()
+ self.headers_sent = True
+ if not self.origin_server or self.client_is_modern():
+ self.send_preamble()
+ self._write(str(self.headers))
+
+ def result_is_file(self):
+ """True if 'self.result' is an instance of 'self.wsgi_file_wrapper'"""
+ wrapper = self.wsgi_file_wrapper
+ return wrapper is not None and isinstance(self.result,wrapper)
+
+ def client_is_modern(self):
+ """True if client can accept status and headers"""
+ return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9'
+
+ def log_exception(self,exc_info):
+ """Log the 'exc_info' tuple in the server log
+
+ Subclasses may override to retarget the output or change its format.
+ """
+ try:
+ from traceback import print_exception
+ stderr = self.get_stderr()
+ print_exception(
+ exc_info[0], exc_info[1], exc_info[2],
+ self.traceback_limit, stderr
+ )
+ stderr.flush()
+ finally:
+ exc_info = None
+
+ def handle_error(self):
+ """Log current error, and send error output to client if possible"""
+ self.log_exception(sys.exc_info())
+ if not self.headers_sent:
+ self.result = self.error_output(self.environ, self.start_response)
+ self.finish_response()
+ # XXX else: attempt advanced recovery techniques for HTML or text?
+
+ def error_output(self, environ, start_response):
+ import traceback
+ start_response(self.error_status, self.error_headers[:], sys.exc_info())
+ return ['\n'.join(traceback.format_exception(*sys.exc_info()))]
+
+ # Pure abstract methods; *must* be overridden in subclasses
+
+ def _write(self,data):
+ self.stdout.write(data)
+ self._write = self.stdout.write
+
+ def _flush(self):
+ self.stdout.flush()
+ self._flush = self.stdout.flush
+
+ def get_stdin(self):
+ return self.stdin
+
+ def get_stderr(self):
+ return self.stderr
+
+ def add_cgi_vars(self):
+ self.environ.update(self.base_env)
+
+class WSGIServer(HTTPServer):
+ """BaseHTTPServer that implements the Python WSGI protocol"""
+ application = None
+
+ def server_bind(self):
+ """Override server_bind to store the server name."""
+ try:
+ HTTPServer.server_bind(self)
+ except Exception, e:
+ raise WSGIServerException, e
+ self.setup_environ()
+
+ def setup_environ(self):
+ # Set up base environment
+ env = self.base_environ = {}
+ env['SERVER_NAME'] = self.server_name
+ env['GATEWAY_INTERFACE'] = 'CGI/1.1'
+ env['SERVER_PORT'] = str(self.server_port)
+ env['REMOTE_HOST']=''
+ env['CONTENT_LENGTH']=''
+ env['SCRIPT_NAME'] = ''
+
+ def get_app(self):
+ return self.application
+
+ def set_app(self,application):
+ self.application = application
+
+class WSGIRequestHandler(BaseHTTPRequestHandler):
+ server_version = "WSGIServer/" + __version__
+
+ def __init__(self, *args, **kwargs):
+ from django.conf import settings
+ self.admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
+ BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
+
+ def get_environ(self):
+ env = self.server.base_environ.copy()
+ env['SERVER_PROTOCOL'] = self.request_version
+ env['REQUEST_METHOD'] = self.command
+ if '?' in self.path:
+ path,query = self.path.split('?',1)
+ else:
+ path,query = self.path,''
+
+ env['PATH_INFO'] = urllib.unquote(path)
+ env['QUERY_STRING'] = query
+ env['REMOTE_ADDR'] = self.client_address[0]
+
+ if self.headers.typeheader is None:
+ env['CONTENT_TYPE'] = self.headers.type
+ else:
+ env['CONTENT_TYPE'] = self.headers.typeheader
+
+ length = self.headers.getheader('content-length')
+ if length:
+ env['CONTENT_LENGTH'] = length
+
+ for h in self.headers.headers:
+ k,v = h.split(':',1)
+ k=k.replace('-','_').upper(); v=v.strip()
+ if k in env:
+ continue # skip content length, type,etc.
+ if 'HTTP_'+k in env:
+ env['HTTP_'+k] += ','+v # comma-separate multiple headers
+ else:
+ env['HTTP_'+k] = v
+ return env
+
+ def get_stderr(self):
+ return sys.stderr
+
+ def handle(self):
+ """Handle a single HTTP request"""
+ self.raw_requestline = self.rfile.readline()
+ if not self.parse_request(): # An error code has been sent, just exit
+ return
+ handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
+ handler.request_handler = self # backpointer for logging
+ handler.run(self.server.get_app())
+
+ def log_message(self, format, *args):
+ # Don't bother logging requests for admin images or the favicon.
+ if self.path.startswith(self.admin_media_prefix) or self.path == '/favicon.ico':
+ return
+ sys.stderr.write("[%s] %s\n" % (self.log_date_time_string(), format % args))
+
+class AdminMediaHandler(object):
+ """
+ WSGI middleware that intercepts calls to the admin media directory, as
+ defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
+ Use this ONLY LOCALLY, for development! This hasn't been tested for
+ security and is not super efficient.
+ """
+ def __init__(self, application, media_dir=None):
+ from django.conf import settings
+ self.application = application
+ if not media_dir:
+ import django
+ self.media_dir = \
+ os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
+ else:
+ self.media_dir = media_dir
+ self.media_url = settings.ADMIN_MEDIA_PREFIX
+
+ def file_path(self, url):
+ """
+ Returns the path to the media file on disk for the given URL.
+
+ The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX. If the
+ resultant file path is outside the media directory, then a ValueError
+ is raised.
+ """
+ # Remove ADMIN_MEDIA_PREFIX.
+ relative_url = url[len(self.media_url):]
+ relative_path = urllib.url2pathname(relative_url)
+ return safe_join(self.media_dir, relative_path)
+
+ def __call__(self, environ, start_response):
+ import os.path
+
+ # Ignore requests that aren't under ADMIN_MEDIA_PREFIX. Also ignore
+ # all requests if ADMIN_MEDIA_PREFIX isn't a relative URL.
+ if self.media_url.startswith('http://') or self.media_url.startswith('https://') \
+ or not environ['PATH_INFO'].startswith(self.media_url):
+ return self.application(environ, start_response)
+
+ # Find the admin file and serve it up, if it exists and is readable.
+ try:
+ file_path = self.file_path(environ['PATH_INFO'])
+ except ValueError: # Resulting file path was not valid.
+ status = '404 NOT FOUND'
+ headers = {'Content-type': 'text/plain'}
+ output = ['Page not found: %s' % environ['PATH_INFO']]
+ start_response(status, headers.items())
+ return output
+ if not os.path.exists(file_path):
+ status = '404 NOT FOUND'
+ headers = {'Content-type': 'text/plain'}
+ output = ['Page not found: %s' % environ['PATH_INFO']]
+ else:
+ try:
+ fp = open(file_path, 'rb')
+ except IOError:
+ status = '401 UNAUTHORIZED'
+ headers = {'Content-type': 'text/plain'}
+ output = ['Permission denied: %s' % environ['PATH_INFO']]
+ else:
+ status = '200 OK'
+ headers = {}
+ output = [fp.read()]
+ fp.close()
+ start_response(status, headers.items())
+ return output
+
+def run(addr, port, wsgi_handler):
+ server_address = (addr, port)
+ httpd = WSGIServer(server_address, WSGIRequestHandler)
+ httpd.set_app(wsgi_handler)
+ httpd.serve_forever()
diff --git a/google_appengine/lib/django/django/core/servers/fastcgi.py b/google_appengine/lib/django/django/core/servers/fastcgi.py
new file mode 100755
index 0000000..649dd69
--- /dev/null
+++ b/google_appengine/lib/django/django/core/servers/fastcgi.py
@@ -0,0 +1,158 @@
+"""
+FastCGI server that implements the WSGI protocol.
+
+Uses the flup python package: http://www.saddi.com/software/flup/
+
+This is a adaptation of the flup package to add FastCGI server support
+to run Django apps from Web servers that support the FastCGI protocol.
+This module can be run standalone or from the django-admin / manage.py
+scripts using the "runfcgi" directive.
+
+Run with the extra option "help" for a list of additional options you can
+pass to this server.
+"""
+
+import sys, os
+
+__version__ = "0.1"
+__all__ = ["runfastcgi"]
+
+FASTCGI_HELP = r"""runfcgi:
+ Run this project as a fastcgi application. To do this, the
+ flup package from http://www.saddi.com/software/flup/ is
+ required.
+
+Usage:
+ django-admin.py runfcgi --settings=yourproject.settings [fcgi settings]
+ manage.py runfcgi [fcgi settings]
+
+Optional Fcgi settings: (setting=value)
+ host=HOSTNAME hostname to listen on..
+ port=PORTNUM port to listen on.
+ socket=FILE UNIX socket to listen on.
+ method=IMPL prefork or threaded (default prefork)
+ maxrequests=NUMBER number of requests a child handles before it is
+ killed and a new child is forked (0 = no limit).
+ maxspare=NUMBER max number of spare processes / threads
+ minspare=NUMBER min number of spare processes / threads.
+ maxchildren=NUMBER hard limit number of processes / threads
+ daemonize=BOOL whether to detach from terminal.
+ pidfile=FILE write the spawned process-id to this file.
+ workdir=DIRECTORY change to this directory when daemonizing
+
+Examples:
+ Run a "standard" fastcgi process on a file-descriptor
+ (for webservers which spawn your processes for you)
+ $ manage.py runfcgi method=threaded
+
+ Run a fastcgi server on a TCP host/port
+ $ manage.py runfcgi method=prefork host=127.0.0.1 port=8025
+
+ Run a fastcgi server on a UNIX domain socket (posix platforms only)
+ $ manage.py runfcgi method=prefork socket=/tmp/fcgi.sock
+
+ Run a fastCGI as a daemon and write the spawned PID in a file
+ $ manage.py runfcgi socket=/tmp/fcgi.sock method=prefork \
+ daemonize=true pidfile=/var/run/django-fcgi.pid
+
+"""
+
+FASTCGI_OPTIONS = {
+ 'host': None,
+ 'port': None,
+ 'socket': None,
+ 'method': 'fork',
+ 'daemonize': None,
+ 'workdir': '/',
+ 'pidfile': None,
+ 'maxspare': 5,
+ 'minspare': 2,
+ 'maxchildren': 50,
+ 'maxrequests': 0,
+}
+
+def fastcgi_help(message=None):
+ print FASTCGI_HELP
+ if message:
+ print message
+ return False
+
+def runfastcgi(argset=[], **kwargs):
+ options = FASTCGI_OPTIONS.copy()
+ options.update(kwargs)
+ for x in argset:
+ if "=" in x:
+ k, v = x.split('=', 1)
+ else:
+ k, v = x, True
+ options[k.lower()] = v
+
+ if "help" in options:
+ return fastcgi_help()
+
+ try:
+ import flup
+ except ImportError, e:
+ print >> sys.stderr, "ERROR: %s" % e
+ print >> sys.stderr, " Unable to load the flup package. In order to run django"
+ print >> sys.stderr, " as a FastCGI application, you will need to get flup from"
+ print >> sys.stderr, " http://www.saddi.com/software/flup/ If you've already"
+ print >> sys.stderr, " installed flup, then make sure you have it in your PYTHONPATH."
+ return False
+
+ if options['method'] in ('prefork', 'fork'):
+ from flup.server.fcgi_fork import WSGIServer
+ wsgi_opts = {
+ 'maxSpare': int(options["maxspare"]),
+ 'minSpare': int(options["minspare"]),
+ 'maxChildren': int(options["maxchildren"]),
+ 'maxRequests': int(options["maxrequests"]),
+ }
+ elif options['method'] in ('thread', 'threaded'):
+ from flup.server.fcgi import WSGIServer
+ wsgi_opts = {
+ 'maxSpare': int(options["maxspare"]),
+ 'minSpare': int(options["minspare"]),
+ 'maxThreads': int(options["maxchildren"]),
+ }
+ else:
+ return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
+
+ wsgi_opts['debug'] = False # Turn off flup tracebacks
+
+ # Prep up and go
+ from django.core.handlers.wsgi import WSGIHandler
+
+ if options["host"] and options["port"] and not options["socket"]:
+ wsgi_opts['bindAddress'] = (options["host"], int(options["port"]))
+ elif options["socket"] and not options["host"] and not options["port"]:
+ wsgi_opts['bindAddress'] = options["socket"]
+ elif not options["socket"] and not options["host"] and not options["port"]:
+ wsgi_opts['bindAddress'] = None
+ else:
+ return fastcgi_help("Invalid combination of host, port, socket.")
+
+ if options["daemonize"] is None:
+ # Default to daemonizing if we're running on a socket/named pipe.
+ daemonize = (wsgi_opts['bindAddress'] is not None)
+ else:
+ if options["daemonize"].lower() in ('true', 'yes', 't'):
+ daemonize = True
+ elif options["daemonize"].lower() in ('false', 'no', 'f'):
+ daemonize = False
+ else:
+ return fastcgi_help("ERROR: Invalid option for daemonize parameter.")
+
+ if daemonize:
+ from django.utils.daemonize import become_daemon
+ become_daemon(our_home_dir=options["workdir"])
+
+ if options["pidfile"]:
+ fp = open(options["pidfile"], "w")
+ fp.write("%d\n" % os.getpid())
+ fp.close()
+
+ WSGIServer(WSGIHandler(), **wsgi_opts).run()
+
+if __name__ == '__main__':
+ runfastcgi(sys.argv[1:])
diff --git a/google_appengine/lib/django/django/core/signals.py b/google_appengine/lib/django/django/core/signals.py
new file mode 100755
index 0000000..7a23607
--- /dev/null
+++ b/google_appengine/lib/django/django/core/signals.py
@@ -0,0 +1,3 @@
+request_started = object()
+request_finished = object()
+got_request_exception = object()
diff --git a/google_appengine/lib/django/django/core/template_loader.py b/google_appengine/lib/django/django/core/template_loader.py
new file mode 100755
index 0000000..ee86178
--- /dev/null
+++ b/google_appengine/lib/django/django/core/template_loader.py
@@ -0,0 +1,7 @@
+# This module is DEPRECATED!
+#
+# You should no longer be using django.template_loader.
+#
+# Use django.template.loader instead.
+
+from django.template.loader import *
diff --git a/google_appengine/lib/django/django/core/urlresolvers.py b/google_appengine/lib/django/django/core/urlresolvers.py
new file mode 100755
index 0000000..3f1004c
--- /dev/null
+++ b/google_appengine/lib/django/django/core/urlresolvers.py
@@ -0,0 +1,241 @@
+"""
+This module converts requested URLs to callback view functions.
+
+RegexURLResolver is the main class here. Its resolve() method takes a URL (as
+a string) and returns a tuple in this format:
+
+ (view_function, function_args, function_kwargs)
+"""
+
+from django.http import Http404
+from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
+import re
+
+class Resolver404(Http404):
+ pass
+
+class NoReverseMatch(Exception):
+ # Don't make this raise an error when used in a template.
+ silent_variable_failure = True
+
+def get_mod_func(callback):
+ # Converts 'django.views.news.stories.story_detail' to
+ # ['django.views.news.stories', 'story_detail']
+ try:
+ dot = callback.rindex('.')
+ except ValueError:
+ return callback, ''
+ return callback[:dot], callback[dot+1:]
+
+def reverse_helper(regex, *args, **kwargs):
+ """
+ Does a "reverse" lookup -- returns the URL for the given args/kwargs.
+ The args/kwargs are applied to the given compiled regular expression.
+ For example:
+
+ >>> reverse_helper(re.compile('^places/(\d+)/$'), 3)
+ 'places/3/'
+ >>> reverse_helper(re.compile('^places/(?P<id>\d+)/$'), id=3)
+ 'places/3/'
+ >>> reverse_helper(re.compile('^people/(?P<state>\w\w)/(\w+)/$'), 'adrian', state='il')
+ 'people/il/adrian/'
+
+ Raises NoReverseMatch if the args/kwargs aren't valid for the regex.
+ """
+ # TODO: Handle nested parenthesis in the following regex.
+ result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), regex.pattern)
+ return result.replace('^', '').replace('$', '')
+
+class MatchChecker(object):
+ "Class used in reverse RegexURLPattern lookup."
+ def __init__(self, args, kwargs):
+ self.args, self.kwargs = args, kwargs
+ self.current_arg = 0
+
+ def __call__(self, match_obj):
+ # match_obj.group(1) is the contents of the parenthesis.
+ # First we need to figure out whether it's a named or unnamed group.
+ #
+ grouped = match_obj.group(1)
+ m = re.search(r'^\?P<(\w+)>(.*?)$', grouped)
+ if m: # If this was a named group...
+ # m.group(1) is the name of the group
+ # m.group(2) is the regex.
+ try:
+ value = self.kwargs[m.group(1)]
+ except KeyError:
+ # It was a named group, but the arg was passed in as a
+ # positional arg or not at all.
+ try:
+ value = self.args[self.current_arg]
+ self.current_arg += 1
+ except IndexError:
+ # The arg wasn't passed in.
+ raise NoReverseMatch('Not enough positional arguments passed in')
+ test_regex = m.group(2)
+ else: # Otherwise, this was a positional (unnamed) group.
+ try:
+ value = self.args[self.current_arg]
+ self.current_arg += 1
+ except IndexError:
+ # The arg wasn't passed in.
+ raise NoReverseMatch('Not enough positional arguments passed in')
+ test_regex = grouped
+ # Note we're using re.match here on purpose because the start of
+ # to string needs to match.
+ if not re.match(test_regex + '$', str(value)): # TODO: Unicode?
+ raise NoReverseMatch("Value %r didn't match regular expression %r" % (value, test_regex))
+ return str(value) # TODO: Unicode?
+
+class RegexURLPattern(object):
+ def __init__(self, regex, callback, default_args=None):
+ # regex is a string representing a regular expression.
+ # callback is either a string like 'foo.views.news.stories.story_detail'
+ # which represents the path to a module and a view function name, or a
+ # callable object (view).
+ self.regex = re.compile(regex)
+ if callable(callback):
+ self._callback = callback
+ else:
+ self._callback = None
+ self._callback_str = callback
+ self.default_args = default_args or {}
+
+ def resolve(self, path):
+ match = self.regex.search(path)
+ if match:
+ # If there are any named groups, use those as kwargs, ignoring
+ # non-named groups. Otherwise, pass all non-named arguments as
+ # positional arguments.
+ kwargs = match.groupdict()
+ if kwargs:
+ args = ()
+ else:
+ args = match.groups()
+ # In both cases, pass any extra_kwargs as **kwargs.
+ kwargs.update(self.default_args)
+
+ return self.callback, args, kwargs
+
+ def _get_callback(self):
+ if self._callback is not None:
+ return self._callback
+ mod_name, func_name = get_mod_func(self._callback_str)
+ try:
+ self._callback = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ except ImportError, e:
+ raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
+ except AttributeError, e:
+ raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
+ return self._callback
+ callback = property(_get_callback)
+
+ def reverse(self, viewname, *args, **kwargs):
+ mod_name, func_name = get_mod_func(viewname)
+ try:
+ lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ except (ImportError, AttributeError):
+ raise NoReverseMatch
+ if lookup_view != self.callback:
+ raise NoReverseMatch
+ return self.reverse_helper(*args, **kwargs)
+
+ def reverse_helper(self, *args, **kwargs):
+ return reverse_helper(self.regex, *args, **kwargs)
+
+class RegexURLResolver(object):
+ def __init__(self, regex, urlconf_name, default_kwargs=None):
+ # regex is a string representing a regular expression.
+ # urlconf_name is a string representing the module containing urlconfs.
+ self.regex = re.compile(regex)
+ self.urlconf_name = urlconf_name
+ self.callback = None
+ self.default_kwargs = default_kwargs or {}
+
+ def resolve(self, path):
+ tried = []
+ match = self.regex.search(path)
+ if match:
+ new_path = path[match.end():]
+ for pattern in self.urlconf_module.urlpatterns:
+ try:
+ sub_match = pattern.resolve(new_path)
+ except Resolver404, e:
+ tried.extend([(pattern.regex.pattern + ' ' + t) for t in e.args[0]['tried']])
+ else:
+ if sub_match:
+ sub_match_dict = dict(self.default_kwargs, **sub_match[2])
+ return sub_match[0], sub_match[1], dict(match.groupdict(), **sub_match_dict)
+ tried.append(pattern.regex.pattern)
+ raise Resolver404, {'tried': tried, 'path': new_path}
+
+ def _get_urlconf_module(self):
+ try:
+ return self._urlconf_module
+ except AttributeError:
+ try:
+ self._urlconf_module = __import__(self.urlconf_name, {}, {}, [''])
+ except ValueError, e:
+ # Invalid urlconf_name, such as "foo.bar." (note trailing period)
+ raise ImproperlyConfigured, "Error while importing URLconf %r: %s" % (self.urlconf_name, e)
+ return self._urlconf_module
+ urlconf_module = property(_get_urlconf_module)
+
+ def _get_url_patterns(self):
+ return self.urlconf_module.urlpatterns
+ url_patterns = property(_get_url_patterns)
+
+ def _resolve_special(self, view_type):
+ callback = getattr(self.urlconf_module, 'handler%s' % view_type)
+ mod_name, func_name = get_mod_func(callback)
+ try:
+ return getattr(__import__(mod_name, {}, {}, ['']), func_name), {}
+ except (ImportError, AttributeError), e:
+ raise ViewDoesNotExist, "Tried %s. Error was: %s" % (callback, str(e))
+
+ def resolve404(self):
+ return self._resolve_special('404')
+
+ def resolve500(self):
+ return self._resolve_special('500')
+
+ def reverse(self, lookup_view, *args, **kwargs):
+ if not callable(lookup_view):
+ mod_name, func_name = get_mod_func(lookup_view)
+ try:
+ lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ except (ImportError, AttributeError):
+ raise NoReverseMatch
+ for pattern in self.urlconf_module.urlpatterns:
+ if isinstance(pattern, RegexURLResolver):
+ try:
+ return pattern.reverse_helper(lookup_view, *args, **kwargs)
+ except NoReverseMatch:
+ continue
+ elif pattern.callback == lookup_view:
+ try:
+ return pattern.reverse_helper(*args, **kwargs)
+ except NoReverseMatch:
+ continue
+ raise NoReverseMatch
+
+ def reverse_helper(self, lookup_view, *args, **kwargs):
+ sub_match = self.reverse(lookup_view, *args, **kwargs)
+ result = reverse_helper(self.regex, *args, **kwargs)
+ return result + sub_match
+
+def resolve(path, urlconf=None):
+ if urlconf is None:
+ from django.conf import settings
+ urlconf = settings.ROOT_URLCONF
+ resolver = RegexURLResolver(r'^/', urlconf)
+ return resolver.resolve(path)
+
+def reverse(viewname, urlconf=None, args=None, kwargs=None):
+ args = args or []
+ kwargs = kwargs or {}
+ if urlconf is None:
+ from django.conf import settings
+ urlconf = settings.ROOT_URLCONF
+ resolver = RegexURLResolver(r'^/', urlconf)
+ return '/' + resolver.reverse(viewname, *args, **kwargs)
diff --git a/google_appengine/lib/django/django/core/validators.py b/google_appengine/lib/django/django/core/validators.py
new file mode 100755
index 0000000..bd7d790
--- /dev/null
+++ b/google_appengine/lib/django/django/core/validators.py
@@ -0,0 +1,573 @@
+"""
+A library of validators that return None and raise ValidationError when the
+provided data isn't valid.
+
+Validators may be callable classes, and they may have an 'always_test'
+attribute. If an 'always_test' attribute exists (regardless of value), the
+validator will *always* be run, regardless of whether its associated
+form field is required.
+"""
+
+import urllib2
+from django.conf import settings
+from django.utils.translation import gettext, gettext_lazy, ngettext
+from django.utils.functional import Promise, lazy
+import re
+
+_datere = r'\d{4}-\d{1,2}-\d{1,2}'
+_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
+alnum_re = re.compile(r'^\w+$')
+alnumurl_re = re.compile(r'^[-\w/]+$')
+ansi_date_re = re.compile('^%s$' % _datere)
+ansi_time_re = re.compile('^%s$' % _timere)
+ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
+email_re = re.compile(
+ r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
+ r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
+integer_re = re.compile(r'^-?\d+$')
+ip4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
+phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
+slug_re = re.compile(r'^[-\w]+$')
+url_re = re.compile(r'^https?://\S+$')
+
+lazy_inter = lazy(lambda a,b: str(a) % b, str)
+
+class ValidationError(Exception):
+ def __init__(self, message):
+ "ValidationError can be passed a string or a list."
+ if isinstance(message, list):
+ self.messages = message
+ else:
+ assert isinstance(message, (basestring, Promise)), ("%s should be a string" % repr(message))
+ self.messages = [message]
+ def __str__(self):
+ # This is needed because, without a __str__(), printing an exception
+ # instance would result in this:
+ # AttributeError: ValidationError instance has no attribute 'args'
+ # See http://www.python.org/doc/current/tut/node10.html#handling
+ return str(self.messages)
+
+class CriticalValidationError(Exception):
+ def __init__(self, message):
+ "ValidationError can be passed a string or a list."
+ if isinstance(message, list):
+ self.messages = message
+ else:
+ assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
+ self.messages = [message]
+ def __str__(self):
+ return str(self.messages)
+
+def isAlphaNumeric(field_data, all_data):
+ if not alnum_re.search(field_data):
+ raise ValidationError, gettext("This value must contain only letters, numbers and underscores.")
+
+def isAlphaNumericURL(field_data, all_data):
+ if not alnumurl_re.search(field_data):
+ raise ValidationError, gettext("This value must contain only letters, numbers, underscores, dashes or slashes.")
+
+def isSlug(field_data, all_data):
+ if not slug_re.search(field_data):
+ raise ValidationError, gettext("This value must contain only letters, numbers, underscores or hyphens.")
+
+def isLowerCase(field_data, all_data):
+ if field_data.lower() != field_data:
+ raise ValidationError, gettext("Uppercase letters are not allowed here.")
+
+def isUpperCase(field_data, all_data):
+ if field_data.upper() != field_data:
+ raise ValidationError, gettext("Lowercase letters are not allowed here.")
+
+def isCommaSeparatedIntegerList(field_data, all_data):
+ for supposed_int in field_data.split(','):
+ try:
+ int(supposed_int)
+ except ValueError:
+ raise ValidationError, gettext("Enter only digits separated by commas.")
+
+def isCommaSeparatedEmailList(field_data, all_data):
+ """
+ Checks that field_data is a string of e-mail addresses separated by commas.
+ Blank field_data values will not throw a validation error, and whitespace
+ is allowed around the commas.
+ """
+ for supposed_email in field_data.split(','):
+ try:
+ isValidEmail(supposed_email.strip(), '')
+ except ValidationError:
+ raise ValidationError, gettext("Enter valid e-mail addresses separated by commas.")
+
+def isValidIPAddress4(field_data, all_data):
+ if not ip4_re.search(field_data):
+ raise ValidationError, gettext("Please enter a valid IP address.")
+
+def isNotEmpty(field_data, all_data):
+ if field_data.strip() == '':
+ raise ValidationError, gettext("Empty values are not allowed here.")
+
+def isOnlyDigits(field_data, all_data):
+ if not field_data.isdigit():
+ raise ValidationError, gettext("Non-numeric characters aren't allowed here.")
+
+def isNotOnlyDigits(field_data, all_data):
+ if field_data.isdigit():
+ raise ValidationError, gettext("This value can't be comprised solely of digits.")
+
+def isInteger(field_data, all_data):
+ # This differs from isOnlyDigits because this accepts the negative sign
+ if not integer_re.search(field_data):
+ raise ValidationError, gettext("Enter a whole number.")
+
+def isOnlyLetters(field_data, all_data):
+ if not field_data.isalpha():
+ raise ValidationError, gettext("Only alphabetical characters are allowed here.")
+
+def _isValidDate(date_string):
+ """
+ A helper function used by isValidANSIDate and isValidANSIDatetime to
+ check if the date is valid. The date string is assumed to already be in
+ YYYY-MM-DD format.
+ """
+ from datetime import date
+ # Could use time.strptime here and catch errors, but datetime.date below
+ # produces much friendlier error messages.
+ year, month, day = map(int, date_string.split('-'))
+ # This check is needed because strftime is used when saving the date
+ # value to the database, and strftime requires that the year be >=1900.
+ if year < 1900:
+ raise ValidationError, gettext('Year must be 1900 or later.')
+ try:
+ date(year, month, day)
+ except ValueError, e:
+ msg = gettext('Invalid date: %s') % gettext(str(e))
+ raise ValidationError, msg
+
+def isValidANSIDate(field_data, all_data):
+ if not ansi_date_re.search(field_data):
+ raise ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.')
+ _isValidDate(field_data)
+
+def isValidANSITime(field_data, all_data):
+ if not ansi_time_re.search(field_data):
+ raise ValidationError, gettext('Enter a valid time in HH:MM format.')
+
+def isValidANSIDatetime(field_data, all_data):
+ if not ansi_datetime_re.search(field_data):
+ raise ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+ _isValidDate(field_data.split()[0])
+
+def isValidEmail(field_data, all_data):
+ if not email_re.search(field_data):
+ raise ValidationError, gettext('Enter a valid e-mail address.')
+
+def isValidImage(field_data, all_data):
+ """
+ Checks that the file-upload field data contains a valid image (GIF, JPG,
+ PNG, possibly others -- whatever the Python Imaging Library supports).
+ """
+ from PIL import Image
+ from cStringIO import StringIO
+ try:
+ content = field_data['content']
+ except TypeError:
+ raise ValidationError, gettext("No file was submitted. Check the encoding type on the form.")
+ try:
+ Image.open(StringIO(content))
+ except IOError: # Python Imaging Library doesn't recognize it as an image
+ raise ValidationError, gettext("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
+
+def isValidImageURL(field_data, all_data):
+ uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
+ try:
+ uc(field_data, all_data)
+ except URLMimeTypeCheck.InvalidContentType:
+ raise ValidationError, gettext("The URL %s does not point to a valid image.") % field_data
+
+def isValidPhone(field_data, all_data):
+ if not phone_re.search(field_data):
+ raise ValidationError, gettext('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
+
+def isValidQuicktimeVideoURL(field_data, all_data):
+ "Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
+ uc = URLMimeTypeCheck(('video/quicktime', 'video/mpeg',))
+ try:
+ uc(field_data, all_data)
+ except URLMimeTypeCheck.InvalidContentType:
+ raise ValidationError, gettext("The URL %s does not point to a valid QuickTime video.") % field_data
+
+def isValidURL(field_data, all_data):
+ if not url_re.search(field_data):
+ raise ValidationError, gettext("A valid URL is required.")
+
+def isValidHTML(field_data, all_data):
+ import urllib, urllib2
+ try:
+ u = urllib2.urlopen('http://validator.w3.org/check', urllib.urlencode({'fragment': field_data, 'output': 'xml'}))
+ except:
+ # Validator or Internet connection is unavailable. Fail silently.
+ return
+ html_is_valid = (u.headers.get('x-w3c-validator-status', 'Invalid') == 'Valid')
+ if html_is_valid:
+ return
+ from xml.dom.minidom import parseString
+ error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
+ raise ValidationError, gettext("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
+
+def isWellFormedXml(field_data, all_data):
+ from xml.dom.minidom import parseString
+ try:
+ parseString(field_data)
+ except Exception, e: # Naked except because we're not sure what will be thrown
+ raise ValidationError, gettext("Badly formed XML: %s") % str(e)
+
+def isWellFormedXmlFragment(field_data, all_data):
+ isWellFormedXml('<root>%s</root>' % field_data, all_data)
+
+def isExistingURL(field_data, all_data):
+ try:
+ headers = {
+ "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+ "Accept-Language" : "en-us,en;q=0.5",
+ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+ "Connection" : "close",
+ "User-Agent": settings.URL_VALIDATOR_USER_AGENT
+ }
+ req = urllib2.Request(field_data,None, headers)
+ u = urllib2.urlopen(req)
+ except ValueError:
+ raise ValidationError, _("Invalid URL: %s") % field_data
+ except urllib2.HTTPError, e:
+ # 401s are valid; they just mean authorization is required.
+ # 301 and 302 are redirects; they just mean look somewhere else.
+ if str(e.code) not in ('401','301','302'):
+ raise ValidationError, _("The URL %s is a broken link.") % field_data
+ except: # urllib2.URLError, httplib.InvalidURL, etc.
+ raise ValidationError, _("The URL %s is a broken link.") % field_data
+
+def isValidUSState(field_data, all_data):
+ "Checks that the given string is a valid two-letter U.S. state abbreviation"
+ states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
+ if field_data.upper() not in states:
+ raise ValidationError, gettext("Enter a valid U.S. state abbreviation.")
+
+def hasNoProfanities(field_data, all_data):
+ """
+ Checks that the given string has no profanities in it. This does a simple
+ check for whether each profanity exists within the string, so 'fuck' will
+ catch 'motherfucker' as well. Raises a ValidationError such as:
+ Watch your mouth! The words "f--k" and "s--t" are not allowed here.
+ """
+ field_data = field_data.lower() # normalize
+ words_seen = [w for w in settings.PROFANITIES_LIST if w in field_data]
+ if words_seen:
+ from django.utils.text import get_text_list
+ plural = len(words_seen) > 1
+ raise ValidationError, ngettext("Watch your mouth! The word %s is not allowed here.",
+ "Watch your mouth! The words %s are not allowed here.", plural) % \
+ get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], 'and')
+
+class AlwaysMatchesOtherField(object):
+ def __init__(self, other_field_name, error_message=None):
+ self.other = other_field_name
+ self.error_message = error_message or lazy_inter(gettext_lazy("This field must match the '%s' field."), self.other)
+ self.always_test = True
+
+ def __call__(self, field_data, all_data):
+ if field_data != all_data[self.other]:
+ raise ValidationError, self.error_message
+
+class ValidateIfOtherFieldEquals(object):
+ def __init__(self, other_field, other_value, validator_list):
+ self.other_field, self.other_value = other_field, other_value
+ self.validator_list = validator_list
+ self.always_test = True
+
+ def __call__(self, field_data, all_data):
+ if all_data.has_key(self.other_field) and all_data[self.other_field] == self.other_value:
+ for v in self.validator_list:
+ v(field_data, all_data)
+
+class RequiredIfOtherFieldNotGiven(object):
+ def __init__(self, other_field_name, error_message=gettext_lazy("Please enter something for at least one field.")):
+ self.other, self.error_message = other_field_name, error_message
+ self.always_test = True
+
+ def __call__(self, field_data, all_data):
+ if not all_data.get(self.other, False) and not field_data:
+ raise ValidationError, self.error_message
+
+class RequiredIfOtherFieldsGiven(object):
+ def __init__(self, other_field_names, error_message=gettext_lazy("Please enter both fields or leave them both empty.")):
+ self.other, self.error_message = other_field_names, error_message
+ self.always_test = True
+
+ def __call__(self, field_data, all_data):
+ for field in self.other:
+ if all_data.get(field, False) and not field_data:
+ raise ValidationError, self.error_message
+
+class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
+ "Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
+ def __init__(self, other_field_name, error_message=gettext_lazy("Please enter both fields or leave them both empty.")):
+ RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
+
+class RequiredIfOtherFieldEquals(object):
+ def __init__(self, other_field, other_value, error_message=None, other_label=None):
+ self.other_field = other_field
+ self.other_value = other_value
+ other_label = other_label or other_value
+ self.error_message = error_message or lazy_inter(gettext_lazy("This field must be given if %(field)s is %(value)s"), {
+ 'field': other_field, 'value': other_label})
+ self.always_test = True
+
+ def __call__(self, field_data, all_data):
+ if all_data.has_key(self.other_field) and all_data[self.other_field] == self.other_value and not field_data:
+ raise ValidationError(self.error_message)
+
+class RequiredIfOtherFieldDoesNotEqual(object):
+ def __init__(self, other_field, other_value, other_label=None, error_message=None):
+ self.other_field = other_field
+ self.other_value = other_value
+ other_label = other_label or other_value
+ self.error_message = error_message or lazy_inter(gettext_lazy("This field must be given if %(field)s is not %(value)s"), {
+ 'field': other_field, 'value': other_label})
+ self.always_test = True
+
+ def __call__(self, field_data, all_data):
+ if all_data.has_key(self.other_field) and all_data[self.other_field] != self.other_value and not field_data:
+ raise ValidationError(self.error_message)
+
+class IsLessThanOtherField(object):
+ def __init__(self, other_field_name, error_message):
+ self.other, self.error_message = other_field_name, error_message
+
+ def __call__(self, field_data, all_data):
+ if field_data > all_data[self.other]:
+ raise ValidationError, self.error_message
+
+class UniqueAmongstFieldsWithPrefix(object):
+ def __init__(self, field_name, prefix, error_message):
+ self.field_name, self.prefix = field_name, prefix
+ self.error_message = error_message or gettext_lazy("Duplicate values are not allowed.")
+
+ def __call__(self, field_data, all_data):
+ for field_name, value in all_data.items():
+ if field_name != self.field_name and value == field_data:
+ raise ValidationError, self.error_message
+
+class NumberIsInRange(object):
+ """
+ Validator that tests if a value is in a range (inclusive).
+ """
+ def __init__(self, lower=None, upper=None, error_message=''):
+ self.lower, self.upper = lower, upper
+ if not error_message:
+ if lower and upper:
+ self.error_message = gettext("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
+ elif lower:
+ self.error_message = gettext("This value must be at least %s.") % lower
+ elif upper:
+ self.error_message = gettext("This value must be no more than %s.") % upper
+ else:
+ self.error_message = error_message
+
+ def __call__(self, field_data, all_data):
+ # Try to make the value numeric. If this fails, we assume another
+ # validator will catch the problem.
+ try:
+ val = float(field_data)
+ except ValueError:
+ return
+
+ # Now validate
+ if self.lower and self.upper and (val < self.lower or val > self.upper):
+ raise ValidationError(self.error_message)
+ elif self.lower and val < self.lower:
+ raise ValidationError(self.error_message)
+ elif self.upper and val > self.upper:
+ raise ValidationError(self.error_message)
+
+class IsAPowerOf(object):
+ """
+ >>> v = IsAPowerOf(2)
+ >>> v(4, None)
+ >>> v(8, None)
+ >>> v(16, None)
+ >>> v(17, None)
+ django.core.validators.ValidationError: ['This value must be a power of 2.']
+ """
+ def __init__(self, power_of):
+ self.power_of = power_of
+
+ def __call__(self, field_data, all_data):
+ from math import log
+ val = log(int(field_data)) / log(self.power_of)
+ if val != int(val):
+ raise ValidationError, gettext("This value must be a power of %s.") % self.power_of
+
+class IsValidFloat(object):
+ def __init__(self, max_digits, decimal_places):
+ self.max_digits, self.decimal_places = max_digits, decimal_places
+
+ def __call__(self, field_data, all_data):
+ data = str(field_data)
+ try:
+ float(data)
+ except ValueError:
+ raise ValidationError, gettext("Please enter a valid decimal number.")
+ # Negative floats require more space to input.
+ max_allowed_length = data.startswith('-') and (self.max_digits + 2) or (self.max_digits + 1)
+ if len(data) > max_allowed_length:
+ raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.",
+ "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
+ if (not '.' in data and len(data) > (max_allowed_length - self.decimal_places - 1)) or ('.' in data and len(data) > (max_allowed_length - (self.decimal_places - len(data.split('.')[1])))):
+ raise ValidationError, ngettext( "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.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places)
+ if '.' in data and len(data.split('.')[1]) > self.decimal_places:
+ raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.",
+ "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
+
+class HasAllowableSize(object):
+ """
+ Checks that the file-upload field data is a certain size. min_size and
+ max_size are measurements in bytes.
+ """
+ def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
+ self.min_size, self.max_size = min_size, max_size
+ self.min_error_message = min_error_message or lazy_inter(gettext_lazy("Make sure your uploaded file is at least %s bytes big."), min_size)
+ self.max_error_message = max_error_message or lazy_inter(gettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size)
+
+ def __call__(self, field_data, all_data):
+ try:
+ content = field_data['content']
+ except TypeError:
+ raise ValidationError, gettext_lazy("No file was submitted. Check the encoding type on the form.")
+ if self.min_size is not None and len(content) < self.min_size:
+ raise ValidationError, self.min_error_message
+ if self.max_size is not None and len(content) > self.max_size:
+ raise ValidationError, self.max_error_message
+
+class MatchesRegularExpression(object):
+ """
+ Checks that the field matches the given regular-expression. The regex
+ should be in string format, not already compiled.
+ """
+ def __init__(self, regexp, error_message=gettext_lazy("The format for this field is wrong.")):
+ self.regexp = re.compile(regexp)
+ self.error_message = error_message
+
+ def __call__(self, field_data, all_data):
+ if not self.regexp.search(field_data):
+ raise ValidationError(self.error_message)
+
+class AnyValidator(object):
+ """
+ This validator tries all given validators. If any one of them succeeds,
+ validation passes. If none of them succeeds, the given message is thrown
+ as a validation error. The message is rather unspecific, so it's best to
+ specify one on instantiation.
+ """
+ def __init__(self, validator_list=None, error_message=gettext_lazy("This field is invalid.")):
+ if validator_list is None: validator_list = []
+ self.validator_list = validator_list
+ self.error_message = error_message
+ for v in validator_list:
+ if hasattr(v, 'always_test'):
+ self.always_test = True
+
+ def __call__(self, field_data, all_data):
+ for v in self.validator_list:
+ try:
+ v(field_data, all_data)
+ return
+ except ValidationError, e:
+ pass
+ raise ValidationError(self.error_message)
+
+class URLMimeTypeCheck(object):
+ "Checks that the provided URL points to a document with a listed mime type"
+ class CouldNotRetrieve(ValidationError):
+ pass
+ class InvalidContentType(ValidationError):
+ pass
+
+ def __init__(self, mime_type_list):
+ self.mime_type_list = mime_type_list
+
+ def __call__(self, field_data, all_data):
+ import urllib2
+ try:
+ isValidURL(field_data, all_data)
+ except ValidationError:
+ raise
+ try:
+ info = urllib2.urlopen(field_data).info()
+ except (urllib2.HTTPError, urllib2.URLError):
+ raise URLMimeTypeCheck.CouldNotRetrieve, gettext("Could not retrieve anything from %s.") % field_data
+ content_type = info['content-type']
+ if content_type not in self.mime_type_list:
+ raise URLMimeTypeCheck.InvalidContentType, gettext("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
+ 'url': field_data, 'contenttype': content_type}
+
+class RelaxNGCompact(object):
+ "Validate against a Relax NG compact schema"
+ def __init__(self, schema_path, additional_root_element=None):
+ self.schema_path = schema_path
+ self.additional_root_element = additional_root_element
+
+ def __call__(self, field_data, all_data):
+ import os, tempfile
+ if self.additional_root_element:
+ field_data = '<%(are)s>%(data)s\n</%(are)s>' % {
+ 'are': self.additional_root_element,
+ 'data': field_data
+ }
+ filename = tempfile.mktemp() # Insecure, but nothing else worked
+ fp = open(filename, 'w')
+ fp.write(field_data)
+ fp.close()
+ if not os.path.exists(settings.JING_PATH):
+ raise Exception, "%s not found!" % settings.JING_PATH
+ p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
+ errors = [line.strip() for line in p.readlines()]
+ p.close()
+ os.unlink(filename)
+ display_errors = []
+ lines = field_data.split('\n')
+ for error in errors:
+ ignored, line, level, message = error.split(':', 3)
+ # Scrape the Jing error messages to reword them more nicely.
+ m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
+ if m:
+ display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
+ {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
+ continue
+ if message.strip() == 'text not allowed here':
+ display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
+ {'line':line, 'start':lines[int(line) - 1][:30]})
+ continue
+ m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
+ if m:
+ display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
+ {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+ continue
+ m = re.search(r'\s*unknown element "(.*?)"', message)
+ if m:
+ display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
+ {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+ continue
+ if message.strip() == 'required attributes missing':
+ display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
+ {'line':line, 'start':lines[int(line) - 1][:30]})
+ continue
+ m = re.search(r'\s*bad value for attribute "(.*?)"', message)
+ if m:
+ display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
+ {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+ continue
+ # Failing all those checks, use the default error message.
+ display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
+ display_errors.append(display_error)
+ if len(display_errors) > 0:
+ raise ValidationError, display_errors
diff --git a/google_appengine/lib/django/django/core/xheaders.py b/google_appengine/lib/django/django/core/xheaders.py
new file mode 100755
index 0000000..3beb930
--- /dev/null
+++ b/google_appengine/lib/django/django/core/xheaders.py
@@ -0,0 +1,22 @@
+"""
+Pages in Django can are served up with custom HTTP headers containing useful
+information about those pages -- namely, the content type and object ID.
+
+This module contains utility functions for retrieving and doing interesting
+things with these special "X-Headers" (so called because the HTTP spec demands
+that custom headers are prefxed with "X-").
+
+Next time you're at slashdot.org, watch out for X-Fry and X-Bender. :)
+"""
+
+def populate_xheaders(request, response, model, object_id):
+ """
+ Adds the "X-Object-Type" and "X-Object-Id" headers to the given
+ HttpResponse according to the given model and object_id -- but only if the
+ given HttpRequest object has an IP address within the INTERNAL_IPS setting
+ or if the request is from a logged in staff member.
+ """
+ from django.conf import settings
+ if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (hasattr(request, 'user') and request.user.is_authenticated() and request.user.is_staff):
+ response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
+ response['X-Object-Id'] = str(object_id)
diff --git a/google_appengine/lib/django/django/db/__init__.py b/google_appengine/lib/django/django/db/__init__.py
new file mode 100755
index 0000000..4176b5a
--- /dev/null
+++ b/google_appengine/lib/django/django/db/__init__.py
@@ -0,0 +1,48 @@
+from django.conf import settings
+from django.core import signals
+from django.dispatch import dispatcher
+
+__all__ = ('backend', 'connection', 'DatabaseError')
+
+if not settings.DATABASE_ENGINE:
+ settings.DATABASE_ENGINE = 'dummy'
+
+try:
+ backend = __import__('django.db.backends.%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
+except ImportError, e:
+ # The database backend wasn't found. Display a helpful error message
+ # listing all possible database backends.
+ from django.core.exceptions import ImproperlyConfigured
+ import os
+ backend_dir = os.path.join(__path__[0], 'backends')
+ available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
+ available_backends.sort()
+ if settings.DATABASE_ENGINE not in available_backends:
+ raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
+ (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
+ else:
+ raise # If there's some other error, this must be an error in Django itself.
+
+get_introspection_module = lambda: __import__('django.db.backends.%s.introspection' % settings.DATABASE_ENGINE, {}, {}, [''])
+get_creation_module = lambda: __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
+runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, {}, {}, ['']).runshell()
+
+connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
+DatabaseError = backend.DatabaseError
+
+# Register an event that closes the database connection
+# when a Django request is finished.
+dispatcher.connect(connection.close, signal=signals.request_finished)
+
+# Register an event that resets connection.queries
+# when a Django request is started.
+def reset_queries():
+ connection.queries = []
+dispatcher.connect(reset_queries, signal=signals.request_started)
+
+# Register an event that rolls back the connection
+# when a Django request has an exception.
+def _rollback_on_exception():
+ from django.db import transaction
+ transaction.rollback_unless_managed()
+dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
diff --git a/google_appengine/lib/django/django/db/backends/__init__.py b/google_appengine/lib/django/django/db/backends/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/ado_mssql/__init__.py b/google_appengine/lib/django/django/db/backends/ado_mssql/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/ado_mssql/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/ado_mssql/base.py b/google_appengine/lib/django/django/db/backends/ado_mssql/base.py
new file mode 100755
index 0000000..8dcb98c
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/ado_mssql/base.py
@@ -0,0 +1,167 @@
+"""
+ADO MSSQL database backend for Django.
+
+Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/
+"""
+
+from django.db.backends import util
+try:
+ import adodbapi as Database
+except ImportError, e:
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "Error loading adodbapi module: %s" % e
+import datetime
+try:
+ import mx
+except ImportError:
+ mx = None
+
+DatabaseError = Database.DatabaseError
+
+# We need to use a special Cursor class because adodbapi expects question-mark
+# param style, but Django expects "%s". This cursor converts question marks to
+# format-string style.
+class Cursor(Database.Cursor):
+ def executeHelper(self, operation, isStoredProcedureCall, parameters=None):
+ if parameters is not None and "%s" in operation:
+ operation = operation.replace("%s", "?")
+ Database.Cursor.executeHelper(self, operation, isStoredProcedureCall, parameters)
+
+class Connection(Database.Connection):
+ def cursor(self):
+ return Cursor(self)
+Database.Connection = Connection
+
+origCVtoP = Database.convertVariantToPython
+def variantToPython(variant, adType):
+ if type(variant) == bool and adType == 11:
+ return variant # bool not 1/0
+ res = origCVtoP(variant, adType)
+ if mx is not None and type(res) == mx.DateTime.mxDateTime.DateTimeType:
+ # Convert ms.DateTime objects to Python datetime.datetime objects.
+ tv = list(res.tuple()[:7])
+ tv[-2] = int(tv[-2])
+ return datetime.datetime(*tuple(tv))
+ if type(res) == float and str(res)[-2:] == ".0":
+ return int(res) # If float but int, then int.
+ return res
+Database.convertVariantToPython = variantToPython
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+
+ def cursor(self):
+ from django.conf import settings
+ if self.connection is None:
+ if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '':
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file."
+ if not settings.DATABASE_HOST:
+ settings.DATABASE_HOST = "127.0.0.1"
+ # TODO: Handle DATABASE_PORT.
+ conn_string = "PROVIDER=SQLOLEDB;DATA SOURCE=%s;UID=%s;PWD=%s;DATABASE=%s" % (settings.DATABASE_HOST, settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
+ self.connection = Database.connect(conn_string)
+ cursor = self.connection.cursor()
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(cursor, self)
+ return cursor
+
+ def _commit(self):
+ if self.connection is not None:
+ return self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ return self.connection.rollback()
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+supports_constraints = True
+
+def quote_name(name):
+ if name.startswith('[') and name.endswith(']'):
+ return name # Quoting once is enough.
+ return '[%s]' % name
+
+dictfetchone = util.dictfetchone
+dictfetchmany = util.dictfetchmany
+dictfetchall = util.dictfetchall
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ cursor.execute("SELECT %s FROM %s WHERE %s = @@IDENTITY" % (pk_name, table_name, pk_name))
+ return cursor.fetchone()[0]
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ return "DATEPART(%s, %s)" % (lookup_type, table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ if lookup_type=='year':
+ return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name
+ if lookup_type=='month':
+ return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name)
+ if lookup_type=='day':
+ return "Convert(datetime, Convert(varchar(12), %s))" % field_name
+
+def get_limit_offset_sql(limit, offset=None):
+ # TODO: This is a guess. Make sure this is correct.
+ sql = "LIMIT %s" % limit
+ if offset and offset != 0:
+ sql += " OFFSET %s" % offset
+ return sql
+
+def get_random_function_sql():
+ return "RAND()"
+
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
+def get_fulltext_search_sql(field_name):
+ raise NotImplementedError
+
+def get_drop_foreignkey_sql():
+ return "DROP CONSTRAINT"
+
+def get_pk_default_value():
+ return "DEFAULT"
+
+def get_sql_flush(sql_styler, full_table_list):
+ """Return a list of SQL statements required to remove all data from
+ all tables in the database (without actually removing the tables
+ themselves) and put the database in an empty 'initial' state
+ """
+ # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # TODO - SQL not actually tested against ADO MSSQL yet!
+ # TODO - autoincrement indices reset required? See other get_sql_flush() implementations
+ sql_list = ['%s %s;' % \
+ (sql_styler.SQL_KEYWORD('TRUNCATE'),
+ sql_styler.SQL_FIELD(quote_name(table))
+ ) for table in full_table_list]
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'LIKE %s',
+ 'contains': 'LIKE %s',
+ 'icontains': 'LIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE %s',
+ 'endswith': 'LIKE %s',
+ 'istartswith': 'LIKE %s',
+ 'iendswith': 'LIKE %s',
+}
diff --git a/google_appengine/lib/django/django/db/backends/ado_mssql/client.py b/google_appengine/lib/django/django/db/backends/ado_mssql/client.py
new file mode 100755
index 0000000..5c197ca
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/ado_mssql/client.py
@@ -0,0 +1,2 @@
+def runshell():
+ raise NotImplementedError
diff --git a/google_appengine/lib/django/django/db/backends/ado_mssql/creation.py b/google_appengine/lib/django/django/db/backends/ado_mssql/creation.py
new file mode 100755
index 0000000..5158ba0
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/ado_mssql/creation.py
@@ -0,0 +1,25 @@
+DATA_TYPES = {
+ 'AutoField': 'int IDENTITY (1, 1)',
+ 'BooleanField': 'bit',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'smalldatetime',
+ 'DateTimeField': 'smalldatetime',
+ 'FileField': 'varchar(100)',
+ 'FilePathField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'int',
+ 'IPAddressField': 'char(15)',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'bit',
+ 'OneToOneField': 'int',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)',
+ 'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)',
+ 'SlugField': 'varchar(%(maxlength)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'text',
+ 'TimeField': 'time',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/google_appengine/lib/django/django/db/backends/ado_mssql/introspection.py b/google_appengine/lib/django/django/db/backends/ado_mssql/introspection.py
new file mode 100755
index 0000000..b125cc9
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/ado_mssql/introspection.py
@@ -0,0 +1,13 @@
+def get_table_list(cursor):
+ raise NotImplementedError
+
+def get_table_description(cursor, table_name):
+ raise NotImplementedError
+
+def get_relations(cursor, table_name):
+ raise NotImplementedError
+
+def get_indexes(cursor, table_name):
+ raise NotImplementedError
+
+DATA_TYPES_REVERSE = {}
diff --git a/google_appengine/lib/django/django/db/backends/dummy/__init__.py b/google_appengine/lib/django/django/db/backends/dummy/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/dummy/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/dummy/base.py b/google_appengine/lib/django/django/db/backends/dummy/base.py
new file mode 100755
index 0000000..e36a99e
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/dummy/base.py
@@ -0,0 +1,44 @@
+"""
+Dummy database backend for Django.
+
+Django uses this if the DATABASE_ENGINE setting is empty (None or empty string).
+
+Each of these API functions, except connection.close(), raises
+ImproperlyConfigured.
+"""
+
+from django.core.exceptions import ImproperlyConfigured
+
+def complain(*args, **kwargs):
+ raise ImproperlyConfigured, "You haven't set the DATABASE_ENGINE setting yet."
+
+class DatabaseError(Exception):
+ pass
+
+class DatabaseWrapper:
+ cursor = complain
+ _commit = complain
+ _rollback = complain
+
+ def __init__(self, **kwargs):
+ pass
+
+ def close(self):
+ pass # close()
+
+supports_constraints = False
+quote_name = complain
+dictfetchone = complain
+dictfetchmany = complain
+dictfetchall = complain
+get_last_insert_id = complain
+get_date_extract_sql = complain
+get_date_trunc_sql = complain
+get_limit_offset_sql = complain
+get_random_function_sql = complain
+get_deferrable_sql = complain
+get_fulltext_search_sql = complain
+get_drop_foreignkey_sql = complain
+get_sql_flush = complain
+
+OPERATOR_MAPPING = {}
diff --git a/google_appengine/lib/django/django/db/backends/dummy/client.py b/google_appengine/lib/django/django/db/backends/dummy/client.py
new file mode 100755
index 0000000..e332987
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/dummy/client.py
@@ -0,0 +1,3 @@
+from django.db.backends.dummy.base import complain
+
+runshell = complain
diff --git a/google_appengine/lib/django/django/db/backends/dummy/creation.py b/google_appengine/lib/django/django/db/backends/dummy/creation.py
new file mode 100755
index 0000000..b82c4fe
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/dummy/creation.py
@@ -0,0 +1 @@
+DATA_TYPES = {}
diff --git a/google_appengine/lib/django/django/db/backends/dummy/introspection.py b/google_appengine/lib/django/django/db/backends/dummy/introspection.py
new file mode 100755
index 0000000..c52a812
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/dummy/introspection.py
@@ -0,0 +1,8 @@
+from django.db.backends.dummy.base import complain
+
+get_table_list = complain
+get_table_description = complain
+get_relations = complain
+get_indexes = complain
+
+DATA_TYPES_REVERSE = {}
diff --git a/google_appengine/lib/django/django/db/backends/mysql/__init__.py b/google_appengine/lib/django/django/db/backends/mysql/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/mysql/base.py b/google_appengine/lib/django/django/db/backends/mysql/base.py
new file mode 100755
index 0000000..9471859
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql/base.py
@@ -0,0 +1,231 @@
+"""
+MySQL database backend for Django.
+
+Requires MySQLdb: http://sourceforge.net/projects/mysql-python
+"""
+
+from django.db.backends import util
+try:
+ import MySQLdb as Database
+except ImportError, e:
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "Error loading MySQLdb module: %s" % e
+
+# We want version (1, 2, 1, 'final', 2) or later. We can't just use
+# lexicographic ordering in this check because then (1, 2, 1, 'gamma')
+# inadvertently passes the version test.
+version = Database.version_info
+if (version < (1,2,1) or (version[:3] == (1, 2, 1) and
+ (len(version) < 5 or version[3] != 'final' or version[4] < 2))):
+ raise ImportError, "MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__
+
+from MySQLdb.converters import conversions
+from MySQLdb.constants import FIELD_TYPE
+import types
+import re
+
+DatabaseError = Database.DatabaseError
+
+# MySQLdb-1.2.1 supports the Python boolean type, and only uses datetime
+# module for time-related columns; older versions could have used mx.DateTime
+# or strings if there were no datetime module. However, MySQLdb still returns
+# TIME columns as timedelta -- they are more like timedelta in terms of actual
+# behavior as they are signed and include days -- and Django expects time, so
+# we still need to override that.
+django_conversions = conversions.copy()
+django_conversions.update({
+ FIELD_TYPE.TIME: util.typecast_time,
+})
+
+# This should match the numerical portion of the version numbers (we can treat
+# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
+# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
+# http://dev.mysql.com/doc/refman/5.0/en/news.html .
+server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
+
+# MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
+# MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
+# point is to raise Warnings as exceptions, this can be done with the Python
+# warning module, and this is setup when the connection is created, and the
+# standard util.CursorDebugWrapper can be used. Also, using sql_mode
+# TRADITIONAL will automatically cause most warnings to be treated as errors.
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+ self.server_version = None
+ self.options = kwargs
+
+ def _valid_connection(self):
+ if self.connection is not None:
+ try:
+ self.connection.ping()
+ return True
+ except DatabaseError:
+ self.connection.close()
+ self.connection = None
+ return False
+
+ def cursor(self):
+ from django.conf import settings
+ from warnings import filterwarnings
+ if not self._valid_connection():
+ kwargs = {
+ 'conv': django_conversions,
+ 'charset': 'utf8',
+ 'use_unicode': False,
+ }
+ if settings.DATABASE_USER:
+ kwargs['user'] = settings.DATABASE_USER
+ if settings.DATABASE_NAME:
+ kwargs['db'] = settings.DATABASE_NAME
+ if settings.DATABASE_PASSWORD:
+ kwargs['passwd'] = settings.DATABASE_PASSWORD
+ if settings.DATABASE_HOST.startswith('/'):
+ kwargs['unix_socket'] = settings.DATABASE_HOST
+ elif settings.DATABASE_HOST:
+ kwargs['host'] = settings.DATABASE_HOST
+ if settings.DATABASE_PORT:
+ kwargs['port'] = int(settings.DATABASE_PORT)
+ kwargs.update(self.options)
+ self.connection = Database.connect(**kwargs)
+ cursor = self.connection.cursor()
+ else:
+ cursor = self.connection.cursor()
+ if settings.DEBUG:
+ filterwarnings("error", category=Database.Warning)
+ return util.CursorDebugWrapper(cursor, self)
+ return cursor
+
+ def _commit(self):
+ if self.connection is not None:
+ self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ try:
+ self.connection.rollback()
+ except Database.NotSupportedError:
+ pass
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+ def get_server_version(self):
+ if not self.server_version:
+ if not self._valid_connection():
+ self.cursor()
+ m = server_version_re.match(self.connection.get_server_info())
+ if not m:
+ raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
+ self.server_version = tuple([int(x) for x in m.groups()])
+ return self.server_version
+
+supports_constraints = True
+
+def quote_name(name):
+ if name.startswith("`") and name.endswith("`"):
+ return name # Quoting once is enough.
+ return "`%s`" % name
+
+dictfetchone = util.dictfetchone
+dictfetchmany = util.dictfetchmany
+dictfetchall = util.dictfetchall
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ return cursor.lastrowid
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
+ return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
+ format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
+ format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
+ try:
+ i = fields.index(lookup_type) + 1
+ except ValueError:
+ sql = field_name
+ else:
+ format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
+ sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
+ return sql
+
+def get_limit_offset_sql(limit, offset=None):
+ sql = "LIMIT "
+ if offset and offset != 0:
+ sql += "%s," % offset
+ return sql + str(limit)
+
+def get_random_function_sql():
+ return "RAND()"
+
+def get_deferrable_sql():
+ return ""
+
+def get_fulltext_search_sql(field_name):
+ return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
+
+def get_drop_foreignkey_sql():
+ return "DROP FOREIGN KEY"
+
+def get_pk_default_value():
+ return "DEFAULT"
+
+def get_sql_flush(style, tables, sequences):
+ """Return a list of SQL statements required to remove all data from
+ all tables in the database (without actually removing the tables
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ # NB: The generated SQL below is specific to MySQL
+ # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # to clear all tables of all data
+ if tables:
+ sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + \
+ ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables] + \
+ ['SET FOREIGN_KEY_CHECKS = 1;']
+
+ # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
+ # to reset sequence indices
+ sql.extend(["%s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('TABLE'),
+ style.SQL_TABLE(quote_name(sequence['table'])),
+ style.SQL_KEYWORD('AUTO_INCREMENT'),
+ style.SQL_FIELD('= 1'),
+ ) for sequence in sequences])
+ return sql
+ else:
+ return []
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'LIKE %s',
+ 'contains': 'LIKE BINARY %s',
+ 'icontains': 'LIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE BINARY %s',
+ 'endswith': 'LIKE BINARY %s',
+ 'istartswith': 'LIKE %s',
+ 'iendswith': 'LIKE %s',
+}
diff --git a/google_appengine/lib/django/django/db/backends/mysql/client.py b/google_appengine/lib/django/django/db/backends/mysql/client.py
new file mode 100755
index 0000000..116074a
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql/client.py
@@ -0,0 +1,27 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ['']
+ db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
+ user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
+ passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
+ host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
+ port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
+ defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
+ # Seems to be no good way to set sql_mode with CLI
+
+ if defaults_file:
+ args += ["--defaults-file=%s" % defaults_file]
+ if user:
+ args += ["--user=%s" % user]
+ if passwd:
+ args += ["--password=%s" % passwd]
+ if host:
+ args += ["--host=%s" % host]
+ if port:
+ args += ["--port=%s" % port]
+ if db:
+ args += [db]
+
+ os.execvp('mysql', args)
diff --git a/google_appengine/lib/django/django/db/backends/mysql/creation.py b/google_appengine/lib/django/django/db/backends/mysql/creation.py
new file mode 100755
index 0000000..22ed901
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql/creation.py
@@ -0,0 +1,29 @@
+# This dictionary maps Field objects to their associated MySQL column
+# types, as strings. Column-type strings can contain format strings; they'll
+# be interpolated against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPES = {
+ 'AutoField': 'integer AUTO_INCREMENT',
+ 'BooleanField': 'bool',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'datetime',
+ 'FileField': 'varchar(100)',
+ 'FilePathField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'char(15)',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'bool',
+ 'OneToOneField': 'integer',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer UNSIGNED',
+ 'PositiveSmallIntegerField': 'smallint UNSIGNED',
+ 'SlugField': 'varchar(%(maxlength)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'longtext',
+ 'TimeField': 'time',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/google_appengine/lib/django/django/db/backends/mysql/introspection.py b/google_appengine/lib/django/django/db/backends/mysql/introspection.py
new file mode 100755
index 0000000..7829457
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql/introspection.py
@@ -0,0 +1,95 @@
+from django.db.backends.mysql.base import quote_name
+from MySQLdb import ProgrammingError, OperationalError
+from MySQLdb.constants import FIELD_TYPE
+import re
+
+foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ cursor.execute("SHOW TABLES")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
+ return cursor.description
+
+def _name_to_index(cursor, table_name):
+ """
+ Returns a dictionary of {field_name: field_index} for the given table.
+ Indexes are 0-based.
+ """
+ return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
+
+def get_relations(cursor, table_name):
+ """
+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+ representing all relationships to the given table. Indexes are 0-based.
+ """
+ my_field_dict = _name_to_index(cursor, table_name)
+ constraints = []
+ relations = {}
+ try:
+ # This should work for MySQL 5.0.
+ cursor.execute("""
+ SELECT column_name, referenced_table_name, referenced_column_name
+ FROM information_schema.key_column_usage
+ WHERE table_name = %s
+ AND table_schema = DATABASE()
+ AND referenced_table_name IS NOT NULL
+ AND referenced_column_name IS NOT NULL""", [table_name])
+ constraints.extend(cursor.fetchall())
+ except (ProgrammingError, OperationalError):
+ # Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
+ # Go through all constraints and save the equal matches.
+ cursor.execute("SHOW CREATE TABLE %s" % quote_name(table_name))
+ for row in cursor.fetchall():
+ pos = 0
+ while True:
+ match = foreign_key_re.search(row[1], pos)
+ if match == None:
+ break
+ pos = match.end()
+ constraints.append(match.groups())
+
+ for my_fieldname, other_table, other_field in constraints:
+ other_field_index = _name_to_index(cursor, other_table)[other_field]
+ my_field_index = my_field_dict[my_fieldname]
+ relations[my_field_index] = (other_field_index, other_table)
+
+ return relations
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
+ indexes = {}
+ for row in cursor.fetchall():
+ indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
+ return indexes
+
+DATA_TYPES_REVERSE = {
+ FIELD_TYPE.BLOB: 'TextField',
+ FIELD_TYPE.CHAR: 'CharField',
+ FIELD_TYPE.DECIMAL: 'FloatField',
+ FIELD_TYPE.DATE: 'DateField',
+ FIELD_TYPE.DATETIME: 'DateTimeField',
+ FIELD_TYPE.DOUBLE: 'FloatField',
+ FIELD_TYPE.FLOAT: 'FloatField',
+ FIELD_TYPE.INT24: 'IntegerField',
+ FIELD_TYPE.LONG: 'IntegerField',
+ FIELD_TYPE.LONGLONG: 'IntegerField',
+ FIELD_TYPE.SHORT: 'IntegerField',
+ FIELD_TYPE.STRING: 'TextField',
+ FIELD_TYPE.TIMESTAMP: 'DateTimeField',
+ FIELD_TYPE.TINY: 'IntegerField',
+ FIELD_TYPE.TINY_BLOB: 'TextField',
+ FIELD_TYPE.MEDIUM_BLOB: 'TextField',
+ FIELD_TYPE.LONG_BLOB: 'TextField',
+ FIELD_TYPE.VAR_STRING: 'CharField',
+}
diff --git a/google_appengine/lib/django/django/db/backends/mysql_old/__init__.py b/google_appengine/lib/django/django/db/backends/mysql_old/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql_old/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/mysql_old/base.py b/google_appengine/lib/django/django/db/backends/mysql_old/base.py
new file mode 100755
index 0000000..4bd8751
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql_old/base.py
@@ -0,0 +1,233 @@
+"""
+MySQL database backend for Django.
+
+Requires MySQLdb: http://sourceforge.net/projects/mysql-python
+"""
+
+from django.db.backends import util
+try:
+ import MySQLdb as Database
+except ImportError, e:
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "Error loading MySQLdb module: %s" % e
+from MySQLdb.converters import conversions
+from MySQLdb.constants import FIELD_TYPE
+import types
+import re
+
+DatabaseError = Database.DatabaseError
+
+django_conversions = conversions.copy()
+django_conversions.update({
+ types.BooleanType: util.rev_typecast_boolean,
+ FIELD_TYPE.DATETIME: util.typecast_timestamp,
+ FIELD_TYPE.DATE: util.typecast_date,
+ FIELD_TYPE.TIME: util.typecast_time,
+})
+
+# This should match the numerical portion of the version numbers (we can treat
+# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
+# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
+# http://dev.mysql.com/doc/refman/5.0/en/news.html .
+server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
+
+# This is an extra debug layer over MySQL queries, to display warnings.
+# It's only used when DEBUG=True.
+class MysqlDebugWrapper:
+ def __init__(self, cursor):
+ self.cursor = cursor
+
+ def execute(self, sql, params=()):
+ try:
+ return self.cursor.execute(sql, params)
+ except Database.Warning, w:
+ self.cursor.execute("SHOW WARNINGS")
+ raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
+
+ def executemany(self, sql, param_list):
+ try:
+ return self.cursor.executemany(sql, param_list)
+ except Database.Warning, w:
+ self.cursor.execute("SHOW WARNINGS")
+ raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
+
+ def __getattr__(self, attr):
+ if self.__dict__.has_key(attr):
+ return self.__dict__[attr]
+ else:
+ return getattr(self.cursor, attr)
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+ self.server_version = None
+ self.options = kwargs
+
+ def _valid_connection(self):
+ if self.connection is not None:
+ try:
+ self.connection.ping()
+ return True
+ except DatabaseError:
+ self.connection.close()
+ self.connection = None
+ return False
+
+ def cursor(self):
+ from django.conf import settings
+ if not self._valid_connection():
+ kwargs = {
+ 'user': settings.DATABASE_USER,
+ 'db': settings.DATABASE_NAME,
+ 'passwd': settings.DATABASE_PASSWORD,
+ 'conv': django_conversions,
+ }
+ if settings.DATABASE_HOST.startswith('/'):
+ kwargs['unix_socket'] = settings.DATABASE_HOST
+ else:
+ kwargs['host'] = settings.DATABASE_HOST
+ if settings.DATABASE_PORT:
+ kwargs['port'] = int(settings.DATABASE_PORT)
+ kwargs.update(self.options)
+ self.connection = Database.connect(**kwargs)
+ cursor = self.connection.cursor()
+ if self.connection.get_server_info() >= '4.1':
+ cursor.execute("SET NAMES 'utf8'")
+ else:
+ cursor = self.connection.cursor()
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
+ return cursor
+
+ def _commit(self):
+ if self.connection is not None:
+ self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ try:
+ self.connection.rollback()
+ except Database.NotSupportedError:
+ pass
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+ def get_server_version(self):
+ if not self.server_version:
+ if not self._valid_connection():
+ self.cursor()
+ m = server_version_re.match(self.connection.get_server_info())
+ if not m:
+ raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
+ self.server_version = tuple([int(x) for x in m.groups()])
+ return self.server_version
+
+supports_constraints = True
+
+def quote_name(name):
+ if name.startswith("`") and name.endswith("`"):
+ return name # Quoting once is enough.
+ return "`%s`" % name
+
+dictfetchone = util.dictfetchone
+dictfetchmany = util.dictfetchmany
+dictfetchall = util.dictfetchall
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ return cursor.lastrowid
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
+ return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
+ format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
+ format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
+ try:
+ i = fields.index(lookup_type) + 1
+ except ValueError:
+ sql = field_name
+ else:
+ format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
+ sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
+ return sql
+
+def get_limit_offset_sql(limit, offset=None):
+ sql = "LIMIT "
+ if offset and offset != 0:
+ sql += "%s," % offset
+ return sql + str(limit)
+
+def get_random_function_sql():
+ return "RAND()"
+
+def get_deferrable_sql():
+ return ""
+
+def get_fulltext_search_sql(field_name):
+ return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
+
+def get_drop_foreignkey_sql():
+ return "DROP FOREIGN KEY"
+
+def get_pk_default_value():
+ return "DEFAULT"
+
+def get_sql_flush(style, tables, sequences):
+ """Return a list of SQL statements required to remove all data from
+ all tables in the database (without actually removing the tables
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ # NB: The generated SQL below is specific to MySQL
+ # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # to clear all tables of all data
+ if tables:
+ sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + \
+ ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables] + \
+ ['SET FOREIGN_KEY_CHECKS = 1;']
+
+ # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
+ # to reset sequence indices
+ sql.extend(["%s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('TABLE'),
+ style.SQL_TABLE(quote_name(sequence['table'])),
+ style.SQL_KEYWORD('AUTO_INCREMENT'),
+ style.SQL_FIELD('= 1'),
+ ) for sequence in sequences])
+ return sql
+ else:
+ return []
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'LIKE %s',
+ 'contains': 'LIKE BINARY %s',
+ 'icontains': 'LIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE BINARY %s',
+ 'endswith': 'LIKE BINARY %s',
+ 'istartswith': 'LIKE %s',
+ 'iendswith': 'LIKE %s',
+}
diff --git a/google_appengine/lib/django/django/db/backends/mysql_old/client.py b/google_appengine/lib/django/django/db/backends/mysql_old/client.py
new file mode 100755
index 0000000..f9d6297
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql_old/client.py
@@ -0,0 +1,14 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ['']
+ args += ["--user=%s" % settings.DATABASE_USER]
+ if settings.DATABASE_PASSWORD:
+ args += ["--password=%s" % settings.DATABASE_PASSWORD]
+ if settings.DATABASE_HOST:
+ args += ["--host=%s" % settings.DATABASE_HOST]
+ if settings.DATABASE_PORT:
+ args += ["--port=%s" % settings.DATABASE_PORT]
+ args += [settings.DATABASE_NAME]
+ os.execvp('mysql', args)
diff --git a/google_appengine/lib/django/django/db/backends/mysql_old/creation.py b/google_appengine/lib/django/django/db/backends/mysql_old/creation.py
new file mode 100755
index 0000000..22ed901
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql_old/creation.py
@@ -0,0 +1,29 @@
+# This dictionary maps Field objects to their associated MySQL column
+# types, as strings. Column-type strings can contain format strings; they'll
+# be interpolated against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPES = {
+ 'AutoField': 'integer AUTO_INCREMENT',
+ 'BooleanField': 'bool',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'datetime',
+ 'FileField': 'varchar(100)',
+ 'FilePathField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'char(15)',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'bool',
+ 'OneToOneField': 'integer',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer UNSIGNED',
+ 'PositiveSmallIntegerField': 'smallint UNSIGNED',
+ 'SlugField': 'varchar(%(maxlength)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'longtext',
+ 'TimeField': 'time',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/google_appengine/lib/django/django/db/backends/mysql_old/introspection.py b/google_appengine/lib/django/django/db/backends/mysql_old/introspection.py
new file mode 100755
index 0000000..5ea626a
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/mysql_old/introspection.py
@@ -0,0 +1,95 @@
+from django.db.backends.mysql_old.base import quote_name
+from MySQLdb import ProgrammingError, OperationalError
+from MySQLdb.constants import FIELD_TYPE
+import re
+
+foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ cursor.execute("SHOW TABLES")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
+ return cursor.description
+
+def _name_to_index(cursor, table_name):
+ """
+ Returns a dictionary of {field_name: field_index} for the given table.
+ Indexes are 0-based.
+ """
+ return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
+
+def get_relations(cursor, table_name):
+ """
+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+ representing all relationships to the given table. Indexes are 0-based.
+ """
+ my_field_dict = _name_to_index(cursor, table_name)
+ constraints = []
+ relations = {}
+ try:
+ # This should work for MySQL 5.0.
+ cursor.execute("""
+ SELECT column_name, referenced_table_name, referenced_column_name
+ FROM information_schema.key_column_usage
+ WHERE table_name = %s
+ AND table_schema = DATABASE()
+ AND referenced_table_name IS NOT NULL
+ AND referenced_column_name IS NOT NULL""", [table_name])
+ constraints.extend(cursor.fetchall())
+ except (ProgrammingError, OperationalError):
+ # Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
+ # Go through all constraints and save the equal matches.
+ cursor.execute("SHOW CREATE TABLE %s" % quote_name(table_name))
+ for row in cursor.fetchall():
+ pos = 0
+ while True:
+ match = foreign_key_re.search(row[1], pos)
+ if match == None:
+ break
+ pos = match.end()
+ constraints.append(match.groups())
+
+ for my_fieldname, other_table, other_field in constraints:
+ other_field_index = _name_to_index(cursor, other_table)[other_field]
+ my_field_index = my_field_dict[my_fieldname]
+ relations[my_field_index] = (other_field_index, other_table)
+
+ return relations
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
+ indexes = {}
+ for row in cursor.fetchall():
+ indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
+ return indexes
+
+DATA_TYPES_REVERSE = {
+ FIELD_TYPE.BLOB: 'TextField',
+ FIELD_TYPE.CHAR: 'CharField',
+ FIELD_TYPE.DECIMAL: 'FloatField',
+ FIELD_TYPE.DATE: 'DateField',
+ FIELD_TYPE.DATETIME: 'DateTimeField',
+ FIELD_TYPE.DOUBLE: 'FloatField',
+ FIELD_TYPE.FLOAT: 'FloatField',
+ FIELD_TYPE.INT24: 'IntegerField',
+ FIELD_TYPE.LONG: 'IntegerField',
+ FIELD_TYPE.LONGLONG: 'IntegerField',
+ FIELD_TYPE.SHORT: 'IntegerField',
+ FIELD_TYPE.STRING: 'TextField',
+ FIELD_TYPE.TIMESTAMP: 'DateTimeField',
+ FIELD_TYPE.TINY: 'IntegerField',
+ FIELD_TYPE.TINY_BLOB: 'TextField',
+ FIELD_TYPE.MEDIUM_BLOB: 'TextField',
+ FIELD_TYPE.LONG_BLOB: 'TextField',
+ FIELD_TYPE.VAR_STRING: 'CharField',
+}
diff --git a/google_appengine/lib/django/django/db/backends/oracle/__init__.py b/google_appengine/lib/django/django/db/backends/oracle/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/oracle/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/oracle/base.py b/google_appengine/lib/django/django/db/backends/oracle/base.py
new file mode 100755
index 0000000..d52ae33
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/oracle/base.py
@@ -0,0 +1,151 @@
+"""
+Oracle database backend for Django.
+
+Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
+"""
+
+from django.db.backends import util
+try:
+ import cx_Oracle as Database
+except ImportError, e:
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
+
+DatabaseError = Database.Error
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+ self.options = kwargs
+
+ def _valid_connection(self):
+ return self.connection is not None
+
+ def cursor(self):
+ from django.conf import settings
+ if not self._valid_connection():
+ if len(settings.DATABASE_HOST.strip()) == 0:
+ settings.DATABASE_HOST = 'localhost'
+ if len(settings.DATABASE_PORT.strip()) != 0:
+ dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME)
+ self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn, **self.options)
+ else:
+ conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
+ self.connection = Database.connect(conn_string, **self.options)
+ return FormatStylePlaceholderCursor(self.connection)
+
+ def _commit(self):
+ if self.connection is not None:
+ self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ try:
+ self.connection.rollback()
+ except Database.NotSupportedError:
+ pass
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+supports_constraints = True
+
+class FormatStylePlaceholderCursor(Database.Cursor):
+ """
+ Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" style.
+ This fixes it -- but note that if you want to use a literal "%s" in a query,
+ you'll need to use "%%s".
+ """
+ def execute(self, query, params=None):
+ if params is None: params = []
+ query = self.convert_arguments(query, len(params))
+ return Database.Cursor.execute(self, query, params)
+
+ def executemany(self, query, params=None):
+ if params is None: params = []
+ query = self.convert_arguments(query, len(params[0]))
+ return Database.Cursor.executemany(self, query, params)
+
+ def convert_arguments(self, query, num_params):
+ # replace occurances of "%s" with ":arg" - Oracle requires colons for parameter placeholders.
+ args = [':arg' for i in range(num_params)]
+ return query % tuple(args)
+
+def quote_name(name):
+ return name
+
+dictfetchone = util.dictfetchone
+dictfetchmany = util.dictfetchmany
+dictfetchall = util.dictfetchall
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ query = "SELECT %s_sq.currval from dual" % table_name
+ cursor.execute(query)
+ return cursor.fetchone()[0]
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://www.psoug.org/reference/date_func.html
+ return "EXTRACT(%s FROM %s)" % (lookup_type, table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ return "EXTRACT(%s FROM TRUNC(%s))" % (lookup_type, field_name)
+
+def get_limit_offset_sql(limit, offset=None):
+ # Limits and offset are too complicated to be handled here.
+ # Instead, they are handled in django/db/query.py.
+ pass
+
+def get_random_function_sql():
+ return "DBMS_RANDOM.RANDOM"
+
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
+def get_fulltext_search_sql(field_name):
+ raise NotImplementedError
+
+def get_drop_foreignkey_sql():
+ return "DROP FOREIGN KEY"
+
+def get_pk_default_value():
+ return "DEFAULT"
+
+def get_sql_flush(style, tables, sequences):
+ """Return a list of SQL statements required to remove all data from
+ all tables in the database (without actually removing the tables
+ themselves) and put the database in an empty 'initial' state
+ """
+ # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # TODO - SQL not actually tested against Oracle yet!
+ # TODO - autoincrement indices reset required? See other get_sql_flush() implementations
+ sql = ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'LIKE %s',
+ 'contains': 'LIKE %s',
+ 'icontains': 'LIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE %s',
+ 'endswith': 'LIKE %s',
+ 'istartswith': 'LIKE %s',
+ 'iendswith': 'LIKE %s',
+}
diff --git a/google_appengine/lib/django/django/db/backends/oracle/client.py b/google_appengine/lib/django/django/db/backends/oracle/client.py
new file mode 100755
index 0000000..7e32ebe
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/oracle/client.py
@@ -0,0 +1,10 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ''
+ args += settings.DATABASE_USER
+ if settings.DATABASE_PASSWORD:
+ args += "/%s" % settings.DATABASE_PASSWORD
+ args += "@%s" % settings.DATABASE_NAME
+ os.execvp('sqlplus', args)
diff --git a/google_appengine/lib/django/django/db/backends/oracle/creation.py b/google_appengine/lib/django/django/db/backends/oracle/creation.py
new file mode 100755
index 0000000..da65df1
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/oracle/creation.py
@@ -0,0 +1,25 @@
+DATA_TYPES = {
+ 'AutoField': 'number(38)',
+ 'BooleanField': 'number(1)',
+ 'CharField': 'varchar2(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'date',
+ 'FileField': 'varchar2(100)',
+ 'FilePathField': 'varchar2(100)',
+ 'FloatField': 'number(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar2(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'char(15)',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'integer',
+ 'OneToOneField': 'integer',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer',
+ 'PositiveSmallIntegerField': 'smallint',
+ 'SlugField': 'varchar(50)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'long',
+ 'TimeField': 'timestamp',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/google_appengine/lib/django/django/db/backends/oracle/introspection.py b/google_appengine/lib/django/django/db/backends/oracle/introspection.py
new file mode 100755
index 0000000..ecc8f37
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/oracle/introspection.py
@@ -0,0 +1,50 @@
+import re
+
+foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ return table_name
+
+def _name_to_index(cursor, table_name):
+ """
+ Returns a dictionary of {field_name: field_index} for the given table.
+ Indexes are 0-based.
+ """
+ return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
+
+def get_relations(cursor, table_name):
+ """
+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+ representing all relationships to the given table. Indexes are 0-based.
+ """
+ raise NotImplementedError
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ raise NotImplementedError
+
+# Maps type codes to Django Field types.
+DATA_TYPES_REVERSE = {
+ 16: 'BooleanField',
+ 21: 'SmallIntegerField',
+ 23: 'IntegerField',
+ 25: 'TextField',
+ 869: 'IPAddressField',
+ 1043: 'CharField',
+ 1082: 'DateField',
+ 1083: 'TimeField',
+ 1114: 'DateTimeField',
+ 1184: 'DateTimeField',
+ 1266: 'TimeField',
+ 1700: 'FloatField',
+}
diff --git a/google_appengine/lib/django/django/db/backends/postgresql/__init__.py b/google_appengine/lib/django/django/db/backends/postgresql/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/postgresql/base.py b/google_appengine/lib/django/django/db/backends/postgresql/base.py
new file mode 100755
index 0000000..54be422
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql/base.py
@@ -0,0 +1,241 @@
+"""
+PostgreSQL database backend for Django.
+
+Requires psycopg 1: http://initd.org/projects/psycopg1
+"""
+
+from django.db.backends import util
+try:
+ import psycopg as Database
+except ImportError, e:
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "Error loading psycopg module: %s" % e
+
+DatabaseError = Database.DatabaseError
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+def smart_basestring(s, charset):
+ if isinstance(s, unicode):
+ return s.encode(charset)
+ return s
+
+class UnicodeCursorWrapper(object):
+ """
+ A thin wrapper around psycopg cursors that allows them to accept Unicode
+ strings as params.
+
+ This is necessary because psycopg doesn't apply any DB quoting to
+ parameters that are Unicode strings. If a param is Unicode, this will
+ convert it to a bytestring using DEFAULT_CHARSET before passing it to
+ psycopg.
+ """
+ def __init__(self, cursor, charset):
+ self.cursor = cursor
+ self.charset = charset
+
+ def execute(self, sql, params=()):
+ return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params])
+
+ def executemany(self, sql, param_list):
+ new_param_list = [tuple([smart_basestring(p, self.charset) for p in params]) for params in param_list]
+ return self.cursor.executemany(sql, new_param_list)
+
+ def __getattr__(self, attr):
+ if self.__dict__.has_key(attr):
+ return self.__dict__[attr]
+ else:
+ return getattr(self.cursor, attr)
+
+postgres_version = None
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+ self.options = kwargs
+
+ def cursor(self):
+ from django.conf import settings
+ set_tz = False
+ if self.connection is None:
+ set_tz = True
+ if settings.DATABASE_NAME == '':
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
+ conn_string = "dbname=%s" % settings.DATABASE_NAME
+ if settings.DATABASE_USER:
+ conn_string = "user=%s %s" % (settings.DATABASE_USER, conn_string)
+ if settings.DATABASE_PASSWORD:
+ conn_string += " password='%s'" % settings.DATABASE_PASSWORD
+ if settings.DATABASE_HOST:
+ conn_string += " host=%s" % settings.DATABASE_HOST
+ if settings.DATABASE_PORT:
+ conn_string += " port=%s" % settings.DATABASE_PORT
+ self.connection = Database.connect(conn_string, **self.options)
+ self.connection.set_isolation_level(1) # make transactions transparent to all cursors
+ cursor = self.connection.cursor()
+ if set_tz:
+ cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET)
+ global postgres_version
+ if not postgres_version:
+ cursor.execute("SELECT version()")
+ postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(cursor, self)
+ return cursor
+
+ def _commit(self):
+ if self.connection is not None:
+ return self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ return self.connection.rollback()
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+supports_constraints = True
+
+def quote_name(name):
+ if name.startswith('"') and name.endswith('"'):
+ return name # Quoting once is enough.
+ return '"%s"' % name
+
+def dictfetchone(cursor):
+ "Returns a row from the cursor as a dict"
+ return cursor.dictfetchone()
+
+def dictfetchmany(cursor, number):
+ "Returns a certain number of rows from a cursor as a dict"
+ return cursor.dictfetchmany(number)
+
+def dictfetchall(cursor):
+ "Returns all rows from a cursor as a dict"
+ return cursor.dictfetchall()
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
+ return cursor.fetchone()[0]
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
+ return "EXTRACT('%s' FROM %s)" % (lookup_type, table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
+ return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
+
+def get_limit_offset_sql(limit, offset=None):
+ sql = "LIMIT %s" % limit
+ if offset and offset != 0:
+ sql += " OFFSET %s" % offset
+ return sql
+
+def get_random_function_sql():
+ return "RANDOM()"
+
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
+def get_fulltext_search_sql(field_name):
+ raise NotImplementedError
+
+def get_drop_foreignkey_sql():
+ return "DROP CONSTRAINT"
+
+def get_pk_default_value():
+ return "DEFAULT"
+
+def get_sql_flush(style, tables, sequences):
+ """Return a list of SQL statements required to remove all data from
+ all tables in the database (without actually removing the tables
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ if tables:
+ if postgres_version[0] >= 8 and postgres_version[1] >= 1:
+ # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to
+ # truncate tables referenced by a foreign key in any other table. The result is a
+ # single SQL TRUNCATE statement.
+ sql = ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
+ )]
+ else:
+ # Older versions of Postgres can't do TRUNCATE in a single call, so they must use
+ # a simple delete.
+ sql = ['%s %s %s;' % \
+ (style.SQL_KEYWORD('DELETE'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+
+ # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
+ # to reset sequence indices
+ for sequence_info in sequences:
+ table_name = sequence_info['table']
+ column_name = sequence_info['column']
+ if column_name and len(column_name)>0:
+ # sequence name in this case will be <table>_<column>_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD('%s_%s_seq' % (table_name, column_name)),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ else:
+ # sequence name in this case will be <table>_id_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD('%s_id_seq' % table_name),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ return sql
+ else:
+ return []
+
+
+# Register these custom typecasts, because Django expects dates/times to be
+# in Python's native (standard-library) datetime/time format, whereas psycopg
+# use mx.DateTime by default.
+try:
+ Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date))
+except AttributeError:
+ raise Exception, "You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'."
+Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time))
+Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
+Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'ILIKE %s',
+ 'contains': 'LIKE %s',
+ 'icontains': 'ILIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE %s',
+ 'endswith': 'LIKE %s',
+ 'istartswith': 'ILIKE %s',
+ 'iendswith': 'ILIKE %s',
+}
diff --git a/google_appengine/lib/django/django/db/backends/postgresql/client.py b/google_appengine/lib/django/django/db/backends/postgresql/client.py
new file mode 100755
index 0000000..8123ec7
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql/client.py
@@ -0,0 +1,15 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ['psql']
+ if settings.DATABASE_USER:
+ args += ["-U", settings.DATABASE_USER]
+ if settings.DATABASE_PASSWORD:
+ args += ["-W"]
+ if settings.DATABASE_HOST:
+ args.extend(["-h", settings.DATABASE_HOST])
+ if settings.DATABASE_PORT:
+ args.extend(["-p", str(settings.DATABASE_PORT)])
+ args += [settings.DATABASE_NAME]
+ os.execvp('psql', args)
diff --git a/google_appengine/lib/django/django/db/backends/postgresql/creation.py b/google_appengine/lib/django/django/db/backends/postgresql/creation.py
new file mode 100755
index 0000000..6c130f3
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql/creation.py
@@ -0,0 +1,29 @@
+# This dictionary maps Field objects to their associated PostgreSQL column
+# types, as strings. Column-type strings can contain format strings; they'll
+# be interpolated against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPES = {
+ 'AutoField': 'serial',
+ 'BooleanField': 'boolean',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'timestamp with time zone',
+ 'FileField': 'varchar(100)',
+ 'FilePathField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'inet',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'boolean',
+ 'OneToOneField': 'integer',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
+ 'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
+ 'SlugField': 'varchar(%(maxlength)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'text',
+ 'TimeField': 'time',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/google_appengine/lib/django/django/db/backends/postgresql/introspection.py b/google_appengine/lib/django/django/db/backends/postgresql/introspection.py
new file mode 100755
index 0000000..6e1d60c
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql/introspection.py
@@ -0,0 +1,83 @@
+from django.db.backends.postgresql.base import quote_name
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ cursor.execute("""
+ SELECT c.relname
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('r', 'v', '')
+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
+ AND pg_catalog.pg_table_is_visible(c.oid)""")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
+ return cursor.description
+
+def get_relations(cursor, table_name):
+ """
+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+ representing all relationships to the given table. Indexes are 0-based.
+ """
+ cursor.execute("""
+ SELECT con.conkey, con.confkey, c2.relname
+ FROM pg_constraint con, pg_class c1, pg_class c2
+ WHERE c1.oid = con.conrelid
+ AND c2.oid = con.confrelid
+ AND c1.relname = %s
+ AND con.contype = 'f'""", [table_name])
+ relations = {}
+ for row in cursor.fetchall():
+ try:
+ # row[0] and row[1] are like "{2}", so strip the curly braces.
+ relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
+ except ValueError:
+ continue
+ return relations
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ # This query retrieves each index on the given table, including the
+ # first associated field name
+ cursor.execute("""
+ SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
+ FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
+ pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
+ WHERE c.oid = idx.indrelid
+ AND idx.indexrelid = c2.oid
+ AND attr.attrelid = c.oid
+ AND attr.attnum = idx.indkey[0]
+ AND c.relname = %s""", [table_name])
+ indexes = {}
+ for row in cursor.fetchall():
+ # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
+ # a string of space-separated integers. This designates the field
+ # indexes (1-based) of the fields that have indexes on the table.
+ # Here, we skip any indexes across multiple fields.
+ if ' ' in row[1]:
+ continue
+ indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
+ return indexes
+
+# Maps type codes to Django Field types.
+DATA_TYPES_REVERSE = {
+ 16: 'BooleanField',
+ 21: 'SmallIntegerField',
+ 23: 'IntegerField',
+ 25: 'TextField',
+ 869: 'IPAddressField',
+ 1043: 'CharField',
+ 1082: 'DateField',
+ 1083: 'TimeField',
+ 1114: 'DateTimeField',
+ 1184: 'DateTimeField',
+ 1266: 'TimeField',
+ 1700: 'FloatField',
+}
diff --git a/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/__init__.py b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/base.py b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/base.py
new file mode 100755
index 0000000..e4724e4
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/base.py
@@ -0,0 +1,186 @@
+"""
+PostgreSQL database backend for Django.
+
+Requires psycopg 2: http://initd.org/projects/psycopg2
+"""
+
+from django.db.backends import util
+try:
+ import psycopg2 as Database
+except ImportError, e:
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e
+
+DatabaseError = Database.DatabaseError
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+postgres_version = None
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+ self.options = kwargs
+
+ def cursor(self):
+ from django.conf import settings
+ set_tz = False
+ if self.connection is None:
+ set_tz = True
+ if settings.DATABASE_NAME == '':
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
+ conn_string = "dbname=%s" % settings.DATABASE_NAME
+ if settings.DATABASE_USER:
+ conn_string = "user=%s %s" % (settings.DATABASE_USER, conn_string)
+ if settings.DATABASE_PASSWORD:
+ conn_string += " password='%s'" % settings.DATABASE_PASSWORD
+ if settings.DATABASE_HOST:
+ conn_string += " host=%s" % settings.DATABASE_HOST
+ if settings.DATABASE_PORT:
+ conn_string += " port=%s" % settings.DATABASE_PORT
+ self.connection = Database.connect(conn_string, **self.options)
+ self.connection.set_isolation_level(1) # make transactions transparent to all cursors
+ cursor = self.connection.cursor()
+ cursor.tzinfo_factory = None
+ if set_tz:
+ cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ global postgres_version
+ if not postgres_version:
+ cursor.execute("SELECT version()")
+ postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(cursor, self)
+ return cursor
+
+ def _commit(self):
+ if self.connection is not None:
+ return self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ return self.connection.rollback()
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+supports_constraints = True
+
+def quote_name(name):
+ if name.startswith('"') and name.endswith('"'):
+ return name # Quoting once is enough.
+ return '"%s"' % name
+
+dictfetchone = util.dictfetchone
+dictfetchmany = util.dictfetchmany
+dictfetchall = util.dictfetchall
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
+ return cursor.fetchone()[0]
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
+ return "EXTRACT('%s' FROM %s)" % (lookup_type, table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
+ return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
+
+def get_limit_offset_sql(limit, offset=None):
+ sql = "LIMIT %s" % limit
+ if offset and offset != 0:
+ sql += " OFFSET %s" % offset
+ return sql
+
+def get_random_function_sql():
+ return "RANDOM()"
+
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
+def get_fulltext_search_sql(field_name):
+ raise NotImplementedError
+
+def get_drop_foreignkey_sql():
+ return "DROP CONSTRAINT"
+
+def get_pk_default_value():
+ return "DEFAULT"
+
+def get_sql_flush(style, tables, sequences):
+ """Return a list of SQL statements required to remove all data from
+ all tables in the database (without actually removing the tables
+ themselves) and put the database in an empty 'initial' state
+ """
+ if tables:
+ if postgres_version[0] >= 8 and postgres_version[1] >= 1:
+ # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to
+ # truncate tables referenced by a foreign key in any other table. The result is a
+ # single SQL TRUNCATE statement
+ sql = ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
+ )]
+ else:
+ sql = ['%s %s %s;' % \
+ (style.SQL_KEYWORD('DELETE'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+
+ # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
+ # to reset sequence indices
+ for sequence in sequences:
+ table_name = sequence['table']
+ column_name = sequence['column']
+ if column_name and len(column_name) > 0:
+ # sequence name in this case will be <table>_<column>_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD('%s_%s_seq' % (table_name, column_name)),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ else:
+ # sequence name in this case will be <table>_id_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD('%s_id_seq' % table_name),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ return sql
+ else:
+ return []
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'ILIKE %s',
+ 'contains': 'LIKE %s',
+ 'icontains': 'ILIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE %s',
+ 'endswith': 'LIKE %s',
+ 'istartswith': 'ILIKE %s',
+ 'iendswith': 'ILIKE %s',
+}
diff --git a/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/client.py b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/client.py
new file mode 100755
index 0000000..c9d879a
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/client.py
@@ -0,0 +1 @@
+from django.db.backends.postgresql.client import *
diff --git a/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/creation.py b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/creation.py
new file mode 100755
index 0000000..8c87e5c
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/creation.py
@@ -0,0 +1 @@
+from django.db.backends.postgresql.creation import *
diff --git a/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/introspection.py b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/introspection.py
new file mode 100755
index 0000000..a546da8
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/postgresql_psycopg2/introspection.py
@@ -0,0 +1,83 @@
+from django.db.backends.postgresql_psycopg2.base import quote_name
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ cursor.execute("""
+ SELECT c.relname
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('r', 'v', '')
+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
+ AND pg_catalog.pg_table_is_visible(c.oid)""")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
+ return cursor.description
+
+def get_relations(cursor, table_name):
+ """
+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+ representing all relationships to the given table. Indexes are 0-based.
+ """
+ cursor.execute("""
+ SELECT con.conkey, con.confkey, c2.relname
+ FROM pg_constraint con, pg_class c1, pg_class c2
+ WHERE c1.oid = con.conrelid
+ AND c2.oid = con.confrelid
+ AND c1.relname = %s
+ AND con.contype = 'f'""", [table_name])
+ relations = {}
+ for row in cursor.fetchall():
+ try:
+ # row[0] and row[1] are like "{2}", so strip the curly braces.
+ relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
+ except ValueError:
+ continue
+ return relations
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ # This query retrieves each index on the given table, including the
+ # first associated field name
+ cursor.execute("""
+ SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
+ FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
+ pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
+ WHERE c.oid = idx.indrelid
+ AND idx.indexrelid = c2.oid
+ AND attr.attrelid = c.oid
+ AND attr.attnum = idx.indkey[0]
+ AND c.relname = %s""", [table_name])
+ indexes = {}
+ for row in cursor.fetchall():
+ # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
+ # a string of space-separated integers. This designates the field
+ # indexes (1-based) of the fields that have indexes on the table.
+ # Here, we skip any indexes across multiple fields.
+ if ' ' in row[1]:
+ continue
+ indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
+ return indexes
+
+# Maps type codes to Django Field types.
+DATA_TYPES_REVERSE = {
+ 16: 'BooleanField',
+ 21: 'SmallIntegerField',
+ 23: 'IntegerField',
+ 25: 'TextField',
+ 869: 'IPAddressField',
+ 1043: 'CharField',
+ 1082: 'DateField',
+ 1083: 'TimeField',
+ 1114: 'DateTimeField',
+ 1184: 'DateTimeField',
+ 1266: 'TimeField',
+ 1700: 'FloatField',
+}
diff --git a/google_appengine/lib/django/django/db/backends/sqlite3/__init__.py b/google_appengine/lib/django/django/db/backends/sqlite3/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/sqlite3/__init__.py
diff --git a/google_appengine/lib/django/django/db/backends/sqlite3/base.py b/google_appengine/lib/django/django/db/backends/sqlite3/base.py
new file mode 100755
index 0000000..4b8a1c6
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/sqlite3/base.py
@@ -0,0 +1,201 @@
+"""
+SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/).
+"""
+
+from django.db.backends import util
+try:
+ try:
+ from sqlite3 import dbapi2 as Database
+ except ImportError:
+ from pysqlite2 import dbapi2 as Database
+except ImportError, e:
+ import sys
+ from django.core.exceptions import ImproperlyConfigured
+ if sys.version_info < (2, 5, 0):
+ module = 'pysqlite2'
+ else:
+ module = 'sqlite3'
+ raise ImproperlyConfigured, "Error loading %s module: %s" % (module, e)
+
+DatabaseError = Database.DatabaseError
+
+Database.register_converter("bool", lambda s: str(s) == '1')
+Database.register_converter("time", util.typecast_time)
+Database.register_converter("date", util.typecast_date)
+Database.register_converter("datetime", util.typecast_timestamp)
+Database.register_converter("timestamp", util.typecast_timestamp)
+Database.register_converter("TIMESTAMP", util.typecast_timestamp)
+
+def utf8rowFactory(cursor, row):
+ def utf8(s):
+ if type(s) == unicode:
+ return s.encode("utf-8")
+ else:
+ return s
+ return [utf8(r) for r in row]
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+ self.options = kwargs
+
+ def cursor(self):
+ from django.conf import settings
+ if self.connection is None:
+ kwargs = {
+ 'database': settings.DATABASE_NAME,
+ 'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
+ }
+ kwargs.update(self.options)
+ self.connection = Database.connect(**kwargs)
+ # Register extract and date_trunc functions.
+ self.connection.create_function("django_extract", 2, _sqlite_extract)
+ self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
+ cursor = self.connection.cursor(factory=SQLiteCursorWrapper)
+ cursor.row_factory = utf8rowFactory
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(cursor, self)
+ else:
+ return cursor
+
+ def _commit(self):
+ if self.connection is not None:
+ self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ self.connection.rollback()
+
+ def close(self):
+ from django.conf import settings
+ # If database is in memory, closing the connection destroys the database.
+ # To prevent accidental data loss, ignore close requests on an in-memory db.
+ if self.connection is not None and settings.DATABASE_NAME != ":memory:":
+ self.connection.close()
+ self.connection = None
+
+class SQLiteCursorWrapper(Database.Cursor):
+ """
+ Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
+ This fixes it -- but note that if you want to use a literal "%s" in a query,
+ you'll need to use "%%s".
+ """
+ def execute(self, query, params=()):
+ query = self.convert_query(query, len(params))
+ return Database.Cursor.execute(self, query, params)
+
+ def executemany(self, query, param_list):
+ query = self.convert_query(query, len(param_list[0]))
+ return Database.Cursor.executemany(self, query, param_list)
+
+ def convert_query(self, query, num_params):
+ return query % tuple("?" * num_params)
+
+supports_constraints = False
+
+def quote_name(name):
+ if name.startswith('"') and name.endswith('"'):
+ return name # Quoting once is enough.
+ return '"%s"' % name
+
+dictfetchone = util.dictfetchone
+dictfetchmany = util.dictfetchmany
+dictfetchall = util.dictfetchall
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ return cursor.lastrowid
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # sqlite doesn't support extract, so we fake it with the user-defined
+ # function _sqlite_extract that's registered in connect(), above.
+ return 'django_extract("%s", %s)' % (lookup_type.lower(), table_name)
+
+def _sqlite_extract(lookup_type, dt):
+ try:
+ dt = util.typecast_timestamp(dt)
+ except (ValueError, TypeError):
+ return None
+ return str(getattr(dt, lookup_type))
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ # sqlite doesn't support DATE_TRUNC, so we fake it as above.
+ return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name)
+
+def get_limit_offset_sql(limit, offset=None):
+ sql = "LIMIT %s" % limit
+ if offset and offset != 0:
+ sql += " OFFSET %s" % offset
+ return sql
+
+def get_random_function_sql():
+ return "RANDOM()"
+
+def get_deferrable_sql():
+ return ""
+
+def get_fulltext_search_sql(field_name):
+ raise NotImplementedError
+
+def get_drop_foreignkey_sql():
+ return ""
+
+def get_pk_default_value():
+ return "NULL"
+
+def get_sql_flush(style, tables, sequences):
+ """Return a list of SQL statements required to remove all data from
+ all tables in the database (without actually removing the tables
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ # NB: The generated SQL below is specific to SQLite
+ # Note: The DELETE FROM... SQL generated below works for SQLite databases
+ # because constraints don't exist
+ sql = ['%s %s %s;' % \
+ (style.SQL_KEYWORD('DELETE'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+ # Note: No requirement for reset of auto-incremented indices (cf. other
+ # get_sql_flush() implementations). Just return SQL at this point
+ return sql
+
+def _sqlite_date_trunc(lookup_type, dt):
+ try:
+ dt = util.typecast_timestamp(dt)
+ except (ValueError, TypeError):
+ return None
+ if lookup_type == 'year':
+ return "%i-01-01 00:00:00" % dt.year
+ elif lookup_type == 'month':
+ return "%i-%02i-01 00:00:00" % (dt.year, dt.month)
+ elif lookup_type == 'day':
+ return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
+
+# SQLite requires LIKE statements to include an ESCAPE clause if the value
+# being escaped has a percent or underscore in it.
+# See http://www.sqlite.org/lang_expr.html for an explanation.
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': "LIKE %s ESCAPE '\\'",
+ 'contains': "LIKE %s ESCAPE '\\'",
+ 'icontains': "LIKE %s ESCAPE '\\'",
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': "LIKE %s ESCAPE '\\'",
+ 'endswith': "LIKE %s ESCAPE '\\'",
+ 'istartswith': "LIKE %s ESCAPE '\\'",
+ 'iendswith': "LIKE %s ESCAPE '\\'",
+}
diff --git a/google_appengine/lib/django/django/db/backends/sqlite3/client.py b/google_appengine/lib/django/django/db/backends/sqlite3/client.py
new file mode 100755
index 0000000..0972183
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/sqlite3/client.py
@@ -0,0 +1,6 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ['', settings.DATABASE_NAME]
+ os.execvp('sqlite3', args)
diff --git a/google_appengine/lib/django/django/db/backends/sqlite3/creation.py b/google_appengine/lib/django/django/db/backends/sqlite3/creation.py
new file mode 100755
index 0000000..77f570b
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/sqlite3/creation.py
@@ -0,0 +1,28 @@
+# SQLite doesn't actually support most of these types, but it "does the right
+# thing" given more verbose field definitions, so leave them as is so that
+# schema inspection is more useful.
+DATA_TYPES = {
+ 'AutoField': 'integer',
+ 'BooleanField': 'bool',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'datetime',
+ 'FileField': 'varchar(100)',
+ 'FilePathField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'char(15)',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'bool',
+ 'OneToOneField': 'integer',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer unsigned',
+ 'PositiveSmallIntegerField': 'smallint unsigned',
+ 'SlugField': 'varchar(%(maxlength)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'text',
+ 'TimeField': 'time',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/google_appengine/lib/django/django/db/backends/sqlite3/introspection.py b/google_appengine/lib/django/django/db/backends/sqlite3/introspection.py
new file mode 100755
index 0000000..4e22c5e
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/sqlite3/introspection.py
@@ -0,0 +1,87 @@
+from django.db.backends.sqlite3.base import quote_name
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ # Skip the sqlite_sequence system table used for autoincrement key
+ # generation.
+ cursor.execute("""
+ SELECT name FROM sqlite_master
+ WHERE type='table' AND NOT name='sqlite_sequence'
+ ORDER BY name""")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ return [(info['name'], info['type'], None, None, None, None,
+ info['null_ok']) for info in _table_info(cursor, table_name)]
+
+def get_relations(cursor, table_name):
+ raise NotImplementedError
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ indexes = {}
+ for info in _table_info(cursor, table_name):
+ indexes[info['name']] = {'primary_key': info['pk'] != 0,
+ 'unique': False}
+ cursor.execute('PRAGMA index_list(%s)' % quote_name(table_name))
+ # seq, name, unique
+ for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]:
+ if not unique:
+ continue
+ cursor.execute('PRAGMA index_info(%s)' % quote_name(index))
+ info = cursor.fetchall()
+ # Skip indexes across multiple fields
+ if len(info) != 1:
+ continue
+ name = info[0][2] # seqno, cid, name
+ indexes[name]['unique'] = True
+ return indexes
+
+def _table_info(cursor, name):
+ cursor.execute('PRAGMA table_info(%s)' % quote_name(name))
+ # cid, name, type, notnull, dflt_value, pk
+ return [{'name': field[1],
+ 'type': field[2],
+ 'null_ok': not field[3],
+ 'pk': field[5] # undocumented
+ } for field in cursor.fetchall()]
+
+# Maps SQL types to Django Field types. Some of the SQL types have multiple
+# entries here because SQLite allows for anything and doesn't normalize the
+# field type; it uses whatever was given.
+BASE_DATA_TYPES_REVERSE = {
+ 'bool': 'BooleanField',
+ 'boolean': 'BooleanField',
+ 'smallint': 'SmallIntegerField',
+ 'smallinteger': 'SmallIntegerField',
+ 'int': 'IntegerField',
+ 'integer': 'IntegerField',
+ 'text': 'TextField',
+ 'char': 'CharField',
+ 'date': 'DateField',
+ 'datetime': 'DateTimeField',
+ 'time': 'TimeField',
+}
+
+# This light wrapper "fakes" a dictionary interface, because some SQLite data
+# types include variables in them -- e.g. "varchar(30)" -- and can't be matched
+# as a simple dictionary lookup.
+class FlexibleFieldLookupDict:
+ def __getitem__(self, key):
+ key = key.lower()
+ try:
+ return BASE_DATA_TYPES_REVERSE[key]
+ except KeyError:
+ import re
+ m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key)
+ if m:
+ return ('CharField', {'maxlength': int(m.group(1))})
+ raise KeyError
+
+DATA_TYPES_REVERSE = FlexibleFieldLookupDict()
diff --git a/google_appengine/lib/django/django/db/backends/util.py b/google_appengine/lib/django/django/db/backends/util.py
new file mode 100755
index 0000000..d8f86fe
--- /dev/null
+++ b/google_appengine/lib/django/django/db/backends/util.py
@@ -0,0 +1,120 @@
+import datetime
+from time import time
+
+class CursorDebugWrapper(object):
+ def __init__(self, cursor, db):
+ self.cursor = cursor
+ self.db = db
+
+ def execute(self, sql, params=()):
+ start = time()
+ try:
+ return self.cursor.execute(sql, params)
+ finally:
+ stop = time()
+ # If params was a list, convert it to a tuple, because string
+ # formatting with '%' only works with tuples or dicts.
+ if not isinstance(params, (tuple, dict)):
+ params = tuple(params)
+ self.db.queries.append({
+ 'sql': sql % params,
+ 'time': "%.3f" % (stop - start),
+ })
+
+ def executemany(self, sql, param_list):
+ start = time()
+ try:
+ return self.cursor.executemany(sql, param_list)
+ finally:
+ stop = time()
+ self.db.queries.append({
+ 'sql': 'MANY: ' + sql + ' ' + str(tuple(param_list)),
+ 'time': "%.3f" % (stop - start),
+ })
+
+ def __getattr__(self, attr):
+ if self.__dict__.has_key(attr):
+ return self.__dict__[attr]
+ else:
+ return getattr(self.cursor, attr)
+
+###############################################
+# Converters from database (string) to Python #
+###############################################
+
+def typecast_date(s):
+ return s and datetime.date(*map(int, s.split('-'))) or None # returns None if s is null
+
+def typecast_time(s): # does NOT store time zone information
+ if not s: return None
+ hour, minutes, seconds = s.split(':')
+ if '.' in seconds: # check whether seconds have a fractional part
+ seconds, microseconds = seconds.split('.')
+ else:
+ microseconds = '0'
+ return datetime.time(int(hour), int(minutes), int(seconds), int(float('.'+microseconds) * 1000000))
+
+def typecast_timestamp(s): # does NOT store time zone information
+ # "2005-07-29 15:48:00.590358-05"
+ # "2005-07-29 09:56:00-05"
+ if not s: return None
+ if not ' ' in s: return typecast_date(s)
+ d, t = s.split()
+ # Extract timezone information, if it exists. Currently we just throw
+ # it away, but in the future we may make use of it.
+ if '-' in t:
+ t, tz = t.split('-', 1)
+ tz = '-' + tz
+ elif '+' in t:
+ t, tz = t.split('+', 1)
+ tz = '+' + tz
+ else:
+ tz = ''
+ dates = d.split('-')
+ times = t.split(':')
+ seconds = times[2]
+ if '.' in seconds: # check whether seconds have a fractional part
+ seconds, microseconds = seconds.split('.')
+ else:
+ microseconds = '0'
+ return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
+ int(times[0]), int(times[1]), int(seconds), int(float('.'+microseconds) * 1000000))
+
+def typecast_boolean(s):
+ if s is None: return None
+ if not s: return False
+ return str(s)[0].lower() == 't'
+
+###############################################
+# Converters from Python to database (string) #
+###############################################
+
+def rev_typecast_boolean(obj, d):
+ return obj and '1' or '0'
+
+##################################################################################
+# Helper functions for dictfetch* for databases that don't natively support them #
+##################################################################################
+
+def _dict_helper(desc, row):
+ "Returns a dictionary for the given cursor.description and result row."
+ return dict(zip([col[0] for col in desc], row))
+
+def dictfetchone(cursor):
+ "Returns a row from the cursor as a dict"
+ row = cursor.fetchone()
+ if not row:
+ return None
+ return _dict_helper(cursor.description, row)
+
+def dictfetchmany(cursor, number):
+ "Returns a certain number of rows from a cursor as a dict"
+ desc = cursor.description
+ for row in cursor.fetchmany(number):
+ yield _dict_helper(desc, row)
+
+def dictfetchall(cursor):
+ "Returns all rows from a cursor as a dict"
+ desc = cursor.description
+ for row in cursor.fetchall():
+ yield _dict_helper(desc, row)
diff --git a/google_appengine/lib/django/django/db/models/__init__.py b/google_appengine/lib/django/django/db/models/__init__.py
new file mode 100755
index 0000000..13832f9
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/__init__.py
@@ -0,0 +1,58 @@
+from django.conf import settings
+from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
+from django.core import validators
+from django.db import backend, connection
+from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
+from django.db.models.query import Q
+from django.db.models.manager import Manager
+from django.db.models.base import Model, AdminOptions
+from django.db.models.fields import *
+from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
+from django.db.models.fields.generic import GenericRelation, GenericRel, GenericForeignKey
+from django.db.models import signals
+from django.utils.functional import curry
+from django.utils.text import capfirst
+
+# Admin stages.
+ADD, CHANGE, BOTH = 1, 2, 3
+
+# Decorator. Takes a function that returns a tuple in this format:
+# (viewname, viewargs, viewkwargs)
+# Returns a function that calls urlresolvers.reverse() on that data, to return
+# the URL for those parameters.
+def permalink(func):
+ from django.core.urlresolvers import reverse
+ def inner(*args, **kwargs):
+ bits = func(*args, **kwargs)
+ viewname = bits[0]
+ return reverse(bits[0], None, *bits[1:3])
+ return inner
+
+class LazyDate(object):
+ """
+ Use in limit_choices_to to compare the field to dates calculated at run time
+ instead of when the model is loaded. For example::
+
+ ... limit_choices_to = {'date__gt' : models.LazyDate(days=-3)} ...
+
+ which will limit the choices to dates greater than three days ago.
+ """
+ def __init__(self, **kwargs):
+ self.delta = datetime.timedelta(**kwargs)
+
+ def __str__(self):
+ return str(self.__get_value__())
+
+ def __repr__(self):
+ return "<LazyDate: %s>" % self.delta
+
+ def __get_value__(self):
+ return (datetime.datetime.now() + self.delta).date()
+
+ def __getattr__(self, attr):
+ if attr == 'delta':
+ # To fix ticket #3377. Note that normal accesses to LazyDate.delta
+ # (after construction) will still work, because they don't go
+ # through __getattr__). This is mainly needed for unpickling.
+ raise AttributeError
+ return getattr(self.__get_value__(), attr)
diff --git a/google_appengine/lib/django/django/db/models/base.py b/google_appengine/lib/django/django/db/models/base.py
new file mode 100755
index 0000000..b70e6fd
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/base.py
@@ -0,0 +1,448 @@
+import django.db.models.manipulators
+import django.db.models.manager
+from django.core import validators
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
+from django.db.models.fields.related import OneToOneRel, ManyToOneRel
+from django.db.models.query import delete_objects
+from django.db.models.options import Options, AdminOptions
+from django.db import connection, backend, transaction
+from django.db.models import signals
+from django.db.models.loading import register_models, get_model
+from django.dispatch import dispatcher
+from django.utils.datastructures import SortedDict
+from django.utils.functional import curry
+from django.conf import settings
+from itertools import izip
+import types
+import sys
+import os
+
+class ModelBase(type):
+ "Metaclass for all models"
+ def __new__(cls, name, bases, attrs):
+ # If this isn't a subclass of Model, don't do anything special.
+ if name == 'Model' or not filter(lambda b: issubclass(b, Model), bases):
+ return super(ModelBase, cls).__new__(cls, name, bases, attrs)
+
+ # Create the class.
+ new_class = type.__new__(cls, name, bases, {'__module__': attrs.pop('__module__')})
+ new_class.add_to_class('_meta', Options(attrs.pop('Meta', None)))
+ new_class.add_to_class('DoesNotExist', types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {}))
+
+ # Build complete list of parents
+ for base in bases:
+ # TODO: Checking for the presence of '_meta' is hackish.
+ if '_meta' in dir(base):
+ new_class._meta.parents.append(base)
+ new_class._meta.parents.extend(base._meta.parents)
+
+ model_module = sys.modules[new_class.__module__]
+
+ if getattr(new_class._meta, 'app_label', None) is None:
+ # Figure out the app_label by looking one level up.
+ # For 'django.contrib.sites.models', this would be 'sites'.
+ new_class._meta.app_label = model_module.__name__.split('.')[-2]
+
+ # Bail out early if we have already created this class.
+ m = get_model(new_class._meta.app_label, name, False)
+ if m is not None:
+ return m
+
+ # Add all attributes to the class.
+ for obj_name, obj in attrs.items():
+ new_class.add_to_class(obj_name, obj)
+
+ # Add Fields inherited from parents
+ for parent in new_class._meta.parents:
+ for field in parent._meta.fields:
+ # Only add parent fields if they aren't defined for this class.
+ try:
+ new_class._meta.get_field(field.name)
+ except FieldDoesNotExist:
+ field.contribute_to_class(new_class, field.name)
+
+ new_class._prepare()
+
+ register_models(new_class._meta.app_label, new_class)
+ # Because of the way imports happen (recursively), we may or may not be
+ # the first class for this model to register with the framework. There
+ # should only be one class for each model, so we must always return the
+ # registered version.
+ return get_model(new_class._meta.app_label, name, False)
+
+class Model(object):
+ __metaclass__ = ModelBase
+
+ def _get_pk_val(self):
+ return getattr(self, self._meta.pk.attname)
+
+ def __repr__(self):
+ return '<%s: %s>' % (self.__class__.__name__, self)
+
+ def __str__(self):
+ return '%s object' % self.__class__.__name__
+
+ def __eq__(self, other):
+ return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __init__(self, *args, **kwargs):
+ dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
+
+ # There is a rather weird disparity here; if kwargs, it's set, then args
+ # overrides it. It should be one or the other; don't duplicate the work
+ # The reason for the kwargs check is that standard iterator passes in by
+ # args, and nstantiation for iteration is 33% faster.
+ args_len = len(args)
+ if args_len > len(self._meta.fields):
+ # Daft, but matches old exception sans the err msg.
+ raise IndexError("Number of args exceeds number of fields")
+
+ fields_iter = iter(self._meta.fields)
+ if not kwargs:
+ # The ordering of the izip calls matter - izip throws StopIteration
+ # when an iter throws it. So if the first iter throws it, the second
+ # is *not* consumed. We rely on this, so don't change the order
+ # without changing the logic.
+ for val, field in izip(args, fields_iter):
+ setattr(self, field.attname, val)
+ else:
+ # Slower, kwargs-ready version.
+ for val, field in izip(args, fields_iter):
+ setattr(self, field.attname, val)
+ kwargs.pop(field.name, None)
+ # Maintain compatibility with existing calls.
+ if isinstance(field.rel, ManyToOneRel):
+ kwargs.pop(field.attname, None)
+
+ # Now we're left with the unprocessed fields that *must* come from
+ # keywords, or default.
+
+ for field in fields_iter:
+ if kwargs:
+ if isinstance(field.rel, ManyToOneRel):
+ try:
+ # Assume object instance was passed in.
+ rel_obj = kwargs.pop(field.name)
+ except KeyError:
+ try:
+ # Object instance wasn't passed in -- must be an ID.
+ val = kwargs.pop(field.attname)
+ except KeyError:
+ val = field.get_default()
+ else:
+ # Object instance was passed in. Special case: You can
+ # pass in "None" for related objects if it's allowed.
+ if rel_obj is None and field.null:
+ val = None
+ else:
+ try:
+ val = getattr(rel_obj, field.rel.get_related_field().attname)
+ except AttributeError:
+ raise TypeError("Invalid value: %r should be a %s instance, not a %s" %
+ (field.name, field.rel.to, type(rel_obj)))
+ else:
+ val = kwargs.pop(field.attname, field.get_default())
+ else:
+ val = field.get_default()
+ setattr(self, field.attname, val)
+
+ if kwargs:
+ for prop in kwargs.keys():
+ try:
+ if isinstance(getattr(self.__class__, prop), property):
+ setattr(self, prop, kwargs.pop(prop))
+ except AttributeError:
+ pass
+ if kwargs:
+ raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
+ dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self)
+
+ def add_to_class(cls, name, value):
+ if name == 'Admin':
+ assert type(value) == types.ClassType, "%r attribute of %s model must be a class, not a %s object" % (name, cls.__name__, type(value))
+ value = AdminOptions(**dict([(k, v) for k, v in value.__dict__.items() if not k.startswith('_')]))
+ if hasattr(value, 'contribute_to_class'):
+ value.contribute_to_class(cls, name)
+ else:
+ setattr(cls, name, value)
+ add_to_class = classmethod(add_to_class)
+
+ def _prepare(cls):
+ # Creates some methods once self._meta has been populated.
+ opts = cls._meta
+ opts._prepare(cls)
+
+ if opts.order_with_respect_to:
+ cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True)
+ cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False)
+ setattr(opts.order_with_respect_to.rel.to, 'get_%s_order' % cls.__name__.lower(), curry(method_get_order, cls))
+ setattr(opts.order_with_respect_to.rel.to, 'set_%s_order' % cls.__name__.lower(), curry(method_set_order, cls))
+
+ # Give the class a docstring -- its definition.
+ if cls.__doc__ is None:
+ cls.__doc__ = "%s(%s)" % (cls.__name__, ", ".join([f.attname for f in opts.fields]))
+
+ if hasattr(cls, 'get_absolute_url'):
+ cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url)
+
+ dispatcher.send(signal=signals.class_prepared, sender=cls)
+
+ _prepare = classmethod(_prepare)
+
+ def save(self):
+ dispatcher.send(signal=signals.pre_save, sender=self.__class__, instance=self)
+
+ non_pks = [f for f in self._meta.fields if not f.primary_key]
+ cursor = connection.cursor()
+
+ # First, try an UPDATE. If that doesn't update anything, do an INSERT.
+ pk_val = self._get_pk_val()
+ pk_set = bool(pk_val)
+ record_exists = True
+ if pk_set:
+ # Determine whether a record with the primary key already exists.
+ cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
+ (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
+ # If it does already exist, do an UPDATE.
+ if cursor.fetchone():
+ db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks]
+ if db_values:
+ cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
+ (backend.quote_name(self._meta.db_table),
+ ','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]),
+ backend.quote_name(self._meta.pk.column)),
+ db_values + [pk_val])
+ else:
+ record_exists = False
+ if not pk_set or not record_exists:
+ field_names = [backend.quote_name(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
+ db_values = [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
+ # If the PK has been manually set, respect that.
+ if pk_set:
+ field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
+ db_values += [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
+ placeholders = ['%s'] * len(field_names)
+ if self._meta.order_with_respect_to:
+ field_names.append(backend.quote_name('_order'))
+ # TODO: This assumes the database supports subqueries.
+ placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
+ (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column)))
+ db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
+ if db_values:
+ cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
+ (backend.quote_name(self._meta.db_table), ','.join(field_names),
+ ','.join(placeholders)), db_values)
+ else:
+ # Create a new record with defaults for everything.
+ cursor.execute("INSERT INTO %s (%s) VALUES (%s)" %
+ (backend.quote_name(self._meta.db_table),
+ backend.quote_name(self._meta.pk.column),
+ backend.get_pk_default_value()))
+ if self._meta.has_auto_field and not pk_set:
+ setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
+ transaction.commit_unless_managed()
+
+ # Run any post-save hooks.
+ dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
+
+ save.alters_data = True
+
+ def validate(self):
+ """
+ First coerces all fields on this instance to their proper Python types.
+ Then runs validation on every field. Returns a dictionary of
+ field_name -> error_list.
+ """
+ error_dict = {}
+ invalid_python = {}
+ for f in self._meta.fields:
+ try:
+ setattr(self, f.attname, f.to_python(getattr(self, f.attname, f.get_default())))
+ except validators.ValidationError, e:
+ error_dict[f.name] = e.messages
+ invalid_python[f.name] = 1
+ for f in self._meta.fields:
+ if f.name in invalid_python:
+ continue
+ errors = f.validate_full(getattr(self, f.attname, f.get_default()), self.__dict__)
+ if errors:
+ error_dict[f.name] = errors
+ return error_dict
+
+ def _collect_sub_objects(self, seen_objs):
+ """
+ Recursively populates seen_objs with all objects related to this object.
+ When done, seen_objs will be in the format:
+ {model_class: {pk_val: obj, pk_val: obj, ...},
+ model_class: {pk_val: obj, pk_val: obj, ...}, ...}
+ """
+ pk_val = self._get_pk_val()
+ if pk_val in seen_objs.setdefault(self.__class__, {}):
+ return
+ seen_objs.setdefault(self.__class__, {})[pk_val] = self
+
+ for related in self._meta.get_all_related_objects():
+ rel_opts_name = related.get_accessor_name()
+ if isinstance(related.field.rel, OneToOneRel):
+ try:
+ sub_obj = getattr(self, rel_opts_name)
+ except ObjectDoesNotExist:
+ pass
+ else:
+ sub_obj._collect_sub_objects(seen_objs)
+ else:
+ for sub_obj in getattr(self, rel_opts_name).all():
+ sub_obj._collect_sub_objects(seen_objs)
+
+ def delete(self):
+ assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
+
+ # Find all the objects than need to be deleted
+ seen_objs = SortedDict()
+ self._collect_sub_objects(seen_objs)
+
+ # Actually delete the objects
+ delete_objects(seen_objs)
+
+ delete.alters_data = True
+
+ def _get_FIELD_display(self, field):
+ value = getattr(self, field.attname)
+ return dict(field.choices).get(value, value)
+
+ def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
+ op = is_next and '>' or '<'
+ where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
+ (backend.quote_name(field.column), op, backend.quote_name(field.column),
+ backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column), op)
+ param = str(getattr(self, field.attname))
+ q = self.__class__._default_manager.filter(**kwargs).order_by((not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name)
+ q._where.append(where)
+ q._params.extend([param, param, getattr(self, self._meta.pk.attname)])
+ try:
+ return q[0]
+ except IndexError:
+ raise self.DoesNotExist, "%s matching query does not exist." % self.__class__._meta.object_name
+
+ def _get_next_or_previous_in_order(self, is_next):
+ cachename = "__%s_order_cache" % is_next
+ if not hasattr(self, cachename):
+ op = is_next and '>' or '<'
+ order_field = self._meta.order_with_respect_to
+ where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
+ (backend.quote_name('_order'), op, backend.quote_name('_order'),
+ backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
+ '%s=%%s' % backend.quote_name(order_field.column)]
+ params = [self._get_pk_val(), getattr(self, order_field.attname)]
+ obj = self._default_manager.order_by('_order').extra(where=where, params=params)[:1].get()
+ setattr(self, cachename, obj)
+ return getattr(self, cachename)
+
+ def _get_FIELD_filename(self, field):
+ if getattr(self, field.attname): # value is not blank
+ return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
+ return ''
+
+ def _get_FIELD_url(self, field):
+ if getattr(self, field.attname): # value is not blank
+ import urlparse
+ return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
+ return ''
+
+ def _get_FIELD_size(self, field):
+ return os.path.getsize(self._get_FIELD_filename(field))
+
+ def _save_FIELD_file(self, field, filename, raw_contents, save=True):
+ directory = field.get_directory_name()
+ try: # Create the date-based directory if it doesn't exist.
+ os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
+ except OSError: # Directory probably already exists.
+ pass
+ filename = field.get_filename(filename)
+
+ # If the filename already exists, keep adding an underscore to the name of
+ # the file until the filename doesn't exist.
+ while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
+ try:
+ dot_index = filename.rindex('.')
+ except ValueError: # filename has no dot
+ filename += '_'
+ else:
+ filename = filename[:dot_index] + '_' + filename[dot_index:]
+
+ # Write the file to disk.
+ setattr(self, field.attname, filename)
+
+ full_filename = self._get_FIELD_filename(field)
+ fp = open(full_filename, 'wb')
+ fp.write(raw_contents)
+ fp.close()
+
+ # Save the width and/or height, if applicable.
+ if isinstance(field, ImageField) and (field.width_field or field.height_field):
+ from django.utils.images import get_image_dimensions
+ width, height = get_image_dimensions(full_filename)
+ if field.width_field:
+ setattr(self, field.width_field, width)
+ if field.height_field:
+ setattr(self, field.height_field, height)
+
+ # Save the object because it has changed unless save is False
+ if save:
+ self.save()
+
+ _save_FIELD_file.alters_data = True
+
+ def _get_FIELD_width(self, field):
+ return self._get_image_dimensions(field)[0]
+
+ def _get_FIELD_height(self, field):
+ return self._get_image_dimensions(field)[1]
+
+ def _get_image_dimensions(self, field):
+ cachename = "__%s_dimensions_cache" % field.name
+ if not hasattr(self, cachename):
+ from django.utils.images import get_image_dimensions
+ filename = self._get_FIELD_filename(field)
+ setattr(self, cachename, get_image_dimensions(filename))
+ return getattr(self, cachename)
+
+############################################
+# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
+############################################
+
+# ORDERING METHODS #########################
+
+def method_set_order(ordered_obj, self, id_list):
+ cursor = connection.cursor()
+ # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
+ sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \
+ (backend.quote_name(ordered_obj._meta.db_table), backend.quote_name('_order'),
+ backend.quote_name(ordered_obj._meta.order_with_respect_to.column),
+ backend.quote_name(ordered_obj._meta.pk.column))
+ rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
+ cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
+ transaction.commit_unless_managed()
+
+def method_get_order(ordered_obj, self):
+ cursor = connection.cursor()
+ # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
+ sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \
+ (backend.quote_name(ordered_obj._meta.pk.column),
+ backend.quote_name(ordered_obj._meta.db_table),
+ backend.quote_name(ordered_obj._meta.order_with_respect_to.column),
+ backend.quote_name('_order'))
+ rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
+ cursor.execute(sql, [rel_val])
+ return [r[0] for r in cursor.fetchall()]
+
+##############################################
+# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
+##############################################
+
+def get_absolute_url(opts, func, self):
+ return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self)
diff --git a/google_appengine/lib/django/django/db/models/fields/__init__.py b/google_appengine/lib/django/django/db/models/fields/__init__.py
new file mode 100755
index 0000000..3972de7
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/fields/__init__.py
@@ -0,0 +1,892 @@
+from django.db.models import signals
+from django.dispatch import dispatcher
+from django.conf import settings
+from django.core import validators
+from django import oldforms
+from django import newforms as forms
+from django.core.exceptions import ObjectDoesNotExist
+from django.utils.functional import curry
+from django.utils.itercompat import tee
+from django.utils.text import capfirst
+from django.utils.translation import gettext, gettext_lazy
+import datetime, os, time
+
+class NOT_PROVIDED:
+ pass
+
+# Values for filter_interface.
+HORIZONTAL, VERTICAL = 1, 2
+
+# The values to use for "blank" in SelectFields. Will be appended to the start of most "choices" lists.
+BLANK_CHOICE_DASH = [("", "---------")]
+BLANK_CHOICE_NONE = [("", "None")]
+
+# prepares a value for use in a LIKE query
+prep_for_like_query = lambda x: str(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
+
+# returns the <ul> class for a given radio_admin value
+get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
+
+class FieldDoesNotExist(Exception):
+ pass
+
+def manipulator_validator_unique(f, opts, self, field_data, all_data):
+ "Validates that the value is unique for this field."
+ lookup_type = f.get_validator_unique_lookup_type()
+ try:
+ old_obj = self.manager.get(**{lookup_type: field_data})
+ except ObjectDoesNotExist:
+ return
+ if getattr(self, 'original_object', None) and self.original_object._get_pk_val() == old_obj._get_pk_val():
+ return
+ raise validators.ValidationError, gettext("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
+
+# A guide to Field parameters:
+#
+# * name: The name of the field specifed in the model.
+# * attname: The attribute to use on the model object. This is the same as
+# "name", except in the case of ForeignKeys, where "_id" is
+# appended.
+# * db_column: The db_column specified in the model (or None).
+# * column: The database column for this field. This is the same as
+# "attname", except if db_column is specified.
+#
+# Code that introspects values, or does other dynamic things, should use
+# attname. For example, this gets the primary key value of object "obj":
+#
+# getattr(obj, opts.pk.attname)
+
+class Field(object):
+
+ # Designates whether empty strings fundamentally are allowed at the
+ # database level.
+ empty_strings_allowed = True
+
+ # Tracks each time a Field instance is created. Used to retain order.
+ creation_counter = 0
+
+ def __init__(self, verbose_name=None, name=None, primary_key=False,
+ maxlength=None, unique=False, blank=False, null=False, db_index=False,
+ core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
+ prepopulate_from=None, unique_for_date=None, unique_for_month=None,
+ unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
+ help_text='', db_column=None):
+ self.name = name
+ self.verbose_name = verbose_name
+ self.primary_key = primary_key
+ self.maxlength, self.unique = maxlength, unique
+ self.blank, self.null = blank, null
+ self.core, self.rel, self.default = core, rel, default
+ self.editable = editable
+ self.serialize = serialize
+ self.validator_list = validator_list or []
+ self.prepopulate_from = prepopulate_from
+ self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
+ self.unique_for_year = unique_for_year
+ self._choices = choices or []
+ self.radio_admin = radio_admin
+ self.help_text = help_text
+ self.db_column = db_column
+
+ # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
+ self.db_index = db_index
+
+ # Increase the creation counter, and save our local copy.
+ self.creation_counter = Field.creation_counter
+ Field.creation_counter += 1
+
+ def __cmp__(self, other):
+ # This is needed because bisect does not take a comparison function.
+ return cmp(self.creation_counter, other.creation_counter)
+
+ def to_python(self, value):
+ """
+ Converts the input value into the expected Python data type, raising
+ validators.ValidationError if the data can't be converted. Returns the
+ converted value. Subclasses should override this.
+ """
+ return value
+
+ def validate_full(self, field_data, all_data):
+ """
+ Returns a list of errors for this field. This is the main interface,
+ as it encapsulates some basic validation logic used by all fields.
+ Subclasses should implement validate(), not validate_full().
+ """
+ if not self.blank and not field_data:
+ return [gettext_lazy('This field is required.')]
+ try:
+ self.validate(field_data, all_data)
+ except validators.ValidationError, e:
+ return e.messages
+ return []
+
+ def validate(self, field_data, all_data):
+ """
+ Raises validators.ValidationError if field_data has any errors.
+ Subclasses should override this to specify field-specific validation
+ logic. This method should assume field_data has already been converted
+ into the appropriate data type by Field.to_python().
+ """
+ pass
+
+ def set_attributes_from_name(self, name):
+ self.name = name
+ self.attname, self.column = self.get_attname_column()
+ self.verbose_name = self.verbose_name or (name and name.replace('_', ' '))
+
+ def contribute_to_class(self, cls, name):
+ self.set_attributes_from_name(name)
+ cls._meta.add_field(self)
+ if self.choices:
+ setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self))
+
+ def get_attname(self):
+ return self.name
+
+ def get_attname_column(self):
+ attname = self.get_attname()
+ column = self.db_column or attname
+ return attname, column
+
+ def get_cache_name(self):
+ return '_%s_cache' % self.name
+
+ def get_internal_type(self):
+ return self.__class__.__name__
+
+ def pre_save(self, model_instance, add):
+ "Returns field's value just before saving."
+ return getattr(model_instance, self.attname)
+
+ def get_db_prep_save(self, value):
+ "Returns field's value prepared for saving into a database."
+ return value
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ "Returns field's value prepared for database lookup."
+ if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'):
+ return [value]
+ elif lookup_type in ('range', 'in'):
+ return value
+ elif lookup_type in ('contains', 'icontains'):
+ return ["%%%s%%" % prep_for_like_query(value)]
+ elif lookup_type == 'iexact':
+ return [prep_for_like_query(value)]
+ elif lookup_type in ('startswith', 'istartswith'):
+ return ["%s%%" % prep_for_like_query(value)]
+ elif lookup_type in ('endswith', 'iendswith'):
+ return ["%%%s" % prep_for_like_query(value)]
+ elif lookup_type == 'isnull':
+ return []
+ elif lookup_type == 'year':
+ try:
+ value = int(value)
+ except ValueError:
+ raise ValueError("The __year lookup type requires an integer argument")
+ return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value]
+ raise TypeError("Field has invalid lookup: %s" % lookup_type)
+
+ def has_default(self):
+ "Returns a boolean of whether this field has a default value."
+ return self.default is not NOT_PROVIDED
+
+ def get_default(self):
+ "Returns the default value for this field."
+ if self.default is not NOT_PROVIDED:
+ if callable(self.default):
+ return self.default()
+ return self.default
+ if not self.empty_strings_allowed or self.null:
+ return None
+ return ""
+
+ def get_manipulator_field_names(self, name_prefix):
+ """
+ Returns a list of field names that this object adds to the manipulator.
+ """
+ return [name_prefix + self.name]
+
+ def prepare_field_objs_and_params(self, manipulator, name_prefix):
+ params = {'validator_list': self.validator_list[:]}
+ if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
+ params['maxlength'] = self.maxlength
+
+ if self.choices:
+ if self.radio_admin:
+ field_objs = [oldforms.RadioSelectField]
+ params['ul_class'] = get_ul_class(self.radio_admin)
+ else:
+ field_objs = [oldforms.SelectField]
+
+ params['choices'] = self.get_choices_default()
+ else:
+ field_objs = self.get_manipulator_field_objs()
+ return (field_objs, params)
+
+ def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
+ """
+ Returns a list of oldforms.FormField instances for this field. It
+ calculates the choices at runtime, not at compile time.
+
+ name_prefix is a prefix to prepend to the "field_name" argument.
+ rel is a boolean specifying whether this field is in a related context.
+ """
+ field_objs, params = self.prepare_field_objs_and_params(manipulator, name_prefix)
+
+ # Add the "unique" validator(s).
+ for field_name_list in opts.unique_together:
+ if field_name_list[0] == self.name:
+ params['validator_list'].append(getattr(manipulator, 'isUnique%s' % '_'.join(field_name_list)))
+
+ # Add the "unique for..." validator(s).
+ if self.unique_for_date:
+ params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_date)))
+ if self.unique_for_month:
+ params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_month)))
+ if self.unique_for_year:
+ params['validator_list'].append(getattr(manipulator, 'isUnique%sFor%s' % (self.name, self.unique_for_year)))
+ if self.unique or (self.primary_key and not rel):
+ params['validator_list'].append(curry(manipulator_validator_unique, self, opts, manipulator))
+
+ # Only add is_required=True if the field cannot be blank. Primary keys
+ # are a special case, and fields in a related context should set this
+ # as False, because they'll be caught by a separate validator --
+ # RequiredIfOtherFieldGiven.
+ params['is_required'] = not self.blank and not self.primary_key and not rel
+
+ # BooleanFields (CheckboxFields) are a special case. They don't take
+ # is_required.
+ if isinstance(self, BooleanField):
+ del params['is_required']
+
+ # If this field is in a related context, check whether any other fields
+ # in the related object have core=True. If so, add a validator --
+ # RequiredIfOtherFieldsGiven -- to this FormField.
+ if rel and not self.blank and not isinstance(self, AutoField) and not isinstance(self, FileField):
+ # First, get the core fields, if any.
+ core_field_names = []
+ for f in opts.fields:
+ if f.core and f != self:
+ core_field_names.extend(f.get_manipulator_field_names(name_prefix))
+ # Now, if there are any, add the validator to this FormField.
+ if core_field_names:
+ params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required.")))
+
+ # Finally, add the field_names.
+ field_names = self.get_manipulator_field_names(name_prefix)
+ return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)]
+
+ def get_validator_unique_lookup_type(self):
+ return '%s__exact' % self.name
+
+ def get_manipulator_new_data(self, new_data, rel=False):
+ """
+ Given the full new_data dictionary (from the manipulator), returns this
+ field's data.
+ """
+ if rel:
+ return new_data.get(self.name, [self.get_default()])[0]
+ val = new_data.get(self.name, self.get_default())
+ if not self.empty_strings_allowed and val == '' and self.null:
+ val = None
+ return val
+
+ def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
+ "Returns a list of tuples used as SelectField choices for this field."
+ first_choice = include_blank and blank_choice or []
+ if self.choices:
+ return first_choice + list(self.choices)
+ rel_model = self.rel.to
+ if hasattr(self.rel, 'get_related_field'):
+ lst = [(getattr(x, self.rel.get_related_field().attname), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
+ else:
+ lst = [(x._get_pk_val(), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
+ return first_choice + lst
+
+ def get_choices_default(self):
+ if self.radio_admin:
+ return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
+ else:
+ return self.get_choices()
+
+ def _get_val_from_obj(self, obj):
+ if obj:
+ return getattr(obj, self.attname)
+ else:
+ return self.get_default()
+
+ def flatten_data(self, follow, obj=None):
+ """
+ Returns a dictionary mapping the field's manipulator field names to its
+ "flattened" string values for the admin view. obj is the instance to
+ extract the values from.
+ """
+ return {self.attname: self._get_val_from_obj(obj)}
+
+ def get_follow(self, override=None):
+ if override != None:
+ return override
+ else:
+ return self.editable
+
+ def bind(self, fieldmapping, original, bound_field_class):
+ return bound_field_class(self, fieldmapping, original)
+
+ def _get_choices(self):
+ if hasattr(self._choices, 'next'):
+ choices, self._choices = tee(self._choices)
+ return choices
+ else:
+ return self._choices
+ choices = property(_get_choices)
+
+ def formfield(self, **kwargs):
+ "Returns a django.newforms.Field instance for this database Field."
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.CharField(**defaults)
+
+ def value_from_object(self, obj):
+ "Returns the value of this field in the given model instance."
+ return getattr(obj, self.attname)
+
+class AutoField(Field):
+ empty_strings_allowed = False
+ def __init__(self, *args, **kwargs):
+ assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
+ kwargs['blank'] = True
+ Field.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value is None:
+ return value
+ try:
+ return int(value)
+ except (TypeError, ValueError):
+ raise validators.ValidationError, gettext("This value must be an integer.")
+
+ def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
+ if not rel:
+ return [] # Don't add a FormField unless it's in a related context.
+ return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.HiddenField]
+
+ def get_manipulator_new_data(self, new_data, rel=False):
+ # Never going to be called
+ # Not in main change pages
+ # ignored in related context
+ if not rel:
+ return None
+ return Field.get_manipulator_new_data(self, new_data, rel)
+
+ def contribute_to_class(self, cls, name):
+ assert not cls._meta.has_auto_field, "A model can't have more than one AutoField."
+ super(AutoField, self).contribute_to_class(cls, name)
+ cls._meta.has_auto_field = True
+
+ def formfield(self, **kwargs):
+ return None
+
+class BooleanField(Field):
+ def __init__(self, *args, **kwargs):
+ kwargs['blank'] = True
+ Field.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value in (True, False): return value
+ if value in ('t', 'True', '1'): return True
+ if value in ('f', 'False', '0'): return False
+ raise validators.ValidationError, gettext("This value must be either True or False.")
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.CheckboxField]
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.BooleanField(**defaults)
+
+class CharField(Field):
+ def get_manipulator_field_objs(self):
+ return [oldforms.TextField]
+
+ def to_python(self, value):
+ if isinstance(value, basestring):
+ return value
+ if value is None:
+ if self.null:
+ return value
+ else:
+ raise validators.ValidationError, gettext_lazy("This field cannot be null.")
+ return str(value)
+
+ def formfield(self, **kwargs):
+ defaults = {'max_length': self.maxlength, 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.CharField(**defaults)
+
+# TODO: Maybe move this into contrib, because it's specialized.
+class CommaSeparatedIntegerField(CharField):
+ def get_manipulator_field_objs(self):
+ return [oldforms.CommaSeparatedIntegerField]
+
+class DateField(Field):
+ empty_strings_allowed = False
+ def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
+ self.auto_now, self.auto_now_add = auto_now, auto_now_add
+ #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
+ if auto_now or auto_now_add:
+ kwargs['editable'] = False
+ kwargs['blank'] = True
+ Field.__init__(self, verbose_name, name, **kwargs)
+
+ def to_python(self, value):
+ if value is None:
+ return value
+ if isinstance(value, datetime.datetime):
+ return value.date()
+ if isinstance(value, datetime.date):
+ return value
+ validators.isValidANSIDate(value, None)
+ try:
+ return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
+ except ValueError:
+ raise validators.ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.')
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ if lookup_type == 'range':
+ value = [str(v) for v in value]
+ elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'):
+ value = value.strftime('%Y-%m-%d')
+ else:
+ value = str(value)
+ return Field.get_db_prep_lookup(self, lookup_type, value)
+
+ def pre_save(self, model_instance, add):
+ if self.auto_now or (self.auto_now_add and add):
+ value = datetime.datetime.now()
+ setattr(model_instance, self.attname, value)
+ return value
+ else:
+ return super(DateField, self).pre_save(model_instance, add)
+
+ def contribute_to_class(self, cls, name):
+ super(DateField,self).contribute_to_class(cls, name)
+ if not self.null:
+ setattr(cls, 'get_next_by_%s' % self.name,
+ curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=True))
+ setattr(cls, 'get_previous_by_%s' % self.name,
+ curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False))
+
+ # Needed because of horrible auto_now[_add] behaviour wrt. editable
+ def get_follow(self, override=None):
+ if override != None:
+ return override
+ else:
+ return self.editable or self.auto_now or self.auto_now_add
+
+ def get_db_prep_save(self, value):
+ # Casts dates into string format for entry into database.
+ if value is not None:
+ value = value.strftime('%Y-%m-%d')
+ return Field.get_db_prep_save(self, value)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.DateField]
+
+ def flatten_data(self, follow, obj=None):
+ val = self._get_val_from_obj(obj)
+ return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.DateField(**defaults)
+
+class DateTimeField(DateField):
+ def to_python(self, value):
+ if value is None:
+ return value
+ if isinstance(value, datetime.datetime):
+ return value
+ if isinstance(value, datetime.date):
+ return datetime.datetime(value.year, value.month, value.day)
+ try: # Seconds are optional, so try converting seconds first.
+ return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6])
+ except ValueError:
+ try: # Try without seconds.
+ return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5])
+ except ValueError: # Try without hour/minutes/seconds.
+ try:
+ return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
+ except ValueError:
+ raise validators.ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+
+ def get_db_prep_save(self, value):
+ # Casts dates into string format for entry into database.
+ if value is not None:
+ # MySQL will throw a warning if microseconds are given, because it
+ # doesn't support microseconds.
+ if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
+ value = value.replace(microsecond=0)
+ value = str(value)
+ return Field.get_db_prep_save(self, value)
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ if lookup_type == 'range':
+ value = [str(v) for v in value]
+ else:
+ value = str(value)
+ return Field.get_db_prep_lookup(self, lookup_type, value)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.DateField, oldforms.TimeField]
+
+ def get_manipulator_field_names(self, name_prefix):
+ return [name_prefix + self.name + '_date', name_prefix + self.name + '_time']
+
+ def get_manipulator_new_data(self, new_data, rel=False):
+ date_field, time_field = self.get_manipulator_field_names('')
+ if rel:
+ d = new_data.get(date_field, [None])[0]
+ t = new_data.get(time_field, [None])[0]
+ else:
+ d = new_data.get(date_field, None)
+ t = new_data.get(time_field, None)
+ if d is not None and t is not None:
+ return datetime.datetime.combine(d, t)
+ return self.get_default()
+
+ def flatten_data(self,follow, obj = None):
+ val = self._get_val_from_obj(obj)
+ date_field, time_field = self.get_manipulator_field_names('')
+ return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
+ time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.DateTimeField(**defaults)
+
+class EmailField(CharField):
+ def __init__(self, *args, **kwargs):
+ kwargs['maxlength'] = 75
+ CharField.__init__(self, *args, **kwargs)
+
+ def get_internal_type(self):
+ return "CharField"
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.EmailField]
+
+ def validate(self, field_data, all_data):
+ validators.isValidEmail(field_data, all_data)
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.EmailField(**defaults)
+
+class FileField(Field):
+ def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
+ self.upload_to = upload_to
+ Field.__init__(self, verbose_name, name, **kwargs)
+
+ def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
+ field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
+ if not self.blank:
+ if rel:
+ # This validator makes sure FileFields work in a related context.
+ class RequiredFileField(object):
+ def __init__(self, other_field_names, other_file_field_name):
+ self.other_field_names = other_field_names
+ self.other_file_field_name = other_file_field_name
+ self.always_test = True
+ def __call__(self, field_data, all_data):
+ if not all_data.get(self.other_file_field_name, False):
+ c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, gettext_lazy("This field is required."))
+ c(field_data, all_data)
+ # First, get the core fields, if any.
+ core_field_names = []
+ for f in opts.fields:
+ if f.core and f != self:
+ core_field_names.extend(f.get_manipulator_field_names(name_prefix))
+ # Now, if there are any, add the validator to this FormField.
+ if core_field_names:
+ field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
+ else:
+ v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, gettext_lazy("This field is required."))
+ v.always_test = True
+ field_list[0].validator_list.append(v)
+ field_list[0].is_required = field_list[1].is_required = False
+
+ # If the raw path is passed in, validate it's under the MEDIA_ROOT.
+ def isWithinMediaRoot(field_data, all_data):
+ f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data))
+ if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))):
+ raise validators.ValidationError, _("Enter a valid filename.")
+ field_list[1].validator_list.append(isWithinMediaRoot)
+ return field_list
+
+ def contribute_to_class(self, cls, name):
+ super(FileField, self).contribute_to_class(cls, name)
+ setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
+ setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
+ setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
+ setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
+ dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
+
+ def delete_file(self, instance):
+ if getattr(instance, self.attname):
+ file_name = getattr(instance, 'get_%s_filename' % self.name)()
+ # If the file exists and no other object of this type references it,
+ # delete it from the filesystem.
+ if os.path.exists(file_name) and \
+ not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}):
+ os.remove(file_name)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.FileUploadField, oldforms.HiddenField]
+
+ def get_manipulator_field_names(self, name_prefix):
+ return [name_prefix + self.name + '_file', name_prefix + self.name]
+
+ def save_file(self, new_data, new_object, original_object, change, rel, save=True):
+ upload_field_name = self.get_manipulator_field_names('')[0]
+ if new_data.get(upload_field_name, False):
+ func = getattr(new_object, 'save_%s_file' % self.name)
+ if rel:
+ func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
+ else:
+ func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
+
+ def get_directory_name(self):
+ return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
+
+ def get_filename(self, filename):
+ from django.utils.text import get_valid_filename
+ f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
+ return os.path.normpath(f)
+
+class FilePathField(Field):
+ def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
+ self.path, self.match, self.recursive = path, match, recursive
+ Field.__init__(self, verbose_name, name, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
+
+class FloatField(Field):
+ empty_strings_allowed = False
+ def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
+ self.max_digits, self.decimal_places = max_digits, decimal_places
+ Field.__init__(self, verbose_name, name, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ return [curry(oldforms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
+
+class ImageField(FileField):
+ def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
+ self.width_field, self.height_field = width_field, height_field
+ FileField.__init__(self, verbose_name, name, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.ImageUploadField, oldforms.HiddenField]
+
+ def contribute_to_class(self, cls, name):
+ super(ImageField, self).contribute_to_class(cls, name)
+ # Add get_BLAH_width and get_BLAH_height methods, but only if the
+ # image field doesn't have width and height cache fields.
+ if not self.width_field:
+ setattr(cls, 'get_%s_width' % self.name, curry(cls._get_FIELD_width, field=self))
+ if not self.height_field:
+ setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self))
+
+ def save_file(self, new_data, new_object, original_object, change, rel, save=True):
+ FileField.save_file(self, new_data, new_object, original_object, change, rel, save)
+ # If the image has height and/or width field(s) and they haven't
+ # changed, set the width and/or height field(s) back to their original
+ # values.
+ if change and (self.width_field or self.height_field) and save:
+ if self.width_field:
+ setattr(new_object, self.width_field, getattr(original_object, self.width_field))
+ if self.height_field:
+ setattr(new_object, self.height_field, getattr(original_object, self.height_field))
+ new_object.save()
+
+class IntegerField(Field):
+ empty_strings_allowed = False
+ def get_manipulator_field_objs(self):
+ return [oldforms.IntegerField]
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.IntegerField(**defaults)
+
+class IPAddressField(Field):
+ def __init__(self, *args, **kwargs):
+ kwargs['maxlength'] = 15
+ Field.__init__(self, *args, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.IPAddressField]
+
+ def validate(self, field_data, all_data):
+ validators.isValidIPAddress4(field_data, None)
+
+class NullBooleanField(Field):
+ def __init__(self, *args, **kwargs):
+ kwargs['null'] = True
+ Field.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value in (None, True, False): return value
+ if value in ('None'): return None
+ if value in ('t', 'True', '1'): return True
+ if value in ('f', 'False', '0'): return False
+ raise validators.ValidationError, gettext("This value must be either None, True or False.")
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.NullBooleanField]
+
+class PhoneNumberField(IntegerField):
+ def get_manipulator_field_objs(self):
+ return [oldforms.PhoneNumberField]
+
+ def validate(self, field_data, all_data):
+ validators.isValidPhone(field_data, all_data)
+
+ def formfield(self, **kwargs):
+ from django.contrib.localflavor.usa.forms import USPhoneNumberField
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return USPhoneNumberField(**defaults)
+
+class PositiveIntegerField(IntegerField):
+ def get_manipulator_field_objs(self):
+ return [oldforms.PositiveIntegerField]
+
+class PositiveSmallIntegerField(IntegerField):
+ def get_manipulator_field_objs(self):
+ return [oldforms.PositiveSmallIntegerField]
+
+class SlugField(Field):
+ def __init__(self, *args, **kwargs):
+ kwargs['maxlength'] = kwargs.get('maxlength', 50)
+ kwargs.setdefault('validator_list', []).append(validators.isSlug)
+ # Set db_index=True unless it's been set manually.
+ if not kwargs.has_key('db_index'):
+ kwargs['db_index'] = True
+ Field.__init__(self, *args, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.TextField]
+
+class SmallIntegerField(IntegerField):
+ def get_manipulator_field_objs(self):
+ return [oldforms.SmallIntegerField]
+
+class TextField(Field):
+ def get_manipulator_field_objs(self):
+ return [oldforms.LargeTextField]
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'widget': forms.Textarea, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.CharField(**defaults)
+
+class TimeField(Field):
+ empty_strings_allowed = False
+ def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
+ self.auto_now, self.auto_now_add = auto_now, auto_now_add
+ if auto_now or auto_now_add:
+ kwargs['editable'] = False
+ Field.__init__(self, verbose_name, name, **kwargs)
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ if lookup_type == 'range':
+ value = [str(v) for v in value]
+ else:
+ value = str(value)
+ return Field.get_db_prep_lookup(self, lookup_type, value)
+
+ def pre_save(self, model_instance, add):
+ if self.auto_now or (self.auto_now_add and add):
+ value = datetime.datetime.now().time()
+ setattr(model_instance, self.attname, value)
+ return value
+ else:
+ return super(TimeField, self).pre_save(model_instance, add)
+
+ def get_db_prep_save(self, value):
+ # Casts dates into string format for entry into database.
+ if value is not None:
+ # MySQL will throw a warning if microseconds are given, because it
+ # doesn't support microseconds.
+ if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
+ value = value.replace(microsecond=0)
+ value = str(value)
+ return Field.get_db_prep_save(self, value)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.TimeField]
+
+ def flatten_data(self,follow, obj = None):
+ val = self._get_val_from_obj(obj)
+ return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')}
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.TimeField(**defaults)
+
+class URLField(CharField):
+ def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
+ kwargs['maxlength'] = kwargs.get('maxlength', 200)
+ if verify_exists:
+ kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
+ self.verify_exists = verify_exists
+ CharField.__init__(self, verbose_name, name, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ return [oldforms.URLField]
+
+ def get_internal_type(self):
+ return "CharField"
+
+ def formfield(self, **kwargs):
+ defaults = {'required': not self.blank, 'verify_exists': self.verify_exists, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.URLField(**defaults)
+
+class USStateField(Field):
+ def get_manipulator_field_objs(self):
+ return [oldforms.USStateField]
+
+class XMLField(TextField):
+ def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
+ self.schema_path = schema_path
+ Field.__init__(self, verbose_name, name, **kwargs)
+
+ def get_internal_type(self):
+ return "TextField"
+
+ def get_manipulator_field_objs(self):
+ return [curry(oldforms.XMLLargeTextField, schema_path=self.schema_path)]
+
+class OrderingField(IntegerField):
+ empty_strings_allowed=False
+ def __init__(self, with_respect_to, **kwargs):
+ self.wrt = with_respect_to
+ kwargs['null'] = True
+ IntegerField.__init__(self, **kwargs )
+
+ def get_internal_type(self):
+ return "IntegerField"
+
+ def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
+ return [oldforms.HiddenField(name_prefix + self.name)]
diff --git a/google_appengine/lib/django/django/db/models/fields/generic.py b/google_appengine/lib/django/django/db/models/fields/generic.py
new file mode 100755
index 0000000..480ee68
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/fields/generic.py
@@ -0,0 +1,260 @@
+"""
+Classes allowing "generic" relations through ContentType and object-id fields.
+"""
+
+from django import oldforms
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import backend
+from django.db.models import signals
+from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
+from django.db.models.loading import get_model
+from django.dispatch import dispatcher
+from django.utils.functional import curry
+
+class GenericForeignKey(object):
+ """
+ Provides a generic relation to any object through content-type/object-id
+ fields.
+ """
+
+ def __init__(self, ct_field="content_type", fk_field="object_id"):
+ self.ct_field = ct_field
+ self.fk_field = fk_field
+
+ def contribute_to_class(self, cls, name):
+ # Make sure the fields exist (these raise FieldDoesNotExist,
+ # which is a fine error to raise here)
+ self.name = name
+ self.model = cls
+ self.cache_attr = "_%s_cache" % name
+
+ # For some reason I don't totally understand, using weakrefs here doesn't work.
+ dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
+
+ # Connect myself as the descriptor for this field
+ setattr(cls, name, self)
+
+ def instance_pre_init(self, signal, sender, args, kwargs):
+ # Handle initalizing an object with the generic FK instaed of
+ # content-type/object-id fields.
+ if kwargs.has_key(self.name):
+ value = kwargs.pop(self.name)
+ kwargs[self.ct_field] = self.get_content_type(value)
+ kwargs[self.fk_field] = value._get_pk_val()
+
+ def get_content_type(self, obj):
+ # Convenience function using get_model avoids a circular import when using this model
+ ContentType = get_model("contenttypes", "contenttype")
+ return ContentType.objects.get_for_model(obj)
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self.name
+
+ try:
+ return getattr(instance, self.cache_attr)
+ except AttributeError:
+ rel_obj = None
+ ct = getattr(instance, self.ct_field)
+ if ct:
+ try:
+ rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field))
+ except ObjectDoesNotExist:
+ pass
+ setattr(instance, self.cache_attr, rel_obj)
+ return rel_obj
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
+
+ ct = None
+ fk = None
+ if value is not None:
+ ct = self.get_content_type(value)
+ fk = value._get_pk_val()
+
+ setattr(instance, self.ct_field, ct)
+ setattr(instance, self.fk_field, fk)
+ setattr(instance, self.cache_attr, value)
+
+class GenericRelation(RelatedField, Field):
+ """Provides an accessor to generic related objects (i.e. comments)"""
+
+ def __init__(self, to, **kwargs):
+ kwargs['verbose_name'] = kwargs.get('verbose_name', None)
+ kwargs['rel'] = GenericRel(to,
+ related_name=kwargs.pop('related_name', None),
+ limit_choices_to=kwargs.pop('limit_choices_to', None),
+ symmetrical=kwargs.pop('symmetrical', True))
+
+ # Override content-type/object-id field names on the related class
+ self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
+ self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
+
+ kwargs['blank'] = True
+ kwargs['editable'] = False
+ kwargs['serialize'] = False
+ Field.__init__(self, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ choices = self.get_choices_default()
+ return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
+
+ def get_choices_default(self):
+ return Field.get_choices(self, include_blank=False)
+
+ def flatten_data(self, follow, obj = None):
+ new_data = {}
+ if obj:
+ instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
+ new_data[self.name] = instance_ids
+ return new_data
+
+ def m2m_db_table(self):
+ return self.rel.to._meta.db_table
+
+ def m2m_column_name(self):
+ return self.object_id_field_name
+
+ def m2m_reverse_name(self):
+ return self.object_id_field_name
+
+ def contribute_to_class(self, cls, name):
+ super(GenericRelation, self).contribute_to_class(cls, name)
+
+ # Save a reference to which model this class is on for future use
+ self.model = cls
+
+ # Add the descriptor for the m2m relation
+ setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self))
+
+ def contribute_to_related_class(self, cls, related):
+ pass
+
+ def set_attributes_from_rel(self):
+ pass
+
+ def get_internal_type(self):
+ return "ManyToManyField"
+
+class ReverseGenericRelatedObjectsDescriptor(object):
+ """
+ This class provides the functionality that makes the related-object
+ managers available as attributes on a model class, for fields that have
+ multiple "remote" values and have a GenericRelation defined in their model
+ (rather than having another model pointed *at* them). In the example
+ "article.publications", the publications attribute is a
+ ReverseGenericRelatedObjectsDescriptor instance.
+ """
+ def __init__(self, field):
+ self.field = field
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ # This import is done here to avoid circular import importing this module
+ from django.contrib.contenttypes.models import ContentType
+
+ # Dynamically create a class that subclasses the related model's
+ # default manager.
+ rel_model = self.field.rel.to
+ superclass = rel_model._default_manager.__class__
+ RelatedManager = create_generic_related_manager(superclass)
+
+ manager = RelatedManager(
+ model = rel_model,
+ instance = instance,
+ symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model),
+ join_table = backend.quote_name(self.field.m2m_db_table()),
+ source_col_name = backend.quote_name(self.field.m2m_column_name()),
+ target_col_name = backend.quote_name(self.field.m2m_reverse_name()),
+ content_type = ContentType.objects.get_for_model(self.field.model),
+ content_type_field_name = self.field.content_type_field_name,
+ object_id_field_name = self.field.object_id_field_name
+ )
+
+ return manager
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ manager = self.__get__(instance)
+ manager.clear()
+ for obj in value:
+ manager.add(obj)
+
+def create_generic_related_manager(superclass):
+ """
+ Factory function for a manager that subclasses 'superclass' (which is a
+ Manager) and adds behavior for generic related objects.
+ """
+
+ class GenericRelatedObjectManager(superclass):
+ def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
+ join_table=None, source_col_name=None, target_col_name=None, content_type=None,
+ content_type_field_name=None, object_id_field_name=None):
+
+ super(GenericRelatedObjectManager, self).__init__()
+ self.core_filters = core_filters or {}
+ self.model = model
+ self.content_type = content_type
+ self.symmetrical = symmetrical
+ self.instance = instance
+ self.join_table = join_table
+ self.join_table = model._meta.db_table
+ self.source_col_name = source_col_name
+ self.target_col_name = target_col_name
+ self.content_type_field_name = content_type_field_name
+ self.object_id_field_name = object_id_field_name
+ self.pk_val = self.instance._get_pk_val()
+
+ def get_query_set(self):
+ query = {
+ '%s__pk' % self.content_type_field_name : self.content_type.id,
+ '%s__exact' % self.object_id_field_name : self.pk_val,
+ }
+ return superclass.get_query_set(self).filter(**query)
+
+ def add(self, *objs):
+ for obj in objs:
+ setattr(obj, self.content_type_field_name, self.content_type)
+ setattr(obj, self.object_id_field_name, self.pk_val)
+ obj.save()
+ add.alters_data = True
+
+ def remove(self, *objs):
+ for obj in objs:
+ obj.delete()
+ remove.alters_data = True
+
+ def clear(self):
+ for obj in self.all():
+ obj.delete()
+ clear.alters_data = True
+
+ def create(self, **kwargs):
+ kwargs[self.content_type_field_name] = self.content_type
+ kwargs[self.object_id_field_name] = self.pk_val
+ obj = self.model(**kwargs)
+ obj.save()
+ return obj
+ create.alters_data = True
+
+ return GenericRelatedObjectManager
+
+class GenericRel(ManyToManyRel):
+ def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True):
+ self.to = to
+ self.num_in_admin = 0
+ self.related_name = related_name
+ self.filter_interface = None
+ self.limit_choices_to = limit_choices_to or {}
+ self.edit_inline = False
+ self.raw_id_admin = False
+ self.symmetrical = symmetrical
+ self.multiple = True
+ assert not (self.raw_id_admin and self.filter_interface), \
+ "Generic relations may not use both raw_id_admin and filter_interface"
diff --git a/google_appengine/lib/django/django/db/models/fields/related.py b/google_appengine/lib/django/django/db/models/fields/related.py
new file mode 100755
index 0000000..fad9c16
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/fields/related.py
@@ -0,0 +1,801 @@
+from django.db import backend, transaction
+from django.db.models import signals, get_model
+from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
+from django.db.models.related import RelatedObject
+from django.utils.text import capfirst
+from django.utils.translation import gettext_lazy, string_concat, ngettext
+from django.utils.functional import curry
+from django.core import validators
+from django import oldforms
+from django import newforms as forms
+from django.dispatch import dispatcher
+
+# For Python 2.3
+if not hasattr(__builtins__, 'set'):
+ from sets import Set as set
+
+# Values for Relation.edit_inline.
+TABULAR, STACKED = 1, 2
+
+RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
+
+pending_lookups = {}
+
+def add_lookup(rel_cls, field):
+ name = field.rel.to
+ module = rel_cls.__module__
+ key = (module, name)
+ # Has the model already been loaded?
+ # If so, resolve the string reference right away
+ model = get_model(rel_cls._meta.app_label, field.rel.to, False)
+ if model:
+ field.rel.to = model
+ field.do_related_class(model, rel_cls)
+ else:
+ # Mark the related field for later lookup
+ pending_lookups.setdefault(key, []).append((rel_cls, field))
+
+def do_pending_lookups(sender):
+ other_cls = sender
+ key = (other_cls.__module__, other_cls.__name__)
+ for rel_cls, field in pending_lookups.setdefault(key, []):
+ field.rel.to = other_cls
+ field.do_related_class(other_cls, rel_cls)
+
+dispatcher.connect(do_pending_lookups, signal=signals.class_prepared)
+
+def manipulator_valid_rel_key(f, self, field_data, all_data):
+ "Validates that the value is a valid foreign key"
+ klass = f.rel.to
+ try:
+ klass._default_manager.get(**{f.rel.field_name: field_data})
+ except klass.DoesNotExist:
+ raise validators.ValidationError, _("Please enter a valid %s.") % f.verbose_name
+
+#HACK
+class RelatedField(object):
+ def contribute_to_class(self, cls, name):
+ sup = super(RelatedField, self)
+
+ # Add an accessor to allow easy determination of the related query path for this field
+ self.related_query_name = curry(self._get_related_query_name, cls._meta)
+
+ if hasattr(sup, 'contribute_to_class'):
+ sup.contribute_to_class(cls, name)
+ other = self.rel.to
+ if isinstance(other, basestring):
+ if other == RECURSIVE_RELATIONSHIP_CONSTANT:
+ self.rel.to = cls.__name__
+ add_lookup(cls, self)
+ else:
+ self.do_related_class(other, cls)
+
+ def set_attributes_from_rel(self):
+ self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
+ self.verbose_name = self.verbose_name or self.rel.to._meta.verbose_name
+ self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name
+
+ def do_related_class(self, other, cls):
+ self.set_attributes_from_rel()
+ related = RelatedObject(other, cls, self)
+ self.contribute_to_related_class(other, related)
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ # If we are doing a lookup on a Related Field, we must be
+ # comparing object instances. The value should be the PK of value,
+ # not value itself.
+ def pk_trace(value):
+ # Value may be a primary key, or an object held in a relation.
+ # If it is an object, then we need to get the primary key value for
+ # that object. In certain conditions (especially one-to-one relations),
+ # the primary key may itself be an object - so we need to keep drilling
+ # down until we hit a value that can be used for a comparison.
+ v = value
+ try:
+ while True:
+ v = getattr(v, v._meta.pk.name)
+ except AttributeError:
+ pass
+ return v
+
+ if lookup_type == 'exact':
+ return [pk_trace(value)]
+ if lookup_type == 'in':
+ return [pk_trace(v) for v in value]
+ elif lookup_type == 'isnull':
+ return []
+ raise TypeError, "Related Field has invalid lookup: %s" % lookup_type
+
+ def _get_related_query_name(self, opts):
+ # This method defines the name that can be used to identify this related object
+ # in a table-spanning query. It uses the lower-cased object_name by default,
+ # but this can be overridden with the "related_name" option.
+ return self.rel.related_name or opts.object_name.lower()
+
+class SingleRelatedObjectDescriptor(object):
+ # This class provides the functionality that makes the related-object
+ # managers available as attributes on a model class, for fields that have
+ # a single "remote" value, on the class pointed to by a related field.
+ # In the example "place.restaurant", the restaurant attribute is a
+ # SingleRelatedObjectDescriptor instance.
+ def __init__(self, related):
+ self.related = related
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
+
+ params = {'%s__pk' % self.related.field.name: instance._get_pk_val()}
+ rel_obj = self.related.model._default_manager.get(**params)
+ return rel_obj
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
+ # Set the value of the related field
+ setattr(value, self.related.field.rel.get_related_field().attname, instance)
+
+ # Clear the cache, if it exists
+ try:
+ delattr(value, self.related.field.get_cache_name())
+ except AttributeError:
+ pass
+
+class ReverseSingleRelatedObjectDescriptor(object):
+ # This class provides the functionality that makes the related-object
+ # managers available as attributes on a model class, for fields that have
+ # a single "remote" value, on the class that defines the related field.
+ # In the example "choice.poll", the poll attribute is a
+ # ReverseSingleRelatedObjectDescriptor instance.
+ def __init__(self, field_with_rel):
+ self.field = field_with_rel
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self.field.name
+ cache_name = self.field.get_cache_name()
+ try:
+ return getattr(instance, cache_name)
+ except AttributeError:
+ val = getattr(instance, self.field.attname)
+ if val is None:
+ # If NULL is an allowed value, return it.
+ if self.field.null:
+ return None
+ raise self.field.rel.to.DoesNotExist
+ other_field = self.field.rel.get_related_field()
+ if other_field.rel:
+ params = {'%s__pk' % self.field.rel.field_name: val}
+ else:
+ params = {'%s__exact' % self.field.rel.field_name: val}
+ rel_obj = self.field.rel.to._default_manager.get(**params)
+ setattr(instance, cache_name, rel_obj)
+ return rel_obj
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self._field.name
+ # Set the value of the related field
+ try:
+ val = getattr(value, self.field.rel.get_related_field().attname)
+ except AttributeError:
+ val = None
+ setattr(instance, self.field.attname, val)
+
+ # Clear the cache, if it exists
+ try:
+ delattr(instance, self.field.get_cache_name())
+ except AttributeError:
+ pass
+
+class ForeignRelatedObjectsDescriptor(object):
+ # This class provides the functionality that makes the related-object
+ # managers available as attributes on a model class, for fields that have
+ # multiple "remote" values and have a ForeignKey pointed at them by
+ # some other model. In the example "poll.choice_set", the choice_set
+ # attribute is a ForeignRelatedObjectsDescriptor instance.
+ def __init__(self, related):
+ self.related = related # RelatedObject instance
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ rel_field = self.related.field
+ rel_model = self.related.model
+
+ # Dynamically create a class that subclasses the related
+ # model's default manager.
+ superclass = self.related.model._default_manager.__class__
+
+ class RelatedManager(superclass):
+ def get_query_set(self):
+ return superclass.get_query_set(self).filter(**(self.core_filters))
+
+ def add(self, *objs):
+ for obj in objs:
+ setattr(obj, rel_field.name, instance)
+ obj.save()
+ add.alters_data = True
+
+ def create(self, **kwargs):
+ new_obj = self.model(**kwargs)
+ self.add(new_obj)
+ return new_obj
+ create.alters_data = True
+
+ # remove() and clear() are only provided if the ForeignKey can have a value of null.
+ if rel_field.null:
+ def remove(self, *objs):
+ val = getattr(instance, rel_field.rel.get_related_field().attname)
+ for obj in objs:
+ # Is obj actually part of this descriptor set?
+ if getattr(obj, rel_field.attname) == val:
+ setattr(obj, rel_field.name, None)
+ obj.save()
+ else:
+ raise rel_field.rel.to.DoesNotExist, "%r is not related to %r." % (obj, instance)
+ remove.alters_data = True
+
+ def clear(self):
+ for obj in self.all():
+ setattr(obj, rel_field.name, None)
+ obj.save()
+ clear.alters_data = True
+
+ manager = RelatedManager()
+ manager.core_filters = {'%s__pk' % rel_field.name: getattr(instance, rel_field.rel.get_related_field().attname)}
+ manager.model = self.related.model
+
+ return manager
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ manager = self.__get__(instance)
+ # If the foreign key can support nulls, then completely clear the related set.
+ # Otherwise, just move the named objects into the set.
+ if self.related.field.null:
+ manager.clear()
+ manager.add(*value)
+
+def create_many_related_manager(superclass):
+ """Creates a manager that subclasses 'superclass' (which is a Manager)
+ and adds behavior for many-to-many related objects."""
+ class ManyRelatedManager(superclass):
+ def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
+ join_table=None, source_col_name=None, target_col_name=None):
+ super(ManyRelatedManager, self).__init__()
+ self.core_filters = core_filters
+ self.model = model
+ self.symmetrical = symmetrical
+ self.instance = instance
+ self.join_table = join_table
+ self.source_col_name = source_col_name
+ self.target_col_name = target_col_name
+ self._pk_val = self.instance._get_pk_val()
+ if self._pk_val is None:
+ raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model)
+
+ def get_query_set(self):
+ return superclass.get_query_set(self).filter(**(self.core_filters))
+
+ def add(self, *objs):
+ self._add_items(self.source_col_name, self.target_col_name, *objs)
+
+ # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
+ if self.symmetrical:
+ self._add_items(self.target_col_name, self.source_col_name, *objs)
+ add.alters_data = True
+
+ def remove(self, *objs):
+ self._remove_items(self.source_col_name, self.target_col_name, *objs)
+
+ # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
+ if self.symmetrical:
+ self._remove_items(self.target_col_name, self.source_col_name, *objs)
+ remove.alters_data = True
+
+ def clear(self):
+ self._clear_items(self.source_col_name)
+
+ # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table
+ if self.symmetrical:
+ self._clear_items(self.target_col_name)
+ clear.alters_data = True
+
+ def create(self, **kwargs):
+ new_obj = self.model(**kwargs)
+ new_obj.save()
+ self.add(new_obj)
+ return new_obj
+ create.alters_data = True
+
+ def _add_items(self, source_col_name, target_col_name, *objs):
+ # join_table: name of the m2m link table
+ # source_col_name: the PK colname in join_table for the source object
+ # target_col_name: the PK colname in join_table for the target object
+ # *objs - objects to add. Either object instances, or primary keys of object instances.
+ from django.db import connection
+
+ # If there aren't any objects, there is nothing to do.
+ if objs:
+ # Check that all the objects are of the right type
+ new_ids = set()
+ for obj in objs:
+ if isinstance(obj, self.model):
+ new_ids.add(obj._get_pk_val())
+ else:
+ new_ids.add(obj)
+ # Add the newly created or already existing objects to the join table.
+ # First find out which items are already added, to avoid adding them twice
+ cursor = connection.cursor()
+ cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
+ (target_col_name, self.join_table, source_col_name,
+ target_col_name, ",".join(['%s'] * len(new_ids))),
+ [self._pk_val] + list(new_ids))
+ if cursor.rowcount is not None and cursor.rowcount != 0:
+ existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)])
+ else:
+ existing_ids = set()
+
+ # Add the ones that aren't there already
+ for obj_id in (new_ids - existing_ids):
+ cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
+ (self.join_table, source_col_name, target_col_name),
+ [self._pk_val, obj_id])
+ transaction.commit_unless_managed()
+
+ def _remove_items(self, source_col_name, target_col_name, *objs):
+ # source_col_name: the PK colname in join_table for the source object
+ # target_col_name: the PK colname in join_table for the target object
+ # *objs - objects to remove
+ from django.db import connection
+
+ # If there aren't any objects, there is nothing to do.
+ if objs:
+ # Check that all the objects are of the right type
+ old_ids = set()
+ for obj in objs:
+ if isinstance(obj, self.model):
+ old_ids.add(obj._get_pk_val())
+ else:
+ old_ids.add(obj)
+ # Remove the specified objects from the join table
+ cursor = connection.cursor()
+ cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
+ (self.join_table, source_col_name,
+ target_col_name, ",".join(['%s'] * len(old_ids))),
+ [self._pk_val] + list(old_ids))
+ transaction.commit_unless_managed()
+
+ def _clear_items(self, source_col_name):
+ # source_col_name: the PK colname in join_table for the source object
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
+ (self.join_table, source_col_name),
+ [self._pk_val])
+ transaction.commit_unless_managed()
+
+ return ManyRelatedManager
+
+class ManyRelatedObjectsDescriptor(object):
+ # This class provides the functionality that makes the related-object
+ # managers available as attributes on a model class, for fields that have
+ # multiple "remote" values and have a ManyToManyField pointed at them by
+ # some other model (rather than having a ManyToManyField themselves).
+ # In the example "publication.article_set", the article_set attribute is a
+ # ManyRelatedObjectsDescriptor instance.
+ def __init__(self, related):
+ self.related = related # RelatedObject instance
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ # Dynamically create a class that subclasses the related
+ # model's default manager.
+ rel_model = self.related.model
+ superclass = rel_model._default_manager.__class__
+ RelatedManager = create_many_related_manager(superclass)
+
+ qn = backend.quote_name
+ manager = RelatedManager(
+ model=rel_model,
+ core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
+ instance=instance,
+ symmetrical=False,
+ join_table=qn(self.related.field.m2m_db_table()),
+ source_col_name=qn(self.related.field.m2m_reverse_name()),
+ target_col_name=qn(self.related.field.m2m_column_name())
+ )
+
+ return manager
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ manager = self.__get__(instance)
+ manager.clear()
+ manager.add(*value)
+
+class ReverseManyRelatedObjectsDescriptor(object):
+ # This class provides the functionality that makes the related-object
+ # managers available as attributes on a model class, for fields that have
+ # multiple "remote" values and have a ManyToManyField defined in their
+ # model (rather than having another model pointed *at* them).
+ # In the example "article.publications", the publications attribute is a
+ # ReverseManyRelatedObjectsDescriptor instance.
+ def __init__(self, m2m_field):
+ self.field = m2m_field
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ # Dynamically create a class that subclasses the related
+ # model's default manager.
+ rel_model=self.field.rel.to
+ superclass = rel_model._default_manager.__class__
+ RelatedManager = create_many_related_manager(superclass)
+
+ qn = backend.quote_name
+ manager = RelatedManager(
+ model=rel_model,
+ core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
+ instance=instance,
+ symmetrical=(self.field.rel.symmetrical and instance.__class__ == rel_model),
+ join_table=qn(self.field.m2m_db_table()),
+ source_col_name=qn(self.field.m2m_column_name()),
+ target_col_name=qn(self.field.m2m_reverse_name())
+ )
+
+ return manager
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ manager = self.__get__(instance)
+ manager.clear()
+ manager.add(*value)
+
+class ForeignKey(RelatedField, Field):
+ empty_strings_allowed = False
+ def __init__(self, to, to_field=None, **kwargs):
+ try:
+ to_name = to._meta.object_name.lower()
+ except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+ assert isinstance(to, basestring), "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
+ else:
+ to_field = to_field or to._meta.pk.name
+ kwargs['verbose_name'] = kwargs.get('verbose_name', '')
+
+ if kwargs.has_key('edit_inline_type'):
+ import warnings
+ warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
+ kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
+
+ kwargs['rel'] = ManyToOneRel(to, to_field,
+ num_in_admin=kwargs.pop('num_in_admin', 3),
+ min_num_in_admin=kwargs.pop('min_num_in_admin', None),
+ max_num_in_admin=kwargs.pop('max_num_in_admin', None),
+ num_extra_on_change=kwargs.pop('num_extra_on_change', 1),
+ edit_inline=kwargs.pop('edit_inline', False),
+ related_name=kwargs.pop('related_name', None),
+ limit_choices_to=kwargs.pop('limit_choices_to', None),
+ lookup_overrides=kwargs.pop('lookup_overrides', None),
+ raw_id_admin=kwargs.pop('raw_id_admin', False))
+ Field.__init__(self, **kwargs)
+
+ self.db_index = True
+
+ def get_attname(self):
+ return '%s_id' % self.name
+
+ def get_validator_unique_lookup_type(self):
+ return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
+
+ def prepare_field_objs_and_params(self, manipulator, name_prefix):
+ params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
+ if self.rel.raw_id_admin:
+ field_objs = self.get_manipulator_field_objs()
+ params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
+ else:
+ if self.radio_admin:
+ field_objs = [oldforms.RadioSelectField]
+ params['ul_class'] = get_ul_class(self.radio_admin)
+ else:
+ if self.null:
+ field_objs = [oldforms.NullSelectField]
+ else:
+ field_objs = [oldforms.SelectField]
+ params['choices'] = self.get_choices_default()
+ return field_objs, params
+
+ def get_manipulator_field_objs(self):
+ rel_field = self.rel.get_related_field()
+ if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
+ return rel_field.get_manipulator_field_objs()
+ else:
+ return [oldforms.IntegerField]
+
+ def get_db_prep_save(self, value):
+ if value == '' or value == None:
+ return None
+ else:
+ return self.rel.get_related_field().get_db_prep_save(value)
+
+ def flatten_data(self, follow, obj=None):
+ if not obj:
+ # In required many-to-one fields with only one available choice,
+ # select that one available choice. Note: For SelectFields
+ # (radio_admin=False), we have to check that the length of choices
+ # is *2*, not 1, because SelectFields always have an initial
+ # "blank" value. Otherwise (radio_admin=True), we check that the
+ # length is 1.
+ if not self.blank and (not self.rel.raw_id_admin or self.choices):
+ choice_list = self.get_choices_default()
+ if self.radio_admin and len(choice_list) == 1:
+ return {self.attname: choice_list[0][0]}
+ if not self.radio_admin and len(choice_list) == 2:
+ return {self.attname: choice_list[1][0]}
+ return Field.flatten_data(self, follow, obj)
+
+ def contribute_to_class(self, cls, name):
+ super(ForeignKey, self).contribute_to_class(cls, name)
+ setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
+
+ def contribute_to_related_class(self, cls, related):
+ setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
+
+ def formfield(self, **kwargs):
+ defaults = {'queryset': self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.ModelChoiceField(**defaults)
+
+class OneToOneField(RelatedField, IntegerField):
+ def __init__(self, to, to_field=None, **kwargs):
+ try:
+ to_name = to._meta.object_name.lower()
+ except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+ assert isinstance(to, basestring), "OneToOneField(%r) is invalid. First parameter to OneToOneField must be either a model, a model name, or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
+ else:
+ to_field = to_field or to._meta.pk.name
+ kwargs['verbose_name'] = kwargs.get('verbose_name', '')
+
+ if kwargs.has_key('edit_inline_type'):
+ import warnings
+ warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
+ kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
+
+ kwargs['rel'] = OneToOneRel(to, to_field,
+ num_in_admin=kwargs.pop('num_in_admin', 0),
+ edit_inline=kwargs.pop('edit_inline', False),
+ related_name=kwargs.pop('related_name', None),
+ limit_choices_to=kwargs.pop('limit_choices_to', None),
+ lookup_overrides=kwargs.pop('lookup_overrides', None),
+ raw_id_admin=kwargs.pop('raw_id_admin', False))
+ kwargs['primary_key'] = True
+ IntegerField.__init__(self, **kwargs)
+
+ self.db_index = True
+
+ def get_attname(self):
+ return '%s_id' % self.name
+
+ def get_validator_unique_lookup_type(self):
+ return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
+
+ # TODO: Copied from ForeignKey... putting this in RelatedField adversely affects
+ # ManyToManyField. This works for now.
+ def prepare_field_objs_and_params(self, manipulator, name_prefix):
+ params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
+ if self.rel.raw_id_admin:
+ field_objs = self.get_manipulator_field_objs()
+ params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
+ else:
+ if self.radio_admin:
+ field_objs = [oldforms.RadioSelectField]
+ params['ul_class'] = get_ul_class(self.radio_admin)
+ else:
+ if self.null:
+ field_objs = [oldforms.NullSelectField]
+ else:
+ field_objs = [oldforms.SelectField]
+ params['choices'] = self.get_choices_default()
+ return field_objs, params
+
+ def contribute_to_class(self, cls, name):
+ super(OneToOneField, self).contribute_to_class(cls, name)
+ setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
+
+ def contribute_to_related_class(self, cls, related):
+ setattr(cls, related.get_accessor_name(), SingleRelatedObjectDescriptor(related))
+ if not cls._meta.one_to_one_field:
+ cls._meta.one_to_one_field = self
+
+ def formfield(self, **kwargs):
+ defaults = {'queryset': self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.ModelChoiceField(**defaults)
+
+class ManyToManyField(RelatedField, Field):
+ def __init__(self, to, **kwargs):
+ kwargs['verbose_name'] = kwargs.get('verbose_name', None)
+ kwargs['rel'] = ManyToManyRel(to,
+ num_in_admin=kwargs.pop('num_in_admin', 0),
+ related_name=kwargs.pop('related_name', None),
+ filter_interface=kwargs.pop('filter_interface', None),
+ limit_choices_to=kwargs.pop('limit_choices_to', None),
+ raw_id_admin=kwargs.pop('raw_id_admin', False),
+ symmetrical=kwargs.pop('symmetrical', True))
+ self.db_table = kwargs.pop('db_table', None)
+ if kwargs["rel"].raw_id_admin:
+ kwargs.setdefault("validator_list", []).append(self.isValidIDList)
+ Field.__init__(self, **kwargs)
+
+ if self.rel.raw_id_admin:
+ msg = gettext_lazy('Separate multiple IDs with commas.')
+ else:
+ msg = gettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
+ self.help_text = string_concat(self.help_text, ' ', msg)
+
+ def get_manipulator_field_objs(self):
+ if self.rel.raw_id_admin:
+ return [oldforms.RawIdAdminField]
+ else:
+ choices = self.get_choices_default()
+ return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
+
+ def get_choices_default(self):
+ return Field.get_choices(self, include_blank=False)
+
+ def _get_m2m_db_table(self, opts):
+ "Function that can be curried to provide the m2m table name for this relation"
+ if self.db_table:
+ return self.db_table
+ else:
+ return '%s_%s' % (opts.db_table, self.name)
+
+ def _get_m2m_column_name(self, related):
+ "Function that can be curried to provide the source column name for the m2m table"
+ # If this is an m2m relation to self, avoid the inevitable name clash
+ if related.model == related.parent_model:
+ return 'from_' + related.model._meta.object_name.lower() + '_id'
+ else:
+ return related.model._meta.object_name.lower() + '_id'
+
+ def _get_m2m_reverse_name(self, related):
+ "Function that can be curried to provide the related column name for the m2m table"
+ # If this is an m2m relation to self, avoid the inevitable name clash
+ if related.model == related.parent_model:
+ return 'to_' + related.parent_model._meta.object_name.lower() + '_id'
+ else:
+ return related.parent_model._meta.object_name.lower() + '_id'
+
+ def isValidIDList(self, field_data, all_data):
+ "Validates that the value is a valid list of foreign keys"
+ mod = self.rel.to
+ try:
+ pks = map(int, field_data.split(','))
+ except ValueError:
+ # the CommaSeparatedIntegerField validator will catch this error
+ return
+ objects = mod._default_manager.in_bulk(pks)
+ if len(objects) != len(pks):
+ badkeys = [k for k in pks if k not in objects]
+ raise validators.ValidationError, ngettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
+ "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
+ 'self': self.verbose_name,
+ 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
+ }
+
+ def flatten_data(self, follow, obj = None):
+ new_data = {}
+ if obj:
+ instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
+ if self.rel.raw_id_admin:
+ new_data[self.name] = ",".join([str(id) for id in instance_ids])
+ else:
+ new_data[self.name] = instance_ids
+ else:
+ # In required many-to-many fields with only one available choice,
+ # select that one available choice.
+ if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin:
+ choices_list = self.get_choices_default()
+ if len(choices_list) == 1:
+ new_data[self.name] = [choices_list[0][0]]
+ return new_data
+
+ def contribute_to_class(self, cls, name):
+ super(ManyToManyField, self).contribute_to_class(cls, name)
+ # Add the descriptor for the m2m relation
+ setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
+
+ # Set up the accessor for the m2m table name for the relation
+ self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
+
+ def contribute_to_related_class(self, cls, related):
+ # m2m relations to self do not have a ManyRelatedObjectsDescriptor,
+ # as it would be redundant - unless the field is non-symmetrical.
+ if related.model != related.parent_model or not self.rel.symmetrical:
+ # Add the descriptor for the m2m relation
+ setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
+
+ # Set up the accessors for the column names on the m2m table
+ self.m2m_column_name = curry(self._get_m2m_column_name, related)
+ self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related)
+
+ def set_attributes_from_rel(self):
+ pass
+
+ def value_from_object(self, obj):
+ "Returns the value of this field in the given model instance."
+ return getattr(obj, self.attname).all()
+
+ def formfield(self, **kwargs):
+ # If initial is passed in, it's a list of related objects, but the
+ # MultipleChoiceField takes a list of IDs.
+ if kwargs.get('initial') is not None:
+ kwargs['initial'] = [i._get_pk_val() for i in kwargs['initial']]
+ defaults = {'queryset' : self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
+ defaults.update(kwargs)
+ return forms.ModelMultipleChoiceField(**defaults)
+
+class ManyToOneRel(object):
+ def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
+ max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
+ related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
+ try:
+ to._meta
+ except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+ assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
+ self.to, self.field_name = to, field_name
+ self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
+ self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
+ self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
+ if limit_choices_to is None:
+ limit_choices_to = {}
+ self.limit_choices_to = limit_choices_to
+ self.lookup_overrides = lookup_overrides or {}
+ self.raw_id_admin = raw_id_admin
+ self.multiple = True
+
+ def get_related_field(self):
+ "Returns the Field in the 'to' object to which this relationship is tied."
+ return self.to._meta.get_field(self.field_name)
+
+class OneToOneRel(ManyToOneRel):
+ def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
+ related_name=None, limit_choices_to=None, lookup_overrides=None,
+ raw_id_admin=False):
+ self.to, self.field_name = to, field_name
+ self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
+ self.related_name = related_name
+ if limit_choices_to is None:
+ limit_choices_to = {}
+ self.limit_choices_to = limit_choices_to
+ self.lookup_overrides = lookup_overrides or {}
+ self.raw_id_admin = raw_id_admin
+ self.multiple = False
+
+class ManyToManyRel(object):
+ def __init__(self, to, num_in_admin=0, related_name=None,
+ filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
+ self.to = to
+ self.num_in_admin = num_in_admin
+ self.related_name = related_name
+ self.filter_interface = filter_interface
+ if limit_choices_to is None:
+ limit_choices_to = {}
+ self.limit_choices_to = limit_choices_to
+ self.edit_inline = False
+ self.raw_id_admin = raw_id_admin
+ self.symmetrical = symmetrical
+ self.multiple = True
+
+ assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
diff --git a/google_appengine/lib/django/django/db/models/loading.py b/google_appengine/lib/django/django/db/models/loading.py
new file mode 100755
index 0000000..f4aff24
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/loading.py
@@ -0,0 +1,116 @@
+"Utilities for loading models and the modules that contain them."
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+import sys
+import os
+
+__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models')
+
+_app_list = [] # Cache of installed apps.
+ # Entry is not placed in app_list cache until entire app is loaded.
+_app_models = {} # Dictionary of models against app label
+ # Each value is a dictionary of model name: model class
+ # Applabel and Model entry exists in cache when individual model is loaded.
+_app_errors = {} # Dictionary of errors that were experienced when loading the INSTALLED_APPS
+ # Key is the app_name of the model, value is the exception that was raised
+ # during model loading.
+_loaded = False # Has the contents of settings.INSTALLED_APPS been loaded?
+ # i.e., has get_apps() been called?
+
+def get_apps():
+ "Returns a list of all installed modules that contain models."
+ global _app_list
+ global _loaded
+ if not _loaded:
+ _loaded = True
+ for app_name in settings.INSTALLED_APPS:
+ try:
+ load_app(app_name)
+ except Exception, e:
+ # Problem importing the app
+ _app_errors[app_name] = e
+ return _app_list
+
+def get_app(app_label, emptyOK=False):
+ "Returns the module containing the models for the given app_label. If the app has no models in it and 'emptyOK' is True, returns None."
+ get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
+ for app_name in settings.INSTALLED_APPS:
+ if app_label == app_name.split('.')[-1]:
+ mod = load_app(app_name)
+ if mod is None:
+ if emptyOK:
+ return None
+ else:
+ return mod
+ raise ImproperlyConfigured, "App with label %s could not be found" % app_label
+
+def load_app(app_name):
+ "Loads the app with the provided fully qualified name, and returns the model module."
+ global _app_list
+ mod = __import__(app_name, {}, {}, ['models'])
+ if not hasattr(mod, 'models'):
+ return None
+ if mod.models not in _app_list:
+ _app_list.append(mod.models)
+ return mod.models
+
+def get_app_errors():
+ "Returns the map of known problems with the INSTALLED_APPS"
+ global _app_errors
+ get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
+ return _app_errors
+
+def get_models(app_mod=None):
+ """
+ Given a module containing models, returns a list of the models. Otherwise
+ returns a list of all installed models.
+ """
+ app_list = get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
+ if app_mod:
+ return _app_models.get(app_mod.__name__.split('.')[-2], {}).values()
+ else:
+ model_list = []
+ for app_mod in app_list:
+ model_list.extend(get_models(app_mod))
+ return model_list
+
+def get_model(app_label, model_name, seed_cache=True):
+ """
+ Returns the model matching the given app_label and case-insensitive
+ model_name.
+
+ Returns None if no model is found.
+ """
+ if seed_cache:
+ get_apps()
+ try:
+ model_dict = _app_models[app_label]
+ except KeyError:
+ return None
+
+ try:
+ return model_dict[model_name.lower()]
+ except KeyError:
+ return None
+
+def register_models(app_label, *models):
+ """
+ Register a set of models as belonging to an app.
+ """
+ for model in models:
+ # Store as 'name: model' pair in a dictionary
+ # in the _app_models dictionary
+ model_name = model._meta.object_name.lower()
+ model_dict = _app_models.setdefault(app_label, {})
+ if model_dict.has_key(model_name):
+ # The same model may be imported via different paths (e.g.
+ # appname.models and project.appname.models). We use the source
+ # filename as a means to detect identity.
+ fname1 = os.path.abspath(sys.modules[model.__module__].__file__)
+ fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__)
+ # Since the filename extension could be .py the first time and .pyc
+ # or .pyo the second time, ignore the extension when comparing.
+ if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
+ continue
+ model_dict[model_name] = model
diff --git a/google_appengine/lib/django/django/db/models/manager.py b/google_appengine/lib/django/django/db/models/manager.py
new file mode 100755
index 0000000..b60eed2
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/manager.py
@@ -0,0 +1,117 @@
+from django.db.models.query import QuerySet, EmptyQuerySet
+from django.dispatch import dispatcher
+from django.db.models import signals
+from django.db.models.fields import FieldDoesNotExist
+
+# Size of each "chunk" for get_iterator calls.
+# Larger values are slightly faster at the expense of more storage space.
+GET_ITERATOR_CHUNK_SIZE = 100
+
+def ensure_default_manager(sender):
+ cls = sender
+ if not hasattr(cls, '_default_manager'):
+ # Create the default manager, if needed.
+ try:
+ cls._meta.get_field('objects')
+ raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % cls.__name__
+ except FieldDoesNotExist:
+ pass
+ cls.add_to_class('objects', Manager())
+
+dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)
+
+class Manager(object):
+ # Tracks each time a Manager instance is created. Used to retain order.
+ creation_counter = 0
+
+ def __init__(self):
+ super(Manager, self).__init__()
+ # Increase the creation counter, and save our local copy.
+ self.creation_counter = Manager.creation_counter
+ Manager.creation_counter += 1
+ self.model = None
+
+ def contribute_to_class(self, model, name):
+ # TODO: Use weakref because of possible memory leak / circular reference.
+ self.model = model
+ setattr(model, name, ManagerDescriptor(self))
+ if not hasattr(model, '_default_manager') or self.creation_counter < model._default_manager.creation_counter:
+ model._default_manager = self
+
+ #######################
+ # PROXIES TO QUERYSET #
+ #######################
+
+ def get_empty_query_set(self):
+ return EmptyQuerySet(self.model)
+
+ def get_query_set(self):
+ """Returns a new QuerySet object. Subclasses can override this method
+ to easily customise the behaviour of the Manager.
+ """
+ return QuerySet(self.model)
+
+ def none(self):
+ return self.get_empty_query_set()
+
+ def all(self):
+ return self.get_query_set()
+
+ def count(self):
+ return self.get_query_set().count()
+
+ def dates(self, *args, **kwargs):
+ return self.get_query_set().dates(*args, **kwargs)
+
+ def distinct(self, *args, **kwargs):
+ return self.get_query_set().distinct(*args, **kwargs)
+
+ def extra(self, *args, **kwargs):
+ return self.get_query_set().extra(*args, **kwargs)
+
+ def get(self, *args, **kwargs):
+ return self.get_query_set().get(*args, **kwargs)
+
+ def get_or_create(self, **kwargs):
+ return self.get_query_set().get_or_create(**kwargs)
+
+ def create(self, **kwargs):
+ return self.get_query_set().create(**kwargs)
+
+ def filter(self, *args, **kwargs):
+ return self.get_query_set().filter(*args, **kwargs)
+
+ def complex_filter(self, *args, **kwargs):
+ return self.get_query_set().complex_filter(*args, **kwargs)
+
+ def exclude(self, *args, **kwargs):
+ return self.get_query_set().exclude(*args, **kwargs)
+
+ def in_bulk(self, *args, **kwargs):
+ return self.get_query_set().in_bulk(*args, **kwargs)
+
+ def iterator(self, *args, **kwargs):
+ return self.get_query_set().iterator(*args, **kwargs)
+
+ def latest(self, *args, **kwargs):
+ return self.get_query_set().latest(*args, **kwargs)
+
+ def order_by(self, *args, **kwargs):
+ return self.get_query_set().order_by(*args, **kwargs)
+
+ def select_related(self, *args, **kwargs):
+ return self.get_query_set().select_related(*args, **kwargs)
+
+ def values(self, *args, **kwargs):
+ return self.get_query_set().values(*args, **kwargs)
+
+class ManagerDescriptor(object):
+ # This class ensures managers aren't accessible via model instances.
+ # For example, Poll.objects works, but poll_obj.objects raises AttributeError.
+ def __init__(self, manager):
+ self.manager = manager
+
+ def __get__(self, instance, type=None):
+ if instance != None:
+ raise AttributeError, "Manager isn't accessible via %s instances" % type.__name__
+ return self.manager
diff --git a/google_appengine/lib/django/django/db/models/manipulators.py b/google_appengine/lib/django/django/db/models/manipulators.py
new file mode 100755
index 0000000..c624cb6
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/manipulators.py
@@ -0,0 +1,335 @@
+from django.core.exceptions import ObjectDoesNotExist
+from django import oldforms
+from django.core import validators
+from django.db.models.fields import FileField, AutoField
+from django.dispatch import dispatcher
+from django.db.models import signals
+from django.utils.functional import curry
+from django.utils.datastructures import DotExpandedDict
+from django.utils.text import capfirst
+import types
+
+def add_manipulators(sender):
+ cls = sender
+ cls.add_to_class('AddManipulator', AutomaticAddManipulator)
+ cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator)
+
+dispatcher.connect(add_manipulators, signal=signals.class_prepared)
+
+class ManipulatorDescriptor(object):
+ # This class provides the functionality that makes the default model
+ # manipulators (AddManipulator and ChangeManipulator) available via the
+ # model class.
+ def __init__(self, name, base):
+ self.man = None # Cache of the manipulator class.
+ self.name = name
+ self.base = base
+
+ def __get__(self, instance, model=None):
+ if instance != None:
+ raise AttributeError, "Manipulator cannot be accessed via instance"
+ else:
+ if not self.man:
+ # Create a class that inherits from the "Manipulator" class
+ # given in the model class (if specified) and the automatic
+ # manipulator.
+ bases = [self.base]
+ if hasattr(model, 'Manipulator'):
+ bases = [model.Manipulator] + bases
+ self.man = types.ClassType(self.name, tuple(bases), {})
+ self.man._prepare(model)
+ return self.man
+
+class AutomaticManipulator(oldforms.Manipulator):
+ def _prepare(cls, model):
+ cls.model = model
+ cls.manager = model._default_manager
+ cls.opts = model._meta
+ for field_name_list in cls.opts.unique_together:
+ setattr(cls, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, cls.opts))
+ for f in cls.opts.fields:
+ if f.unique_for_date:
+ setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_date), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_date), cls.opts, 'date'))
+ if f.unique_for_month:
+ setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_month), cls.opts, 'month'))
+ if f.unique_for_year:
+ setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_year), cls.opts, 'year'))
+ _prepare = classmethod(_prepare)
+
+ def contribute_to_class(cls, other_cls, name):
+ setattr(other_cls, name, ManipulatorDescriptor(name, cls))
+ contribute_to_class = classmethod(contribute_to_class)
+
+ def __init__(self, follow=None):
+ self.follow = self.opts.get_follow(follow)
+ self.fields = []
+
+ for f in self.opts.fields + self.opts.many_to_many:
+ if self.follow.get(f.name, False):
+ self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change))
+
+ # Add fields for related objects.
+ for f in self.opts.get_all_related_objects():
+ if self.follow.get(f.name, False):
+ fol = self.follow[f.name]
+ self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change, fol))
+
+ # Add field for ordering.
+ if self.change and self.opts.get_ordered_objects():
+ self.fields.append(oldforms.CommaSeparatedIntegerField(field_name="order_"))
+
+ def save(self, new_data):
+ # TODO: big cleanup when core fields go -> use recursive manipulators.
+ params = {}
+ for f in self.opts.fields:
+ # Fields with auto_now_add should keep their original value in the change stage.
+ auto_now_add = self.change and getattr(f, 'auto_now_add', False)
+ if self.follow.get(f.name, None) and not auto_now_add:
+ param = f.get_manipulator_new_data(new_data)
+ else:
+ if self.change:
+ param = getattr(self.original_object, f.attname)
+ else:
+ param = f.get_default()
+ params[f.attname] = param
+
+ if self.change:
+ params[self.opts.pk.attname] = self.obj_key
+
+ # First, create the basic object itself.
+ new_object = self.model(**params)
+
+ # Now that the object's been created, save any uploaded files.
+ for f in self.opts.fields:
+ if isinstance(f, FileField):
+ f.save_file(new_data, new_object, self.change and self.original_object or None, self.change, rel=False, save=False)
+
+ # Now save the object
+ new_object.save()
+
+ # Calculate which primary fields have changed.
+ if self.change:
+ self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
+ for f in self.opts.fields:
+ if not f.primary_key and str(getattr(self.original_object, f.attname)) != str(getattr(new_object, f.attname)):
+ self.fields_changed.append(f.verbose_name)
+
+ # Save many-to-many objects. Example: Set sites for a poll.
+ for f in self.opts.many_to_many:
+ if self.follow.get(f.name, None):
+ if not f.rel.edit_inline:
+ if f.rel.raw_id_admin:
+ new_vals = new_data.get(f.name, ())
+ else:
+ new_vals = new_data.getlist(f.name)
+ # First, clear the existing values.
+ rel_manager = getattr(new_object, f.name)
+ rel_manager.clear()
+ # Then, set the new values.
+ for n in new_vals:
+ rel_manager.add(f.rel.to._default_manager.get(pk=n))
+ # TODO: Add to 'fields_changed'
+
+ expanded_data = DotExpandedDict(dict(new_data))
+ # Save many-to-one objects. Example: Add the Choice objects for a Poll.
+ for related in self.opts.get_all_related_objects():
+ # Create obj_list, which is a DotExpandedDict such as this:
+ # [('0', {'id': ['940'], 'choice': ['This is the first choice']}),
+ # ('1', {'id': ['941'], 'choice': ['This is the second choice']}),
+ # ('2', {'id': [''], 'choice': ['']})]
+ child_follow = self.follow.get(related.name, None)
+
+ if child_follow:
+ obj_list = expanded_data.get(related.var_name, {}).items()
+ if not obj_list:
+ continue
+
+ obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
+
+ # For each related item...
+ for _, rel_new_data in obj_list:
+
+ params = {}
+
+ # Keep track of which core=True fields were provided.
+ # If all core fields were given, the related object will be saved.
+ # If none of the core fields were given, the object will be deleted.
+ # If some, but not all, of the fields were given, the validator would
+ # have caught that.
+ all_cores_given, all_cores_blank = True, True
+
+ # Get a reference to the old object. We'll use it to compare the
+ # old to the new, to see which fields have changed.
+ old_rel_obj = None
+ if self.change:
+ if rel_new_data[related.opts.pk.name][0]:
+ try:
+ old_rel_obj = getattr(self.original_object, related.get_accessor_name()).get(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]})
+ except ObjectDoesNotExist:
+ pass
+
+ for f in related.opts.fields:
+ if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''):
+ all_cores_given = False
+ elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''):
+ all_cores_blank = False
+ # If this field isn't editable, give it the same value it had
+ # previously, according to the given ID. If the ID wasn't
+ # given, use a default value. FileFields are also a special
+ # case, because they'll be dealt with later.
+
+ if f == related.field:
+ param = getattr(new_object, related.field.rel.get_related_field().attname)
+ elif (not self.change) and isinstance(f, AutoField):
+ param = None
+ elif self.change and (isinstance(f, FileField) or not child_follow.get(f.name, None)):
+ if old_rel_obj:
+ param = getattr(old_rel_obj, f.column)
+ else:
+ param = f.get_default()
+ else:
+ param = f.get_manipulator_new_data(rel_new_data, rel=True)
+ if param != None:
+ params[f.attname] = param
+
+ # Create the related item.
+ new_rel_obj = related.model(**params)
+
+ # If all the core fields were provided (non-empty), save the item.
+ if all_cores_given:
+ new_rel_obj.save()
+
+ # Save any uploaded files.
+ for f in related.opts.fields:
+ if child_follow.get(f.name, None):
+ if isinstance(f, FileField) and rel_new_data.get(f.name, False):
+ f.save_file(rel_new_data, new_rel_obj, self.change and old_rel_obj or None, old_rel_obj is not None, rel=True)
+
+ # Calculate whether any fields have changed.
+ if self.change:
+ if not old_rel_obj: # This object didn't exist before.
+ self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj))
+ else:
+ for f in related.opts.fields:
+ if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)):
+ self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
+
+ # Save many-to-many objects.
+ for f in related.opts.many_to_many:
+ if child_follow.get(f.name, None) and not f.rel.edit_inline:
+ new_value = rel_new_data[f.attname]
+ if f.rel.raw_id_admin:
+ new_value = new_value[0]
+ setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=new_value))
+ if self.change:
+ self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
+
+ # If, in the change stage, all of the core fields were blank and
+ # the primary key (ID) was provided, delete the item.
+ if self.change and all_cores_blank and old_rel_obj:
+ new_rel_obj.delete()
+ self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
+
+ # Save the order, if applicable.
+ if self.change and self.opts.get_ordered_objects():
+ order = new_data['order_'] and map(int, new_data['order_'].split(',')) or []
+ for rel_opts in self.opts.get_ordered_objects():
+ getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
+ return new_object
+
+ def get_related_objects(self):
+ return self.opts.get_followed_related_objects(self.follow)
+
+ def flatten_data(self):
+ new_data = {}
+ obj = self.change and self.original_object or None
+ for f in self.opts.get_data_holders(self.follow):
+ fol = self.follow.get(f.name)
+ new_data.update(f.flatten_data(fol, obj))
+ return new_data
+
+class AutomaticAddManipulator(AutomaticManipulator):
+ change = False
+
+class AutomaticChangeManipulator(AutomaticManipulator):
+ change = True
+ def __init__(self, obj_key, follow=None):
+ self.obj_key = obj_key
+ try:
+ self.original_object = self.manager.get(pk=obj_key)
+ except ObjectDoesNotExist:
+ # If the object doesn't exist, this might be a manipulator for a
+ # one-to-one related object that hasn't created its subobject yet.
+ # For example, this might be a Restaurant for a Place that doesn't
+ # yet have restaurant information.
+ if self.opts.one_to_one_field:
+ # Sanity check -- Make sure the "parent" object exists.
+ # For example, make sure the Place exists for the Restaurant.
+ # Let the ObjectDoesNotExist exception propagate up.
+ limit_choices_to = self.opts.one_to_one_field.rel.limit_choices_to
+ lookup_kwargs = {'%s__exact' % self.opts.one_to_one_field.rel.field_name: obj_key}
+ self.opts.one_to_one_field.rel.to.get_model_module().complex_filter(limit_choices_to).get(**lookup_kwargs)
+ params = dict([(f.attname, f.get_default()) for f in self.opts.fields])
+ params[self.opts.pk.attname] = obj_key
+ self.original_object = self.opts.get_model_module().Klass(**params)
+ else:
+ raise
+ super(AutomaticChangeManipulator, self).__init__(follow=follow)
+
+def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
+ from django.db.models.fields.related import ManyToOneRel
+ from django.utils.text import get_text_list
+ field_list = [opts.get_field(field_name) for field_name in field_name_list]
+ if isinstance(field_list[0].rel, ManyToOneRel):
+ kwargs = {'%s__%s__iexact' % (field_name_list[0], field_list[0].rel.field_name): field_data}
+ else:
+ kwargs = {'%s__iexact' % field_name_list[0]: field_data}
+ for f in field_list[1:]:
+ # This is really not going to work for fields that have different
+ # form fields, e.g. DateTime.
+ # This validation needs to occur after html2python to be effective.
+ field_val = all_data.get(f.name, None)
+ if field_val is None:
+ # This will be caught by another validator, assuming the field
+ # doesn't have blank=True.
+ return
+ if isinstance(f.rel, ManyToOneRel):
+ kwargs['%s__pk' % f.name] = field_val
+ else:
+ kwargs['%s__iexact' % f.name] = field_val
+ try:
+ old_obj = self.manager.get(**kwargs)
+ except ObjectDoesNotExist:
+ return
+ if hasattr(self, 'original_object') and self.original_object._get_pk_val() == old_obj._get_pk_val():
+ pass
+ else:
+ raise validators.ValidationError, _("%(object)s with this %(type)s already exists for the given %(field)s.") % \
+ {'object': capfirst(opts.verbose_name), 'type': field_list[0].verbose_name, 'field': get_text_list([f.verbose_name for f in field_list[1:]], _('and'))}
+
+def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data):
+ from django.db.models.fields.related import ManyToOneRel
+ date_str = all_data.get(date_field.get_manipulator_field_names('')[0], None)
+ date_val = oldforms.DateField.html2python(date_str)
+ if date_val is None:
+ return # Date was invalid. This will be caught by another validator.
+ lookup_kwargs = {'%s__year' % date_field.name: date_val.year}
+ if isinstance(from_field.rel, ManyToOneRel):
+ lookup_kwargs['%s__pk' % from_field.name] = field_data
+ else:
+ lookup_kwargs['%s__iexact' % from_field.name] = field_data
+ if lookup_type in ('month', 'date'):
+ lookup_kwargs['%s__month' % date_field.name] = date_val.month
+ if lookup_type == 'date':
+ lookup_kwargs['%s__day' % date_field.name] = date_val.day
+ try:
+ old_obj = self.manager.get(**lookup_kwargs)
+ except ObjectDoesNotExist:
+ return
+ else:
+ if hasattr(self, 'original_object') and self.original_object._get_pk_val() == old_obj._get_pk_val():
+ pass
+ else:
+ format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y'
+ raise validators.ValidationError, "Please enter a different %s. The one you entered is already being used for %s." % \
+ (from_field.verbose_name, date_val.strftime(format_string))
diff --git a/google_appengine/lib/django/django/db/models/options.py b/google_appengine/lib/django/django/db/models/options.py
new file mode 100755
index 0000000..51cf0a0
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/options.py
@@ -0,0 +1,274 @@
+from django.conf import settings
+from django.db.models.related import RelatedObject
+from django.db.models.fields.related import ManyToManyRel
+from django.db.models.fields import AutoField, FieldDoesNotExist
+from django.db.models.loading import get_models
+from django.db.models.query import orderlist2sql
+from django.db.models import Manager
+from bisect import bisect
+import re
+
+# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
+get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
+
+DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
+ 'unique_together', 'permissions', 'get_latest_by',
+ 'order_with_respect_to', 'app_label')
+
+class Options(object):
+ def __init__(self, meta):
+ self.fields, self.many_to_many = [], []
+ self.module_name, self.verbose_name = None, None
+ self.verbose_name_plural = None
+ self.db_table = ''
+ self.ordering = []
+ self.unique_together = []
+ self.permissions = []
+ self.object_name, self.app_label = None, None
+ self.get_latest_by = None
+ self.order_with_respect_to = None
+ self.admin = None
+ self.meta = meta
+ self.pk = None
+ self.has_auto_field = False
+ self.one_to_one_field = None
+ self.parents = []
+
+ def contribute_to_class(self, cls, name):
+ cls._meta = self
+ self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
+ # First, construct the default values for these options.
+ self.object_name = cls.__name__
+ self.module_name = self.object_name.lower()
+ self.verbose_name = get_verbose_name(self.object_name)
+ # Next, apply any overridden values from 'class Meta'.
+ if self.meta:
+ meta_attrs = self.meta.__dict__
+ del meta_attrs['__module__']
+ del meta_attrs['__doc__']
+ for attr_name in DEFAULT_NAMES:
+ setattr(self, attr_name, meta_attrs.pop(attr_name, getattr(self, attr_name)))
+ # verbose_name_plural is a special case because it uses a 's'
+ # by default.
+ setattr(self, 'verbose_name_plural', meta_attrs.pop('verbose_name_plural', self.verbose_name + 's'))
+ # Any leftover attributes must be invalid.
+ if meta_attrs != {}:
+ raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
+ else:
+ self.verbose_name_plural = self.verbose_name + 's'
+ del self.meta
+
+ def _prepare(self, model):
+ if self.order_with_respect_to:
+ self.order_with_respect_to = self.get_field(self.order_with_respect_to)
+ self.ordering = ('_order',)
+ else:
+ self.order_with_respect_to = None
+
+ if self.pk is None:
+ auto = AutoField(verbose_name='ID', primary_key=True)
+ auto.creation_counter = -1
+ model.add_to_class('id', auto)
+
+ # If the db_table wasn't provided, use the app_label + module_name.
+ if not self.db_table:
+ self.db_table = "%s_%s" % (self.app_label, self.module_name)
+
+ def add_field(self, field):
+ # Insert the given field in the order in which it was created, using
+ # the "creation_counter" attribute of the field.
+ # Move many-to-many related fields from self.fields into self.many_to_many.
+ if field.rel and isinstance(field.rel, ManyToManyRel):
+ self.many_to_many.insert(bisect(self.many_to_many, field), field)
+ else:
+ self.fields.insert(bisect(self.fields, field), field)
+ if not self.pk and field.primary_key:
+ self.pk = field
+ field.serialize = False
+
+ def __repr__(self):
+ return '<Options for %s>' % self.object_name
+
+ def __str__(self):
+ return "%s.%s" % (self.app_label, self.module_name)
+
+ def get_field(self, name, many_to_many=True):
+ "Returns the requested field by name. Raises FieldDoesNotExist on error."
+ to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
+ for f in to_search:
+ if f.name == name:
+ return f
+ raise FieldDoesNotExist, '%s has no field named %r' % (self.object_name, name)
+
+ def get_order_sql(self, table_prefix=''):
+ "Returns the full 'ORDER BY' clause for this object, according to self.ordering."
+ if not self.ordering: return ''
+ pre = table_prefix and (table_prefix + '.') or ''
+ return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre)
+
+ def get_add_permission(self):
+ return 'add_%s' % self.object_name.lower()
+
+ def get_change_permission(self):
+ return 'change_%s' % self.object_name.lower()
+
+ def get_delete_permission(self):
+ return 'delete_%s' % self.object_name.lower()
+
+ def get_all_related_objects(self):
+ try: # Try the cache first.
+ return self._all_related_objects
+ except AttributeError:
+ rel_objs = []
+ for klass in get_models():
+ for f in klass._meta.fields:
+ if f.rel and self == f.rel.to._meta:
+ rel_objs.append(RelatedObject(f.rel.to, klass, f))
+ self._all_related_objects = rel_objs
+ return rel_objs
+
+ def get_followed_related_objects(self, follow=None):
+ if follow == None:
+ follow = self.get_follow()
+ return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
+
+ def get_data_holders(self, follow=None):
+ if follow == None:
+ follow = self.get_follow()
+ return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)]
+
+ def get_follow(self, override=None):
+ follow = {}
+ for f in self.fields + self.many_to_many + self.get_all_related_objects():
+ if override and override.has_key(f.name):
+ child_override = override[f.name]
+ else:
+ child_override = None
+ fol = f.get_follow(child_override)
+ if fol != None:
+ follow[f.name] = fol
+ return follow
+
+ def get_all_related_many_to_many_objects(self):
+ try: # Try the cache first.
+ return self._all_related_many_to_many_objects
+ except AttributeError:
+ rel_objs = []
+ for klass in get_models():
+ for f in klass._meta.many_to_many:
+ if f.rel and self == f.rel.to._meta:
+ rel_objs.append(RelatedObject(f.rel.to, klass, f))
+ self._all_related_many_to_many_objects = rel_objs
+ return rel_objs
+
+ def get_ordered_objects(self):
+ "Returns a list of Options objects that are ordered with respect to this object."
+ if not hasattr(self, '_ordered_objects'):
+ objects = []
+ # TODO
+ #for klass in get_models(get_app(self.app_label)):
+ # opts = klass._meta
+ # if opts.order_with_respect_to and opts.order_with_respect_to.rel \
+ # and self == opts.order_with_respect_to.rel.to._meta:
+ # objects.append(opts)
+ self._ordered_objects = objects
+ return self._ordered_objects
+
+ def has_field_type(self, field_type, follow=None):
+ """
+ Returns True if this object's admin form has at least one of the given
+ field_type (e.g. FileField).
+ """
+ # TODO: follow
+ if not hasattr(self, '_field_types'):
+ self._field_types = {}
+ if not self._field_types.has_key(field_type):
+ try:
+ # First check self.fields.
+ for f in self.fields:
+ if isinstance(f, field_type):
+ raise StopIteration
+ # Failing that, check related fields.
+ for related in self.get_followed_related_objects(follow):
+ for f in related.opts.fields:
+ if isinstance(f, field_type):
+ raise StopIteration
+ except StopIteration:
+ self._field_types[field_type] = True
+ else:
+ self._field_types[field_type] = False
+ return self._field_types[field_type]
+
+class AdminOptions(object):
+ def __init__(self, fields=None, js=None, list_display=None, list_display_links=None, list_filter=None,
+ date_hierarchy=None, save_as=False, ordering=None, search_fields=None,
+ save_on_top=False, list_select_related=False, manager=None, list_per_page=100):
+ self.fields = fields
+ self.js = js or []
+ self.list_display = list_display or ['__str__']
+ self.list_display_links = list_display_links or []
+ self.list_filter = list_filter or []
+ self.date_hierarchy = date_hierarchy
+ self.save_as, self.ordering = save_as, ordering
+ self.search_fields = search_fields or []
+ self.save_on_top = save_on_top
+ self.list_select_related = list_select_related
+ self.list_per_page = list_per_page
+ self.manager = manager or Manager()
+
+ def get_field_sets(self, opts):
+ "Returns a list of AdminFieldSet objects for this AdminOptions object."
+ if self.fields is None:
+ field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),)
+ else:
+ field_struct = self.fields
+ new_fieldset_list = []
+ for fieldset in field_struct:
+ fs_options = fieldset[1]
+ classes = fs_options.get('classes', ())
+ description = fs_options.get('description', '')
+ new_fieldset_list.append(AdminFieldSet(fieldset[0], classes,
+ opts.get_field, fs_options['fields'], description))
+ return new_fieldset_list
+
+ def contribute_to_class(self, cls, name):
+ cls._meta.admin = self
+ # Make sure the admin manager has access to the model
+ self.manager.model = cls
+
+class AdminFieldSet(object):
+ def __init__(self, name, classes, field_locator_func, line_specs, description):
+ self.name = name
+ self.field_lines = [AdminFieldLine(field_locator_func, line_spec) for line_spec in line_specs]
+ self.classes = classes
+ self.description = description
+
+ def __repr__(self):
+ return "FieldSet: (%s, %s)" % (self.name, self.field_lines)
+
+ def bind(self, field_mapping, original, bound_field_set_class):
+ return bound_field_set_class(self, field_mapping, original)
+
+ def __iter__(self):
+ for field_line in self.field_lines:
+ yield field_line
+
+ def __len__(self):
+ return len(self.field_lines)
+
+class AdminFieldLine(object):
+ def __init__(self, field_locator_func, linespec):
+ if isinstance(linespec, basestring):
+ self.fields = [field_locator_func(linespec)]
+ else:
+ self.fields = [field_locator_func(field_name) for field_name in linespec]
+
+ def bind(self, field_mapping, original, bound_field_line_class):
+ return bound_field_line_class(self, field_mapping, original)
+
+ def __iter__(self):
+ for field in self.fields:
+ yield field
+
+ def __len__(self):
+ return len(self.fields)
diff --git a/google_appengine/lib/django/django/db/models/query.py b/google_appengine/lib/django/django/db/models/query.py
new file mode 100755
index 0000000..e019055
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/query.py
@@ -0,0 +1,1079 @@
+from django.db import backend, connection, transaction
+from django.db.models.fields import DateField, FieldDoesNotExist
+from django.db.models.fields.generic import GenericRelation
+from django.db.models import signals
+from django.dispatch import dispatcher
+from django.utils.datastructures import SortedDict
+import operator
+import re
+
+# For Python 2.3
+if not hasattr(__builtins__, 'set'):
+ from sets import Set as set
+
+# The string constant used to separate query parts
+LOOKUP_SEPARATOR = '__'
+
+# The list of valid query types
+QUERY_TERMS = (
+ 'exact', 'iexact', 'contains', 'icontains',
+ 'gt', 'gte', 'lt', 'lte', 'in',
+ 'startswith', 'istartswith', 'endswith', 'iendswith',
+ 'range', 'year', 'month', 'day', 'isnull', 'search',
+)
+
+# Size of each "chunk" for get_iterator calls.
+# Larger values are slightly faster at the expense of more storage space.
+GET_ITERATOR_CHUNK_SIZE = 100
+
+class EmptyResultSet(Exception):
+ pass
+
+####################
+# HELPER FUNCTIONS #
+####################
+
+# Django currently supports two forms of ordering.
+# Form 1 (deprecated) example:
+# order_by=(('pub_date', 'DESC'), ('headline', 'ASC'), (None, 'RANDOM'))
+# Form 2 (new-style) example:
+# order_by=('-pub_date', 'headline', '?')
+# Form 1 is deprecated and will no longer be supported for Django's first
+# official release. The following code converts from Form 1 to Form 2.
+
+LEGACY_ORDERING_MAPPING = {'ASC': '_', 'DESC': '-_', 'RANDOM': '?'}
+
+def handle_legacy_orderlist(order_list):
+ if not order_list or isinstance(order_list[0], basestring):
+ return order_list
+ else:
+ import warnings
+ new_order_list = [LEGACY_ORDERING_MAPPING[j.upper()].replace('_', str(i)) for i, j in order_list]
+ warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning)
+ return new_order_list
+
+def orderfield2column(f, opts):
+ try:
+ return opts.get_field(f, False).column
+ except FieldDoesNotExist:
+ return f
+
+def orderlist2sql(order_list, opts, prefix=''):
+ if prefix.endswith('.'):
+ prefix = backend.quote_name(prefix[:-1]) + '.'
+ output = []
+ for f in handle_legacy_orderlist(order_list):
+ if f.startswith('-'):
+ output.append('%s%s DESC' % (prefix, backend.quote_name(orderfield2column(f[1:], opts))))
+ elif f == '?':
+ output.append(backend.get_random_function_sql())
+ else:
+ output.append('%s%s ASC' % (prefix, backend.quote_name(orderfield2column(f, opts))))
+ return ', '.join(output)
+
+def quote_only_if_word(word):
+ if re.search('\W', word): # Don't quote if there are spaces or non-word chars.
+ return word
+ else:
+ return backend.quote_name(word)
+
+class QuerySet(object):
+ "Represents a lazy database lookup for a set of objects"
+ def __init__(self, model=None):
+ self.model = model
+ self._filters = Q()
+ self._order_by = None # Ordering, e.g. ('date', '-name'). If None, use model's ordering.
+ self._select_related = False # Whether to fill cache for related objects.
+ self._max_related_depth = 0 # Maximum "depth" for select_related
+ self._distinct = False # Whether the query should use SELECT DISTINCT.
+ self._select = {} # Dictionary of attname -> SQL.
+ self._where = [] # List of extra WHERE clauses to use.
+ self._params = [] # List of params to use for extra WHERE clauses.
+ self._tables = [] # List of extra tables to use.
+ self._offset = None # OFFSET clause.
+ self._limit = None # LIMIT clause.
+ self._result_cache = None
+
+ ########################
+ # PYTHON MAGIC METHODS #
+ ########################
+
+ def __repr__(self):
+ return repr(self._get_data())
+
+ def __len__(self):
+ return len(self._get_data())
+
+ def __iter__(self):
+ return iter(self._get_data())
+
+ def __getitem__(self, k):
+ "Retrieve an item or slice from the set of results."
+ if not isinstance(k, (slice, int)):
+ raise TypeError
+ assert (not isinstance(k, slice) and (k >= 0)) \
+ or (isinstance(k, slice) and (k.start is None or k.start >= 0) and (k.stop is None or k.stop >= 0)), \
+ "Negative indexing is not supported."
+ if self._result_cache is None:
+ if isinstance(k, slice):
+ # Offset:
+ if self._offset is None:
+ offset = k.start
+ elif k.start is None:
+ offset = self._offset
+ else:
+ offset = self._offset + k.start
+ # Now adjust offset to the bounds of any existing limit:
+ if self._limit is not None and k.start is not None:
+ limit = self._limit - k.start
+ else:
+ limit = self._limit
+
+ # Limit:
+ if k.stop is not None and k.start is not None:
+ if limit is None:
+ limit = k.stop - k.start
+ else:
+ limit = min((k.stop - k.start), limit)
+ else:
+ if limit is None:
+ limit = k.stop
+ else:
+ if k.stop is not None:
+ limit = min(k.stop, limit)
+
+ if k.step is None:
+ return self._clone(_offset=offset, _limit=limit)
+ else:
+ return list(self._clone(_offset=offset, _limit=limit))[::k.step]
+ else:
+ try:
+ return list(self._clone(_offset=k, _limit=1))[0]
+ except self.model.DoesNotExist, e:
+ raise IndexError, e.args
+ else:
+ return self._result_cache[k]
+
+ def __and__(self, other):
+ combined = self._combine(other)
+ combined._filters = self._filters & other._filters
+ return combined
+
+ def __or__(self, other):
+ combined = self._combine(other)
+ combined._filters = self._filters | other._filters
+ return combined
+
+ ####################################
+ # METHODS THAT DO DATABASE QUERIES #
+ ####################################
+
+ def iterator(self):
+ "Performs the SELECT database lookup of this QuerySet."
+ try:
+ select, sql, params = self._get_sql_clause()
+ except EmptyResultSet:
+ raise StopIteration
+
+ # self._select is a dictionary, and dictionaries' key order is
+ # undefined, so we convert it to a list of tuples.
+ extra_select = self._select.items()
+
+ cursor = connection.cursor()
+ cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
+ fill_cache = self._select_related
+ index_end = len(self.model._meta.fields)
+ while 1:
+ rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
+ if not rows:
+ raise StopIteration
+ for row in rows:
+ if fill_cache:
+ obj, index_end = get_cached_row(klass=self.model, row=row,
+ index_start=0, max_depth=self._max_related_depth)
+ else:
+ obj = self.model(*row[:index_end])
+ for i, k in enumerate(extra_select):
+ setattr(obj, k[0], row[index_end+i])
+ yield obj
+
+ def count(self):
+ """
+ Performs a SELECT COUNT() and returns the number of records as an
+ integer.
+
+ If the queryset is already cached (i.e. self._result_cache is set) this
+ simply returns the length of the cached results set to avoid multiple
+ SELECT COUNT(*) calls.
+ """
+ if self._result_cache is not None:
+ return len(self._result_cache)
+
+ counter = self._clone()
+ counter._order_by = ()
+ counter._select_related = False
+
+ offset = counter._offset
+ limit = counter._limit
+ counter._offset = None
+ counter._limit = None
+
+ try:
+ select, sql, params = counter._get_sql_clause()
+ except EmptyResultSet:
+ return 0
+
+ cursor = connection.cursor()
+ if self._distinct:
+ id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
+ backend.quote_name(self.model._meta.pk.column))
+ cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params)
+ else:
+ cursor.execute("SELECT COUNT(*)" + sql, params)
+ count = cursor.fetchone()[0]
+
+ # Apply any offset and limit constraints manually, since using LIMIT or
+ # OFFSET in SQL doesn't change the output of COUNT.
+ if offset:
+ count = max(0, count - offset)
+ if limit:
+ count = min(limit, count)
+
+ return count
+
+ def get(self, *args, **kwargs):
+ "Performs the SELECT and returns a single object matching the given keyword arguments."
+ clone = self.filter(*args, **kwargs)
+ # clean up SQL by removing unneeded ORDER BY
+ if not clone._order_by:
+ clone._order_by = ()
+ obj_list = list(clone)
+ if len(obj_list) < 1:
+ raise self.model.DoesNotExist, "%s matching query does not exist." % self.model._meta.object_name
+ assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs)
+ return obj_list[0]
+
+ def create(self, **kwargs):
+ """
+ Create a new object with the given kwargs, saving it to the database
+ and returning the created object.
+ """
+ obj = self.model(**kwargs)
+ obj.save()
+ return obj
+
+ def get_or_create(self, **kwargs):
+ """
+ Looks up an object with the given kwargs, creating one if necessary.
+ Returns a tuple of (object, created), where created is a boolean
+ specifying whether an object was created.
+ """
+ assert len(kwargs), 'get_or_create() must be passed at least one keyword argument'
+ defaults = kwargs.pop('defaults', {})
+ try:
+ return self.get(**kwargs), False
+ except self.model.DoesNotExist:
+ params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
+ params.update(defaults)
+ obj = self.model(**params)
+ obj.save()
+ return obj, True
+
+ def latest(self, field_name=None):
+ """
+ Returns the latest object, according to the model's 'get_latest_by'
+ option or optional given field_name.
+ """
+ latest_by = field_name or self.model._meta.get_latest_by
+ assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
+ assert self._limit is None and self._offset is None, \
+ "Cannot change a query once a slice has been taken."
+ return self._clone(_limit=1, _order_by=('-'+latest_by,)).get()
+
+ def in_bulk(self, id_list):
+ """
+ Returns a dictionary mapping each of the given IDs to the object with
+ that ID.
+ """
+ assert self._limit is None and self._offset is None, \
+ "Cannot use 'limit' or 'offset' with in_bulk"
+ assert isinstance(id_list, (tuple, list)), "in_bulk() must be provided with a list of IDs."
+ id_list = list(id_list)
+ if id_list == []:
+ return {}
+ qs = self._clone()
+ qs._where.append("%s.%s IN (%s)" % (backend.quote_name(self.model._meta.db_table), backend.quote_name(self.model._meta.pk.column), ",".join(['%s'] * len(id_list))))
+ qs._params.extend(id_list)
+ return dict([(obj._get_pk_val(), obj) for obj in qs.iterator()])
+
+ def delete(self):
+ """
+ Deletes the records in the current QuerySet.
+ """
+ assert self._limit is None and self._offset is None, \
+ "Cannot use 'limit' or 'offset' with delete."
+
+ del_query = self._clone()
+
+ # disable non-supported fields
+ del_query._select_related = False
+ del_query._order_by = []
+
+ # Delete objects in chunks to prevent an the list of
+ # related objects from becoming too long
+ more_objects = True
+ while more_objects:
+ # Collect all the objects to be deleted in this chunk, and all the objects
+ # that are related to the objects that are to be deleted
+ seen_objs = SortedDict()
+ more_objects = False
+ for object in del_query[0:GET_ITERATOR_CHUNK_SIZE]:
+ more_objects = True
+ object._collect_sub_objects(seen_objs)
+
+ # If one or more objects were found, delete them.
+ # Otherwise, stop looping.
+ if more_objects:
+ delete_objects(seen_objs)
+
+ # Clear the result cache, in case this QuerySet gets reused.
+ self._result_cache = None
+ delete.alters_data = True
+
+ ##################################################
+ # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
+ ##################################################
+
+ def values(self, *fields):
+ return self._clone(klass=ValuesQuerySet, _fields=fields)
+
+ def dates(self, field_name, kind, order='ASC'):
+ """
+ Returns a list of datetime objects representing all available dates
+ for the given field_name, scoped to 'kind'.
+ """
+ assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."
+ assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'."
+ # Let the FieldDoesNotExist exception propagate.
+ field = self.model._meta.get_field(field_name, many_to_many=False)
+ assert isinstance(field, DateField), "%r isn't a DateField." % field_name
+ return self._clone(klass=DateQuerySet, _field=field, _kind=kind, _order=order)
+
+ ##################################################################
+ # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
+ ##################################################################
+
+ def filter(self, *args, **kwargs):
+ "Returns a new QuerySet instance with the args ANDed to the existing set."
+ return self._filter_or_exclude(None, *args, **kwargs)
+
+ def exclude(self, *args, **kwargs):
+ "Returns a new QuerySet instance with NOT (args) ANDed to the existing set."
+ return self._filter_or_exclude(QNot, *args, **kwargs)
+
+ def _filter_or_exclude(self, mapper, *args, **kwargs):
+ # mapper is a callable used to transform Q objects,
+ # or None for identity transform
+ if mapper is None:
+ mapper = lambda x: x
+ if len(args) > 0 or len(kwargs) > 0:
+ assert self._limit is None and self._offset is None, \
+ "Cannot filter a query once a slice has been taken."
+
+ clone = self._clone()
+ if len(kwargs) > 0:
+ clone._filters = clone._filters & mapper(Q(**kwargs))
+ if len(args) > 0:
+ clone._filters = clone._filters & reduce(operator.and_, map(mapper, args))
+ return clone
+
+ def complex_filter(self, filter_obj):
+ """Returns a new QuerySet instance with filter_obj added to the filters.
+ filter_obj can be a Q object (has 'get_sql' method) or a dictionary of
+ keyword lookup arguments."""
+ # This exists to support framework features such as 'limit_choices_to',
+ # and usually it will be more natural to use other methods.
+ if hasattr(filter_obj, 'get_sql'):
+ return self._filter_or_exclude(None, filter_obj)
+ else:
+ return self._filter_or_exclude(None, **filter_obj)
+
+ def select_related(self, true_or_false=True, depth=0):
+ "Returns a new QuerySet instance with '_select_related' modified."
+ return self._clone(_select_related=true_or_false, _max_related_depth=depth)
+
+ def order_by(self, *field_names):
+ "Returns a new QuerySet instance with the ordering changed."
+ assert self._limit is None and self._offset is None, \
+ "Cannot reorder a query once a slice has been taken."
+ return self._clone(_order_by=field_names)
+
+ def distinct(self, true_or_false=True):
+ "Returns a new QuerySet instance with '_distinct' modified."
+ return self._clone(_distinct=true_or_false)
+
+ def extra(self, select=None, where=None, params=None, tables=None):
+ assert self._limit is None and self._offset is None, \
+ "Cannot change a query once a slice has been taken"
+ clone = self._clone()
+ if select: clone._select.update(select)
+ if where: clone._where.extend(where)
+ if params: clone._params.extend(params)
+ if tables: clone._tables.extend(tables)
+ return clone
+
+ ###################
+ # PRIVATE METHODS #
+ ###################
+
+ def _clone(self, klass=None, **kwargs):
+ if klass is None:
+ klass = self.__class__
+ c = klass()
+ c.model = self.model
+ c._filters = self._filters
+ c._order_by = self._order_by
+ c._select_related = self._select_related
+ c._max_related_depth = self._max_related_depth
+ c._distinct = self._distinct
+ c._select = self._select.copy()
+ c._where = self._where[:]
+ c._params = self._params[:]
+ c._tables = self._tables[:]
+ c._offset = self._offset
+ c._limit = self._limit
+ c.__dict__.update(kwargs)
+ return c
+
+ def _combine(self, other):
+ assert self._limit is None and self._offset is None \
+ and other._limit is None and other._offset is None, \
+ "Cannot combine queries once a slice has been taken."
+ assert self._distinct == other._distinct, \
+ "Cannot combine a unique query with a non-unique query"
+ # use 'other's order by
+ # (so that A.filter(args1) & A.filter(args2) does the same as
+ # A.filter(args1).filter(args2)
+ combined = other._clone()
+ if self._select: combined._select.update(self._select)
+ if self._where: combined._where.extend(self._where)
+ if self._params: combined._params.extend(self._params)
+ if self._tables: combined._tables.extend(self._tables)
+ # If 'self' is ordered and 'other' isn't, propagate 'self's ordering
+ if (self._order_by is not None and len(self._order_by) > 0) and \
+ (combined._order_by is None or len(combined._order_by) == 0):
+ combined._order_by = self._order_by
+ return combined
+
+ def _get_data(self):
+ if self._result_cache is None:
+ self._result_cache = list(self.iterator())
+ return self._result_cache
+
+ def _get_sql_clause(self):
+ opts = self.model._meta
+
+ # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
+ select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
+ tables = [quote_only_if_word(t) for t in self._tables]
+ joins = SortedDict()
+ where = self._where[:]
+ params = self._params[:]
+
+ # Convert self._filters into SQL.
+ joins2, where2, params2 = self._filters.get_sql(opts)
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+
+ # Add additional tables and WHERE clauses based on select_related.
+ if self._select_related:
+ fill_table_cache(opts, select, tables, where,
+ old_prefix=opts.db_table,
+ cache_tables_seen=[opts.db_table],
+ max_depth=self._max_related_depth)
+
+ # Add any additional SELECTs.
+ if self._select:
+ select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
+
+ # Start composing the body of the SQL statement.
+ sql = [" FROM", backend.quote_name(opts.db_table)]
+
+ # Compose the join dictionary into SQL describing the joins.
+ if joins:
+ sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition)
+ for (alias, (table, join_type, condition)) in joins.items()]))
+
+ # Compose the tables clause into SQL.
+ if tables:
+ sql.append(", " + ", ".join(tables))
+
+ # Compose the where clause into SQL.
+ if where:
+ sql.append(where and "WHERE " + " AND ".join(where))
+
+ # ORDER BY clause
+ order_by = []
+ if self._order_by is not None:
+ ordering_to_use = self._order_by
+ else:
+ ordering_to_use = opts.ordering
+ for f in handle_legacy_orderlist(ordering_to_use):
+ if f == '?': # Special case.
+ order_by.append(backend.get_random_function_sql())
+ else:
+ if f.startswith('-'):
+ col_name = f[1:]
+ order = "DESC"
+ else:
+ col_name = f
+ order = "ASC"
+ if "." in col_name:
+ table_prefix, col_name = col_name.split('.', 1)
+ table_prefix = backend.quote_name(table_prefix) + '.'
+ else:
+ # Use the database table as a column prefix if it wasn't given,
+ # and if the requested column isn't a custom SELECT.
+ if "." not in col_name and col_name not in (self._select or ()):
+ table_prefix = backend.quote_name(opts.db_table) + '.'
+ else:
+ table_prefix = ''
+ order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order))
+ if order_by:
+ sql.append("ORDER BY " + ", ".join(order_by))
+
+ # LIMIT and OFFSET clauses
+ if self._limit is not None:
+ sql.append("%s " % backend.get_limit_offset_sql(self._limit, self._offset))
+ else:
+ assert self._offset is None, "'offset' is not allowed without 'limit'"
+
+ return select, " ".join(sql), params
+
+class ValuesQuerySet(QuerySet):
+ def __init__(self, *args, **kwargs):
+ super(ValuesQuerySet, self).__init__(*args, **kwargs)
+ # select_related and select aren't supported in values().
+ self._select_related = False
+ self._select = {}
+
+ def iterator(self):
+ try:
+ select, sql, params = self._get_sql_clause()
+ except EmptyResultSet:
+ raise StopIteration
+
+ # self._fields is a list of field names to fetch.
+ if self._fields:
+ columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields]
+ field_names = self._fields
+ else: # Default to all fields.
+ columns = [f.column for f in self.model._meta.fields]
+ field_names = [f.attname for f in self.model._meta.fields]
+
+ select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
+ cursor = connection.cursor()
+ cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
+ while 1:
+ rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
+ if not rows:
+ raise StopIteration
+ for row in rows:
+ yield dict(zip(field_names, row))
+
+ def _clone(self, klass=None, **kwargs):
+ c = super(ValuesQuerySet, self)._clone(klass, **kwargs)
+ c._fields = self._fields[:]
+ return c
+
+class DateQuerySet(QuerySet):
+ def iterator(self):
+ from django.db.backends.util import typecast_timestamp
+ self._order_by = () # Clear this because it'll mess things up otherwise.
+ if self._field.null:
+ self._where.append('%s.%s IS NOT NULL' % \
+ (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
+
+ try:
+ select, sql, params = self._get_sql_clause()
+ except EmptyResultSet:
+ raise StopIteration
+
+ sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \
+ (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table),
+ backend.quote_name(self._field.column))), sql, self._order)
+ cursor = connection.cursor()
+ cursor.execute(sql, params)
+ # We have to manually run typecast_timestamp(str()) on the results, because
+ # MySQL doesn't automatically cast the result of date functions as datetime
+ # objects -- MySQL returns the values as strings, instead.
+ return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
+
+ def _clone(self, klass=None, **kwargs):
+ c = super(DateQuerySet, self)._clone(klass, **kwargs)
+ c._field = self._field
+ c._kind = self._kind
+ c._order = self._order
+ return c
+
+class EmptyQuerySet(QuerySet):
+ def __init__(self, model=None):
+ super(EmptyQuerySet, self).__init__(model)
+ self._result_cache = []
+
+ def count(self):
+ return 0
+
+ def delete(self):
+ pass
+
+ def _clone(self, klass=None, **kwargs):
+ c = super(EmptyQuerySet, self)._clone(klass, **kwargs)
+ c._result_cache = []
+ return c
+
+ def _get_sql_clause(self):
+ raise EmptyResultSet
+
+class QOperator(object):
+ "Base class for QAnd and QOr"
+ def __init__(self, *args):
+ self.args = args
+
+ def get_sql(self, opts):
+ joins, where, params = SortedDict(), [], []
+ for val in self.args:
+ try:
+ joins2, where2, params2 = val.get_sql(opts)
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ except EmptyResultSet:
+ if not isinstance(self, QOr):
+ raise EmptyResultSet
+ if where:
+ return joins, ['(%s)' % self.operator.join(where)], params
+ return joins, [], params
+
+class QAnd(QOperator):
+ "Encapsulates a combined query that uses 'AND'."
+ operator = ' AND '
+ def __or__(self, other):
+ return QOr(self, other)
+
+ def __and__(self, other):
+ if isinstance(other, QAnd):
+ return QAnd(*(self.args+other.args))
+ elif isinstance(other, (Q, QOr)):
+ return QAnd(*(self.args+(other,)))
+ else:
+ raise TypeError, other
+
+class QOr(QOperator):
+ "Encapsulates a combined query that uses 'OR'."
+ operator = ' OR '
+ def __and__(self, other):
+ return QAnd(self, other)
+
+ def __or__(self, other):
+ if isinstance(other, QOr):
+ return QOr(*(self.args+other.args))
+ elif isinstance(other, (Q, QAnd)):
+ return QOr(*(self.args+(other,)))
+ else:
+ raise TypeError, other
+
+class Q(object):
+ "Encapsulates queries as objects that can be combined logically."
+ def __init__(self, **kwargs):
+ self.kwargs = kwargs
+
+ def __and__(self, other):
+ return QAnd(self, other)
+
+ def __or__(self, other):
+ return QOr(self, other)
+
+ def get_sql(self, opts):
+ return parse_lookup(self.kwargs.items(), opts)
+
+class QNot(Q):
+ "Encapsulates NOT (...) queries as objects"
+ def __init__(self, q):
+ "Creates a negation of the q object passed in."
+ self.q = q
+
+ def get_sql(self, opts):
+ try:
+ joins, where, params = self.q.get_sql(opts)
+ where2 = ['(NOT (%s))' % " AND ".join(where)]
+ except EmptyResultSet:
+ return SortedDict(), [], []
+ return joins, where2, params
+
+def get_where_clause(lookup_type, table_prefix, field_name, value):
+ if table_prefix.endswith('.'):
+ table_prefix = backend.quote_name(table_prefix[:-1])+'.'
+ field_name = backend.quote_name(field_name)
+ try:
+ return '%s%s %s' % (table_prefix, field_name, (backend.OPERATOR_MAPPING[lookup_type] % '%s'))
+ except KeyError:
+ pass
+ if lookup_type == 'in':
+ in_string = ','.join(['%s' for id in value])
+ if in_string:
+ return '%s%s IN (%s)' % (table_prefix, field_name, in_string)
+ else:
+ raise EmptyResultSet
+ elif lookup_type in ('range', 'year'):
+ return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name)
+ elif lookup_type in ('month', 'day'):
+ return "%s = %%s" % backend.get_date_extract_sql(lookup_type, table_prefix + field_name)
+ elif lookup_type == 'isnull':
+ return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
+ elif lookup_type == 'search':
+ return backend.get_fulltext_search_sql(table_prefix + field_name)
+ raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
+
+def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0):
+ """Helper function that recursively returns an object with cache filled"""
+
+ # If we've got a max_depth set and we've exceeded that depth, bail now.
+ if max_depth and cur_depth > max_depth:
+ return None
+
+ index_end = index_start + len(klass._meta.fields)
+ obj = klass(*row[index_start:index_end])
+ for f in klass._meta.fields:
+ if f.rel and not f.null:
+ cached_row = get_cached_row(f.rel.to, row, index_end, max_depth, cur_depth+1)
+ if cached_row:
+ rel_obj, index_end = cached_row
+ setattr(obj, f.get_cache_name(), rel_obj)
+ return obj, index_end
+
+def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen, max_depth=0, cur_depth=0):
+ """
+ Helper function that recursively populates the select, tables and where (in
+ place) for select_related queries.
+ """
+
+ # If we've got a max_depth set and we've exceeded that depth, bail now.
+ if max_depth and cur_depth > max_depth:
+ return None
+
+ qn = backend.quote_name
+ for f in opts.fields:
+ if f.rel and not f.null:
+ db_table = f.rel.to._meta.db_table
+ if db_table not in cache_tables_seen:
+ tables.append(qn(db_table))
+ else: # The table was already seen, so give it a table alias.
+ new_prefix = '%s%s' % (db_table, len(cache_tables_seen))
+ tables.append('%s %s' % (qn(db_table), qn(new_prefix)))
+ db_table = new_prefix
+ cache_tables_seen.append(db_table)
+ where.append('%s.%s = %s.%s' % \
+ (qn(old_prefix), qn(f.column), qn(db_table), qn(f.rel.get_related_field().column)))
+ select.extend(['%s.%s' % (qn(db_table), qn(f2.column)) for f2 in f.rel.to._meta.fields])
+ fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen, max_depth, cur_depth+1)
+
+def parse_lookup(kwarg_items, opts):
+ # Helper function that handles converting API kwargs
+ # (e.g. "name__exact": "tom") to SQL.
+ # Returns a tuple of (joins, where, params).
+
+ # 'joins' is a sorted dictionary describing the tables that must be joined
+ # to complete the query. The dictionary is sorted because creation order
+ # is significant; it is a dictionary to ensure uniqueness of alias names.
+ #
+ # Each key-value pair follows the form
+ # alias: (table, join_type, condition)
+ # where
+ # alias is the AS alias for the joined table
+ # table is the actual table name to be joined
+ # join_type is the type of join (INNER JOIN, LEFT OUTER JOIN, etc)
+ # condition is the where-like statement over which narrows the join.
+ # alias will be derived from the lookup list name.
+ #
+ # At present, this method only every returns INNER JOINs; the option is
+ # there for others to implement custom Q()s, etc that return other join
+ # types.
+ joins, where, params = SortedDict(), [], []
+
+ for kwarg, value in kwarg_items:
+ path = kwarg.split(LOOKUP_SEPARATOR)
+ # Extract the last elements of the kwarg.
+ # The very-last is the lookup_type (equals, like, etc).
+ # The second-last is the table column on which the lookup_type is
+ # to be performed. If this name is 'pk', it will be substituted with
+ # the name of the primary key.
+ # If there is only one part, or the last part is not a query
+ # term, assume that the query is an __exact
+ lookup_type = path.pop()
+ if lookup_type == 'pk':
+ lookup_type = 'exact'
+ path.append(None)
+ elif len(path) == 0 or lookup_type not in QUERY_TERMS:
+ path.append(lookup_type)
+ lookup_type = 'exact'
+
+ if len(path) < 1:
+ raise TypeError, "Cannot parse keyword query %r" % kwarg
+
+ if value is None:
+ # Interpret '__exact=None' as the sql '= NULL'; otherwise, reject
+ # all uses of None as a query value.
+ if lookup_type != 'exact':
+ raise ValueError, "Cannot use None as a query value"
+
+ joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None)
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ return joins, where, params
+
+class FieldFound(Exception):
+ "Exception used to short circuit field-finding operations."
+ pass
+
+def find_field(name, field_list, related_query):
+ """
+ Finds a field with a specific name in a list of field instances.
+ Returns None if there are no matches, or several matches.
+ """
+ if related_query:
+ matches = [f for f in field_list if f.field.related_query_name() == name]
+ else:
+ matches = [f for f in field_list if f.name == name]
+ if len(matches) != 1:
+ return None
+ return matches[0]
+
+def lookup_inner(path, lookup_type, value, opts, table, column):
+ qn = backend.quote_name
+ joins, where, params = SortedDict(), [], []
+ current_opts = opts
+ current_table = table
+ current_column = column
+ intermediate_table = None
+ join_required = False
+
+ name = path.pop(0)
+ # Has the primary key been requested? If so, expand it out
+ # to be the name of the current class' primary key
+ if name is None or name == 'pk':
+ name = current_opts.pk.name
+
+ # Try to find the name in the fields associated with the current class
+ try:
+ # Does the name belong to a defined many-to-many field?
+ field = find_field(name, current_opts.many_to_many, False)
+ if field:
+ new_table = current_table + '__' + name
+ new_opts = field.rel.to._meta
+ new_column = new_opts.pk.column
+
+ # Need to create an intermediate table join over the m2m table
+ # This process hijacks current_table/column to point to the
+ # intermediate table.
+ current_table = "m2m_" + new_table
+ intermediate_table = field.m2m_db_table()
+ join_column = field.m2m_reverse_name()
+ intermediate_column = field.m2m_column_name()
+
+ raise FieldFound
+
+ # Does the name belong to a reverse defined many-to-many field?
+ field = find_field(name, current_opts.get_all_related_many_to_many_objects(), True)
+ if field:
+ new_table = current_table + '__' + name
+ new_opts = field.opts
+ new_column = new_opts.pk.column
+
+ # Need to create an intermediate table join over the m2m table.
+ # This process hijacks current_table/column to point to the
+ # intermediate table.
+ current_table = "m2m_" + new_table
+ intermediate_table = field.field.m2m_db_table()
+ join_column = field.field.m2m_column_name()
+ intermediate_column = field.field.m2m_reverse_name()
+
+ raise FieldFound
+
+ # Does the name belong to a one-to-many field?
+ field = find_field(name, current_opts.get_all_related_objects(), True)
+ if field:
+ new_table = table + '__' + name
+ new_opts = field.opts
+ new_column = field.field.column
+ join_column = opts.pk.column
+
+ # 1-N fields MUST be joined, regardless of any other conditions.
+ join_required = True
+
+ raise FieldFound
+
+ # Does the name belong to a one-to-one, many-to-one, or regular field?
+ field = find_field(name, current_opts.fields, False)
+ if field:
+ if field.rel: # One-to-One/Many-to-one field
+ new_table = current_table + '__' + name
+ new_opts = field.rel.to._meta
+ new_column = new_opts.pk.column
+ join_column = field.column
+ raise FieldFound
+ elif path:
+ # For regular fields, if there are still items on the path,
+ # an error has been made. We munge "name" so that the error
+ # properly identifies the cause of the problem.
+ name += LOOKUP_SEPARATOR + path[0]
+ else:
+ raise FieldFound
+
+ except FieldFound: # Match found, loop has been shortcut.
+ pass
+ else: # No match found.
+ raise TypeError, "Cannot resolve keyword '%s' into field" % name
+
+ # Check whether an intermediate join is required between current_table
+ # and new_table.
+ if intermediate_table:
+ joins[qn(current_table)] = (
+ qn(intermediate_table), "LEFT OUTER JOIN",
+ "%s.%s = %s.%s" % (qn(table), qn(current_opts.pk.column), qn(current_table), qn(intermediate_column))
+ )
+
+ if path:
+ # There are elements left in the path. More joins are required.
+ if len(path) == 1 and path[0] in (new_opts.pk.name, None) \
+ and lookup_type in ('exact', 'isnull') and not join_required:
+ # If the next and final name query is for a primary key,
+ # and the search is for isnull/exact, then the current
+ # (for N-1) or intermediate (for N-N) table can be used
+ # for the search. No need to join an extra table just
+ # to check the primary key.
+ new_table = current_table
+ else:
+ # There are 1 or more name queries pending, and we have ruled out
+ # any shortcuts; therefore, a join is required.
+ joins[qn(new_table)] = (
+ qn(new_opts.db_table), "INNER JOIN",
+ "%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column))
+ )
+ # If we have made the join, we don't need to tell subsequent
+ # recursive calls about the column name we joined on.
+ join_column = None
+
+ # There are name queries remaining. Recurse deeper.
+ joins2, where2, params2 = lookup_inner(path, lookup_type, value, new_opts, new_table, join_column)
+
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ else:
+ # No elements left in path. Current element is the element on which
+ # the search is being performed.
+
+ if join_required:
+ # Last query term is a RelatedObject
+ if field.field.rel.multiple:
+ # RelatedObject is from a 1-N relation.
+ # Join is required; query operates on joined table.
+ column = new_opts.pk.name
+ joins[qn(new_table)] = (
+ qn(new_opts.db_table), "INNER JOIN",
+ "%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column))
+ )
+ current_table = new_table
+ else:
+ # RelatedObject is from a 1-1 relation,
+ # No need to join; get the pk value from the related object,
+ # and compare using that.
+ column = current_opts.pk.name
+ elif intermediate_table:
+ # Last query term is a related object from an N-N relation.
+ # Join from intermediate table is sufficient.
+ column = join_column
+ elif name == current_opts.pk.name and lookup_type in ('exact', 'isnull') and current_column:
+ # Last query term is for a primary key. If previous iterations
+ # introduced a current/intermediate table that can be used to
+ # optimize the query, then use that table and column name.
+ column = current_column
+ else:
+ # Last query term was a normal field.
+ column = field.column
+
+ where.append(get_where_clause(lookup_type, current_table + '.', column, value))
+ params.extend(field.get_db_prep_lookup(lookup_type, value))
+
+ return joins, where, params
+
+def delete_objects(seen_objs):
+ "Iterate through a list of seen classes, and remove any instances that are referred to"
+ qn = backend.quote_name
+ ordered_classes = seen_objs.keys()
+ ordered_classes.reverse()
+
+ cursor = connection.cursor()
+
+ for cls in ordered_classes:
+ seen_objs[cls] = seen_objs[cls].items()
+ seen_objs[cls].sort()
+
+ # Pre notify all instances to be deleted
+ for pk_val, instance in seen_objs[cls]:
+ dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
+
+ pk_list = [pk for pk,instance in seen_objs[cls]]
+ for related in cls._meta.get_all_related_many_to_many_objects():
+ if not isinstance(related.field, GenericRelation):
+ for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
+ cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
+ (qn(related.field.m2m_db_table()),
+ qn(related.field.m2m_reverse_name()),
+ ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
+ pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
+ for f in cls._meta.many_to_many:
+ if isinstance(f, GenericRelation):
+ from django.contrib.contenttypes.models import ContentType
+ query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
+ args_extra = [ContentType.objects.get_for_model(cls).id]
+ else:
+ query_extra = ''
+ args_extra = []
+ for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
+ cursor.execute(("DELETE FROM %s WHERE %s IN (%s)" % \
+ (qn(f.m2m_db_table()), qn(f.m2m_column_name()),
+ ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]]))) + query_extra,
+ pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra)
+ for field in cls._meta.fields:
+ if field.rel and field.null and field.rel.to in seen_objs:
+ for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
+ cursor.execute("UPDATE %s SET %s=NULL WHERE %s IN (%s)" % \
+ (qn(cls._meta.db_table), qn(field.column), qn(cls._meta.pk.column),
+ ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
+ pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
+
+ # Now delete the actual data
+ for cls in ordered_classes:
+ seen_objs[cls].reverse()
+ pk_list = [pk for pk,instance in seen_objs[cls]]
+ for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
+ cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
+ (qn(cls._meta.db_table), qn(cls._meta.pk.column),
+ ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
+ pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
+
+ # Last cleanup; set NULLs where there once was a reference to the object,
+ # NULL the primary key of the found objects, and perform post-notification.
+ for pk_val, instance in seen_objs[cls]:
+ for field in cls._meta.fields:
+ if field.rel and field.null and field.rel.to in seen_objs:
+ setattr(instance, field.attname, None)
+
+ setattr(instance, cls._meta.pk.attname, None)
+ dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
+
+ transaction.commit_unless_managed()
diff --git a/google_appengine/lib/django/django/db/models/related.py b/google_appengine/lib/django/django/db/models/related.py
new file mode 100755
index 0000000..2c1dc5c
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/related.py
@@ -0,0 +1,142 @@
+class BoundRelatedObject(object):
+ def __init__(self, related_object, field_mapping, original):
+ self.relation = related_object
+ self.field_mappings = field_mapping[related_object.name]
+
+ def template_name(self):
+ raise NotImplementedError
+
+ def __repr__(self):
+ return repr(self.__dict__)
+
+class RelatedObject(object):
+ def __init__(self, parent_model, model, field):
+ self.parent_model = parent_model
+ self.model = model
+ self.opts = model._meta
+ self.field = field
+ self.edit_inline = field.rel.edit_inline
+ self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name)
+ self.var_name = self.opts.object_name.lower()
+
+ def flatten_data(self, follow, obj=None):
+ new_data = {}
+ rel_instances = self.get_list(obj)
+ for i, rel_instance in enumerate(rel_instances):
+ instance_data = {}
+ for f in self.opts.fields + self.opts.many_to_many:
+ # TODO: Fix for recursive manipulators.
+ fol = follow.get(f.name, None)
+ if fol:
+ field_data = f.flatten_data(fol, rel_instance)
+ for name, value in field_data.items():
+ instance_data['%s.%d.%s' % (self.var_name, i, name)] = value
+ new_data.update(instance_data)
+ return new_data
+
+ def extract_data(self, data):
+ """
+ Pull out the data meant for inline objects of this class,
+ i.e. anything starting with our module name.
+ """
+ return data # TODO
+
+ def get_list(self, parent_instance=None):
+ "Get the list of this type of object from an instance of the parent class."
+ if parent_instance is not None:
+ attr = getattr(parent_instance, self.get_accessor_name())
+ if self.field.rel.multiple:
+ # For many-to-many relationships, return a list of objects
+ # corresponding to the xxx_num_in_admin options of the field
+ objects = list(attr.all())
+
+ count = len(objects) + self.field.rel.num_extra_on_change
+ if self.field.rel.min_num_in_admin:
+ count = max(count, self.field.rel.min_num_in_admin)
+ if self.field.rel.max_num_in_admin:
+ count = min(count, self.field.rel.max_num_in_admin)
+
+ change = count - len(objects)
+ if change > 0:
+ return objects + [None] * change
+ if change < 0:
+ return objects[:change]
+ else: # Just right
+ return objects
+ else:
+ # A one-to-one relationship, so just return the single related
+ # object
+ return [attr]
+ else:
+ if self.field.rel.min_num_in_admin:
+ return [None] * max(self.field.rel.num_in_admin, self.field.rel.min_num_in_admin)
+ else:
+ return [None] * self.field.rel.num_in_admin
+
+ def get_db_prep_lookup(self, lookup_type, value):
+ # Defer to the actual field definition for db prep
+ return self.field.get_db_prep_lookup(lookup_type, value)
+
+ def editable_fields(self):
+ "Get the fields in this class that should be edited inline."
+ return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
+
+ def get_follow(self, override=None):
+ if isinstance(override, bool):
+ if override:
+ over = {}
+ else:
+ return None
+ else:
+ if override:
+ over = override.copy()
+ elif self.edit_inline:
+ over = {}
+ else:
+ return None
+
+ over[self.field.name] = False
+ return self.opts.get_follow(over)
+
+ def get_manipulator_fields(self, opts, manipulator, change, follow):
+ if self.field.rel.multiple:
+ if change:
+ attr = getattr(manipulator.original_object, self.get_accessor_name())
+ count = attr.count()
+ count += self.field.rel.num_extra_on_change
+ else:
+ count = self.field.rel.num_in_admin
+ if self.field.rel.min_num_in_admin:
+ count = max(count, self.field.rel.min_num_in_admin)
+ if self.field.rel.max_num_in_admin:
+ count = min(count, self.field.rel.max_num_in_admin)
+ else:
+ count = 1
+
+ fields = []
+ for i in range(count):
+ for f in self.opts.fields + self.opts.many_to_many:
+ if follow.get(f.name, False):
+ prefix = '%s.%d.' % (self.var_name, i)
+ fields.extend(f.get_manipulator_fields(self.opts, manipulator, change,
+ name_prefix=prefix, rel=True))
+ return fields
+
+ def __repr__(self):
+ return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
+
+ def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
+ return bound_related_object_class(self, field_mapping, original)
+
+ def get_accessor_name(self):
+ # This method encapsulates the logic that decides what name to give an
+ # accessor descriptor that retrieves related many-to-one or
+ # many-to-many objects. It uses the lower-cased object_name + "_set",
+ # but this can be overridden with the "related_name" option.
+ if self.field.rel.multiple:
+ # If this is a symmetrical m2m relation on self, there is no reverse accessor.
+ if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
+ return None
+ return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
+ else:
+ return self.field.rel.related_name or (self.opts.object_name.lower())
diff --git a/google_appengine/lib/django/django/db/models/signals.py b/google_appengine/lib/django/django/db/models/signals.py
new file mode 100755
index 0000000..2171cb1
--- /dev/null
+++ b/google_appengine/lib/django/django/db/models/signals.py
@@ -0,0 +1,12 @@
+class_prepared = object()
+
+pre_init= object()
+post_init = object()
+
+pre_save = object()
+post_save = object()
+
+pre_delete = object()
+post_delete = object()
+
+post_syncdb = object()
diff --git a/google_appengine/lib/django/django/db/transaction.py b/google_appengine/lib/django/django/db/transaction.py
new file mode 100755
index 0000000..4a0658e
--- /dev/null
+++ b/google_appengine/lib/django/django/db/transaction.py
@@ -0,0 +1,222 @@
+"""
+This module implements a transaction manager that can be used to define
+transaction handling in a request or view function. It is used by transaction
+control middleware and decorators.
+
+The transaction manager can be in managed or in auto state. Auto state means the
+system is using a commit-on-save strategy (actually it's more like
+commit-on-change). As soon as the .save() or .delete() (or related) methods are
+called, a commit is made.
+
+Managed transactions don't do those commits, but will need some kind of manual
+or implicit commits or rollbacks.
+"""
+
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
+from django.db import connection
+from django.conf import settings
+
+class TransactionManagementError(Exception):
+ """
+ This exception is thrown when something bad happens with transaction
+ management.
+ """
+ pass
+
+# The state is a dictionary of lists. The key to the dict is the current
+# thread and the list is handled as a stack of values.
+state = {}
+
+# The dirty flag is set by *_unless_managed functions to denote that the
+# code under transaction management has changed things to require a
+# database commit.
+dirty = {}
+
+def enter_transaction_management():
+ """
+ Enters transaction management for a running thread. It must be balanced with
+ the appropriate leave_transaction_management call, since the actual state is
+ managed as a stack.
+
+ The state and dirty flag are carried over from the surrounding block or
+ from the settings, if there is no surrounding block (dirty is always false
+ when no current block is running).
+ """
+ thread_ident = thread.get_ident()
+ if state.has_key(thread_ident) and state[thread_ident]:
+ state[thread_ident].append(state[thread_ident][-1])
+ else:
+ state[thread_ident] = []
+ state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
+ if not dirty.has_key(thread_ident):
+ dirty[thread_ident] = False
+
+def leave_transaction_management():
+ """
+ Leaves transaction management for a running thread. A dirty flag is carried
+ over to the surrounding block, as a commit will commit all changes, even
+ those from outside. (Commits are on connection level.)
+ """
+ thread_ident = thread.get_ident()
+ if state.has_key(thread_ident) and state[thread_ident]:
+ del state[thread_ident][-1]
+ else:
+ raise TransactionManagementError("This code isn't under transaction management")
+ if dirty.get(thread_ident, False):
+ rollback()
+ raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
+ dirty[thread_ident] = False
+
+def is_dirty():
+ """
+ Returns True if the current transaction requires a commit for changes to
+ happen.
+ """
+ return dirty.get(thread.get_ident(), False)
+
+def set_dirty():
+ """
+ Sets a dirty flag for the current thread and code streak. This can be used
+ to decide in a managed block of code to decide whether there are open
+ changes waiting for commit.
+ """
+ thread_ident = thread.get_ident()
+ if dirty.has_key(thread_ident):
+ dirty[thread_ident] = True
+ else:
+ raise TransactionManagementError("This code isn't under transaction management")
+
+def set_clean():
+ """
+ Resets a dirty flag for the current thread and code streak. This can be used
+ to decide in a managed block of code to decide whether a commit or rollback
+ should happen.
+ """
+ thread_ident = thread.get_ident()
+ if dirty.has_key(thread_ident):
+ dirty[thread_ident] = False
+ else:
+ raise TransactionManagementError("This code isn't under transaction management")
+
+def is_managed():
+ """
+ Checks whether the transaction manager is in manual or in auto state.
+ """
+ thread_ident = thread.get_ident()
+ if state.has_key(thread_ident):
+ if state[thread_ident]:
+ return state[thread_ident][-1]
+ return settings.TRANSACTIONS_MANAGED
+
+def managed(flag=True):
+ """
+ Puts the transaction manager into a manual state: managed transactions have
+ to be committed explicitly by the user. If you switch off transaction
+ management and there is a pending commit/rollback, the data will be
+ commited.
+ """
+ thread_ident = thread.get_ident()
+ top = state.get(thread_ident, None)
+ if top:
+ top[-1] = flag
+ if not flag and is_dirty():
+ connection._commit()
+ set_clean()
+ else:
+ raise TransactionManagementError("This code isn't under transaction management")
+
+def commit_unless_managed():
+ """
+ Commits changes if the system is not in managed transaction mode.
+ """
+ if not is_managed():
+ connection._commit()
+ else:
+ set_dirty()
+
+def rollback_unless_managed():
+ """
+ Rolls back changes if the system is not in managed transaction mode.
+ """
+ if not is_managed():
+ connection._rollback()
+ else:
+ set_dirty()
+
+def commit():
+ """
+ Does the commit itself and resets the dirty flag.
+ """
+ connection._commit()
+ set_clean()
+
+def rollback():
+ """
+ This function does the rollback itself and resets the dirty flag.
+ """
+ connection._rollback()
+ set_clean()
+
+##############
+# DECORATORS #
+##############
+
+def autocommit(func):
+ """
+ Decorator that activates commit on save. This is Django's default behavior;
+ this decorator is useful if you globally activated transaction management in
+ your settings file and want the default behavior in some view functions.
+ """
+ def _autocommit(*args, **kw):
+ try:
+ enter_transaction_management()
+ managed(False)
+ return func(*args, **kw)
+ finally:
+ leave_transaction_management()
+ return _autocommit
+
+def commit_on_success(func):
+ """
+ This decorator activates commit on response. This way, if the view function
+ runs successfully, a commit is made; if the viewfunc produces an exception,
+ a rollback is made. This is one of the most common ways to do transaction
+ control in web apps.
+ """
+ def _commit_on_success(*args, **kw):
+ try:
+ enter_transaction_management()
+ managed(True)
+ try:
+ res = func(*args, **kw)
+ except Exception, e:
+ if is_dirty():
+ rollback()
+ raise
+ else:
+ if is_dirty():
+ commit()
+ return res
+ finally:
+ leave_transaction_management()
+ return _commit_on_success
+
+def commit_manually(func):
+ """
+ Decorator that activates manual transaction control. It just disables
+ automatic transaction control and doesn't do any commit/rollback of its
+ own -- it's up to the user to call the commit and rollback functions
+ themselves.
+ """
+ def _commit_manually(*args, **kw):
+ try:
+ enter_transaction_management()
+ managed(True)
+ return func(*args, **kw)
+ finally:
+ leave_transaction_management()
+
+ return _commit_manually
diff --git a/google_appengine/lib/django/django/dispatch/__init__.py b/google_appengine/lib/django/django/dispatch/__init__.py
new file mode 100755
index 0000000..bccae2a
--- /dev/null
+++ b/google_appengine/lib/django/django/dispatch/__init__.py
@@ -0,0 +1,6 @@
+"""Multi-consumer multi-producer dispatching mechanism
+"""
+__version__ = "1.0.0"
+__author__ = "Patrick K. O'Brien"
+__license__ = "BSD-style, see license.txt for details"
+
diff --git a/google_appengine/lib/django/django/dispatch/dispatcher.py b/google_appengine/lib/django/django/dispatch/dispatcher.py
new file mode 100755
index 0000000..029c59f
--- /dev/null
+++ b/google_appengine/lib/django/django/dispatch/dispatcher.py
@@ -0,0 +1,495 @@
+"""Multiple-producer-multiple-consumer signal-dispatching
+
+dispatcher is the core of the PyDispatcher system,
+providing the primary API and the core logic for the
+system.
+
+Module attributes of note:
+
+ Any -- Singleton used to signal either "Any Sender" or
+ "Any Signal". See documentation of the _Any class.
+ Anonymous -- Singleton used to signal "Anonymous Sender"
+ See documentation of the _Anonymous class.
+
+Internal attributes:
+ WEAKREF_TYPES -- tuple of types/classes which represent
+ weak references to receivers, and thus must be de-
+ referenced on retrieval to retrieve the callable
+ object
+ connections -- { senderkey (id) : { signal : [receivers...]}}
+ senders -- { senderkey (id) : weakref(sender) }
+ used for cleaning up sender references on sender
+ deletion
+ sendersBack -- { receiverkey (id) : [senderkey (id)...] }
+ used for cleaning up receiver references on receiver
+ deletion, (considerably speeds up the cleanup process
+ vs. the original code.)
+"""
+import types, weakref
+from django.dispatch import saferef, robustapply, errors
+
+__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
+__cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $"
+__version__ = "$Revision: 1.9 $"[11:-2]
+
+
+class _Parameter:
+ """Used to represent default parameter values."""
+ def __repr__(self):
+ return self.__class__.__name__
+
+class _Any(_Parameter):
+ """Singleton used to signal either "Any Sender" or "Any Signal"
+
+ The Any object can be used with connect, disconnect,
+ send, or sendExact to signal that the parameter given
+ Any should react to all senders/signals, not just
+ a particular sender/signal.
+ """
+Any = _Any()
+
+class _Anonymous(_Parameter):
+ """Singleton used to signal "Anonymous Sender"
+
+ The Anonymous object is used to signal that the sender
+ of a message is not specified (as distinct from being
+ "any sender"). Registering callbacks for Anonymous
+ will only receive messages sent without senders. Sending
+ with anonymous will only send messages to those receivers
+ registered for Any or Anonymous.
+
+ Note:
+ The default sender for connect is Any, while the
+ default sender for send is Anonymous. This has
+ the effect that if you do not specify any senders
+ in either function then all messages are routed
+ as though there was a single sender (Anonymous)
+ being used everywhere.
+ """
+Anonymous = _Anonymous()
+
+WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
+
+connections = {}
+senders = {}
+sendersBack = {}
+
+
+def connect(receiver, signal=Any, sender=Any, weak=True):
+ """Connect receiver to sender for signal
+
+ receiver -- a callable Python object which is to receive
+ messages/signals/events. Receivers must be hashable
+ objects.
+
+ if weak is True, then receiver must be weak-referencable
+ (more precisely saferef.safeRef() must be able to create
+ a reference to the receiver).
+
+ Receivers are fairly flexible in their specification,
+ as the machinery in the robustApply module takes care
+ of most of the details regarding figuring out appropriate
+ subsets of the sent arguments to apply to a given
+ receiver.
+
+ Note:
+ if receiver is itself a weak reference (a callable),
+ it will be de-referenced by the system's machinery,
+ so *generally* weak references are not suitable as
+ receivers, though some use might be found for the
+ facility whereby a higher-level library passes in
+ pre-weakrefed receiver references.
+
+ signal -- the signal to which the receiver should respond
+
+ if Any, receiver will receive any signal from the
+ indicated sender (which might also be Any, but is not
+ necessarily Any).
+
+ Otherwise must be a hashable Python object other than
+ None (DispatcherError raised on None).
+
+ sender -- the sender to which the receiver should respond
+
+ if Any, receiver will receive the indicated signals
+ from any sender.
+
+ if Anonymous, receiver will only receive indicated
+ signals from send/sendExact which do not specify a
+ sender, or specify Anonymous explicitly as the sender.
+
+ Otherwise can be any python object.
+
+ weak -- whether to use weak references to the receiver
+ By default, the module will attempt to use weak
+ references to the receiver objects. If this parameter
+ is false, then strong references will be used.
+
+ returns None, may raise DispatcherTypeError
+ """
+ if signal is None:
+ raise errors.DispatcherTypeError(
+ 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
+ )
+ if weak:
+ receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
+ senderkey = id(sender)
+
+ signals = connections.setdefault(senderkey, {})
+
+ # Keep track of senders for cleanup.
+ # Is Anonymous something we want to clean up?
+ if sender not in (None, Anonymous, Any):
+ def remove(object, senderkey=senderkey):
+ _removeSender(senderkey=senderkey)
+ # Skip objects that can not be weakly referenced, which means
+ # they won't be automatically cleaned up, but that's too bad.
+ try:
+ weakSender = weakref.ref(sender, remove)
+ senders[senderkey] = weakSender
+ except:
+ pass
+
+ receiverID = id(receiver)
+ # get current set, remove any current references to
+ # this receiver in the set, including back-references
+ if signals.has_key(signal):
+ receivers = signals[signal]
+ _removeOldBackRefs(senderkey, signal, receiver, receivers)
+ else:
+ receivers = signals[signal] = []
+ try:
+ current = sendersBack.get( receiverID )
+ if current is None:
+ sendersBack[ receiverID ] = current = []
+ if senderkey not in current:
+ current.append(senderkey)
+ except:
+ pass
+
+ receivers.append(receiver)
+
+
+
+def disconnect(receiver, signal=Any, sender=Any, weak=True):
+ """Disconnect receiver from sender for signal
+
+ receiver -- the registered receiver to disconnect
+ signal -- the registered signal to disconnect
+ sender -- the registered sender to disconnect
+ weak -- the weakref state to disconnect
+
+ disconnect reverses the process of connect,
+ the semantics for the individual elements are
+ logically equivalent to a tuple of
+ (receiver, signal, sender, weak) used as a key
+ to be deleted from the internal routing tables.
+ (The actual process is slightly more complex
+ but the semantics are basically the same).
+
+ Note:
+ Using disconnect is not required to cleanup
+ routing when an object is deleted, the framework
+ will remove routes for deleted objects
+ automatically. It's only necessary to disconnect
+ if you want to stop routing to a live object.
+
+ returns None, may raise DispatcherTypeError or
+ DispatcherKeyError
+ """
+ if signal is None:
+ raise errors.DispatcherTypeError(
+ 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
+ )
+ if weak: receiver = saferef.safeRef(receiver)
+ senderkey = id(sender)
+ try:
+ signals = connections[senderkey]
+ receivers = signals[signal]
+ except KeyError:
+ raise errors.DispatcherKeyError(
+ """No receivers found for signal %r from sender %r""" %(
+ signal,
+ sender
+ )
+ )
+ try:
+ # also removes from receivers
+ _removeOldBackRefs(senderkey, signal, receiver, receivers)
+ except ValueError:
+ raise errors.DispatcherKeyError(
+ """No connection to receiver %s for signal %s from sender %s""" %(
+ receiver,
+ signal,
+ sender
+ )
+ )
+ _cleanupConnections(senderkey, signal)
+
+def getReceivers( sender = Any, signal = Any ):
+ """Get list of receivers from global tables
+
+ This utility function allows you to retrieve the
+ raw list of receivers from the connections table
+ for the given sender and signal pair.
+
+ Note:
+ there is no guarantee that this is the actual list
+ stored in the connections table, so the value
+ should be treated as a simple iterable/truth value
+ rather than, for instance a list to which you
+ might append new records.
+
+ Normally you would use liveReceivers( getReceivers( ...))
+ to retrieve the actual receiver objects as an iterable
+ object.
+ """
+ existing = connections.get(id(sender))
+ if existing is not None:
+ return existing.get(signal, [])
+ return []
+
+def liveReceivers(receivers):
+ """Filter sequence of receivers to get resolved, live receivers
+
+ This is a generator which will iterate over
+ the passed sequence, checking for weak references
+ and resolving them, then returning all live
+ receivers.
+ """
+ for receiver in receivers:
+ if isinstance( receiver, WEAKREF_TYPES):
+ # Dereference the weak reference.
+ receiver = receiver()
+ if receiver is not None:
+ yield receiver
+ else:
+ yield receiver
+
+
+
+def getAllReceivers( sender = Any, signal = Any ):
+ """Get list of all receivers from global tables
+
+ This gets all dereferenced receivers which should receive
+ the given signal from sender, each receiver should
+ be produced only once by the resulting generator
+ """
+ receivers = {}
+ # Get receivers that receive *this* signal from *this* sender.
+ # Add receivers that receive *any* signal from *this* sender.
+ # Add receivers that receive *this* signal from *any* sender.
+ # Add receivers that receive *any* signal from *any* sender.
+ l = []
+ i = id(sender)
+ if i in connections:
+ sender_receivers = connections[i]
+ if signal in sender_receivers:
+ l.extend(sender_receivers[signal])
+ if signal is not Any and Any in sender_receivers:
+ l.extend(sender_receivers[Any])
+
+ if sender is not Any:
+ i = id(Any)
+ if i in connections:
+ sender_receivers = connections[i]
+ if sender_receivers is not None:
+ if signal in sender_receivers:
+ l.extend(sender_receivers[signal])
+ if signal is not Any and Any in sender_receivers:
+ l.extend(sender_receivers[Any])
+
+ for receiver in l:
+ try:
+ if not receiver in receivers:
+ if isinstance(receiver, WEAKREF_TYPES):
+ receiver = receiver()
+ # this should only (rough guess) be possible if somehow, deref'ing
+ # triggered a wipe.
+ if receiver is None:
+ continue
+ receivers[receiver] = 1
+ yield receiver
+ except TypeError:
+ # dead weakrefs raise TypeError on hash...
+ pass
+
+def send(signal=Any, sender=Anonymous, *arguments, **named):
+ """Send signal from sender to all connected receivers.
+
+ signal -- (hashable) signal value, see connect for details
+
+ sender -- the sender of the signal
+
+ if Any, only receivers registered for Any will receive
+ the message.
+
+ if Anonymous, only receivers registered to receive
+ messages from Anonymous or Any will receive the message
+
+ Otherwise can be any python object (normally one
+ registered with a connect if you actually want
+ something to occur).
+
+ arguments -- positional arguments which will be passed to
+ *all* receivers. Note that this may raise TypeErrors
+ if the receivers do not allow the particular arguments.
+ Note also that arguments are applied before named
+ arguments, so they should be used with care.
+
+ named -- named arguments which will be filtered according
+ to the parameters of the receivers to only provide those
+ acceptable to the receiver.
+
+ Return a list of tuple pairs [(receiver, response), ... ]
+
+ if any receiver raises an error, the error propagates back
+ through send, terminating the dispatch loop, so it is quite
+ possible to not have all receivers called if a raises an
+ error.
+ """
+ # Call each receiver with whatever arguments it can accept.
+ # Return a list of tuple pairs [(receiver, response), ... ].
+ responses = []
+ for receiver in getAllReceivers(sender, signal):
+ response = robustapply.robustApply(
+ receiver,
+ signal=signal,
+ sender=sender,
+ *arguments,
+ **named
+ )
+ responses.append((receiver, response))
+ return responses
+
+
+def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
+ """Send signal only to those receivers registered for exact message
+
+ sendExact allows for avoiding Any/Anonymous registered
+ handlers, sending only to those receivers explicitly
+ registered for a particular signal on a particular
+ sender.
+ """
+ responses = []
+ for receiver in liveReceivers(getReceivers(sender, signal)):
+ response = robustapply.robustApply(
+ receiver,
+ signal=signal,
+ sender=sender,
+ *arguments,
+ **named
+ )
+ responses.append((receiver, response))
+ return responses
+
+
+def _removeReceiver(receiver):
+ """Remove receiver from connections."""
+ if not sendersBack:
+ # During module cleanup the mapping will be replaced with None
+ return False
+ backKey = id(receiver)
+ for senderkey in sendersBack.get(backKey,()):
+ try:
+ signals = connections[senderkey].keys()
+ except KeyError,err:
+ pass
+ else:
+ for signal in signals:
+ try:
+ receivers = connections[senderkey][signal]
+ except KeyError:
+ pass
+ else:
+ try:
+ receivers.remove( receiver )
+ except Exception, err:
+ pass
+ _cleanupConnections(senderkey, signal)
+ try:
+ del sendersBack[ backKey ]
+ except KeyError:
+ pass
+
+def _cleanupConnections(senderkey, signal):
+ """Delete any empty signals for senderkey. Delete senderkey if empty."""
+ try:
+ receivers = connections[senderkey][signal]
+ except:
+ pass
+ else:
+ if not receivers:
+ # No more connected receivers. Therefore, remove the signal.
+ try:
+ signals = connections[senderkey]
+ except KeyError:
+ pass
+ else:
+ del signals[signal]
+ if not signals:
+ # No more signal connections. Therefore, remove the sender.
+ _removeSender(senderkey)
+
+def _removeSender(senderkey):
+ """Remove senderkey from connections."""
+ _removeBackrefs(senderkey)
+
+ connections.pop(senderkey, None)
+ senders.pop(senderkey, None)
+
+
+def _removeBackrefs( senderkey):
+ """Remove all back-references to this senderkey"""
+ for receiver_list in connections.pop(senderkey, {}).values():
+ for receiver in receiver_list:
+ _killBackref( receiver, senderkey )
+
+
+def _removeOldBackRefs(senderkey, signal, receiver, receivers):
+ """Kill old sendersBack references from receiver
+
+ This guards against multiple registration of the same
+ receiver for a given signal and sender leaking memory
+ as old back reference records build up.
+
+ Also removes old receiver instance from receivers
+ """
+ try:
+ index = receivers.index(receiver)
+ # need to scan back references here and remove senderkey
+ except ValueError:
+ return False
+ else:
+ oldReceiver = receivers[index]
+ del receivers[index]
+ found = 0
+ signals = connections.get(signal)
+ if signals is not None:
+ for sig,recs in connections.get(signal,{}).iteritems():
+ if sig != signal:
+ for rec in recs:
+ if rec is oldReceiver:
+ found = 1
+ break
+ if not found:
+ _killBackref( oldReceiver, senderkey )
+ return True
+ return False
+
+
+def _killBackref( receiver, senderkey ):
+ """Do the actual removal of back reference from receiver to senderkey"""
+ receiverkey = id(receiver)
+ receivers_list = sendersBack.get( receiverkey, () )
+ while senderkey in receivers_list:
+ try:
+ receivers_list.remove( senderkey )
+ except:
+ break
+ if not receivers_list:
+ try:
+ del sendersBack[ receiverkey ]
+ except KeyError:
+ pass
+ return True
diff --git a/google_appengine/lib/django/django/dispatch/errors.py b/google_appengine/lib/django/django/dispatch/errors.py
new file mode 100755
index 0000000..a4c924d
--- /dev/null
+++ b/google_appengine/lib/django/django/dispatch/errors.py
@@ -0,0 +1,10 @@
+"""Error types for dispatcher mechanism
+"""
+
+class DispatcherError(Exception):
+ """Base class for all Dispatcher errors"""
+class DispatcherKeyError(KeyError, DispatcherError):
+ """Error raised when unknown (sender,signal) set specified"""
+class DispatcherTypeError(TypeError, DispatcherError):
+ """Error raised when inappropriate signal-type specified (None)"""
+
diff --git a/google_appengine/lib/django/django/dispatch/robust.py b/google_appengine/lib/django/django/dispatch/robust.py
new file mode 100755
index 0000000..8b1590d
--- /dev/null
+++ b/google_appengine/lib/django/django/dispatch/robust.py
@@ -0,0 +1,57 @@
+"""Module implementing error-catching version of send (sendRobust)"""
+from django.dispatch.dispatcher import Any, Anonymous, liveReceivers, getAllReceivers
+from django.dispatch.robustapply import robustApply
+
+def sendRobust(
+ signal=Any,
+ sender=Anonymous,
+ *arguments, **named
+):
+ """Send signal from sender to all connected receivers catching errors
+
+ signal -- (hashable) signal value, see connect for details
+
+ sender -- the sender of the signal
+
+ if Any, only receivers registered for Any will receive
+ the message.
+
+ if Anonymous, only receivers registered to receive
+ messages from Anonymous or Any will receive the message
+
+ Otherwise can be any python object (normally one
+ registered with a connect if you actually want
+ something to occur).
+
+ arguments -- positional arguments which will be passed to
+ *all* receivers. Note that this may raise TypeErrors
+ if the receivers do not allow the particular arguments.
+ Note also that arguments are applied before named
+ arguments, so they should be used with care.
+
+ named -- named arguments which will be filtered according
+ to the parameters of the receivers to only provide those
+ acceptable to the receiver.
+
+ Return a list of tuple pairs [(receiver, response), ... ]
+
+ if any receiver raises an error (specifically any subclass of Exception),
+ the error instance is returned as the result for that receiver.
+ """
+ # Call each receiver with whatever arguments it can accept.
+ # Return a list of tuple pairs [(receiver, response), ... ].
+ responses = []
+ for receiver in liveReceivers(getAllReceivers(sender, signal)):
+ try:
+ response = robustApply(
+ receiver,
+ signal=signal,
+ sender=sender,
+ *arguments,
+ **named
+ )
+ except Exception, err:
+ responses.append((receiver, err))
+ else:
+ responses.append((receiver, response))
+ return responses
diff --git a/google_appengine/lib/django/django/dispatch/robustapply.py b/google_appengine/lib/django/django/dispatch/robustapply.py
new file mode 100755
index 0000000..14ba2b5
--- /dev/null
+++ b/google_appengine/lib/django/django/dispatch/robustapply.py
@@ -0,0 +1,47 @@
+"""Robust apply mechanism
+
+Provides a function "call", which can sort out
+what arguments a given callable object can take,
+and subset the given arguments to match only
+those which are acceptable.
+"""
+
+def function( receiver ):
+ """Get function-like callable object for given receiver
+
+ returns (function_or_method, codeObject, fromMethod)
+
+ If fromMethod is true, then the callable already
+ has its first argument bound
+ """
+ if hasattr(receiver, '__call__'):
+ # receiver is a class instance; assume it is callable.
+ # Reassign receiver to the actual method that will be called.
+ if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'):
+ receiver = receiver.__call__
+ if hasattr( receiver, 'im_func' ):
+ # an instance-method...
+ return receiver, receiver.im_func.func_code, 1
+ elif not hasattr( receiver, 'func_code'):
+ raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))
+ return receiver, receiver.func_code, 0
+
+def robustApply(receiver, *arguments, **named):
+ """Call receiver with arguments and an appropriate subset of named
+ """
+ receiver, codeObject, startIndex = function( receiver )
+ acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
+ for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
+ if named.has_key( name ):
+ raise TypeError(
+ """Argument %r specified both positionally and as a keyword for calling %r"""% (
+ name, receiver,
+ )
+ )
+ if not (codeObject.co_flags & 8):
+ # fc does not have a **kwds type parameter, therefore
+ # remove unacceptable arguments.
+ for arg in named.keys():
+ if arg not in acceptable:
+ del named[arg]
+ return receiver(*arguments, **named)
diff --git a/google_appengine/lib/django/django/dispatch/saferef.py b/google_appengine/lib/django/django/dispatch/saferef.py
new file mode 100755
index 0000000..74b7a41
--- /dev/null
+++ b/google_appengine/lib/django/django/dispatch/saferef.py
@@ -0,0 +1,165 @@
+"""Refactored "safe reference" from dispatcher.py"""
+import weakref, traceback
+
+def safeRef(target, onDelete = None):
+ """Return a *safe* weak reference to a callable target
+
+ target -- the object to be weakly referenced, if it's a
+ bound method reference, will create a BoundMethodWeakref,
+ otherwise creates a simple weakref.
+ onDelete -- if provided, will have a hard reference stored
+ to the callable to be called after the safe reference
+ goes out of scope with the reference object, (either a
+ weakref or a BoundMethodWeakref) as argument.
+ """
+ if hasattr(target, 'im_self'):
+ if target.im_self is not None:
+ # Turn a bound method into a BoundMethodWeakref instance.
+ # Keep track of these instances for lookup by disconnect().
+ assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,)
+ reference = BoundMethodWeakref(
+ target=target,
+ onDelete=onDelete
+ )
+ return reference
+ if callable(onDelete):
+ return weakref.ref(target, onDelete)
+ else:
+ return weakref.ref( target )
+
+class BoundMethodWeakref(object):
+ """'Safe' and reusable weak references to instance methods
+
+ BoundMethodWeakref objects provide a mechanism for
+ referencing a bound method without requiring that the
+ method object itself (which is normally a transient
+ object) is kept alive. Instead, the BoundMethodWeakref
+ object keeps weak references to both the object and the
+ function which together define the instance method.
+
+ Attributes:
+ key -- the identity key for the reference, calculated
+ by the class's calculateKey method applied to the
+ target instance method
+ deletionMethods -- sequence of callable objects taking
+ single argument, a reference to this object which
+ will be called when *either* the target object or
+ target function is garbage collected (i.e. when
+ this object becomes invalid). These are specified
+ as the onDelete parameters of safeRef calls.
+ weakSelf -- weak reference to the target object
+ weakFunc -- weak reference to the target function
+
+ Class Attributes:
+ _allInstances -- class attribute pointing to all live
+ BoundMethodWeakref objects indexed by the class's
+ calculateKey(target) method applied to the target
+ objects. This weak value dictionary is used to
+ short-circuit creation so that multiple references
+ to the same (object, function) pair produce the
+ same BoundMethodWeakref instance.
+
+ """
+ _allInstances = weakref.WeakValueDictionary()
+ def __new__( cls, target, onDelete=None, *arguments,**named ):
+ """Create new instance or return current instance
+
+ Basically this method of construction allows us to
+ short-circuit creation of references to already-
+ referenced instance methods. The key corresponding
+ to the target is calculated, and if there is already
+ an existing reference, that is returned, with its
+ deletionMethods attribute updated. Otherwise the
+ new instance is created and registered in the table
+ of already-referenced methods.
+ """
+ key = cls.calculateKey(target)
+ current =cls._allInstances.get(key)
+ if current is not None:
+ current.deletionMethods.append( onDelete)
+ return current
+ else:
+ base = super( BoundMethodWeakref, cls).__new__( cls )
+ cls._allInstances[key] = base
+ base.__init__( target, onDelete, *arguments,**named)
+ return base
+ def __init__(self, target, onDelete=None):
+ """Return a weak-reference-like instance for a bound method
+
+ target -- the instance-method target for the weak
+ reference, must have im_self and im_func attributes
+ and be reconstructable via:
+ target.im_func.__get__( target.im_self )
+ which is true of built-in instance methods.
+ onDelete -- optional callback which will be called
+ when this weak reference ceases to be valid
+ (i.e. either the object or the function is garbage
+ collected). Should take a single argument,
+ which will be passed a pointer to this object.
+ """
+ def remove(weak, self=self):
+ """Set self.isDead to true when method or instance is destroyed"""
+ methods = self.deletionMethods[:]
+ del self.deletionMethods[:]
+ try:
+ del self.__class__._allInstances[ self.key ]
+ except KeyError:
+ pass
+ for function in methods:
+ try:
+ if callable( function ):
+ function( self )
+ except Exception, e:
+ try:
+ traceback.print_exc()
+ except AttributeError, err:
+ print '''Exception during saferef %s cleanup function %s: %s'''%(
+ self, function, e
+ )
+ self.deletionMethods = [onDelete]
+ self.key = self.calculateKey( target )
+ self.weakSelf = weakref.ref(target.im_self, remove)
+ self.weakFunc = weakref.ref(target.im_func, remove)
+ self.selfName = str(target.im_self)
+ self.funcName = str(target.im_func.__name__)
+ def calculateKey( cls, target ):
+ """Calculate the reference key for this reference
+
+ Currently this is a two-tuple of the id()'s of the
+ target object and the target function respectively.
+ """
+ return (id(target.im_self),id(target.im_func))
+ calculateKey = classmethod( calculateKey )
+ def __str__(self):
+ """Give a friendly representation of the object"""
+ return """%s( %s.%s )"""%(
+ self.__class__.__name__,
+ self.selfName,
+ self.funcName,
+ )
+ __repr__ = __str__
+ def __nonzero__( self ):
+ """Whether we are still a valid reference"""
+ return self() is not None
+ def __cmp__( self, other ):
+ """Compare with another reference"""
+ if not isinstance (other,self.__class__):
+ return cmp( self.__class__, type(other) )
+ return cmp( self.key, other.key)
+ def __call__(self):
+ """Return a strong reference to the bound method
+
+ If the target cannot be retrieved, then will
+ return None, otherwise returns a bound instance
+ method for our object and function.
+
+ Note:
+ You may call this method any number of times,
+ as it does not invalidate the reference.
+ """
+ target = self.weakSelf()
+ if target is not None:
+ function = self.weakFunc()
+ if function is not None:
+ return function.__get__(target)
+ return None
diff --git a/google_appengine/lib/django/django/forms/__init__.py b/google_appengine/lib/django/django/forms/__init__.py
new file mode 100755
index 0000000..68d3d24
--- /dev/null
+++ b/google_appengine/lib/django/django/forms/__init__.py
@@ -0,0 +1 @@
+from django.oldforms import *
diff --git a/google_appengine/lib/django/django/http/__init__.py b/google_appengine/lib/django/django/http/__init__.py
new file mode 100755
index 0000000..0ae90c4
--- /dev/null
+++ b/google_appengine/lib/django/django/http/__init__.py
@@ -0,0 +1,304 @@
+import os
+from Cookie import SimpleCookie
+from pprint import pformat
+from urllib import urlencode, quote
+from django.utils.datastructures import MultiValueDict
+
+RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
+
+try:
+ # The mod_python version is more efficient, so try importing it first.
+ from mod_python.util import parse_qsl
+except ImportError:
+ from cgi import parse_qsl
+
+class Http404(Exception):
+ pass
+
+class HttpRequest(object):
+ "A basic HTTP request"
+ def __init__(self):
+ self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
+ self.path = ''
+ self.method = None
+
+ def __repr__(self):
+ return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
+ (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
+ pformat(self.META))
+
+ def __getitem__(self, key):
+ for d in (self.POST, self.GET):
+ if d.has_key(key):
+ return d[key]
+ raise KeyError, "%s not found in either POST or GET" % key
+
+ def has_key(self, key):
+ return self.GET.has_key(key) or self.POST.has_key(key)
+
+ def get_full_path(self):
+ return ''
+
+ def is_secure(self):
+ return os.environ.get("HTTPS") == "on"
+
+def parse_file_upload(header_dict, post_data):
+ "Returns a tuple of (POST MultiValueDict, FILES MultiValueDict)"
+ import email, email.Message
+ from cgi import parse_header
+ raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
+ raw_message += '\r\n\r\n' + post_data
+ msg = email.message_from_string(raw_message)
+ POST = MultiValueDict()
+ FILES = MultiValueDict()
+ for submessage in msg.get_payload():
+ if submessage and isinstance(submessage, email.Message.Message):
+ name_dict = parse_header(submessage['Content-Disposition'])[1]
+ # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
+ # or {'name': 'blah'} for POST fields
+ # We assume all uploaded files have a 'filename' set.
+ if name_dict.has_key('filename'):
+ assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
+ if not name_dict['filename'].strip():
+ continue
+ # IE submits the full path, so trim everything but the basename.
+ # (We can't use os.path.basename because it expects Linux paths.)
+ filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
+ FILES.appendlist(name_dict['name'], {
+ 'filename': filename,
+ 'content-type': (submessage.has_key('Content-Type') and submessage['Content-Type'] or None),
+ 'content': submessage.get_payload(),
+ })
+ else:
+ POST.appendlist(name_dict['name'], submessage.get_payload())
+ return POST, FILES
+
+class QueryDict(MultiValueDict):
+ """A specialized MultiValueDict that takes a query string when initialized.
+ This is immutable unless you create a copy of it."""
+ def __init__(self, query_string, mutable=False):
+ MultiValueDict.__init__(self)
+ self._mutable = True
+ for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
+ self.appendlist(key, value)
+ self._mutable = mutable
+
+ def _assert_mutable(self):
+ if not self._mutable:
+ raise AttributeError, "This QueryDict instance is immutable"
+
+ def __setitem__(self, key, value):
+ self._assert_mutable()
+ MultiValueDict.__setitem__(self, key, value)
+
+ def __copy__(self):
+ result = self.__class__('', mutable=True)
+ for key, value in dict.items(self):
+ dict.__setitem__(result, key, value)
+ return result
+
+ def __deepcopy__(self, memo={}):
+ import copy
+ result = self.__class__('', mutable=True)
+ memo[id(self)] = result
+ for key, value in dict.items(self):
+ dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo))
+ return result
+
+ def setlist(self, key, list_):
+ self._assert_mutable()
+ MultiValueDict.setlist(self, key, list_)
+
+ def appendlist(self, key, value):
+ self._assert_mutable()
+ MultiValueDict.appendlist(self, key, value)
+
+ def update(self, other_dict):
+ self._assert_mutable()
+ MultiValueDict.update(self, other_dict)
+
+ def pop(self, key):
+ self._assert_mutable()
+ return MultiValueDict.pop(self, key)
+
+ def popitem(self):
+ self._assert_mutable()
+ return MultiValueDict.popitem(self)
+
+ def clear(self):
+ self._assert_mutable()
+ MultiValueDict.clear(self)
+
+ def setdefault(self, *args):
+ self._assert_mutable()
+ return MultiValueDict.setdefault(self, *args)
+
+ def copy(self):
+ "Returns a mutable copy of this object."
+ return self.__deepcopy__()
+
+ def urlencode(self):
+ output = []
+ for k, list_ in self.lists():
+ output.extend([urlencode({k: v}) for v in list_])
+ return '&'.join(output)
+
+def parse_cookie(cookie):
+ if cookie == '':
+ return {}
+ c = SimpleCookie()
+ c.load(cookie)
+ cookiedict = {}
+ for key in c.keys():
+ cookiedict[key] = c.get(key).value
+ return cookiedict
+
+class HttpResponse(object):
+ "A basic HTTP response, with content and dictionary-accessed headers"
+ def __init__(self, content='', mimetype=None):
+ from django.conf import settings
+ self._charset = settings.DEFAULT_CHARSET
+ if not mimetype:
+ mimetype = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, settings.DEFAULT_CHARSET)
+ if not isinstance(content, basestring) and hasattr(content, '__iter__'):
+ self._container = content
+ self._is_string = False
+ else:
+ self._container = [content]
+ self._is_string = True
+ self.headers = {'Content-Type': mimetype}
+ self.cookies = SimpleCookie()
+ self.status_code = 200
+
+ def __str__(self):
+ "Full HTTP message, including headers"
+ return '\n'.join(['%s: %s' % (key, value)
+ for key, value in self.headers.items()]) \
+ + '\n\n' + self.content
+
+ def __setitem__(self, header, value):
+ self.headers[header] = value
+
+ def __delitem__(self, header):
+ try:
+ del self.headers[header]
+ except KeyError:
+ pass
+
+ def __getitem__(self, header):
+ return self.headers[header]
+
+ def has_header(self, header):
+ "Case-insensitive check for a header"
+ header = header.lower()
+ for key in self.headers.keys():
+ if key.lower() == header:
+ return True
+ return False
+
+ def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
+ self.cookies[key] = value
+ for var in ('max_age', 'path', 'domain', 'secure', 'expires'):
+ val = locals()[var]
+ if val is not None:
+ self.cookies[key][var.replace('_', '-')] = val
+
+ def delete_cookie(self, key, path='/', domain=None):
+ self.cookies[key] = ''
+ if path is not None:
+ self.cookies[key]['path'] = path
+ if domain is not None:
+ self.cookies[key]['domain'] = domain
+ self.cookies[key]['expires'] = 0
+ self.cookies[key]['max-age'] = 0
+
+ def _get_content(self):
+ content = ''.join(self._container)
+ if isinstance(content, unicode):
+ content = content.encode(self._charset)
+ return content
+
+ def _set_content(self, value):
+ self._container = [value]
+ self._is_string = True
+
+ content = property(_get_content, _set_content)
+
+ def __iter__(self):
+ self._iterator = self._container.__iter__()
+ return self
+
+ def next(self):
+ chunk = self._iterator.next()
+ if isinstance(chunk, unicode):
+ chunk = chunk.encode(self._charset)
+ return chunk
+
+ def close(self):
+ if hasattr(self._container, 'close'):
+ self._container.close()
+
+ # The remaining methods partially implement the file-like object interface.
+ # See http://docs.python.org/lib/bltin-file-objects.html
+ def write(self, content):
+ if not self._is_string:
+ raise Exception, "This %s instance is not writable" % self.__class__
+ self._container.append(content)
+
+ def flush(self):
+ pass
+
+ def tell(self):
+ if not self._is_string:
+ raise Exception, "This %s instance cannot tell its position" % self.__class__
+ return sum([len(chunk) for chunk in self._container])
+
+class HttpResponseRedirect(HttpResponse):
+ def __init__(self, redirect_to):
+ HttpResponse.__init__(self)
+ self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
+ self.status_code = 302
+
+class HttpResponsePermanentRedirect(HttpResponse):
+ def __init__(self, redirect_to):
+ HttpResponse.__init__(self)
+ self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
+ self.status_code = 301
+
+class HttpResponseNotModified(HttpResponse):
+ def __init__(self):
+ HttpResponse.__init__(self)
+ self.status_code = 304
+
+class HttpResponseNotFound(HttpResponse):
+ def __init__(self, *args, **kwargs):
+ HttpResponse.__init__(self, *args, **kwargs)
+ self.status_code = 404
+
+class HttpResponseForbidden(HttpResponse):
+ def __init__(self, *args, **kwargs):
+ HttpResponse.__init__(self, *args, **kwargs)
+ self.status_code = 403
+
+class HttpResponseNotAllowed(HttpResponse):
+ def __init__(self, permitted_methods):
+ HttpResponse.__init__(self)
+ self['Allow'] = ', '.join(permitted_methods)
+ self.status_code = 405
+
+class HttpResponseGone(HttpResponse):
+ def __init__(self, *args, **kwargs):
+ HttpResponse.__init__(self, *args, **kwargs)
+ self.status_code = 410
+
+class HttpResponseServerError(HttpResponse):
+ def __init__(self, *args, **kwargs):
+ HttpResponse.__init__(self, *args, **kwargs)
+ self.status_code = 500
+
+def get_host(request):
+ "Gets the HTTP host from the environment or request headers."
+ host = request.META.get('HTTP_X_FORWARDED_HOST', '')
+ if not host:
+ host = request.META.get('HTTP_HOST', '')
+ return host
diff --git a/google_appengine/lib/django/django/middleware/__init__.py b/google_appengine/lib/django/django/middleware/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/__init__.py
diff --git a/google_appengine/lib/django/django/middleware/cache.py b/google_appengine/lib/django/django/middleware/cache.py
new file mode 100755
index 0000000..58800b2
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/cache.py
@@ -0,0 +1,84 @@
+from django.conf import settings
+from django.core.cache import cache
+from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers
+
+class CacheMiddleware(object):
+ """
+ Cache middleware. If this is enabled, each Django-powered page will be
+ cached for CACHE_MIDDLEWARE_SECONDS seconds. Cache is based on URLs.
+
+ Only parameter-less GET or HEAD-requests with status code 200 are cached.
+
+ If CACHE_MIDDLEWARE_ANONYMOUS_ONLY is set to True, only anonymous requests
+ (i.e., those node made by a logged-in user) will be cached. This is a
+ simple and effective way of avoiding the caching of the Django admin (and
+ any other user-specific content).
+
+ This middleware expects that a HEAD request is answered with a response
+ exactly like the corresponding GET request.
+
+ When a hit occurs, a shallow copy of the original response object is
+ returned from process_request.
+
+ Pages will be cached based on the contents of the request headers
+ listed in the response's "Vary" header. This means that pages shouldn't
+ change their "Vary" header.
+
+ This middleware also sets ETag, Last-Modified, Expires and Cache-Control
+ headers on the response object.
+ """
+ def __init__(self, cache_timeout=None, key_prefix=None, cache_anonymous_only=None):
+ self.cache_timeout = cache_timeout
+ if cache_timeout is None:
+ self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
+ self.key_prefix = key_prefix
+ if key_prefix is None:
+ self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+ if cache_anonymous_only is None:
+ self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
+ else:
+ self.cache_anonymous_only = cache_anonymous_only
+
+ def process_request(self, request):
+ "Checks whether the page is already cached and returns the cached version if available."
+ if self.cache_anonymous_only:
+ assert hasattr(request, 'user'), "The Django cache middleware with CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True requires authentication middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.auth.middleware.AuthenticationMiddleware' before the CacheMiddleware."
+
+ if not request.method in ('GET', 'HEAD') or request.GET:
+ request._cache_update_cache = False
+ return None # Don't bother checking the cache.
+
+ if self.cache_anonymous_only and request.user.is_authenticated():
+ request._cache_update_cache = False
+ return None # Don't cache requests from authenticated users.
+
+ cache_key = get_cache_key(request, self.key_prefix)
+ if cache_key is None:
+ request._cache_update_cache = True
+ return None # No cache information available, need to rebuild.
+
+ response = cache.get(cache_key, None)
+ if response is None:
+ request._cache_update_cache = True
+ return None # No cache information available, need to rebuild.
+
+ request._cache_update_cache = False
+ return response
+
+ def process_response(self, request, response):
+ "Sets the cache, if needed."
+ if not hasattr(request, '_cache_update_cache') or not request._cache_update_cache:
+ # We don't need to update the cache, just return.
+ return response
+ if request.method != 'GET':
+ # This is a stronger requirement than above. It is needed
+ # because of interactions between this middleware and the
+ # HTTPMiddleware, which throws the body of a HEAD-request
+ # away before this middleware gets a chance to cache it.
+ return response
+ if not response.status_code == 200:
+ return response
+ patch_response_headers(response, self.cache_timeout)
+ cache_key = learn_cache_key(request, response, self.cache_timeout, self.key_prefix)
+ cache.set(cache_key, response, self.cache_timeout)
+ return response
diff --git a/google_appengine/lib/django/django/middleware/common.py b/google_appengine/lib/django/django/middleware/common.py
new file mode 100755
index 0000000..6283214
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/common.py
@@ -0,0 +1,96 @@
+from django.conf import settings
+from django import http
+from django.core.mail import mail_managers
+import md5
+import re
+
+class CommonMiddleware(object):
+ """
+ "Common" middleware for taking care of some basic operations:
+
+ - Forbids access to User-Agents in settings.DISALLOWED_USER_AGENTS
+
+ - URL rewriting: Based on the APPEND_SLASH and PREPEND_WWW settings,
+ this middleware appends missing slashes and/or prepends missing "www."s.
+
+ - ETags: If the USE_ETAGS setting is set, ETags will be calculated from
+ the entire page content and Not Modified responses will be returned
+ appropriately.
+ """
+
+ def process_request(self, request):
+ """
+ Check for denied User-Agents and rewrite the URL based on
+ settings.APPEND_SLASH and settings.PREPEND_WWW
+ """
+
+ # Check for denied User-Agents
+ if request.META.has_key('HTTP_USER_AGENT'):
+ for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
+ if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
+ return http.HttpResponseForbidden('<h1>Forbidden</h1>')
+
+ # Check for a redirect based on settings.APPEND_SLASH and settings.PREPEND_WWW
+ host = http.get_host(request)
+ old_url = [host, request.path]
+ new_url = old_url[:]
+ if settings.PREPEND_WWW and old_url[0] and not old_url[0].startswith('www.'):
+ new_url[0] = 'www.' + old_url[0]
+ # Append a slash if append_slash is set and the URL doesn't have a
+ # trailing slash or a file extension.
+ if settings.APPEND_SLASH and (old_url[1][-1] != '/') and ('.' not in old_url[1].split('/')[-1]):
+ new_url[1] = new_url[1] + '/'
+ if settings.DEBUG and request.method == 'POST':
+ raise RuntimeError, "You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to %s%s (note the trailing slash), or set APPEND_SLASH=False in your Django settings." % (new_url[0], new_url[1])
+ if new_url != old_url:
+ # Redirect
+ if new_url[0]:
+ newurl = "%s://%s%s" % (request.is_secure() and 'https' or 'http', new_url[0], new_url[1])
+ else:
+ newurl = new_url[1]
+ if request.GET:
+ newurl += '?' + request.GET.urlencode()
+ return http.HttpResponsePermanentRedirect(newurl)
+
+ return None
+
+ def process_response(self, request, response):
+ "Check for a flat page (for 404s) and calculate the Etag, if needed."
+ if response.status_code == 404:
+ if settings.SEND_BROKEN_LINK_EMAILS:
+ # If the referrer was from an internal link or a non-search-engine site,
+ # send a note to the managers.
+ domain = http.get_host(request)
+ referer = request.META.get('HTTP_REFERER', None)
+ is_internal = _is_internal_request(domain, referer)
+ path = request.get_full_path()
+ if referer and not _is_ignorable_404(path) and (is_internal or '?' not in referer):
+ ua = request.META.get('HTTP_USER_AGENT', '<none>')
+ mail_managers("Broken %slink on %s" % ((is_internal and 'INTERNAL ' or ''), domain),
+ "Referrer: %s\nRequested URL: %s\nUser agent: %s\n" % (referer, request.get_full_path(), ua))
+ return response
+
+ # Use ETags, if requested.
+ if settings.USE_ETAGS:
+ etag = md5.new(response.content).hexdigest()
+ if request.META.get('HTTP_IF_NONE_MATCH') == etag:
+ response = http.HttpResponseNotModified()
+ else:
+ response['ETag'] = etag
+
+ return response
+
+def _is_ignorable_404(uri):
+ "Returns True if a 404 at the given URL *shouldn't* notify the site managers"
+ for start in settings.IGNORABLE_404_STARTS:
+ if uri.startswith(start):
+ return True
+ for end in settings.IGNORABLE_404_ENDS:
+ if uri.endswith(end):
+ return True
+ return False
+
+def _is_internal_request(domain, referer):
+ "Return true if the referring URL is the same domain as the current request"
+ # Different subdomains are treated as different domains.
+ return referer is not None and re.match("^https?://%s/" % re.escape(domain), referer)
diff --git a/google_appengine/lib/django/django/middleware/doc.py b/google_appengine/lib/django/django/middleware/doc.py
new file mode 100755
index 0000000..48c155c
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/doc.py
@@ -0,0 +1,18 @@
+from django.conf import settings
+from django import http
+
+class XViewMiddleware(object):
+ """
+ Adds an X-View header to internal HEAD requests -- used by the documentation system.
+ """
+ def process_view(self, request, view_func, view_args, view_kwargs):
+ """
+ If the request method is HEAD and either the IP is internal or the
+ user is a logged-in staff member, quickly return with an x-header
+ indicating the view function. This is used by the documentation module
+ to lookup the view function for an arbitrary page.
+ """
+ if request.method == 'HEAD' and (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (request.user.is_authenticated() and request.user.is_staff)):
+ response = http.HttpResponse()
+ response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__)
+ return response
diff --git a/google_appengine/lib/django/django/middleware/gzip.py b/google_appengine/lib/django/django/middleware/gzip.py
new file mode 100755
index 0000000..a7c7448
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/gzip.py
@@ -0,0 +1,29 @@
+import re
+from django.utils.text import compress_string
+from django.utils.cache import patch_vary_headers
+
+re_accepts_gzip = re.compile(r'\bgzip\b')
+
+class GZipMiddleware(object):
+ """
+ This middleware compresses content if the browser allows gzip compression.
+ It sets the Vary header accordingly, so that caches will base their storage
+ on the Accept-Encoding header.
+ """
+ def process_response(self, request, response):
+ patch_vary_headers(response, ('Accept-Encoding',))
+
+ # Avoid gzipping if we've already got a content-encoding or if the
+ # content-type is Javascript (silly IE...)
+ is_js = "javascript" in response.headers.get('Content-Type', '').lower()
+ if response.has_header('Content-Encoding') or is_js:
+ return response
+
+ ae = request.META.get('HTTP_ACCEPT_ENCODING', '')
+ if not re_accepts_gzip.search(ae):
+ return response
+
+ response.content = compress_string(response.content)
+ response['Content-Encoding'] = 'gzip'
+ response['Content-Length'] = str(len(response.content))
+ return response
diff --git a/google_appengine/lib/django/django/middleware/http.py b/google_appengine/lib/django/django/middleware/http.py
new file mode 100755
index 0000000..3ebd8ff
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/http.py
@@ -0,0 +1,61 @@
+import datetime
+
+class ConditionalGetMiddleware(object):
+ """
+ Handles conditional GET operations. If the response has a ETag or
+ Last-Modified header, and the request has If-None-Match or
+ If-Modified-Since, the response is replaced by an HttpNotModified.
+
+ Removes the content from any response to a HEAD request.
+
+ Also sets the Date and Content-Length response-headers.
+ """
+ def process_response(self, request, response):
+ now = datetime.datetime.utcnow()
+ response['Date'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
+ if not response.has_header('Content-Length'):
+ response['Content-Length'] = str(len(response.content))
+
+ if response.has_header('ETag'):
+ if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None)
+ if if_none_match == response['ETag']:
+ response.status_code = 304
+ response.content = ''
+ response['Content-Length'] = '0'
+
+ if response.has_header('Last-Modified'):
+ last_mod = response['Last-Modified']
+ if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
+ if if_modified_since == response['Last-Modified']:
+ response.status_code = 304
+ response.content = ''
+ response['Content-Length'] = '0'
+
+ if request.method == 'HEAD':
+ response.content = ''
+
+ return response
+
+class SetRemoteAddrFromForwardedFor(object):
+ """
+ Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the
+ latter is set. This is useful if you're sitting behind a reverse proxy that
+ causes each request's REMOTE_ADDR to be set to 127.0.0.1.
+
+ Note that this does NOT validate HTTP_X_FORWARDED_FOR. If you're not behind
+ a reverse proxy that sets HTTP_X_FORWARDED_FOR automatically, do not use
+ this middleware. Anybody can spoof the value of HTTP_X_FORWARDED_FOR, and
+ because this sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, that means
+ anybody can "fake" their IP address. Only use this when you can absolutely
+ trust the value of HTTP_X_FORWARDED_FOR.
+ """
+ def process_request(self, request):
+ try:
+ real_ip = request.META['HTTP_X_FORWARDED_FOR']
+ except KeyError:
+ return None
+ else:
+ # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
+ # Take just the first one.
+ real_ip = real_ip.split(",")[0]
+ request.META['REMOTE_ADDR'] = real_ip
diff --git a/google_appengine/lib/django/django/middleware/locale.py b/google_appengine/lib/django/django/middleware/locale.py
new file mode 100755
index 0000000..dd154e1
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/locale.py
@@ -0,0 +1,24 @@
+"this is the locale selecting middleware that will look at accept headers"
+
+from django.utils.cache import patch_vary_headers
+from django.utils import translation
+
+class LocaleMiddleware(object):
+ """
+ This is a very simple middleware that parses a request
+ and decides what translation object to install in the current
+ thread context. This allows pages to be dynamically
+ translated to the language the user desires (if the language
+ is available, of course).
+ """
+
+ def process_request(self, request):
+ language = translation.get_language_from_request(request)
+ translation.activate(language)
+ request.LANGUAGE_CODE = translation.get_language()
+
+ def process_response(self, request, response):
+ patch_vary_headers(response, ('Accept-Language',))
+ response['Content-Language'] = translation.get_language()
+ translation.deactivate()
+ return response
diff --git a/google_appengine/lib/django/django/middleware/transaction.py b/google_appengine/lib/django/django/middleware/transaction.py
new file mode 100755
index 0000000..96b1538
--- /dev/null
+++ b/google_appengine/lib/django/django/middleware/transaction.py
@@ -0,0 +1,27 @@
+from django.db import transaction
+
+class TransactionMiddleware(object):
+ """
+ Transaction middleware. If this is enabled, each view function will be run
+ with commit_on_response activated - that way a save() doesn't do a direct
+ commit, the commit is done when a successful response is created. If an
+ exception happens, the database is rolled back.
+ """
+ def process_request(self, request):
+ """Enters transaction management"""
+ transaction.enter_transaction_management()
+ transaction.managed(True)
+
+ def process_exception(self, request, exception):
+ """Rolls back the database and leaves transaction management"""
+ if transaction.is_dirty():
+ transaction.rollback()
+ transaction.leave_transaction_management()
+
+ def process_response(self, request, response):
+ """Commits and leaves transaction management."""
+ if transaction.is_managed():
+ if transaction.is_dirty():
+ transaction.commit()
+ transaction.leave_transaction_management()
+ return response
diff --git a/google_appengine/lib/django/django/newforms/__init__.py b/google_appengine/lib/django/django/newforms/__init__.py
new file mode 100755
index 0000000..0d9c68f
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/__init__.py
@@ -0,0 +1,17 @@
+"""
+Django validation and HTML form handling.
+
+TODO:
+ Default value for field
+ Field labels
+ Nestable Forms
+ FatalValidationError -- short-circuits all other validators on a form
+ ValidationWarning
+ "This form field requires foo.js" and form.js_includes()
+"""
+
+from util import ValidationError
+from widgets import *
+from fields import *
+from forms import *
+from models import *
diff --git a/google_appengine/lib/django/django/newforms/extras/__init__.py b/google_appengine/lib/django/django/newforms/extras/__init__.py
new file mode 100755
index 0000000..a7f6a9b
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/extras/__init__.py
@@ -0,0 +1 @@
+from widgets import *
diff --git a/google_appengine/lib/django/django/newforms/extras/widgets.py b/google_appengine/lib/django/django/newforms/extras/widgets.py
new file mode 100755
index 0000000..1011934
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/extras/widgets.py
@@ -0,0 +1,59 @@
+"""
+Extra HTML Widget classes
+"""
+
+from django.newforms.widgets import Widget, Select
+from django.utils.dates import MONTHS
+import datetime
+
+__all__ = ('SelectDateWidget',)
+
+class SelectDateWidget(Widget):
+ """
+ A Widget that splits date input into three <select> boxes.
+
+ This also serves as an example of a Widget that has more than one HTML
+ element and hence implements value_from_datadict.
+ """
+ month_field = '%s_month'
+ day_field = '%s_day'
+ year_field = '%s_year'
+
+ def __init__(self, attrs=None, years=None):
+ # years is an optional list/tuple of years to use in the "year" select box.
+ self.attrs = attrs or {}
+ if years:
+ self.years = years
+ else:
+ this_year = datetime.date.today().year
+ self.years = range(this_year, this_year+10)
+
+ def render(self, name, value, attrs=None):
+ try:
+ value = datetime.date(*map(int, value.split('-')))
+ year_val, month_val, day_val = value.year, value.month, value.day
+ except (AttributeError, TypeError, ValueError):
+ year_val = month_val = day_val = None
+
+ output = []
+
+ month_choices = MONTHS.items()
+ month_choices.sort()
+ select_html = Select(choices=month_choices).render(self.month_field % name, month_val)
+ output.append(select_html)
+
+ day_choices = [(i, i) for i in range(1, 32)]
+ select_html = Select(choices=day_choices).render(self.day_field % name, day_val)
+ output.append(select_html)
+
+ year_choices = [(i, i) for i in self.years]
+ select_html = Select(choices=year_choices).render(self.year_field % name, year_val)
+ output.append(select_html)
+
+ return u'\n'.join(output)
+
+ def value_from_datadict(self, data, name):
+ y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name)
+ if y and m and d:
+ return '%s-%s-%s' % (y, m, d)
+ return None
diff --git a/google_appengine/lib/django/django/newforms/fields.py b/google_appengine/lib/django/django/newforms/fields.py
new file mode 100755
index 0000000..8e3da03
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/fields.py
@@ -0,0 +1,492 @@
+"""
+Field classes
+"""
+
+from django.utils.translation import gettext
+from util import ErrorList, ValidationError, smart_unicode
+from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple
+import datetime
+import re
+import time
+
+__all__ = (
+ 'Field', 'CharField', 'IntegerField',
+ 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
+ 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
+ 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
+ 'RegexField', 'EmailField', 'URLField', 'BooleanField',
+ 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
+ 'ComboField', 'MultiValueField',
+ 'SplitDateTimeField',
+)
+
+# These values, if given to to_python(), will trigger the self.required check.
+EMPTY_VALUES = (None, '')
+
+try:
+ set # Only available in Python 2.4+
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
+
+class Field(object):
+ widget = TextInput # Default widget to use when rendering this type of Field.
+ hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
+
+ # Tracks each time a Field instance is created. Used to retain order.
+ creation_counter = 0
+
+ def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None):
+ # required -- Boolean that specifies whether the field is required.
+ # True by default.
+ # widget -- A Widget class, or instance of a Widget class, that should be
+ # used for this Field when displaying it. Each Field has a default
+ # Widget that it'll use if you don't specify this. In most cases,
+ # the default widget is TextInput.
+ # label -- A verbose name for this field, for use in displaying this field in
+ # a form. By default, Django will use a "pretty" version of the form
+ # field name, if the Field is part of a Form.
+ # initial -- A value to use in this Field's initial display. This value is
+ # *not* used as a fallback if data isn't given.
+ # help_text -- An optional string to use as "help text" for this Field.
+ if label is not None:
+ label = smart_unicode(label)
+ self.required, self.label, self.initial = required, label, initial
+ self.help_text = smart_unicode(help_text or '')
+ widget = widget or self.widget
+ if isinstance(widget, type):
+ widget = widget()
+
+ # Hook into self.widget_attrs() for any Field-specific HTML attributes.
+ extra_attrs = self.widget_attrs(widget)
+ if extra_attrs:
+ widget.attrs.update(extra_attrs)
+
+ self.widget = widget
+
+ # Increase the creation counter, and save our local copy.
+ self.creation_counter = Field.creation_counter
+ Field.creation_counter += 1
+
+ def clean(self, value):
+ """
+ Validates the given value and returns its "cleaned" value as an
+ appropriate Python object.
+
+ Raises ValidationError for any errors.
+ """
+ if self.required and value in EMPTY_VALUES:
+ raise ValidationError(gettext(u'This field is required.'))
+ return value
+
+ def widget_attrs(self, widget):
+ """
+ Given a Widget instance (*not* a Widget class), returns a dictionary of
+ any HTML attributes that should be added to the Widget, based on this
+ Field.
+ """
+ return {}
+
+class CharField(Field):
+ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
+ self.max_length, self.min_length = max_length, min_length
+ super(CharField, self).__init__(*args, **kwargs)
+
+ def clean(self, value):
+ "Validates max_length and min_length. Returns a Unicode object."
+ super(CharField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ value = smart_unicode(value)
+ if self.max_length is not None and len(value) > self.max_length:
+ raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
+ if self.min_length is not None and len(value) < self.min_length:
+ raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
+ return value
+
+ def widget_attrs(self, widget):
+ if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
+ return {'maxlength': str(self.max_length)}
+
+class IntegerField(Field):
+ def __init__(self, max_value=None, min_value=None, *args, **kwargs):
+ self.max_value, self.min_value = max_value, min_value
+ super(IntegerField, self).__init__(*args, **kwargs)
+
+ def clean(self, value):
+ """
+ Validates that int() can be called on the input. Returns the result
+ of int(). Returns None for empty values.
+ """
+ super(IntegerField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ try:
+ value = int(value)
+ except (ValueError, TypeError):
+ raise ValidationError(gettext(u'Enter a whole number.'))
+ if self.max_value is not None and value > self.max_value:
+ raise ValidationError(gettext(u'Ensure this value is less than or equal to %s.') % self.max_value)
+ if self.min_value is not None and value < self.min_value:
+ raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
+ return value
+
+DEFAULT_DATE_INPUT_FORMATS = (
+ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
+ '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
+ '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
+ '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
+ '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
+)
+
+class DateField(Field):
+ def __init__(self, input_formats=None, *args, **kwargs):
+ super(DateField, self).__init__(*args, **kwargs)
+ self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
+
+ def clean(self, value):
+ """
+ Validates that the input can be converted to a date. Returns a Python
+ datetime.date object.
+ """
+ super(DateField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ if isinstance(value, datetime.datetime):
+ return value.date()
+ if isinstance(value, datetime.date):
+ return value
+ for format in self.input_formats:
+ try:
+ return datetime.date(*time.strptime(value, format)[:3])
+ except ValueError:
+ continue
+ raise ValidationError(gettext(u'Enter a valid date.'))
+
+DEFAULT_TIME_INPUT_FORMATS = (
+ '%H:%M:%S', # '14:30:59'
+ '%H:%M', # '14:30'
+)
+
+class TimeField(Field):
+ def __init__(self, input_formats=None, *args, **kwargs):
+ super(TimeField, self).__init__(*args, **kwargs)
+ self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
+
+ def clean(self, value):
+ """
+ Validates that the input can be converted to a time. Returns a Python
+ datetime.time object.
+ """
+ super(TimeField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ if isinstance(value, datetime.time):
+ return value
+ for format in self.input_formats:
+ try:
+ return datetime.time(*time.strptime(value, format)[3:6])
+ except ValueError:
+ continue
+ raise ValidationError(gettext(u'Enter a valid time.'))
+
+DEFAULT_DATETIME_INPUT_FORMATS = (
+ '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
+ '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
+ '%Y-%m-%d', # '2006-10-25'
+ '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
+ '%m/%d/%Y %H:%M', # '10/25/2006 14:30'
+ '%m/%d/%Y', # '10/25/2006'
+ '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
+ '%m/%d/%y %H:%M', # '10/25/06 14:30'
+ '%m/%d/%y', # '10/25/06'
+)
+
+class DateTimeField(Field):
+ def __init__(self, input_formats=None, *args, **kwargs):
+ super(DateTimeField, self).__init__(*args, **kwargs)
+ self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
+
+ def clean(self, value):
+ """
+ Validates that the input can be converted to a datetime. Returns a
+ Python datetime.datetime object.
+ """
+ super(DateTimeField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ if isinstance(value, datetime.datetime):
+ return value
+ if isinstance(value, datetime.date):
+ return datetime.datetime(value.year, value.month, value.day)
+ for format in self.input_formats:
+ try:
+ return datetime.datetime(*time.strptime(value, format)[:6])
+ except ValueError:
+ continue
+ raise ValidationError(gettext(u'Enter a valid date/time.'))
+
+class RegexField(Field):
+ def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
+ """
+ regex can be either a string or a compiled regular expression object.
+ error_message is an optional error message to use, if
+ 'Enter a valid value' is too generic for you.
+ """
+ super(RegexField, self).__init__(*args, **kwargs)
+ if isinstance(regex, basestring):
+ regex = re.compile(regex)
+ self.regex = regex
+ self.max_length, self.min_length = max_length, min_length
+ self.error_message = error_message or gettext(u'Enter a valid value.')
+
+ def clean(self, value):
+ """
+ Validates that the input matches the regular expression. Returns a
+ Unicode object.
+ """
+ super(RegexField, self).clean(value)
+ if value in EMPTY_VALUES:
+ value = u''
+ value = smart_unicode(value)
+ if value == u'':
+ return value
+ if self.max_length is not None and len(value) > self.max_length:
+ raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
+ if self.min_length is not None and len(value) < self.min_length:
+ raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
+ if not self.regex.search(value):
+ raise ValidationError(self.error_message)
+ return value
+
+email_re = re.compile(
+ r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
+ r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
+
+class EmailField(RegexField):
+ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
+ RegexField.__init__(self, email_re, max_length, min_length,
+ gettext(u'Enter a valid e-mail address.'), *args, **kwargs)
+
+url_re = re.compile(
+ r'^https?://' # http:// or https://
+ r'(?:[A-Z0-9-]+\.)+[A-Z]{2,6}' # domain
+ r'(?::\d+)?' # optional port
+ r'(?:/?|/\S+)$', re.IGNORECASE)
+
+try:
+ from django.conf import settings
+ URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
+except ImportError:
+ # It's OK if Django settings aren't configured.
+ URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
+
+class URLField(RegexField):
+ def __init__(self, max_length=None, min_length=None, verify_exists=False,
+ validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
+ super(URLField, self).__init__(url_re, max_length, min_length, gettext(u'Enter a valid URL.'), *args, **kwargs)
+ self.verify_exists = verify_exists
+ self.user_agent = validator_user_agent
+
+ def clean(self, value):
+ value = super(URLField, self).clean(value)
+ if value == u'':
+ return value
+ if self.verify_exists:
+ import urllib2
+ from django.conf import settings
+ headers = {
+ "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+ "Accept-Language": "en-us,en;q=0.5",
+ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+ "Connection": "close",
+ "User-Agent": self.user_agent,
+ }
+ try:
+ req = urllib2.Request(value, None, headers)
+ u = urllib2.urlopen(req)
+ except ValueError:
+ raise ValidationError(gettext(u'Enter a valid URL.'))
+ except: # urllib2.URLError, httplib.InvalidURL, etc.
+ raise ValidationError(gettext(u'This URL appears to be a broken link.'))
+ return value
+
+class BooleanField(Field):
+ widget = CheckboxInput
+
+ def clean(self, value):
+ "Returns a Python boolean object."
+ super(BooleanField, self).clean(value)
+ return bool(value)
+
+class NullBooleanField(BooleanField):
+ """
+ A field whose valid values are None, True and False. Invalid values are
+ cleaned to None.
+ """
+ widget = NullBooleanSelect
+
+ def clean(self, value):
+ return {True: True, False: False}.get(value, None)
+
+class ChoiceField(Field):
+ def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None, help_text=None):
+ super(ChoiceField, self).__init__(required, widget, label, initial, help_text)
+ self.choices = choices
+
+ def _get_choices(self):
+ return self._choices
+
+ def _set_choices(self, value):
+ # Setting choices also sets the choices on the widget.
+ # choices can be any iterable, but we call list() on it because
+ # it will be consumed more than once.
+ self._choices = self.widget.choices = list(value)
+
+ choices = property(_get_choices, _set_choices)
+
+ def clean(self, value):
+ """
+ Validates that the input is in self.choices.
+ """
+ value = super(ChoiceField, self).clean(value)
+ if value in EMPTY_VALUES:
+ value = u''
+ value = smart_unicode(value)
+ if value == u'':
+ return value
+ valid_values = set([str(k) for k, v in self.choices])
+ if value not in valid_values:
+ raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
+ return value
+
+class MultipleChoiceField(ChoiceField):
+ hidden_widget = MultipleHiddenInput
+
+ def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None, help_text=None):
+ super(MultipleChoiceField, self).__init__(choices, required, widget, label, initial, help_text)
+
+ def clean(self, value):
+ """
+ Validates that the input is a list or tuple.
+ """
+ if self.required and not value:
+ raise ValidationError(gettext(u'This field is required.'))
+ elif not self.required and not value:
+ return []
+ if not isinstance(value, (list, tuple)):
+ raise ValidationError(gettext(u'Enter a list of values.'))
+ new_value = []
+ for val in value:
+ val = smart_unicode(val)
+ new_value.append(val)
+ # Validate that each value in the value list is in self.choices.
+ valid_values = set([smart_unicode(k) for k, v in self.choices])
+ for val in new_value:
+ if val not in valid_values:
+ raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
+ return new_value
+
+class ComboField(Field):
+ """
+ A Field whose clean() method calls multiple Field clean() methods.
+ """
+ def __init__(self, fields=(), *args, **kwargs):
+ super(ComboField, self).__init__(*args, **kwargs)
+ # Set 'required' to False on the individual fields, because the
+ # required validation will be handled by ComboField, not by those
+ # individual fields.
+ for f in fields:
+ f.required = False
+ self.fields = fields
+
+ def clean(self, value):
+ """
+ Validates the given value against all of self.fields, which is a
+ list of Field instances.
+ """
+ super(ComboField, self).clean(value)
+ for field in self.fields:
+ value = field.clean(value)
+ return value
+
+class MultiValueField(Field):
+ """
+ A Field that is composed of multiple Fields.
+
+ Its clean() method takes a "decompressed" list of values. Each value in
+ this list is cleaned by the corresponding field -- the first value is
+ cleaned by the first field, the second value is cleaned by the second
+ field, etc. Once all fields are cleaned, the list of clean values is
+ "compressed" into a single value.
+
+ Subclasses should implement compress(), which specifies how a list of
+ valid values should be converted to a single value. Subclasses should not
+ have to implement clean().
+
+ You'll probably want to use this with MultiWidget.
+ """
+ def __init__(self, fields=(), *args, **kwargs):
+ super(MultiValueField, self).__init__(*args, **kwargs)
+ # Set 'required' to False on the individual fields, because the
+ # required validation will be handled by MultiValueField, not by those
+ # individual fields.
+ for f in fields:
+ f.required = False
+ self.fields = fields
+
+ def clean(self, value):
+ """
+ Validates every value in the given list. A value is validated against
+ the corresponding Field in self.fields.
+
+ For example, if this MultiValueField was instantiated with
+ fields=(DateField(), TimeField()), clean() would call
+ DateField.clean(value[0]) and TimeField.clean(value[1]).
+ """
+ clean_data = []
+ errors = ErrorList()
+ if self.required and not value:
+ raise ValidationError(gettext(u'This field is required.'))
+ elif not self.required and not value:
+ return self.compress([])
+ if not isinstance(value, (list, tuple)):
+ raise ValidationError(gettext(u'Enter a list of values.'))
+ for i, field in enumerate(self.fields):
+ try:
+ field_value = value[i]
+ except KeyError:
+ field_value = None
+ if self.required and field_value in EMPTY_VALUES:
+ raise ValidationError(gettext(u'This field is required.'))
+ try:
+ clean_data.append(field.clean(field_value))
+ except ValidationError, e:
+ # Collect all validation errors in a single list, which we'll
+ # raise at the end of clean(), rather than raising a single
+ # exception for the first error we encounter.
+ errors.extend(e.messages)
+ if errors:
+ raise ValidationError(errors)
+ return self.compress(clean_data)
+
+ def compress(self, data_list):
+ """
+ Returns a single value for the given list of values. The values can be
+ assumed to be valid.
+
+ For example, if this MultiValueField was instantiated with
+ fields=(DateField(), TimeField()), this might return a datetime
+ object created by combining the date and time in data_list.
+ """
+ raise NotImplementedError('Subclasses must implement this method.')
+
+class SplitDateTimeField(MultiValueField):
+ def __init__(self, *args, **kwargs):
+ fields = (DateField(), TimeField())
+ super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
+
+ def compress(self, data_list):
+ if data_list:
+ return datetime.datetime.combine(*data_list)
+ return None
diff --git a/google_appengine/lib/django/django/newforms/forms.py b/google_appengine/lib/django/django/newforms/forms.py
new file mode 100755
index 0000000..2b3aa97
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/forms.py
@@ -0,0 +1,309 @@
+"""
+Form classes
+"""
+
+from django.utils.datastructures import SortedDict, MultiValueDict
+from django.utils.html import escape
+from fields import Field
+from widgets import TextInput, Textarea, HiddenInput, MultipleHiddenInput
+from util import flatatt, StrAndUnicode, ErrorDict, ErrorList, ValidationError
+import copy
+
+__all__ = ('BaseForm', 'Form')
+
+NON_FIELD_ERRORS = '__all__'
+
+def pretty_name(name):
+ "Converts 'first_name' to 'First name'"
+ name = name[0].upper() + name[1:]
+ return name.replace('_', ' ')
+
+class SortedDictFromList(SortedDict):
+ "A dictionary that keeps its keys in the order in which they're inserted."
+ # This is different than django.utils.datastructures.SortedDict, because
+ # this takes a list/tuple as the argument to __init__().
+ def __init__(self, data=None):
+ if data is None: data = []
+ self.keyOrder = [d[0] for d in data]
+ dict.__init__(self, dict(data))
+
+ def copy(self):
+ return SortedDictFromList([(k, copy.copy(v)) for k, v in self.items()])
+
+class DeclarativeFieldsMetaclass(type):
+ """
+ Metaclass that converts Field attributes to a dictionary called
+ 'base_fields', taking into account parent class 'base_fields' as well.
+ """
+ def __new__(cls, name, bases, attrs):
+ fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
+ fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
+
+ # If this class is subclassing another Form, add that Form's fields.
+ # Note that we loop over the bases in *reverse*. This is necessary in
+ # order to preserve the correct order of fields.
+ for base in bases[::-1]:
+ if hasattr(base, 'base_fields'):
+ fields = base.base_fields.items() + fields
+
+ attrs['base_fields'] = SortedDictFromList(fields)
+ return type.__new__(cls, name, bases, attrs)
+
+class BaseForm(StrAndUnicode):
+ # This is the main implementation of all the Form logic. Note that this
+ # class is different than Form. See the comments by the Form class for more
+ # information. Any improvements to the form API should be made to *this*
+ # class, not to the Form class.
+ def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None):
+ self.is_bound = data is not None
+ self.data = data or {}
+ self.auto_id = auto_id
+ self.prefix = prefix
+ self.initial = initial or {}
+ self.__errors = None # Stores the errors after clean() has been called.
+
+ # The base_fields class attribute is the *class-wide* definition of
+ # fields. Because a particular *instance* of the class might want to
+ # alter self.fields, we create self.fields here by copying base_fields.
+ # Instances should always modify self.fields; they should not modify
+ # self.base_fields.
+ self.fields = self.base_fields.copy()
+
+ def __unicode__(self):
+ return self.as_table()
+
+ def __iter__(self):
+ for name, field in self.fields.items():
+ yield BoundField(self, field, name)
+
+ def __getitem__(self, name):
+ "Returns a BoundField with the given name."
+ try:
+ field = self.fields[name]
+ except KeyError:
+ raise KeyError('Key %r not found in Form' % name)
+ return BoundField(self, field, name)
+
+ def _errors(self):
+ "Returns an ErrorDict for self.data"
+ if self.__errors is None:
+ self.full_clean()
+ return self.__errors
+ errors = property(_errors)
+
+ def is_valid(self):
+ """
+ Returns True if the form has no errors. Otherwise, False. If errors are
+ being ignored, returns False.
+ """
+ return self.is_bound and not bool(self.errors)
+
+ def add_prefix(self, field_name):
+ """
+ Returns the field name with a prefix appended, if this Form has a
+ prefix set.
+
+ Subclasses may wish to override.
+ """
+ return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
+
+ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
+ "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
+ top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
+ output, hidden_fields = [], []
+ for name, field in self.fields.items():
+ bf = BoundField(self, field, name)
+ bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable.
+ if bf.is_hidden:
+ if bf_errors:
+ top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
+ hidden_fields.append(unicode(bf))
+ else:
+ if errors_on_separate_row and bf_errors:
+ output.append(error_row % bf_errors)
+ label = bf.label and bf.label_tag(escape(bf.label + ':')) or ''
+ if field.help_text:
+ help_text = help_text_html % field.help_text
+ else:
+ help_text = u''
+ output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text})
+ if top_errors:
+ output.insert(0, error_row % top_errors)
+ if hidden_fields: # Insert any hidden fields in the last row.
+ str_hidden = u''.join(hidden_fields)
+ if output:
+ last_row = output[-1]
+ # Chop off the trailing row_ender (e.g. '</td></tr>') and insert the hidden fields.
+ output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
+ else: # If there aren't any rows in the output, just append the hidden fields.
+ output.append(str_hidden)
+ return u'\n'.join(output)
+
+ def as_table(self):
+ "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
+ return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False)
+
+ def as_ul(self):
+ "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
+ return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
+
+ def as_p(self):
+ "Returns this form rendered as HTML <p>s."
+ return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'<p>%s</p>', '</p>', u' %s', True)
+
+ def non_field_errors(self):
+ """
+ Returns an ErrorList of errors that aren't associated with a particular
+ field -- i.e., from Form.clean(). Returns an empty ErrorList if there
+ are none.
+ """
+ return self.errors.get(NON_FIELD_ERRORS, ErrorList())
+
+ def full_clean(self):
+ """
+ Cleans all of self.data and populates self.__errors and self.clean_data.
+ """
+ errors = ErrorDict()
+ if not self.is_bound: # Stop further processing.
+ self.__errors = errors
+ return
+ self.clean_data = {}
+ for name, field in self.fields.items():
+ # value_from_datadict() gets the data from the dictionary.
+ # Each widget type knows how to retrieve its own data, because some
+ # widgets split data over several HTML fields.
+ value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
+ try:
+ value = field.clean(value)
+ self.clean_data[name] = value
+ if hasattr(self, 'clean_%s' % name):
+ value = getattr(self, 'clean_%s' % name)()
+ self.clean_data[name] = value
+ except ValidationError, e:
+ errors[name] = e.messages
+ try:
+ self.clean_data = self.clean()
+ except ValidationError, e:
+ errors[NON_FIELD_ERRORS] = e.messages
+ if errors:
+ delattr(self, 'clean_data')
+ self.__errors = errors
+
+ def clean(self):
+ """
+ Hook for doing any extra form-wide cleaning after Field.clean() been
+ called on every field. Any ValidationError raised by this method will
+ not be associated with a particular field; it will have a special-case
+ association with the field named '__all__'.
+ """
+ return self.clean_data
+
+class Form(BaseForm):
+ "A collection of Fields, plus their associated data."
+ # This is a separate class from BaseForm in order to abstract the way
+ # self.fields is specified. This class (Form) is the one that does the
+ # fancy metaclass stuff purely for the semantic sugar -- it allows one
+ # to define a form using declarative syntax.
+ # BaseForm itself has no way of designating self.fields.
+ __metaclass__ = DeclarativeFieldsMetaclass
+
+class BoundField(StrAndUnicode):
+ "A Field plus data"
+ def __init__(self, form, field, name):
+ self.form = form
+ self.field = field
+ self.name = name
+ self.html_name = form.add_prefix(name)
+ if self.field.label is None:
+ self.label = pretty_name(name)
+ else:
+ self.label = self.field.label
+ self.help_text = field.help_text or ''
+
+ def __unicode__(self):
+ "Renders this field as an HTML widget."
+ # Use the 'widget' attribute on the field to determine which type
+ # of HTML widget to use.
+ value = self.as_widget(self.field.widget)
+ if not isinstance(value, basestring):
+ # Some Widget render() methods -- notably RadioSelect -- return a
+ # "special" object rather than a string. Call the __str__() on that
+ # object to get its rendered value.
+ value = value.__str__()
+ return value
+
+ def _errors(self):
+ """
+ Returns an ErrorList for this field. Returns an empty ErrorList
+ if there are none.
+ """
+ return self.form.errors.get(self.name, ErrorList())
+ errors = property(_errors)
+
+ def as_widget(self, widget, attrs=None):
+ attrs = attrs or {}
+ auto_id = self.auto_id
+ if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
+ attrs['id'] = auto_id
+ if not self.form.is_bound:
+ data = self.form.initial.get(self.name, self.field.initial)
+ else:
+ data = self.data
+ return widget.render(self.html_name, data, attrs=attrs)
+
+ def as_text(self, attrs=None):
+ """
+ Returns a string of HTML for representing this as an <input type="text">.
+ """
+ return self.as_widget(TextInput(), attrs)
+
+ def as_textarea(self, attrs=None):
+ "Returns a string of HTML for representing this as a <textarea>."
+ return self.as_widget(Textarea(), attrs)
+
+ def as_hidden(self, attrs=None):
+ """
+ Returns a string of HTML for representing this as an <input type="hidden">.
+ """
+ return self.as_widget(self.field.hidden_widget(), attrs)
+
+ def _data(self):
+ """
+ Returns the data for this BoundField, or None if it wasn't given.
+ """
+ return self.field.widget.value_from_datadict(self.form.data, self.html_name)
+ data = property(_data)
+
+ def label_tag(self, contents=None, attrs=None):
+ """
+ Wraps the given contents in a <label>, if the field has an ID attribute.
+ Does not HTML-escape the contents. If contents aren't given, uses the
+ field's HTML-escaped label.
+
+ If attrs are given, they're used as HTML attributes on the <label> tag.
+ """
+ contents = contents or escape(self.label)
+ widget = self.field.widget
+ id_ = widget.attrs.get('id') or self.auto_id
+ if id_:
+ attrs = attrs and flatatt(attrs) or ''
+ contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
+ return contents
+
+ def _is_hidden(self):
+ "Returns True if this BoundField's widget is hidden."
+ return self.field.widget.is_hidden
+ is_hidden = property(_is_hidden)
+
+ def _auto_id(self):
+ """
+ Calculates and returns the ID attribute for this BoundField, if the
+ associated Form has specified auto_id. Returns an empty string otherwise.
+ """
+ auto_id = self.form.auto_id
+ if auto_id and '%s' in str(auto_id):
+ return str(auto_id) % self.html_name
+ elif auto_id:
+ return self.html_name
+ return ''
+ auto_id = property(_auto_id)
diff --git a/google_appengine/lib/django/django/newforms/models.py b/google_appengine/lib/django/django/newforms/models.py
new file mode 100755
index 0000000..616c714
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/models.py
@@ -0,0 +1,190 @@
+"""
+Helper functions for creating Form classes from Django models
+and database field objects.
+"""
+
+from django.utils.translation import gettext
+from util import ValidationError
+from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
+from fields import Field, ChoiceField
+from widgets import Select, SelectMultiple, MultipleHiddenInput
+
+__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
+ 'ModelChoiceField', 'ModelMultipleChoiceField')
+
+def model_save(self, commit=True):
+ """
+ Creates and returns model instance according to self.clean_data.
+
+ This method is created for any form_for_model Form.
+ """
+ if self.errors:
+ raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
+ return save_instance(self, self._model(), commit)
+
+def save_instance(form, instance, commit=True):
+ """
+ Saves bound Form ``form``'s clean_data into model instance ``instance``.
+
+ Assumes ``form`` has a field for every non-AutoField database field in
+ ``instance``. If commit=True, then the changes to ``instance`` will be
+ saved to the database. Returns ``instance``.
+ """
+ from django.db import models
+ opts = instance.__class__._meta
+ if form.errors:
+ raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
+ clean_data = form.clean_data
+ for f in opts.fields:
+ if not f.editable or isinstance(f, models.AutoField):
+ continue
+ setattr(instance, f.name, clean_data[f.name])
+ if commit:
+ instance.save()
+ for f in opts.many_to_many:
+ setattr(instance, f.attname, clean_data[f.name])
+ # GOTCHA: If many-to-many data is given and commit=False, the many-to-many
+ # data will be lost. This happens because a many-to-many options cannot be
+ # set on an object until after it's saved. Maybe we should raise an
+ # exception in that case.
+ return instance
+
+def make_instance_save(instance):
+ "Returns the save() method for a form_for_instance Form."
+ def save(self, commit=True):
+ return save_instance(self, instance, commit)
+ return save
+
+def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield()):
+ """
+ Returns a Form class for the given Django model class.
+
+ Provide ``form`` if you want to use a custom BaseForm subclass.
+
+ Provide ``formfield_callback`` if you want to define different logic for
+ determining the formfield for a given database field. It's a callable that
+ takes a database Field instance and returns a form Field instance.
+ """
+ opts = model._meta
+ field_list = []
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ formfield = formfield_callback(f)
+ if formfield:
+ field_list.append((f.name, formfield))
+ fields = SortedDictFromList(field_list)
+ return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save})
+
+def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
+ """
+ Returns a Form class for the given Django model instance.
+
+ Provide ``form`` if you want to use a custom BaseForm subclass.
+
+ Provide ``formfield_callback`` if you want to define different logic for
+ determining the formfield for a given database field. It's a callable that
+ takes a database Field instance, plus **kwargs, and returns a form Field
+ instance with the given kwargs (i.e. 'initial').
+ """
+ model = instance.__class__
+ opts = model._meta
+ field_list = []
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ current_value = f.value_from_object(instance)
+ formfield = formfield_callback(f, initial=current_value)
+ if formfield:
+ field_list.append((f.name, formfield))
+ fields = SortedDictFromList(field_list)
+ return type(opts.object_name + 'InstanceForm', (form,),
+ {'base_fields': fields, '_model': model, 'save': make_instance_save(instance)})
+
+def form_for_fields(field_list):
+ "Returns a Form class for the given list of Django database field instances."
+ fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable])
+ return type('FormForFields', (BaseForm,), {'base_fields': fields})
+
+class QuerySetIterator(object):
+ def __init__(self, queryset, empty_label, cache_choices):
+ self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices
+
+ def __iter__(self):
+ if self.empty_label is not None:
+ yield (u"", self.empty_label)
+ for obj in self.queryset:
+ yield (obj._get_pk_val(), str(obj))
+ # Clear the QuerySet cache if required.
+ if not self.cache_choices:
+ self.queryset._result_cache = None
+
+class ModelChoiceField(ChoiceField):
+ "A ChoiceField whose choices are a model QuerySet."
+ # This class is a subclass of ChoiceField for purity, but it doesn't
+ # actually use any of ChoiceField's implementation.
+ def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
+ required=True, widget=Select, label=None, initial=None, help_text=None):
+ self.queryset = queryset
+ self.empty_label = empty_label
+ self.cache_choices = cache_choices
+ # Call Field instead of ChoiceField __init__() because we don't need
+ # ChoiceField.__init__().
+ Field.__init__(self, required, widget, label, initial, help_text)
+ self.widget.choices = self.choices
+
+ def _get_choices(self):
+ # If self._choices is set, then somebody must have manually set
+ # the property self.choices. In this case, just return self._choices.
+ if hasattr(self, '_choices'):
+ return self._choices
+ # Otherwise, execute the QuerySet in self.queryset to determine the
+ # choices dynamically. Return a fresh QuerySetIterator that has not
+ # been consumed. Note that we're instantiating a new QuerySetIterator
+ # *each* time _get_choices() is called (and, thus, each time
+ # self.choices is accessed) so that we can ensure the QuerySet has not
+ # been consumed.
+ return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices)
+
+ def _set_choices(self, value):
+ # This method is copied from ChoiceField._set_choices(). It's necessary
+ # because property() doesn't allow a subclass to overwrite only
+ # _get_choices without implementing _set_choices.
+ self._choices = self.widget.choices = list(value)
+
+ choices = property(_get_choices, _set_choices)
+
+ def clean(self, value):
+ Field.clean(self, value)
+ if value in ('', None):
+ return None
+ try:
+ value = self.queryset.model._default_manager.get(pk=value)
+ except self.queryset.model.DoesNotExist:
+ raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
+ return value
+
+class ModelMultipleChoiceField(ModelChoiceField):
+ "A MultipleChoiceField whose choices are a model QuerySet."
+ hidden_widget = MultipleHiddenInput
+ def __init__(self, queryset, cache_choices=False, required=True,
+ widget=SelectMultiple, label=None, initial=None, help_text=None):
+ super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices,
+ required, widget, label, initial, help_text)
+
+ def clean(self, value):
+ if self.required and not value:
+ raise ValidationError(gettext(u'This field is required.'))
+ elif not self.required and not value:
+ return []
+ if not isinstance(value, (list, tuple)):
+ raise ValidationError(gettext(u'Enter a list of values.'))
+ final_values = []
+ for val in value:
+ try:
+ obj = self.queryset.model._default_manager.get(pk=val)
+ except self.queryset.model.DoesNotExist:
+ raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
+ else:
+ final_values.append(obj)
+ return final_values
diff --git a/google_appengine/lib/django/django/newforms/util.py b/google_appengine/lib/django/django/newforms/util.py
new file mode 100755
index 0000000..51a8efd
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/util.py
@@ -0,0 +1,74 @@
+from django.conf import settings
+from django.utils.html import escape
+
+# Converts a dictionary to a single string with key="value", XML-style with
+# a leading space. Assumes keys do not need to be XML-escaped.
+flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
+
+def smart_unicode(s):
+ if not isinstance(s, basestring):
+ if hasattr(s, '__unicode__'):
+ s = unicode(s)
+ else:
+ s = unicode(str(s), settings.DEFAULT_CHARSET)
+ elif not isinstance(s, unicode):
+ s = unicode(s, settings.DEFAULT_CHARSET)
+ return s
+
+class StrAndUnicode(object):
+ """
+ A class whose __str__ returns its __unicode__ as a bytestring
+ according to settings.DEFAULT_CHARSET.
+
+ Useful as a mix-in.
+ """
+ def __str__(self):
+ return self.__unicode__().encode(settings.DEFAULT_CHARSET)
+
+class ErrorDict(dict):
+ """
+ A collection of errors that knows how to display itself in various formats.
+
+ The dictionary keys are the field names, and the values are the errors.
+ """
+ def __str__(self):
+ return self.as_ul()
+
+ def as_ul(self):
+ if not self: return u''
+ return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s%s</li>' % (k, v) for k, v in self.items()])
+
+ def as_text(self):
+ return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % i for i in v])) for k, v in self.items()])
+
+class ErrorList(list):
+ """
+ A collection of errors that knows how to display itself in various formats.
+ """
+ def __str__(self):
+ return self.as_ul()
+
+ def as_ul(self):
+ if not self: return u''
+ return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s</li>' % e for e in self])
+
+ def as_text(self):
+ if not self: return u''
+ return u'\n'.join([u'* %s' % e for e in self])
+
+class ValidationError(Exception):
+ def __init__(self, message):
+ "ValidationError can be passed a string or a list."
+ if isinstance(message, list):
+ self.messages = ErrorList([smart_unicode(msg) for msg in message])
+ else:
+ assert isinstance(message, basestring), ("%s should be a basestring" % repr(message))
+ message = smart_unicode(message)
+ self.messages = ErrorList([message])
+
+ def __str__(self):
+ # This is needed because, without a __str__(), printing an exception
+ # instance would result in this:
+ # AttributeError: ValidationError instance has no attribute 'args'
+ # See http://www.python.org/doc/current/tut/node10.html#handling
+ return repr(self.messages)
diff --git a/google_appengine/lib/django/django/newforms/widgets.py b/google_appengine/lib/django/django/newforms/widgets.py
new file mode 100755
index 0000000..18bba31
--- /dev/null
+++ b/google_appengine/lib/django/django/newforms/widgets.py
@@ -0,0 +1,353 @@
+"""
+HTML Widget classes
+"""
+
+__all__ = (
+ 'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput',
+ 'FileInput', 'Textarea', 'CheckboxInput',
+ 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
+ 'MultiWidget', 'SplitDateTimeWidget',
+)
+
+from util import flatatt, StrAndUnicode, smart_unicode
+from django.utils.datastructures import MultiValueDict
+from django.utils.html import escape
+from django.utils.translation import gettext
+from itertools import chain
+
+try:
+ set # Only available in Python 2.4+
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
+
+class Widget(object):
+ is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
+
+ def __init__(self, attrs=None):
+ self.attrs = attrs or {}
+
+ def render(self, name, value, attrs=None):
+ """
+ Returns this Widget rendered as HTML, as a Unicode string.
+
+ The 'value' given is not guaranteed to be valid input, so subclass
+ implementations should program defensively.
+ """
+ raise NotImplementedError
+
+ def build_attrs(self, extra_attrs=None, **kwargs):
+ "Helper function for building an attribute dictionary."
+ attrs = dict(self.attrs, **kwargs)
+ if extra_attrs:
+ attrs.update(extra_attrs)
+ return attrs
+
+ def value_from_datadict(self, data, name):
+ """
+ Given a dictionary of data and this widget's name, returns the value
+ of this widget. Returns None if it's not provided.
+ """
+ return data.get(name, None)
+
+ def id_for_label(self, id_):
+ """
+ Returns the HTML ID attribute of this Widget for use by a <label>,
+ given the ID of the field. Returns None if no ID is available.
+
+ This hook is necessary because some widgets have multiple HTML
+ elements and, thus, multiple IDs. In that case, this method should
+ return an ID value that corresponds to the first ID in the widget's
+ tags.
+ """
+ return id_
+ id_for_label = classmethod(id_for_label)
+
+class Input(Widget):
+ """
+ Base class for all <input> widgets (except type='checkbox' and
+ type='radio', which are special).
+ """
+ input_type = None # Subclasses must define this.
+
+ def render(self, name, value, attrs=None):
+ if value is None: value = ''
+ final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
+ if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
+ return u'<input%s />' % flatatt(final_attrs)
+
+class TextInput(Input):
+ input_type = 'text'
+
+class PasswordInput(Input):
+ input_type = 'password'
+
+ def __init__(self, attrs=None, render_value=True):
+ self.attrs = attrs or {}
+ self.render_value = render_value
+
+ def render(self, name, value, attrs=None):
+ if not self.render_value: value=None
+ return super(PasswordInput, self).render(name, value, attrs)
+
+class HiddenInput(Input):
+ input_type = 'hidden'
+ is_hidden = True
+
+class MultipleHiddenInput(HiddenInput):
+ """
+ A widget that handles <input type="hidden"> for fields that have a list
+ of values.
+ """
+ def __init__(self, attrs=None, choices=()):
+ # choices can be any iterable
+ self.attrs = attrs or {}
+ self.choices = choices
+
+ def render(self, name, value, attrs=None, choices=()):
+ if value is None: value = []
+ final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
+ return u'\n'.join([(u'<input%s />' % flatatt(dict(value=smart_unicode(v), **final_attrs))) for v in value])
+
+ def value_from_datadict(self, data, name):
+ if isinstance(data, MultiValueDict):
+ return data.getlist(name)
+ return data.get(name, None)
+
+class FileInput(Input):
+ input_type = 'file'
+
+class Textarea(Widget):
+ def render(self, name, value, attrs=None):
+ if value is None: value = ''
+ value = smart_unicode(value)
+ final_attrs = self.build_attrs(attrs, name=name)
+ return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
+
+class CheckboxInput(Widget):
+ def __init__(self, attrs=None, check_test=bool):
+ # check_test is a callable that takes a value and returns True
+ # if the checkbox should be checked for that value.
+ self.attrs = attrs or {}
+ self.check_test = check_test
+
+ def render(self, name, value, attrs=None):
+ final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
+ try:
+ result = self.check_test(value)
+ except: # Silently catch exceptions
+ result = False
+ if result:
+ final_attrs['checked'] = 'checked'
+ if value not in ('', True, False, None):
+ final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
+ return u'<input%s />' % flatatt(final_attrs)
+
+class Select(Widget):
+ def __init__(self, attrs=None, choices=()):
+ self.attrs = attrs or {}
+ # choices can be any iterable, but we may need to render this widget
+ # multiple times. Thus, collapse it into a list so it can be consumed
+ # more than once.
+ self.choices = list(choices)
+
+ def render(self, name, value, attrs=None, choices=()):
+ if value is None: value = ''
+ final_attrs = self.build_attrs(attrs, name=name)
+ output = [u'<select%s>' % flatatt(final_attrs)]
+ str_value = smart_unicode(value) # Normalize to string.
+ for option_value, option_label in chain(self.choices, choices):
+ option_value = smart_unicode(option_value)
+ selected_html = (option_value == str_value) and u' selected="selected"' or ''
+ output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
+ output.append(u'</select>')
+ return u'\n'.join(output)
+
+class NullBooleanSelect(Select):
+ """
+ A Select Widget intended to be used with NullBooleanField.
+ """
+ def __init__(self, attrs=None):
+ choices = ((u'1', gettext('Unknown')), (u'2', gettext('Yes')), (u'3', gettext('No')))
+ super(NullBooleanSelect, self).__init__(attrs, choices)
+
+ def render(self, name, value, attrs=None, choices=()):
+ try:
+ value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value]
+ except KeyError:
+ value = u'1'
+ return super(NullBooleanSelect, self).render(name, value, attrs, choices)
+
+ def value_from_datadict(self, data, name):
+ value = data.get(name, None)
+ return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
+
+class SelectMultiple(Widget):
+ def __init__(self, attrs=None, choices=()):
+ # choices can be any iterable
+ self.attrs = attrs or {}
+ self.choices = choices
+
+ def render(self, name, value, attrs=None, choices=()):
+ if value is None: value = []
+ final_attrs = self.build_attrs(attrs, name=name)
+ output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
+ str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
+ for option_value, option_label in chain(self.choices, choices):
+ option_value = smart_unicode(option_value)
+ selected_html = (option_value in str_values) and ' selected="selected"' or ''
+ output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
+ output.append(u'</select>')
+ return u'\n'.join(output)
+
+ def value_from_datadict(self, data, name):
+ if isinstance(data, MultiValueDict):
+ return data.getlist(name)
+ return data.get(name, None)
+
+class RadioInput(StrAndUnicode):
+ "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
+ def __init__(self, name, value, attrs, choice, index):
+ self.name, self.value = name, value
+ self.attrs = attrs
+ self.choice_value = smart_unicode(choice[0])
+ self.choice_label = smart_unicode(choice[1])
+ self.index = index
+
+ def __unicode__(self):
+ return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
+
+ def is_checked(self):
+ return self.value == self.choice_value
+
+ def tag(self):
+ if self.attrs.has_key('id'):
+ self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
+ final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
+ if self.is_checked():
+ final_attrs['checked'] = 'checked'
+ return u'<input%s />' % flatatt(final_attrs)
+
+class RadioFieldRenderer(StrAndUnicode):
+ "An object used by RadioSelect to enable customization of radio widgets."
+ def __init__(self, name, value, attrs, choices):
+ self.name, self.value, self.attrs = name, value, attrs
+ self.choices = choices
+
+ def __iter__(self):
+ for i, choice in enumerate(self.choices):
+ yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
+
+ def __getitem__(self, idx):
+ choice = self.choices[idx] # Let the IndexError propogate
+ return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
+
+ def __unicode__(self):
+ "Outputs a <ul> for this set of radio fields."
+ return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
+
+class RadioSelect(Select):
+ def render(self, name, value, attrs=None, choices=()):
+ "Returns a RadioFieldRenderer instance rather than a Unicode string."
+ if value is None: value = ''
+ str_value = smart_unicode(value) # Normalize to string.
+ attrs = attrs or {}
+ return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
+
+ def id_for_label(self, id_):
+ # RadioSelect is represented by multiple <input type="radio"> fields,
+ # each of which has a distinct ID. The IDs are made distinct by a "_X"
+ # suffix, where X is the zero-based index of the radio field. Thus,
+ # the label for a RadioSelect should reference the first one ('_0').
+ if id_:
+ id_ += '_0'
+ return id_
+ id_for_label = classmethod(id_for_label)
+
+class CheckboxSelectMultiple(SelectMultiple):
+ def render(self, name, value, attrs=None, choices=()):
+ if value is None: value = []
+ has_id = attrs and attrs.has_key('id')
+ final_attrs = self.build_attrs(attrs, name=name)
+ output = [u'<ul>']
+ str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
+ for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
+ # If an ID attribute was given, add a numeric index as a suffix,
+ # so that the checkboxes don't all have the same ID attribute.
+ if has_id:
+ final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
+ cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
+ option_value = smart_unicode(option_value)
+ rendered_cb = cb.render(name, option_value)
+ output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label))))
+ output.append(u'</ul>')
+ return u'\n'.join(output)
+
+ def id_for_label(self, id_):
+ # See the comment for RadioSelect.id_for_label()
+ if id_:
+ id_ += '_0'
+ return id_
+ id_for_label = classmethod(id_for_label)
+
+class MultiWidget(Widget):
+ """
+ A widget that is composed of multiple widgets.
+
+ Its render() method takes a "decompressed" list of values, not a single
+ value. Each value in this list is rendered in the corresponding widget --
+ the first value is rendered in the first widget, the second value is
+ rendered in the second widget, etc.
+
+ Subclasses should implement decompress(), which specifies how a single
+ value should be converted to a list of values. Subclasses should not
+ have to implement clean().
+
+ Subclasses may implement format_output(), which takes the list of rendered
+ widgets and returns HTML that formats them any way you'd like.
+
+ You'll probably want to use this with MultiValueField.
+ """
+ def __init__(self, widgets, attrs=None):
+ self.widgets = [isinstance(w, type) and w() or w for w in widgets]
+ super(MultiWidget, self).__init__(attrs)
+
+ def render(self, name, value, attrs=None):
+ # value is a list of values, each corresponding to a widget
+ # in self.widgets.
+ if not isinstance(value, list):
+ value = self.decompress(value)
+ output = []
+ for i, widget in enumerate(self.widgets):
+ try:
+ widget_value = value[i]
+ except KeyError:
+ widget_value = None
+ output.append(widget.render(name + '_%s' % i, widget_value, attrs))
+ return self.format_output(output)
+
+ def value_from_datadict(self, data, name):
+ return [data.get(name + '_%s' % i) for i in range(len(self.widgets))]
+
+ def format_output(self, rendered_widgets):
+ return u''.join(rendered_widgets)
+
+ def decompress(self, value):
+ """
+ Returns a list of decompressed values for the given compressed value.
+ The given value can be assumed to be valid, but not necessarily
+ non-empty.
+ """
+ raise NotImplementedError('Subclasses must implement this method.')
+
+class SplitDateTimeWidget(MultiWidget):
+ """
+ A Widget that splits datetime input into two <input type="text"> boxes.
+ """
+ def __init__(self, attrs=None):
+ widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
+ super(SplitDateTimeWidget, self).__init__(widgets, attrs)
+
+ def decompress(self, value):
+ if value:
+ return [value.date(), value.time()]
+ return [None, None]
diff --git a/google_appengine/lib/django/django/oldforms/__init__.py b/google_appengine/lib/django/django/oldforms/__init__.py
new file mode 100755
index 0000000..5610198
--- /dev/null
+++ b/google_appengine/lib/django/django/oldforms/__init__.py
@@ -0,0 +1,1015 @@
+from django.core import validators
+from django.core.exceptions import PermissionDenied
+from django.utils.html import escape
+from django.conf import settings
+from django.utils.translation import gettext, ngettext
+
+FORM_FIELD_ID_PREFIX = 'id_'
+
+class EmptyValue(Exception):
+ "This is raised when empty data is provided"
+ pass
+
+class Manipulator(object):
+ # List of permission strings. User must have at least one to manipulate.
+ # None means everybody has permission.
+ required_permission = ''
+
+ def __init__(self):
+ # List of FormField objects
+ self.fields = []
+
+ def __getitem__(self, field_name):
+ "Looks up field by field name; raises KeyError on failure"
+ for field in self.fields:
+ if field.field_name == field_name:
+ return field
+ raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields))
+
+ def __delitem__(self, field_name):
+ "Deletes the field with the given field name; raises KeyError on failure"
+ for i, field in enumerate(self.fields):
+ if field.field_name == field_name:
+ del self.fields[i]
+ return
+ raise KeyError, "Field %s not found" % field_name
+
+ def check_permissions(self, user):
+ """Confirms user has required permissions to use this manipulator; raises
+ PermissionDenied on failure."""
+ if self.required_permission is None:
+ return
+ if user.has_perm(self.required_permission):
+ return
+ raise PermissionDenied
+
+ def prepare(self, new_data):
+ """
+ Makes any necessary preparations to new_data, in place, before data has
+ been validated.
+ """
+ for field in self.fields:
+ field.prepare(new_data)
+
+ def get_validation_errors(self, new_data):
+ "Returns dictionary mapping field_names to error-message lists"
+ errors = {}
+ self.prepare(new_data)
+ for field in self.fields:
+ errors.update(field.get_validation_errors(new_data))
+ val_name = 'validate_%s' % field.field_name
+ if hasattr(self, val_name):
+ val = getattr(self, val_name)
+ try:
+ field.run_validator(new_data, val)
+ except (validators.ValidationError, validators.CriticalValidationError), e:
+ errors.setdefault(field.field_name, []).extend(e.messages)
+
+# if field.is_required and not new_data.get(field.field_name, False):
+# errors.setdefault(field.field_name, []).append(gettext_lazy('This field is required.'))
+# continue
+# try:
+# validator_list = field.validator_list
+# if hasattr(self, 'validate_%s' % field.field_name):
+# validator_list.append(getattr(self, 'validate_%s' % field.field_name))
+# for validator in validator_list:
+# if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'):
+# try:
+# if hasattr(field, 'requires_data_list'):
+# validator(new_data.getlist(field.field_name), new_data)
+# else:
+# validator(new_data.get(field.field_name, ''), new_data)
+# except validators.ValidationError, e:
+# errors.setdefault(field.field_name, []).extend(e.messages)
+# # If a CriticalValidationError is raised, ignore any other ValidationErrors
+# # for this particular field
+# except validators.CriticalValidationError, e:
+# errors.setdefault(field.field_name, []).extend(e.messages)
+ return errors
+
+ def save(self, new_data):
+ "Saves the changes and returns the new object"
+ # changes is a dictionary-like object keyed by field_name
+ raise NotImplementedError
+
+ def do_html2python(self, new_data):
+ """
+ Convert the data from HTML data types to Python datatypes, changing the
+ object in place. This happens after validation but before storage. This
+ must happen after validation because html2python functions aren't
+ expected to deal with invalid input.
+ """
+ for field in self.fields:
+ field.convert_post_data(new_data)
+
+class FormWrapper(object):
+ """
+ A wrapper linking a Manipulator to the template system.
+ This allows dictionary-style lookups of formfields. It also handles feeding
+ prepopulated data and validation error messages to the formfield objects.
+ """
+ def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True):
+ self.manipulator = manipulator
+ if data is None:
+ data = {}
+ if error_dict is None:
+ error_dict = {}
+ self.data = data
+ self.error_dict = error_dict
+ self._inline_collections = None
+ self.edit_inline = edit_inline
+
+ def __repr__(self):
+ return repr(self.__dict__)
+
+ def __getitem__(self, key):
+ for field in self.manipulator.fields:
+ if field.field_name == key:
+ data = field.extract_data(self.data)
+ return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
+ if self.edit_inline:
+ self.fill_inline_collections()
+ for inline_collection in self._inline_collections:
+ # The 'orig_name' comparison is for backwards compatibility
+ # with hand-crafted forms.
+ if inline_collection.name == key or (':' not in key and inline_collection.orig_name == key):
+ return inline_collection
+ raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key
+
+ def fill_inline_collections(self):
+ if not self._inline_collections:
+ ic = []
+ related_objects = self.manipulator.get_related_objects()
+ for rel_obj in related_objects:
+ data = rel_obj.extract_data(self.data)
+ inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
+ ic.append(inline_collection)
+ self._inline_collections = ic
+
+ def has_errors(self):
+ return self.error_dict != {}
+
+ def _get_fields(self):
+ try:
+ return self._fields
+ except AttributeError:
+ self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields]
+ return self._fields
+
+ fields = property(_get_fields)
+
+class FormFieldWrapper(object):
+ "A bridge between the template system and an individual form field. Used by FormWrapper."
+ def __init__(self, formfield, data, error_list):
+ self.formfield, self.data, self.error_list = formfield, data, error_list
+ self.field_name = self.formfield.field_name # for convenience in templates
+
+ def __str__(self):
+ "Renders the field"
+ return str(self.formfield.render(self.data))
+
+ def __repr__(self):
+ return '<FormFieldWrapper for "%s">' % self.formfield.field_name
+
+ def field_list(self):
+ """
+ Like __str__(), but returns a list. Use this when the field's render()
+ method returns a list.
+ """
+ return self.formfield.render(self.data)
+
+ def errors(self):
+ return self.error_list
+
+ def html_error_list(self):
+ if self.errors():
+ return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
+ else:
+ return ''
+
+ def get_id(self):
+ return self.formfield.get_id()
+
+class FormFieldCollection(FormFieldWrapper):
+ "A utility class that gives the template access to a dict of FormFieldWrappers"
+ def __init__(self, formfield_dict):
+ self.formfield_dict = formfield_dict
+
+ def __str__(self):
+ return str(self.formfield_dict)
+
+ def __getitem__(self, template_key):
+ "Look up field by template key; raise KeyError on failure"
+ return self.formfield_dict[template_key]
+
+ def __repr__(self):
+ return "<FormFieldCollection: %s>" % self.formfield_dict
+
+ def errors(self):
+ "Returns list of all errors in this collection's formfields"
+ errors = []
+ for field in self.formfield_dict.values():
+ if hasattr(field, 'errors'):
+ errors.extend(field.errors())
+ return errors
+
+ def has_errors(self):
+ return bool(len(self.errors()))
+
+ def html_combined_error_list(self):
+ return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
+
+class InlineObjectCollection(object):
+ "An object that acts like a sparse list of form field collections."
+ def __init__(self, parent_manipulator, rel_obj, data, errors):
+ self.parent_manipulator = parent_manipulator
+ self.rel_obj = rel_obj
+ self.data = data
+ self.errors = errors
+ self._collections = None
+ self.name = rel_obj.name
+ # This is the name used prior to fixing #1839. Needs for backwards
+ # compatibility.
+ self.orig_name = rel_obj.opts.module_name
+
+ def __len__(self):
+ self.fill()
+ return self._collections.__len__()
+
+ def __getitem__(self, k):
+ self.fill()
+ return self._collections.__getitem__(k)
+
+ def __setitem__(self, k, v):
+ self.fill()
+ return self._collections.__setitem__(k,v)
+
+ def __delitem__(self, k):
+ self.fill()
+ return self._collections.__delitem__(k)
+
+ def __iter__(self):
+ self.fill()
+ return iter(self._collections.values())
+
+ def items(self):
+ self.fill()
+ return self._collections.items()
+
+ def fill(self):
+ if self._collections:
+ return
+ else:
+ var_name = self.rel_obj.opts.object_name.lower()
+ collections = {}
+ orig = None
+ if hasattr(self.parent_manipulator, 'original_object'):
+ orig = self.parent_manipulator.original_object
+ orig_list = self.rel_obj.get_list(orig)
+
+ for i, instance in enumerate(orig_list):
+ collection = {'original': instance}
+ for f in self.rel_obj.editable_fields():
+ for field_name in f.get_manipulator_field_names(''):
+ full_field_name = '%s.%d.%s' % (var_name, i, field_name)
+ field = self.parent_manipulator[full_field_name]
+ data = field.extract_data(self.data)
+ errors = self.errors.get(full_field_name, [])
+ collection[field_name] = FormFieldWrapper(field, data, errors)
+ collections[i] = FormFieldCollection(collection)
+ self._collections = collections
+
+
+class FormField(object):
+ """Abstract class representing a form field.
+
+ Classes that extend FormField should define the following attributes:
+ field_name
+ The field's name for use by programs.
+ validator_list
+ A list of validation tests (callback functions) that the data for
+ this field must pass in order to be added or changed.
+ is_required
+ A Boolean. Is it a required field?
+ Subclasses should also implement a render(data) method, which is responsible
+ for rending the form field in XHTML.
+ """
+ def __str__(self):
+ return self.render('')
+
+ def __repr__(self):
+ return 'FormField "%s"' % self.field_name
+
+ def prepare(self, new_data):
+ "Hook for doing something to new_data (in place) before validation."
+ pass
+
+ def html2python(data):
+ "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type"
+ return data
+ html2python = staticmethod(html2python)
+
+ def render(self, data):
+ raise NotImplementedError
+
+ def get_member_name(self):
+ if hasattr(self, 'member_name'):
+ return self.member_name
+ else:
+ return self.field_name
+
+ def extract_data(self, data_dict):
+ if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
+ data = data_dict.getlist(self.get_member_name())
+ else:
+ data = data_dict.get(self.get_member_name(), None)
+ if data is None:
+ data = ''
+ return data
+
+ def convert_post_data(self, new_data):
+ name = self.get_member_name()
+ if new_data.has_key(self.field_name):
+ d = new_data.getlist(self.field_name)
+ try:
+ converted_data = [self.__class__.html2python(data) for data in d]
+ except ValueError:
+ converted_data = d
+ new_data.setlist(name, converted_data)
+ else:
+ try:
+ #individual fields deal with None values themselves
+ new_data.setlist(name, [self.__class__.html2python(None)])
+ except EmptyValue:
+ new_data.setlist(name, [])
+
+
+ def run_validator(self, new_data, validator):
+ if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'):
+ if hasattr(self, 'requires_data_list'):
+ validator(new_data.getlist(self.field_name), new_data)
+ else:
+ validator(new_data.get(self.field_name, ''), new_data)
+
+ def get_validation_errors(self, new_data):
+ errors = {}
+ if self.is_required and not new_data.get(self.field_name, False):
+ errors.setdefault(self.field_name, []).append(gettext('This field is required.'))
+ return errors
+ try:
+ for validator in self.validator_list:
+ try:
+ self.run_validator(new_data, validator)
+ except validators.ValidationError, e:
+ errors.setdefault(self.field_name, []).extend(e.messages)
+ # If a CriticalValidationError is raised, ignore any other ValidationErrors
+ # for this particular field
+ except validators.CriticalValidationError, e:
+ errors.setdefault(self.field_name, []).extend(e.messages)
+ return errors
+
+ def get_id(self):
+ "Returns the HTML 'id' attribute for this form field."
+ return FORM_FIELD_ID_PREFIX + self.field_name
+
+####################
+# GENERIC WIDGETS #
+####################
+
+class TextField(FormField):
+ input_type = "text"
+ def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.length, self.maxlength = length, maxlength
+ self.is_required = is_required
+ self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
+ if member_name != None:
+ self.member_name = member_name
+
+ def isValidLength(self, data, form):
+ if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength:
+ raise validators.ValidationError, ngettext("Ensure your text is less than %s character.",
+ "Ensure your text is less than %s characters.", self.maxlength) % self.maxlength
+
+ def hasNoNewlines(self, data, form):
+ if data and '\n' in data:
+ raise validators.ValidationError, gettext("Line breaks are not allowed here.")
+
+ def render(self, data):
+ if data is None:
+ data = ''
+ maxlength = ''
+ if self.maxlength:
+ maxlength = 'maxlength="%s" ' % self.maxlength
+ if isinstance(data, unicode):
+ data = data.encode(settings.DEFAULT_CHARSET)
+ return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
+ (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ self.field_name, self.length, escape(data), maxlength)
+
+ def html2python(data):
+ return data
+ html2python = staticmethod(html2python)
+
+class PasswordField(TextField):
+ input_type = "password"
+
+class LargeTextField(TextField):
+ def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.rows, self.cols, self.is_required = rows, cols, is_required
+ self.validator_list = validator_list[:]
+ if maxlength:
+ self.validator_list.append(self.isValidLength)
+ self.maxlength = maxlength
+
+ def render(self, data):
+ if data is None:
+ data = ''
+ if isinstance(data, unicode):
+ data = data.encode(settings.DEFAULT_CHARSET)
+ return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
+ (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ self.field_name, self.rows, self.cols, escape(data))
+
+class HiddenField(FormField):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.field_name, self.is_required = field_name, is_required
+ self.validator_list = validator_list[:]
+
+ def render(self, data):
+ return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
+ (self.get_id(), self.field_name, escape(data))
+
+class CheckboxField(FormField):
+ def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.checked_by_default = checked_by_default
+ self.is_required = is_required
+ self.validator_list = validator_list[:]
+
+ def render(self, data):
+ checked_html = ''
+ if data or (data is '' and self.checked_by_default):
+ checked_html = ' checked="checked"'
+ return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
+ (self.get_id(), self.__class__.__name__,
+ self.field_name, checked_html)
+
+ def html2python(data):
+ "Convert value from browser ('on' or '') to a Python boolean"
+ if data == 'on':
+ return True
+ return False
+ html2python = staticmethod(html2python)
+
+class SelectField(FormField):
+ def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ if choices is None: choices = []
+ self.field_name = field_name
+ # choices is a list of (value, human-readable key) tuples because order matters
+ self.choices, self.size, self.is_required = choices, size, is_required
+ self.validator_list = [self.isValidChoice] + validator_list
+ if member_name != None:
+ self.member_name = member_name
+
+ def render(self, data):
+ output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
+ (self.get_id(), self.__class__.__name__,
+ self.is_required and ' required' or '', self.field_name, self.size)]
+ str_data = str(data) # normalize to string
+ for value, display_name in self.choices:
+ selected_html = ''
+ if str(value) == str_data:
+ selected_html = ' selected="selected"'
+ output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
+ output.append(' </select>')
+ return '\n'.join(output)
+
+ def isValidChoice(self, data, form):
+ str_data = str(data)
+ str_choices = [str(item[0]) for item in self.choices]
+ if str_data not in str_choices:
+ raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices}
+
+class NullSelectField(SelectField):
+ "This SelectField converts blank fields to None"
+ def html2python(data):
+ if not data:
+ return None
+ return data
+ html2python = staticmethod(html2python)
+
+class RadioSelectField(FormField):
+ def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ if choices is None: choices = []
+ self.field_name = field_name
+ # choices is a list of (value, human-readable key) tuples because order matters
+ self.choices, self.is_required = choices, is_required
+ self.validator_list = [self.isValidChoice] + validator_list
+ self.ul_class = ul_class
+ if member_name != None:
+ self.member_name = member_name
+
+ def render(self, data):
+ """
+ Returns a special object, RadioFieldRenderer, that is iterable *and*
+ has a default str() rendered output.
+
+ This allows for flexible use in templates. You can just use the default
+ rendering:
+
+ {{ field_name }}
+
+ ...which will output the radio buttons in an unordered list.
+ Or, you can manually traverse each radio option for special layout:
+
+ {% for option in field_name.field_list %}
+ {{ option.field }} {{ option.label }}<br />
+ {% endfor %}
+ """
+ class RadioFieldRenderer:
+ def __init__(self, datalist, ul_class):
+ self.datalist, self.ul_class = datalist, ul_class
+ def __str__(self):
+ "Default str() output for this radio field -- a <ul>"
+ output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
+ output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
+ output.append('</ul>')
+ return ''.join(output)
+ def __iter__(self):
+ for d in self.datalist:
+ yield d
+ def __len__(self):
+ return len(self.datalist)
+ datalist = []
+ str_data = str(data) # normalize to string
+ for i, (value, display_name) in enumerate(self.choices):
+ selected_html = ''
+ if str(value) == str_data:
+ selected_html = ' checked="checked"'
+ datalist.append({
+ 'value': value,
+ 'name': display_name,
+ 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
+ (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
+ 'label': '<label for="%s">%s</label>' % \
+ (self.get_id() + '_' + str(i), display_name),
+ })
+ return RadioFieldRenderer(datalist, self.ul_class)
+
+ def isValidChoice(self, data, form):
+ str_data = str(data)
+ str_choices = [str(item[0]) for item in self.choices]
+ if str_data not in str_choices:
+ raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices}
+
+class NullBooleanField(SelectField):
+ "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ SelectField.__init__(self, field_name, choices=[('1', _('Unknown')), ('2', _('Yes')), ('3', _('No'))],
+ is_required=is_required, validator_list=validator_list)
+
+ def render(self, data):
+ if data is None: data = '1'
+ elif data == True: data = '2'
+ elif data == False: data = '3'
+ return SelectField.render(self, data)
+
+ def html2python(data):
+ return {None: None, '1': None, '2': True, '3': False}[data]
+ html2python = staticmethod(html2python)
+
+class SelectMultipleField(SelectField):
+ requires_data_list = True
+ def render(self, data):
+ output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
+ (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ self.field_name, self.size)]
+ str_data_list = map(str, data) # normalize to strings
+ for value, choice in self.choices:
+ selected_html = ''
+ if str(value) in str_data_list:
+ selected_html = ' selected="selected"'
+ output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
+ output.append(' </select>')
+ return '\n'.join(output)
+
+ def isValidChoice(self, field_data, all_data):
+ # data is something like ['1', '2', '3']
+ str_choices = [str(item[0]) for item in self.choices]
+ for val in map(str, field_data):
+ if val not in str_choices:
+ raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices}
+
+ def html2python(data):
+ if data is None:
+ raise EmptyValue
+ return data
+ html2python = staticmethod(html2python)
+
+class CheckboxSelectMultipleField(SelectMultipleField):
+ """
+ This has an identical interface to SelectMultipleField, except the rendered
+ widget is different. Instead of a <select multiple>, this widget outputs a
+ <ul> of <input type="checkbox">es.
+
+ Of course, that results in multiple form elements for the same "single"
+ field, so this class's prepare() method flattens the split data elements
+ back into the single list that validators, renderers and save() expect.
+ """
+ requires_data_list = True
+ def __init__(self, field_name, choices=None, ul_class='', validator_list=None):
+ if validator_list is None: validator_list = []
+ if choices is None: choices = []
+ self.ul_class = ul_class
+ SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
+
+ def prepare(self, new_data):
+ # new_data has "split" this field into several fields, so flatten it
+ # back into a single list.
+ data_list = []
+ for value, readable_value in self.choices:
+ if new_data.get('%s%s' % (self.field_name, value), '') == 'on':
+ data_list.append(value)
+ new_data.setlist(self.field_name, data_list)
+
+ def render(self, data):
+ output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
+ str_data_list = map(str, data) # normalize to strings
+ for value, choice in self.choices:
+ checked_html = ''
+ if str(value) in str_data_list:
+ checked_html = ' checked="checked"'
+ field_name = '%s%s' % (self.field_name, value)
+ output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s value="on" /> <label for="%s">%s</label></li>' % \
+ (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
+ self.get_id() + escape(value), choice))
+ output.append('</ul>')
+ return '\n'.join(output)
+
+####################
+# FILE UPLOADS #
+####################
+
+class FileUploadField(FormField):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.field_name, self.is_required = field_name, is_required
+ self.validator_list = [self.isNonEmptyFile] + validator_list
+
+ def isNonEmptyFile(self, field_data, all_data):
+ try:
+ content = field_data['content']
+ except TypeError:
+ raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.")
+ if not content:
+ raise validators.CriticalValidationError, gettext("The submitted file is empty.")
+
+ def render(self, data):
+ return '<input type="file" id="%s" class="v%s" name="%s" />' % \
+ (self.get_id(), self.__class__.__name__, self.field_name)
+
+ def html2python(data):
+ if data is None:
+ raise EmptyValue
+ return data
+ html2python = staticmethod(html2python)
+
+class ImageUploadField(FileUploadField):
+ "A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image."
+ def __init__(self, *args, **kwargs):
+ FileUploadField.__init__(self, *args, **kwargs)
+ self.validator_list.insert(0, self.isValidImage)
+
+ def isValidImage(self, field_data, all_data):
+ try:
+ validators.isValidImage(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+####################
+# INTEGERS/FLOATS #
+####################
+
+class IntegerField(TextField):
+ def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isInteger] + validator_list
+ if member_name is not None:
+ self.member_name = member_name
+ TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isInteger(self, field_data, all_data):
+ try:
+ validators.isInteger(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ if data == '' or data is None:
+ return None
+ return int(data)
+ html2python = staticmethod(html2python)
+
+class SmallIntegerField(IntegerField):
+ def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isSmallInteger] + validator_list
+ IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isSmallInteger(self, field_data, all_data):
+ if not -32768 <= int(field_data) <= 32767:
+ raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
+
+class PositiveIntegerField(IntegerField):
+ def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isPositive] + validator_list
+ IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isPositive(self, field_data, all_data):
+ if int(field_data) < 0:
+ raise validators.CriticalValidationError, gettext("Enter a positive number.")
+
+class PositiveSmallIntegerField(IntegerField):
+ def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isPositiveSmall] + validator_list
+ IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+
+ def isPositiveSmall(self, field_data, all_data):
+ if not 0 <= int(field_data) <= 32767:
+ raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
+
+class FloatField(TextField):
+ def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.max_digits, self.decimal_places = max_digits, decimal_places
+ validator_list = [self.isValidFloat] + validator_list
+ TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list)
+
+ def isValidFloat(self, field_data, all_data):
+ v = validators.IsValidFloat(self.max_digits, self.decimal_places)
+ try:
+ v(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ if data == '' or data is None:
+ return None
+ return float(data)
+ html2python = staticmethod(html2python)
+
+####################
+# DATES AND TIMES #
+####################
+
+class DatetimeField(TextField):
+ """A FormField that automatically converts its data to a datetime.datetime object.
+ The data should be in the format YYYY-MM-DD HH:MM:SS."""
+ def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ self.field_name = field_name
+ self.length, self.maxlength = length, maxlength
+ self.is_required = is_required
+ self.validator_list = [validators.isValidANSIDatetime] + validator_list
+
+ def html2python(data):
+ "Converts the field into a datetime.datetime object"
+ import datetime
+ try:
+ date, time = data.split()
+ y, m, d = date.split('-')
+ timebits = time.split(':')
+ h, mn = timebits[:2]
+ if len(timebits) > 2:
+ s = int(timebits[2])
+ else:
+ s = 0
+ return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s)
+ except ValueError:
+ return None
+ html2python = staticmethod(html2python)
+
+class DateField(TextField):
+ """A FormField that automatically converts its data to a datetime.date object.
+ The data should be in the format YYYY-MM-DD."""
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidDate] + validator_list
+ TextField.__init__(self, field_name, length=10, maxlength=10,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidDate(self, field_data, all_data):
+ try:
+ validators.isValidANSIDate(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ "Converts the field into a datetime.date object"
+ import time, datetime
+ try:
+ time_tuple = time.strptime(data, '%Y-%m-%d')
+ return datetime.date(*time_tuple[0:3])
+ except (ValueError, TypeError):
+ return None
+ html2python = staticmethod(html2python)
+
+class TimeField(TextField):
+ """A FormField that automatically converts its data to a datetime.time object.
+ The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm."""
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidTime] + validator_list
+ TextField.__init__(self, field_name, length=8, maxlength=8,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidTime(self, field_data, all_data):
+ try:
+ validators.isValidANSITime(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ "Converts the field into a datetime.time object"
+ import time, datetime
+ try:
+ part_list = data.split('.')
+ try:
+ time_tuple = time.strptime(part_list[0], '%H:%M:%S')
+ except ValueError: # seconds weren't provided
+ time_tuple = time.strptime(part_list[0], '%H:%M')
+ t = datetime.time(*time_tuple[3:6])
+ if (len(part_list) == 2):
+ t = t.replace(microsecond=int(part_list[1]))
+ return t
+ except (ValueError, TypeError, AttributeError):
+ return None
+ html2python = staticmethod(html2python)
+
+####################
+# INTERNET-RELATED #
+####################
+
+class EmailField(TextField):
+ "A convenience FormField for validating e-mail addresses"
+ def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidEmail] + validator_list
+ TextField.__init__(self, field_name, length, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidEmail(self, field_data, all_data):
+ try:
+ validators.isValidEmail(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+class URLField(TextField):
+ "A convenience FormField for validating URLs"
+ def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidURL] + validator_list
+ TextField.__init__(self, field_name, length=length, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidURL(self, field_data, all_data):
+ try:
+ validators.isValidURL(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+class IPAddressField(TextField):
+ def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidIPAddress] + validator_list
+ TextField.__init__(self, field_name, length=length, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidIPAddress(self, field_data, all_data):
+ try:
+ validators.isValidIPAddress4(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ return data or None
+ html2python = staticmethod(html2python)
+
+####################
+# MISCELLANEOUS #
+####################
+
+class FilePathField(SelectField):
+ "A SelectField whose choices are the files in a given directory."
+ def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None):
+ import os
+ from django.db.models import BLANK_CHOICE_DASH
+ if match is not None:
+ import re
+ match_re = re.compile(match)
+ choices = not is_required and BLANK_CHOICE_DASH[:] or []
+ if recursive:
+ for root, dirs, files in os.walk(path):
+ for f in files:
+ if match is None or match_re.search(f):
+ choices.append((os.path.join(root, f), f))
+ else:
+ try:
+ for f in os.listdir(path):
+ full_file = os.path.join(path, f)
+ if os.path.isfile(full_file) and (match is None or match_re.search(f)):
+ choices.append((full_file, f))
+ except OSError:
+ pass
+ SelectField.__init__(self, field_name, choices, 1, is_required, validator_list)
+
+class PhoneNumberField(TextField):
+ "A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidPhone] + validator_list
+ TextField.__init__(self, field_name, length=12, maxlength=12,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidPhone(self, field_data, all_data):
+ try:
+ validators.isValidPhone(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+class USStateField(TextField):
+ "A convenience FormField for validating U.S. states (e.g. 'IL')"
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isValidUSState] + validator_list
+ TextField.__init__(self, field_name, length=2, maxlength=2,
+ is_required=is_required, validator_list=validator_list)
+
+ def isValidUSState(self, field_data, all_data):
+ try:
+ validators.isValidUSState(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def html2python(data):
+ if data:
+ return data.upper() # Should always be stored in upper case
+ return data
+ html2python = staticmethod(html2python)
+
+class CommaSeparatedIntegerField(TextField):
+ "A convenience FormField for validating comma-separated integer fields"
+ def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
+ validator_list = [self.isCommaSeparatedIntegerList] + validator_list
+ TextField.__init__(self, field_name, length=20, maxlength=maxlength,
+ is_required=is_required, validator_list=validator_list)
+
+ def isCommaSeparatedIntegerList(self, field_data, all_data):
+ try:
+ validators.isCommaSeparatedIntegerList(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
+
+ def render(self, data):
+ if data is None:
+ data = ''
+ elif isinstance(data, (list, tuple)):
+ data = ','.join(data)
+ return super(CommaSeparatedIntegerField, self).render(data)
+
+class RawIdAdminField(CommaSeparatedIntegerField):
+ def html2python(data):
+ if data:
+ return data.split(',')
+ else:
+ return []
+ html2python = staticmethod(html2python)
+
+class XMLLargeTextField(LargeTextField):
+ """
+ A LargeTextField with an XML validator. The schema_path argument is the
+ full path to a Relax NG compact schema to validate against.
+ """
+ def __init__(self, field_name, schema_path, **kwargs):
+ self.schema_path = schema_path
+ kwargs.setdefault('validator_list', []).insert(0, self.isValidXML)
+ LargeTextField.__init__(self, field_name, **kwargs)
+
+ def isValidXML(self, field_data, all_data):
+ v = validators.RelaxNGCompact(self.schema_path)
+ try:
+ v(field_data, all_data)
+ except validators.ValidationError, e:
+ raise validators.CriticalValidationError, e.messages
diff --git a/google_appengine/lib/django/django/shortcuts/__init__.py b/google_appengine/lib/django/django/shortcuts/__init__.py
new file mode 100755
index 0000000..81381d0
--- /dev/null
+++ b/google_appengine/lib/django/django/shortcuts/__init__.py
@@ -0,0 +1,32 @@
+# This module collects helper functions and classes that "span" multiple levels
+# of MVC. In other words, these functions/classes introduce controlled coupling
+# for convenience's sake.
+
+from django.template import loader
+from django.http import HttpResponse, Http404
+from django.db.models.manager import Manager
+
+def render_to_response(*args, **kwargs):
+ return HttpResponse(loader.render_to_string(*args, **kwargs))
+load_and_render = render_to_response # For backwards compatibility.
+
+def get_object_or_404(klass, *args, **kwargs):
+ if isinstance(klass, Manager):
+ manager = klass
+ klass = manager.model
+ else:
+ manager = klass._default_manager
+ try:
+ return manager.get(*args, **kwargs)
+ except klass.DoesNotExist:
+ raise Http404('No %s matches the given query.' % klass._meta.object_name)
+
+def get_list_or_404(klass, *args, **kwargs):
+ if isinstance(klass, Manager):
+ manager = klass
+ else:
+ manager = klass._default_manager
+ obj_list = list(manager.filter(*args, **kwargs))
+ if not obj_list:
+ raise Http404('No %s matches the given query.' % manager.model._meta.object_name)
+ return obj_list
diff --git a/google_appengine/lib/django/django/template/__init__.py b/google_appengine/lib/django/django/template/__init__.py
new file mode 100755
index 0000000..0d8990a
--- /dev/null
+++ b/google_appengine/lib/django/django/template/__init__.py
@@ -0,0 +1,918 @@
+"""
+This is the Django template system.
+
+How it works:
+
+The Lexer.tokenize() function converts a template string (i.e., a string containing
+markup with custom template tags) to tokens, which can be either plain text
+(TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
+
+The Parser() class takes a list of tokens in its constructor, and its parse()
+method returns a compiled template -- which is, under the hood, a list of
+Node objects.
+
+Each Node is responsible for creating some sort of output -- e.g. simple text
+(TextNode), variable values in a given context (VariableNode), results of basic
+logic (IfNode), results of looping (ForNode), or anything else. The core Node
+types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
+define their own custom node types.
+
+Each Node has a render() method, which takes a Context and returns a string of
+the rendered node. For example, the render() method of a Variable Node returns
+the variable's value as a string. The render() method of an IfNode returns the
+rendered output of whatever was inside the loop, recursively.
+
+The Template class is a convenient wrapper that takes care of template
+compilation and rendering.
+
+Usage:
+
+The only thing you should ever use directly in this file is the Template class.
+Create a compiled template object with a template_string, then call render()
+with a context. In the compilation stage, the TemplateSyntaxError exception
+will be raised if the template doesn't have proper syntax.
+
+Sample code:
+
+>>> import template
+>>> s = '''
+... <html>
+... {% if test %}
+... <h1>{{ varvalue }}</h1>
+... {% endif %}
+... </html>
+... '''
+>>> t = template.Template(s)
+
+(t is now a compiled template, and its render() method can be called multiple
+times with multiple contexts)
+
+>>> c = template.Context({'test':True, 'varvalue': 'Hello'})
+>>> t.render(c)
+'\n<html>\n\n <h1>Hello</h1>\n\n</html>\n'
+>>> c = template.Context({'test':False, 'varvalue': 'Hello'})
+>>> t.render(c)
+'\n<html>\n\n</html>\n'
+"""
+import re
+from inspect import getargspec
+from django.conf import settings
+from django.template.context import Context, RequestContext, ContextPopException
+from django.utils.functional import curry
+from django.utils.text import smart_split
+
+__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
+
+TOKEN_TEXT = 0
+TOKEN_VAR = 1
+TOKEN_BLOCK = 2
+TOKEN_COMMENT = 3
+
+# template syntax constants
+FILTER_SEPARATOR = '|'
+FILTER_ARGUMENT_SEPARATOR = ':'
+VARIABLE_ATTRIBUTE_SEPARATOR = '.'
+BLOCK_TAG_START = '{%'
+BLOCK_TAG_END = '%}'
+VARIABLE_TAG_START = '{{'
+VARIABLE_TAG_END = '}}'
+COMMENT_TAG_START = '{#'
+COMMENT_TAG_END = '#}'
+SINGLE_BRACE_START = '{'
+SINGLE_BRACE_END = '}'
+
+ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
+
+# what to report as the origin for templates that come from non-loader sources
+# (e.g. strings)
+UNKNOWN_SOURCE="&lt;unknown source&gt;"
+
+# match a variable or block tag and capture the entire tag, including start/end delimiters
+tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
+ re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
+ re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
+# matches if the string is valid number
+number_re = re.compile(r'[-+]?(\d+|\d*\.\d+)$')
+
+# global dictionary of libraries that have been loaded using get_library
+libraries = {}
+# global list of libraries to load by default for a new parser
+builtins = []
+
+class TemplateSyntaxError(Exception):
+ def __str__(self):
+ try:
+ import cStringIO as StringIO
+ except ImportError:
+ import StringIO
+ output = StringIO.StringIO()
+ output.write(Exception.__str__(self))
+ # Check if we wrapped an exception and print that too.
+ if hasattr(self, 'exc_info'):
+ import traceback
+ output.write('\n\nOriginal ')
+ e = self.exc_info
+ traceback.print_exception(e[0], e[1], e[2], 500, output)
+ return output.getvalue()
+
+class TemplateDoesNotExist(Exception):
+ pass
+
+class VariableDoesNotExist(Exception):
+
+ def __init__(self, msg, params=()):
+ self.msg = msg
+ self.params = params
+
+ def __str__(self):
+ return self.msg % self.params
+
+class InvalidTemplateLibrary(Exception):
+ pass
+
+class Origin(object):
+ def __init__(self, name):
+ self.name = name
+
+ def reload(self):
+ raise NotImplementedError
+
+ def __str__(self):
+ return self.name
+
+class StringOrigin(Origin):
+ def __init__(self, source):
+ super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
+ self.source = source
+
+ def reload(self):
+ return self.source
+
+class Template(object):
+ def __init__(self, template_string, origin=None, name='<Unknown Template>'):
+ "Compilation stage"
+ if settings.TEMPLATE_DEBUG and origin == None:
+ origin = StringOrigin(template_string)
+ # Could do some crazy stack-frame stuff to record where this string
+ # came from...
+ self.nodelist = compile_string(template_string, origin)
+ self.name = name
+
+ def __iter__(self):
+ for node in self.nodelist:
+ for subnode in node:
+ yield subnode
+
+ def render(self, context):
+ "Display stage -- can be called many times"
+ return self.nodelist.render(context)
+
+def compile_string(template_string, origin):
+ "Compiles template_string into NodeList ready for rendering"
+ lexer = lexer_factory(template_string, origin)
+ parser = parser_factory(lexer.tokenize())
+ return parser.parse()
+
+class Token(object):
+ def __init__(self, token_type, contents):
+ "The token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT"
+ self.token_type, self.contents = token_type, contents
+
+ def __str__(self):
+ return '<%s token: "%s...">' % \
+ ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
+ self.contents[:20].replace('\n', ''))
+
+ def split_contents(self):
+ return list(smart_split(self.contents))
+
+class Lexer(object):
+ def __init__(self, template_string, origin):
+ self.template_string = template_string
+ self.origin = origin
+
+ def tokenize(self):
+ "Return a list of tokens from a given template_string"
+ # remove all empty strings, because the regex has a tendency to add them
+ bits = filter(None, tag_re.split(self.template_string))
+ return map(self.create_token, bits)
+
+ def create_token(self,token_string):
+ "Convert the given token string into a new Token object and return it"
+ if token_string.startswith(VARIABLE_TAG_START):
+ token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
+ elif token_string.startswith(BLOCK_TAG_START):
+ token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
+ elif token_string.startswith(COMMENT_TAG_START):
+ token = Token(TOKEN_COMMENT, '')
+ else:
+ token = Token(TOKEN_TEXT, token_string)
+ return token
+
+class DebugLexer(Lexer):
+ def __init__(self, template_string, origin):
+ super(DebugLexer, self).__init__(template_string, origin)
+
+ def tokenize(self):
+ "Return a list of tokens from a given template_string"
+ token_tups, upto = [], 0
+ for match in tag_re.finditer(self.template_string):
+ start, end = match.span()
+ if start > upto:
+ token_tups.append( (self.template_string[upto:start], (upto, start)) )
+ upto = start
+ token_tups.append( (self.template_string[start:end], (start,end)) )
+ upto = end
+ last_bit = self.template_string[upto:]
+ if last_bit:
+ token_tups.append( (last_bit, (upto, upto + len(last_bit))) )
+ return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
+
+ def create_token(self, token_string, source):
+ token = super(DebugLexer, self).create_token(token_string)
+ token.source = source
+ return token
+
+class Parser(object):
+ def __init__(self, tokens):
+ self.tokens = tokens
+ self.tags = {}
+ self.filters = {}
+ for lib in builtins:
+ self.add_library(lib)
+
+ def parse(self, parse_until=None):
+ if parse_until is None: parse_until = []
+ nodelist = self.create_nodelist()
+ while self.tokens:
+ token = self.next_token()
+ if token.token_type == TOKEN_TEXT:
+ self.extend_nodelist(nodelist, TextNode(token.contents), token)
+ elif token.token_type == TOKEN_VAR:
+ if not token.contents:
+ self.empty_variable(token)
+ filter_expression = self.compile_filter(token.contents)
+ var_node = self.create_variable_node(filter_expression)
+ self.extend_nodelist(nodelist, var_node,token)
+ elif token.token_type == TOKEN_BLOCK:
+ if token.contents in parse_until:
+ # put token back on token list so calling code knows why it terminated
+ self.prepend_token(token)
+ return nodelist
+ try:
+ command = token.contents.split()[0]
+ except IndexError:
+ self.empty_block_tag(token)
+ # execute callback function for this tag and append resulting node
+ self.enter_command(command, token)
+ try:
+ compile_func = self.tags[command]
+ except KeyError:
+ self.invalid_block_tag(token, command)
+ try:
+ compiled_result = compile_func(self, token)
+ except TemplateSyntaxError, e:
+ if not self.compile_function_error(token, e):
+ raise
+ self.extend_nodelist(nodelist, compiled_result, token)
+ self.exit_command()
+ if parse_until:
+ self.unclosed_block_tag(parse_until)
+ return nodelist
+
+ def skip_past(self, endtag):
+ while self.tokens:
+ token = self.next_token()
+ if token.token_type == TOKEN_BLOCK and token.contents == endtag:
+ return
+ self.unclosed_block_tag([endtag])
+
+ def create_variable_node(self, filter_expression):
+ return VariableNode(filter_expression)
+
+ def create_nodelist(self):
+ return NodeList()
+
+ def extend_nodelist(self, nodelist, node, token):
+ nodelist.append(node)
+
+ def enter_command(self, command, token):
+ pass
+
+ def exit_command(self):
+ pass
+
+ def error(self, token, msg ):
+ return TemplateSyntaxError(msg)
+
+ def empty_variable(self, token):
+ raise self.error( token, "Empty variable tag")
+
+ def empty_block_tag(self, token):
+ raise self.error( token, "Empty block tag")
+
+ def invalid_block_tag(self, token, command):
+ raise self.error( token, "Invalid block tag: '%s'" % command)
+
+ def unclosed_block_tag(self, parse_until):
+ raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
+
+ def compile_function_error(self, token, e):
+ pass
+
+ def next_token(self):
+ return self.tokens.pop(0)
+
+ def prepend_token(self, token):
+ self.tokens.insert(0, token)
+
+ def delete_first_token(self):
+ del self.tokens[0]
+
+ def add_library(self, lib):
+ self.tags.update(lib.tags)
+ self.filters.update(lib.filters)
+
+ def compile_filter(self, token):
+ "Convenient wrapper for FilterExpression"
+ return FilterExpression(token, self)
+
+ def find_filter(self, filter_name):
+ if self.filters.has_key(filter_name):
+ return self.filters[filter_name]
+ else:
+ raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
+
+class DebugParser(Parser):
+ def __init__(self, lexer):
+ super(DebugParser, self).__init__(lexer)
+ self.command_stack = []
+
+ def enter_command(self, command, token):
+ self.command_stack.append( (command, token.source) )
+
+ def exit_command(self):
+ self.command_stack.pop()
+
+ def error(self, token, msg):
+ return self.source_error(token.source, msg)
+
+ def source_error(self, source,msg):
+ e = TemplateSyntaxError(msg)
+ e.source = source
+ return e
+
+ def create_nodelist(self):
+ return DebugNodeList()
+
+ def create_variable_node(self, contents):
+ return DebugVariableNode(contents)
+
+ def extend_nodelist(self, nodelist, node, token):
+ node.source = token.source
+ super(DebugParser, self).extend_nodelist(nodelist, node, token)
+
+ def unclosed_block_tag(self, parse_until):
+ command, source = self.command_stack.pop()
+ msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until))
+ raise self.source_error( source, msg)
+
+ def compile_function_error(self, token, e):
+ if not hasattr(e, 'source'):
+ e.source = token.source
+
+def lexer_factory(*args, **kwargs):
+ if settings.TEMPLATE_DEBUG:
+ return DebugLexer(*args, **kwargs)
+ else:
+ return Lexer(*args, **kwargs)
+
+def parser_factory(*args, **kwargs):
+ if settings.TEMPLATE_DEBUG:
+ return DebugParser(*args, **kwargs)
+ else:
+ return Parser(*args, **kwargs)
+
+class TokenParser(object):
+ """
+ Subclass this and implement the top() method to parse a template line. When
+ instantiating the parser, pass in the line from the Django template parser.
+
+ The parser's "tagname" instance-variable stores the name of the tag that
+ the filter was called with.
+ """
+ def __init__(self, subject):
+ self.subject = subject
+ self.pointer = 0
+ self.backout = []
+ self.tagname = self.tag()
+
+ def top(self):
+ "Overload this method to do the actual parsing and return the result."
+ raise NotImplemented
+
+ def more(self):
+ "Returns True if there is more stuff in the tag."
+ return self.pointer < len(self.subject)
+
+ def back(self):
+ "Undoes the last microparser. Use this for lookahead and backtracking."
+ if not len(self.backout):
+ raise TemplateSyntaxError, "back called without some previous parsing"
+ self.pointer = self.backout.pop()
+
+ def tag(self):
+ "A microparser that just returns the next tag from the line."
+ subject = self.subject
+ i = self.pointer
+ if i >= len(subject):
+ raise TemplateSyntaxError, "expected another tag, found end of string: %s" % subject
+ p = i
+ while i < len(subject) and subject[i] not in (' ', '\t'):
+ i += 1
+ s = subject[p:i]
+ while i < len(subject) and subject[i] in (' ', '\t'):
+ i += 1
+ self.backout.append(self.pointer)
+ self.pointer = i
+ return s
+
+ def value(self):
+ "A microparser that parses for a value: some string constant or variable name."
+ subject = self.subject
+ i = self.pointer
+ if i >= len(subject):
+ raise TemplateSyntaxError, "Searching for value. Expected another value but found end of string: %s" % subject
+ if subject[i] in ('"', "'"):
+ p = i
+ i += 1
+ while i < len(subject) and subject[i] != subject[p]:
+ i += 1
+ if i >= len(subject):
+ raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % (i, subject)
+ i += 1
+ res = subject[p:i]
+ while i < len(subject) and subject[i] in (' ', '\t'):
+ i += 1
+ self.backout.append(self.pointer)
+ self.pointer = i
+ return res
+ else:
+ p = i
+ while i < len(subject) and subject[i] not in (' ', '\t'):
+ if subject[i] in ('"', "'"):
+ c = subject[i]
+ i += 1
+ while i < len(subject) and subject[i] != c:
+ i += 1
+ if i >= len(subject):
+ raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
+ i += 1
+ s = subject[p:i]
+ while i < len(subject) and subject[i] in (' ', '\t'):
+ i += 1
+ self.backout.append(self.pointer)
+ self.pointer = i
+ return s
+
+
+
+
+filter_raw_string = r"""
+^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
+^"(?P<constant>%(str)s)"|
+^(?P<var>[%(var_chars)s]+)|
+ (?:%(filter_sep)s
+ (?P<filter_name>\w+)
+ (?:%(arg_sep)s
+ (?:
+ %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
+ "(?P<constant_arg>%(str)s)"|
+ (?P<var_arg>[%(var_chars)s]+)
+ )
+ )?
+ )""" % {
+ 'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
+ 'var_chars': "A-Za-z0-9\_\." ,
+ 'filter_sep': re.escape(FILTER_SEPARATOR),
+ 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
+ 'i18n_open' : re.escape("_("),
+ 'i18n_close' : re.escape(")"),
+ }
+
+filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
+filter_re = re.compile(filter_raw_string)
+
+class FilterExpression(object):
+ """
+ Parses a variable token and its optional filters (all as a single string),
+ and return a list of tuples of the filter name and arguments.
+ Sample:
+ >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
+ >>> p = FilterParser(token)
+ >>> p.filters
+ [('default', 'Default value'), ('date', 'Y-m-d')]
+ >>> p.var
+ 'variable'
+
+ This class should never be instantiated outside of the
+ get_filters_from_token helper function.
+ """
+ def __init__(self, token, parser):
+ self.token = token
+ matches = filter_re.finditer(token)
+ var = None
+ filters = []
+ upto = 0
+ for match in matches:
+ start = match.start()
+ if upto != start:
+ raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \
+ (token[:upto], token[upto:start], token[start:])
+ if var == None:
+ var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
+ if i18n_constant:
+ var = '"%s"' % _(i18n_constant)
+ elif constant:
+ var = '"%s"' % constant
+ upto = match.end()
+ if var == None:
+ raise TemplateSyntaxError, "Could not find variable at start of %s" % token
+ elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
+ raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
+ else:
+ filter_name = match.group("filter_name")
+ args = []
+ constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
+ if i18n_arg:
+ args.append((False, _(i18n_arg.replace(r'\"', '"'))))
+ elif constant_arg is not None:
+ args.append((False, constant_arg.replace(r'\"', '"')))
+ elif var_arg:
+ args.append((True, var_arg))
+ filter_func = parser.find_filter(filter_name)
+ self.args_check(filter_name,filter_func, args)
+ filters.append( (filter_func,args))
+ upto = match.end()
+ if upto != len(token):
+ raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
+ self.var, self.filters = var, filters
+
+ def resolve(self, context, ignore_failures=False):
+ try:
+ obj = resolve_variable(self.var, context)
+ except VariableDoesNotExist:
+ if ignore_failures:
+ obj = None
+ else:
+ if settings.TEMPLATE_STRING_IF_INVALID:
+ return settings.TEMPLATE_STRING_IF_INVALID
+ else:
+ obj = settings.TEMPLATE_STRING_IF_INVALID
+ for func, args in self.filters:
+ arg_vals = []
+ for lookup, arg in args:
+ if not lookup:
+ arg_vals.append(arg)
+ else:
+ arg_vals.append(resolve_variable(arg, context))
+ obj = func(obj, *arg_vals)
+ return obj
+
+ def args_check(name, func, provided):
+ provided = list(provided)
+ plen = len(provided)
+ # Check to see if a decorator is providing the real function.
+ func = getattr(func, '_decorated_function', func)
+ args, varargs, varkw, defaults = getargspec(func)
+ # First argument is filter input.
+ args.pop(0)
+ if defaults:
+ nondefs = args[:-len(defaults)]
+ else:
+ nondefs = args
+ # Args without defaults must be provided.
+ try:
+ for arg in nondefs:
+ provided.pop(0)
+ except IndexError:
+ # Not enough
+ raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
+
+ # Defaults can be overridden.
+ defaults = defaults and list(defaults) or []
+ try:
+ for parg in provided:
+ defaults.pop(0)
+ except IndexError:
+ # Too many.
+ raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
+
+ return True
+ args_check = staticmethod(args_check)
+
+ def __str__(self):
+ return self.token
+
+def resolve_variable(path, context):
+ """
+ Returns the resolved variable, which may contain attribute syntax, within
+ the given context. The variable may be a hard-coded string (if it begins
+ and ends with single or double quote marks).
+
+ >>> c = {'article': {'section':'News'}}
+ >>> resolve_variable('article.section', c)
+ 'News'
+ >>> resolve_variable('article', c)
+ {'section': 'News'}
+ >>> class AClass: pass
+ >>> c = AClass()
+ >>> c.article = AClass()
+ >>> c.article.section = 'News'
+ >>> resolve_variable('article.section', c)
+ 'News'
+
+ (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
+ """
+ if number_re.match(path):
+ number_type = '.' in path and float or int
+ current = number_type(path)
+ elif path[0] in ('"', "'") and path[0] == path[-1]:
+ current = path[1:-1]
+ else:
+ current = context
+ bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
+ while bits:
+ try: # dictionary lookup
+ current = current[bits[0]]
+ except (TypeError, AttributeError, KeyError):
+ try: # attribute lookup
+ current = getattr(current, bits[0])
+ if callable(current):
+ if getattr(current, 'alters_data', False):
+ current = settings.TEMPLATE_STRING_IF_INVALID
+ else:
+ try: # method call (assuming no args required)
+ current = current()
+ except TypeError: # arguments *were* required
+ # GOTCHA: This will also catch any TypeError
+ # raised in the function itself.
+ current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
+ except Exception, e:
+ if getattr(e, 'silent_variable_failure', False):
+ current = settings.TEMPLATE_STRING_IF_INVALID
+ else:
+ raise
+ except (TypeError, AttributeError):
+ try: # list-index lookup
+ current = current[int(bits[0])]
+ except (IndexError, # list index out of range
+ ValueError, # invalid literal for int()
+ KeyError, # current is a dict without `int(bits[0])` key
+ TypeError, # unsubscriptable object
+ ):
+ raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute
+ except Exception, e:
+ if getattr(e, 'silent_variable_failure', False):
+ current = settings.TEMPLATE_STRING_IF_INVALID
+ else:
+ raise
+ del bits[0]
+ return current
+
+class Node(object):
+ def render(self, context):
+ "Return the node rendered as a string"
+ pass
+
+ def __iter__(self):
+ yield self
+
+ def get_nodes_by_type(self, nodetype):
+ "Return a list of all nodes (within this node and its nodelist) of the given type"
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ if hasattr(self, 'nodelist'):
+ nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
+ return nodes
+
+class NodeList(list):
+ def render(self, context):
+ bits = []
+ for node in self:
+ if isinstance(node, Node):
+ bits.append(self.render_node(node, context))
+ else:
+ bits.append(node)
+ return ''.join(bits)
+
+ def get_nodes_by_type(self, nodetype):
+ "Return a list of all nodes of the given type"
+ nodes = []
+ for node in self:
+ nodes.extend(node.get_nodes_by_type(nodetype))
+ return nodes
+
+ def render_node(self, node, context):
+ return(node.render(context))
+
+class DebugNodeList(NodeList):
+ def render_node(self, node, context):
+ try:
+ result = node.render(context)
+ except TemplateSyntaxError, e:
+ if not hasattr(e, 'source'):
+ e.source = node.source
+ raise
+ except Exception, e:
+ from sys import exc_info
+ wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
+ wrapped.source = node.source
+ wrapped.exc_info = exc_info()
+ raise wrapped
+ return result
+
+class TextNode(Node):
+ def __init__(self, s):
+ self.s = s
+
+ def __repr__(self):
+ return "<Text Node: '%s'>" % self.s[:25]
+
+ def render(self, context):
+ return self.s
+
+class VariableNode(Node):
+ def __init__(self, filter_expression):
+ self.filter_expression = filter_expression
+
+ def __repr__(self):
+ return "<Variable Node: %s>" % self.filter_expression
+
+ def encode_output(self, output):
+ # Check type so that we don't run str() on a Unicode object
+ if not isinstance(output, basestring):
+ try:
+ return str(output)
+ except UnicodeEncodeError:
+ # If __str__() returns a Unicode object, convert it to bytestring.
+ return unicode(output).encode(settings.DEFAULT_CHARSET)
+ elif isinstance(output, unicode):
+ return output.encode(settings.DEFAULT_CHARSET)
+ else:
+ return output
+
+ def render(self, context):
+ output = self.filter_expression.resolve(context)
+ return self.encode_output(output)
+
+class DebugVariableNode(VariableNode):
+ def render(self, context):
+ try:
+ output = self.filter_expression.resolve(context)
+ except TemplateSyntaxError, e:
+ if not hasattr(e, 'source'):
+ e.source = self.source
+ raise
+ return self.encode_output(output)
+
+def generic_tag_compiler(params, defaults, name, node_class, parser, token):
+ "Returns a template.Node subclass."
+ bits = token.split_contents()[1:]
+ bmax = len(params)
+ def_len = defaults and len(defaults) or 0
+ bmin = bmax - def_len
+ if(len(bits) < bmin or len(bits) > bmax):
+ if bmin == bmax:
+ message = "%s takes %s arguments" % (name, bmin)
+ else:
+ message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
+ raise TemplateSyntaxError, message
+ return node_class(bits)
+
+class Library(object):
+ def __init__(self):
+ self.filters = {}
+ self.tags = {}
+
+ def tag(self, name=None, compile_function=None):
+ if name == None and compile_function == None:
+ # @register.tag()
+ return self.tag_function
+ elif name != None and compile_function == None:
+ if(callable(name)):
+ # @register.tag
+ return self.tag_function(name)
+ else:
+ # @register.tag('somename') or @register.tag(name='somename')
+ def dec(func):
+ return self.tag(name, func)
+ return dec
+ elif name != None and compile_function != None:
+ # register.tag('somename', somefunc)
+ self.tags[name] = compile_function
+ return compile_function
+ else:
+ raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
+
+ def tag_function(self,func):
+ self.tags[getattr(func, "_decorated_function", func).__name__] = func
+ return func
+
+ def filter(self, name=None, filter_func=None):
+ if name == None and filter_func == None:
+ # @register.filter()
+ return self.filter_function
+ elif filter_func == None:
+ if(callable(name)):
+ # @register.filter
+ return self.filter_function(name)
+ else:
+ # @register.filter('somename') or @register.filter(name='somename')
+ def dec(func):
+ return self.filter(name, func)
+ return dec
+ elif name != None and filter_func != None:
+ # register.filter('somename', somefunc)
+ self.filters[name] = filter_func
+ return filter_func
+ else:
+ raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)
+
+ def filter_function(self, func):
+ self.filters[getattr(func, "_decorated_function", func).__name__] = func
+ return func
+
+ def simple_tag(self,func):
+ params, xx, xxx, defaults = getargspec(func)
+
+ class SimpleNode(Node):
+ def __init__(self, vars_to_resolve):
+ self.vars_to_resolve = vars_to_resolve
+
+ def render(self, context):
+ resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
+ return func(*resolved_vars)
+
+ compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
+ compile_func.__doc__ = func.__doc__
+ self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
+ return func
+
+ def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
+ def dec(func):
+ params, xx, xxx, defaults = getargspec(func)
+ if takes_context:
+ if params[0] == 'context':
+ params = params[1:]
+ else:
+ raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
+
+ class InclusionNode(Node):
+ def __init__(self, vars_to_resolve):
+ self.vars_to_resolve = vars_to_resolve
+
+ def render(self, context):
+ resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
+ if takes_context:
+ args = [context] + resolved_vars
+ else:
+ args = resolved_vars
+
+ dict = func(*args)
+
+ if not getattr(self, 'nodelist', False):
+ from django.template.loader import get_template, select_template
+ if hasattr(file_name, '__iter__'):
+ t = select_template(file_name)
+ else:
+ t = get_template(file_name)
+ self.nodelist = t.nodelist
+ return self.nodelist.render(context_class(dict))
+
+ compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
+ compile_func.__doc__ = func.__doc__
+ self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
+ return func
+ return dec
+
+def get_library(module_name):
+ lib = libraries.get(module_name, None)
+ if not lib:
+ try:
+ mod = __import__(module_name, {}, {}, [''])
+ except ImportError, e:
+ raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e)
+ try:
+ lib = mod.register
+ libraries[module_name] = lib
+ except AttributeError:
+ raise InvalidTemplateLibrary, "Template library %s does not have a variable named 'register'" % module_name
+ return lib
+
+def add_to_builtins(module_name):
+ builtins.append(get_library(module_name))
+
+add_to_builtins('django.template.defaulttags')
+add_to_builtins('django.template.defaultfilters')
diff --git a/google_appengine/lib/django/django/template/__init__.pyc b/google_appengine/lib/django/django/template/__init__.pyc
new file mode 100644
index 0000000..31efcb5
--- /dev/null
+++ b/google_appengine/lib/django/django/template/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/template/context.py b/google_appengine/lib/django/django/template/context.py
new file mode 100755
index 0000000..25397b0
--- /dev/null
+++ b/google_appengine/lib/django/django/template/context.py
@@ -0,0 +1,100 @@
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+
+_standard_context_processors = None
+
+class ContextPopException(Exception):
+ "pop() has been called more times than push()"
+ pass
+
+class Context(object):
+ "A stack container for variable context"
+ def __init__(self, dict_=None):
+ dict_ = dict_ or {}
+ self.dicts = [dict_]
+
+ def __repr__(self):
+ return repr(self.dicts)
+
+ def __iter__(self):
+ for d in self.dicts:
+ yield d
+
+ def push(self):
+ self.dicts = [{}] + self.dicts
+
+ def pop(self):
+ if len(self.dicts) == 1:
+ raise ContextPopException
+ del self.dicts[0]
+
+ def __setitem__(self, key, value):
+ "Set a variable in the current context"
+ self.dicts[0][key] = value
+
+ def __getitem__(self, key):
+ "Get a variable's value, starting at the current context and going upward"
+ for d in self.dicts:
+ if d.has_key(key):
+ return d[key]
+ raise KeyError(key)
+
+ def __delitem__(self, key):
+ "Delete a variable from the current context"
+ del self.dicts[0][key]
+
+ def has_key(self, key):
+ for d in self.dicts:
+ if d.has_key(key):
+ return True
+ return False
+
+ def __contains__(self, key):
+ return self.has_key(key)
+
+ def get(self, key, otherwise=None):
+ for d in self.dicts:
+ if d.has_key(key):
+ return d[key]
+ return otherwise
+
+ def update(self, other_dict):
+ "Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
+ self.dicts = [other_dict] + self.dicts
+
+# This is a function rather than module-level procedural code because we only
+# want it to execute if somebody uses RequestContext.
+def get_standard_processors():
+ global _standard_context_processors
+ if _standard_context_processors is None:
+ processors = []
+ for path in settings.TEMPLATE_CONTEXT_PROCESSORS:
+ i = path.rfind('.')
+ module, attr = path[:i], path[i+1:]
+ try:
+ mod = __import__(module, {}, {}, [attr])
+ except ImportError, e:
+ raise ImproperlyConfigured, 'Error importing request processor module %s: "%s"' % (module, e)
+ try:
+ func = getattr(mod, attr)
+ except AttributeError:
+ raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable request processor' % (module, attr)
+ processors.append(func)
+ _standard_context_processors = tuple(processors)
+ return _standard_context_processors
+
+class RequestContext(Context):
+ """
+ This subclass of template.Context automatically populates itself using
+ the processors defined in TEMPLATE_CONTEXT_PROCESSORS.
+ Additional processors can be specified as a list of callables
+ using the "processors" keyword argument.
+ """
+ def __init__(self, request, dict=None, processors=None):
+ Context.__init__(self, dict)
+ if processors is None:
+ processors = ()
+ else:
+ processors = tuple(processors)
+ for processor in get_standard_processors() + processors:
+ self.update(processor(request))
diff --git a/google_appengine/lib/django/django/template/context.pyc b/google_appengine/lib/django/django/template/context.pyc
new file mode 100644
index 0000000..15f933c
--- /dev/null
+++ b/google_appengine/lib/django/django/template/context.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/template/defaultfilters.py b/google_appengine/lib/django/django/template/defaultfilters.py
new file mode 100755
index 0000000..a025365
--- /dev/null
+++ b/google_appengine/lib/django/django/template/defaultfilters.py
@@ -0,0 +1,617 @@
+"Default variable filters"
+
+from django.template import resolve_variable, Library
+from django.conf import settings
+from django.utils.translation import gettext
+import re
+import random as random_module
+
+register = Library()
+
+#######################
+# STRING DECORATOR #
+#######################
+
+def smart_string(obj):
+ # FUTURE: Unicode strings should probably be normalized to a specific
+ # encoding and non-unicode strings should be converted to unicode too.
+# if isinstance(obj, unicode):
+# obj = obj.encode(settings.DEFAULT_CHARSET)
+# else:
+# obj = unicode(obj, settings.DEFAULT_CHARSET)
+ # FUTURE: Replace dumb string logic below with cool unicode logic above.
+ if not isinstance(obj, basestring):
+ obj = str(obj)
+ return obj
+
+def stringfilter(func):
+ """
+ Decorator for filters which should only receive strings. The object passed
+ as the first positional argument will be converted to a string.
+ """
+ def _dec(*args, **kwargs):
+ if args:
+ args = list(args)
+ args[0] = smart_string(args[0])
+ return func(*args, **kwargs)
+
+ # Include a reference to the real function (used to check original
+ # arguments by the template parser).
+ _dec._decorated_function = getattr(func, '_decorated_function', func)
+ return _dec
+
+###################
+# STRINGS #
+###################
+
+
+def addslashes(value):
+ "Adds slashes - useful for passing strings to JavaScript, for example."
+ return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
+addslashes = stringfilter(addslashes)
+
+def capfirst(value):
+ "Capitalizes the first character of the value"
+ return value and value[0].upper() + value[1:]
+capfirst = stringfilter(capfirst)
+
+def fix_ampersands(value):
+ "Replaces ampersands with ``&amp;`` entities"
+ from django.utils.html import fix_ampersands
+ return fix_ampersands(value)
+fix_ampersands = stringfilter(fix_ampersands)
+
+def floatformat(text, arg=-1):
+ """
+ If called without an argument, displays a floating point
+ number as 34.2 -- but only if there's a point to be displayed.
+ With a positive numeric argument, it displays that many decimal places
+ always.
+ With a negative numeric argument, it will display that many decimal
+ places -- but only if there's places to be displayed.
+ Examples:
+
+ * num1 = 34.23234
+ * num2 = 34.00000
+ * num1|floatformat results in 34.2
+ * num2|floatformat is 34
+ * num1|floatformat:3 is 34.232
+ * num2|floatformat:3 is 34.000
+ * num1|floatformat:-3 is 34.232
+ * num2|floatformat:-3 is 34
+ """
+ try:
+ f = float(text)
+ except ValueError:
+ return ''
+ try:
+ d = int(arg)
+ except ValueError:
+ return smart_string(f)
+ m = f - int(f)
+ if not m and d < 0:
+ return '%d' % int(f)
+ else:
+ formatstr = '%%.%df' % abs(d)
+ return formatstr % f
+
+def linenumbers(value):
+ "Displays text with line numbers"
+ from django.utils.html import escape
+ lines = value.split('\n')
+ # Find the maximum width of the line count, for use with zero padding string format command
+ width = str(len(str(len(lines))))
+ for i, line in enumerate(lines):
+ lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
+ return '\n'.join(lines)
+linenumbers = stringfilter(linenumbers)
+
+def lower(value):
+ "Converts a string into all lowercase"
+ return value.lower()
+lower = stringfilter(lower)
+
+def make_list(value):
+ """
+ Returns the value turned into a list. For an integer, it's a list of
+ digits. For a string, it's a list of characters.
+ """
+ return list(value)
+make_list = stringfilter(make_list)
+
+def slugify(value):
+ "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
+ value = re.sub('[^\w\s-]', '', value).strip().lower()
+ return re.sub('[-\s]+', '-', value)
+slugify = stringfilter(slugify)
+
+def stringformat(value, arg):
+ """
+ Formats the variable according to the argument, a string formatting specifier.
+ This specifier uses Python string formating syntax, with the exception that
+ the leading "%" is dropped.
+
+ See http://docs.python.org/lib/typesseq-strings.html for documentation
+ of Python string formatting
+ """
+ try:
+ return ("%" + str(arg)) % value
+ except (ValueError, TypeError):
+ return ""
+
+def title(value):
+ "Converts a string into titlecase"
+ return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
+title = stringfilter(title)
+
+def truncatewords(value, arg):
+ """
+ Truncates a string after a certain number of words
+
+ Argument: Number of words to truncate after
+ """
+ from django.utils.text import truncate_words
+ try:
+ length = int(arg)
+ except ValueError: # invalid literal for int()
+ return value # Fail silently.
+ if not isinstance(value, basestring):
+ value = str(value)
+ return truncate_words(value, length)
+truncatewords = stringfilter(truncatewords)
+
+def truncatewords_html(value, arg):
+ """
+ Truncates HTML after a certain number of words
+
+ Argument: Number of words to truncate after
+ """
+ from django.utils.text import truncate_html_words
+ try:
+ length = int(arg)
+ except ValueError: # invalid literal for int()
+ return value # Fail silently.
+ if not isinstance(value, basestring):
+ value = str(value)
+ return truncate_html_words(value, length)
+truncatewords_html = stringfilter(truncatewords_html)
+
+def upper(value):
+ "Converts a string into all uppercase"
+ return value.upper()
+upper = stringfilter(upper)
+
+def urlencode(value):
+ "Escapes a value for use in a URL"
+ import urllib
+ if not isinstance(value, basestring):
+ value = str(value)
+ return urllib.quote(value)
+urlencode = stringfilter(urlencode)
+
+def urlize(value):
+ "Converts URLs in plain text into clickable links"
+ from django.utils.html import urlize
+ return urlize(value, nofollow=True)
+urlize = stringfilter(urlize)
+
+def urlizetrunc(value, limit):
+ """
+ Converts URLs into clickable links, truncating URLs to the given character limit,
+ and adding 'rel=nofollow' attribute to discourage spamming.
+
+ Argument: Length to truncate URLs to.
+ """
+ from django.utils.html import urlize
+ return urlize(value, trim_url_limit=int(limit), nofollow=True)
+urlizetrunc = stringfilter(urlizetrunc)
+
+def wordcount(value):
+ "Returns the number of words"
+ return len(value.split())
+wordcount = stringfilter(wordcount)
+
+def wordwrap(value, arg):
+ """
+ Wraps words at specified line length
+
+ Argument: number of characters to wrap the text at.
+ """
+ from django.utils.text import wrap
+ return wrap(value, int(arg))
+wordwrap = stringfilter(wordwrap)
+
+def ljust(value, arg):
+ """
+ Left-aligns the value in a field of a given width
+
+ Argument: field size
+ """
+ return value.ljust(int(arg))
+ljust = stringfilter(ljust)
+
+def rjust(value, arg):
+ """
+ Right-aligns the value in a field of a given width
+
+ Argument: field size
+ """
+ return value.rjust(int(arg))
+rjust = stringfilter(rjust)
+
+def center(value, arg):
+ "Centers the value in a field of a given width"
+ return value.center(int(arg))
+center = stringfilter(center)
+
+def cut(value, arg):
+ "Removes all values of arg from the given string"
+ return value.replace(arg, '')
+cut = stringfilter(cut)
+
+###################
+# HTML STRINGS #
+###################
+
+def escape(value):
+ "Escapes a string's HTML"
+ from django.utils.html import escape
+ return escape(value)
+escape = stringfilter(escape)
+
+def linebreaks(value):
+ "Converts newlines into <p> and <br />s"
+ from django.utils.html import linebreaks
+ return linebreaks(value)
+linebreaks = stringfilter(linebreaks)
+
+def linebreaksbr(value):
+ "Converts newlines into <br />s"
+ return value.replace('\n', '<br />')
+linebreaksbr = stringfilter(linebreaksbr)
+
+def removetags(value, tags):
+ "Removes a space separated list of [X]HTML tags from the output"
+ tags = [re.escape(tag) for tag in tags.split()]
+ tags_re = '(%s)' % '|'.join(tags)
+ starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re)
+ endtag_re = re.compile('</%s>' % tags_re)
+ value = starttag_re.sub('', value)
+ value = endtag_re.sub('', value)
+ return value
+removetags = stringfilter(removetags)
+
+def striptags(value):
+ "Strips all [X]HTML tags"
+ from django.utils.html import strip_tags
+ return strip_tags(value)
+striptags = stringfilter(striptags)
+
+###################
+# LISTS #
+###################
+
+def dictsort(value, arg):
+ """
+ Takes a list of dicts, returns that list sorted by the property given in
+ the argument.
+ """
+ decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated.sort()
+ return [item[1] for item in decorated]
+
+def dictsortreversed(value, arg):
+ """
+ Takes a list of dicts, returns that list sorted in reverse order by the
+ property given in the argument.
+ """
+ decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated.sort()
+ decorated.reverse()
+ return [item[1] for item in decorated]
+
+def first(value):
+ "Returns the first item in a list"
+ try:
+ return value[0]
+ except IndexError:
+ return ''
+
+def join(value, arg):
+ "Joins a list with a string, like Python's ``str.join(list)``"
+ try:
+ return arg.join(map(smart_string, value))
+ except AttributeError: # fail silently but nicely
+ return value
+
+def length(value):
+ "Returns the length of the value - useful for lists"
+ return len(value)
+
+def length_is(value, arg):
+ "Returns a boolean of whether the value's length is the argument"
+ return len(value) == int(arg)
+
+def random(value):
+ "Returns a random item from the list"
+ return random_module.choice(value)
+
+def slice_(value, arg):
+ """
+ Returns a slice of the list.
+
+ Uses the same syntax as Python's list slicing; see
+ http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
+ for an introduction.
+ """
+ try:
+ bits = []
+ for x in arg.split(':'):
+ if len(x) == 0:
+ bits.append(None)
+ else:
+ bits.append(int(x))
+ return value[slice(*bits)]
+
+ except (ValueError, TypeError):
+ return value # Fail silently.
+
+def unordered_list(value):
+ """
+ Recursively takes a self-nested list and returns an HTML unordered list --
+ WITHOUT opening and closing <ul> tags.
+
+ The list is assumed to be in the proper format. For example, if ``var`` contains
+ ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
+ then ``{{ var|unordered_list }}`` would return::
+
+ <li>States
+ <ul>
+ <li>Kansas
+ <ul>
+ <li>Lawrence</li>
+ <li>Topeka</li>
+ </ul>
+ </li>
+ <li>Illinois</li>
+ </ul>
+ </li>
+ """
+ def _helper(value, tabs):
+ indent = '\t' * tabs
+ if value[1]:
+ return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
+ '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
+ else:
+ return '%s<li>%s</li>' % (indent, value[0])
+ return _helper(value, 1)
+
+###################
+# INTEGERS #
+###################
+
+def add(value, arg):
+ "Adds the arg to the value"
+ return int(value) + int(arg)
+
+def get_digit(value, arg):
+ """
+ Given a whole number, returns the requested digit of it, where 1 is the
+ right-most digit, 2 is the second-right-most digit, etc. Returns the
+ original value for invalid input (if input or argument is not an integer,
+ or if argument is less than 1). Otherwise, output is always an integer.
+ """
+ try:
+ arg = int(arg)
+ value = int(value)
+ except ValueError:
+ return value # Fail silently for an invalid argument
+ if arg < 1:
+ return value
+ try:
+ return int(str(value)[-arg])
+ except IndexError:
+ return 0
+
+###################
+# DATES #
+###################
+
+def date(value, arg=None):
+ "Formats a date according to the given format"
+ from django.utils.dateformat import format
+ if not value:
+ return ''
+ if arg is None:
+ arg = settings.DATE_FORMAT
+ return format(value, arg)
+
+def time(value, arg=None):
+ "Formats a time according to the given format"
+ from django.utils.dateformat import time_format
+ if value in (None, ''):
+ return ''
+ if arg is None:
+ arg = settings.TIME_FORMAT
+ return time_format(value, arg)
+
+def timesince(value, arg=None):
+ 'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
+ from django.utils.timesince import timesince
+ if not value:
+ return ''
+ if arg:
+ return timesince(arg, value)
+ return timesince(value)
+
+def timeuntil(value, arg=None):
+ 'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
+ from django.utils.timesince import timesince
+ from datetime import datetime
+ if not value:
+ return ''
+ if arg:
+ return timesince(arg, value)
+ return timesince(datetime.now(), value)
+
+###################
+# LOGIC #
+###################
+
+def default(value, arg):
+ "If value is unavailable, use given default"
+ return value or arg
+
+def default_if_none(value, arg):
+ "If value is None, use given default"
+ if value is None:
+ return arg
+ return value
+
+def divisibleby(value, arg):
+ "Returns true if the value is devisible by the argument"
+ return int(value) % int(arg) == 0
+
+def yesno(value, arg=None):
+ """
+ Given a string mapping values for true, false and (optionally) None,
+ returns one of those strings accoding to the value:
+
+ ========== ====================== ==================================
+ Value Argument Outputs
+ ========== ====================== ==================================
+ ``True`` ``"yeah,no,maybe"`` ``yeah``
+ ``False`` ``"yeah,no,maybe"`` ``no``
+ ``None`` ``"yeah,no,maybe"`` ``maybe``
+ ``None`` ``"yeah,no"`` ``"no"`` (converts None to False
+ if no mapping for None is given.
+ ========== ====================== ==================================
+ """
+ if arg is None:
+ arg = gettext('yes,no,maybe')
+ bits = arg.split(',')
+ if len(bits) < 2:
+ return value # Invalid arg.
+ try:
+ yes, no, maybe = bits
+ except ValueError: # unpack list of wrong size (no "maybe" value provided)
+ yes, no, maybe = bits[0], bits[1], bits[1]
+ if value is None:
+ return maybe
+ if value:
+ return yes
+ return no
+
+###################
+# MISC #
+###################
+
+def filesizeformat(bytes):
+ """
+ Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
+ bytes, etc).
+ """
+ try:
+ bytes = float(bytes)
+ except TypeError:
+ return "0 bytes"
+
+ if bytes < 1024:
+ return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
+ if bytes < 1024 * 1024:
+ return "%.1f KB" % (bytes / 1024)
+ if bytes < 1024 * 1024 * 1024:
+ return "%.1f MB" % (bytes / (1024 * 1024))
+ return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
+
+def pluralize(value, arg='s'):
+ """
+ Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes'
+ By default, 's' is used as a suffix; if an argument is provided, that string
+ is used instead. If the provided argument contains a comma, the text before
+ the comma is used for the singular case.
+ """
+ if not ',' in arg:
+ arg = ',' + arg
+ bits = arg.split(',')
+ if len(bits) > 2:
+ return ''
+ singular_suffix, plural_suffix = bits[:2]
+
+ try:
+ if int(value) != 1:
+ return plural_suffix
+ except ValueError: # invalid string that's not a number
+ pass
+ except TypeError: # value isn't a string or a number; maybe it's a list?
+ try:
+ if len(value) != 1:
+ return plural_suffix
+ except TypeError: # len() of unsized object
+ pass
+ return singular_suffix
+
+def phone2numeric(value):
+ "Takes a phone number and converts it in to its numerical equivalent"
+ from django.utils.text import phone2numeric
+ return phone2numeric(value)
+
+def pprint(value):
+ "A wrapper around pprint.pprint -- for debugging, really"
+ from pprint import pformat
+ try:
+ return pformat(value)
+ except Exception, e:
+ return "Error in formatting:%s" % e
+
+# Syntax: register.filter(name of filter, callback)
+register.filter(add)
+register.filter(addslashes)
+register.filter(capfirst)
+register.filter(center)
+register.filter(cut)
+register.filter(date)
+register.filter(default)
+register.filter(default_if_none)
+register.filter(dictsort)
+register.filter(dictsortreversed)
+register.filter(divisibleby)
+register.filter(escape)
+register.filter(filesizeformat)
+register.filter(first)
+register.filter(fix_ampersands)
+register.filter(floatformat)
+register.filter(get_digit)
+register.filter(join)
+register.filter(length)
+register.filter(length_is)
+register.filter(linebreaks)
+register.filter(linebreaksbr)
+register.filter(linenumbers)
+register.filter(ljust)
+register.filter(lower)
+register.filter(make_list)
+register.filter(phone2numeric)
+register.filter(pluralize)
+register.filter(pprint)
+register.filter(removetags)
+register.filter(random)
+register.filter(rjust)
+register.filter('slice', slice_)
+register.filter(slugify)
+register.filter(stringformat)
+register.filter(striptags)
+register.filter(time)
+register.filter(timesince)
+register.filter(timeuntil)
+register.filter(title)
+register.filter(truncatewords)
+register.filter(truncatewords_html)
+register.filter(unordered_list)
+register.filter(upper)
+register.filter(urlencode)
+register.filter(urlize)
+register.filter(urlizetrunc)
+register.filter(wordcount)
+register.filter(wordwrap)
+register.filter(yesno)
diff --git a/google_appengine/lib/django/django/template/defaultfilters.pyc b/google_appengine/lib/django/django/template/defaultfilters.pyc
new file mode 100644
index 0000000..0cd602b
--- /dev/null
+++ b/google_appengine/lib/django/django/template/defaultfilters.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/template/defaulttags.py b/google_appengine/lib/django/django/template/defaulttags.py
new file mode 100755
index 0000000..b18fa1d
--- /dev/null
+++ b/google_appengine/lib/django/django/template/defaulttags.py
@@ -0,0 +1,969 @@
+"Default tags used by the template system, available to all templates."
+
+from django.template import Node, NodeList, Template, Context, resolve_variable
+from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
+from django.template import get_library, Library, InvalidTemplateLibrary
+from django.conf import settings
+import sys
+
+register = Library()
+
+class CommentNode(Node):
+ def render(self, context):
+ return ''
+
+class CycleNode(Node):
+ def __init__(self, cyclevars, variable_name=None):
+ self.cyclevars = cyclevars
+ self.cyclevars_len = len(cyclevars)
+ self.counter = -1
+ self.variable_name = variable_name
+
+ def render(self, context):
+ self.counter += 1
+ value = self.cyclevars[self.counter % self.cyclevars_len]
+ if self.variable_name:
+ context[self.variable_name] = value
+ return value
+
+class DebugNode(Node):
+ def render(self, context):
+ from pprint import pformat
+ output = [pformat(val) for val in context]
+ output.append('\n\n')
+ output.append(pformat(sys.modules))
+ return ''.join(output)
+
+class FilterNode(Node):
+ def __init__(self, filter_expr, nodelist):
+ self.filter_expr, self.nodelist = filter_expr, nodelist
+
+ def render(self, context):
+ output = self.nodelist.render(context)
+ # apply filters
+ return self.filter_expr.resolve(Context({'var': output}))
+
+class FirstOfNode(Node):
+ def __init__(self, vars):
+ self.vars = vars
+
+ def render(self, context):
+ for var in self.vars:
+ try:
+ value = resolve_variable(var, context)
+ except VariableDoesNotExist:
+ continue
+ if value:
+ return str(value)
+ return ''
+
+class ForNode(Node):
+ def __init__(self, loopvar, sequence, reversed, nodelist_loop):
+ self.loopvar, self.sequence = loopvar, sequence
+ self.reversed = reversed
+ self.nodelist_loop = nodelist_loop
+
+ def __repr__(self):
+ if self.reversed:
+ reversed = ' reversed'
+ else:
+ reversed = ''
+ return "<For Node: for %s in %s, tail_len: %d%s>" % \
+ (self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
+
+ def __iter__(self):
+ for node in self.nodelist_loop:
+ yield node
+
+ def get_nodes_by_type(self, nodetype):
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
+ return nodes
+
+ def render(self, context):
+ nodelist = NodeList()
+ if context.has_key('forloop'):
+ parentloop = context['forloop']
+ else:
+ parentloop = {}
+ context.push()
+ try:
+ values = self.sequence.resolve(context, True)
+ except VariableDoesNotExist:
+ values = []
+ if values is None:
+ values = []
+ if not hasattr(values, '__len__'):
+ values = list(values)
+ len_values = len(values)
+ if self.reversed:
+ # From http://www.python.org/doc/current/tut/node11.html
+ def reverse(data):
+ for index in range(len(data)-1, -1, -1):
+ yield data[index]
+ values = reverse(values)
+ for i, item in enumerate(values):
+ context['forloop'] = {
+ # shortcuts for current loop iteration number
+ 'counter0': i,
+ 'counter': i+1,
+ # reverse counter iteration numbers
+ 'revcounter': len_values - i,
+ 'revcounter0': len_values - i - 1,
+ # boolean values designating first and last times through loop
+ 'first': (i == 0),
+ 'last': (i == len_values - 1),
+ 'parentloop': parentloop,
+ }
+ context[self.loopvar] = item
+ for node in self.nodelist_loop:
+ nodelist.append(node.render(context))
+ context.pop()
+ return nodelist.render(context)
+
+class IfChangedNode(Node):
+ def __init__(self, nodelist, *varlist):
+ self.nodelist = nodelist
+ self._last_seen = None
+ self._varlist = varlist
+
+ def render(self, context):
+ if context.has_key('forloop') and context['forloop']['first']:
+ self._last_seen = None
+ try:
+ if self._varlist:
+ # Consider multiple parameters.
+ # This automatically behaves like a OR evaluation of the multiple variables.
+ compare_to = [resolve_variable(var, context) for var in self._varlist]
+ else:
+ compare_to = self.nodelist.render(context)
+ except VariableDoesNotExist:
+ compare_to = None
+
+ if compare_to != self._last_seen:
+ firstloop = (self._last_seen == None)
+ self._last_seen = compare_to
+ context.push()
+ context['ifchanged'] = {'firstloop': firstloop}
+ content = self.nodelist.render(context)
+ context.pop()
+ return content
+ else:
+ return ''
+
+class IfEqualNode(Node):
+ def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
+ self.var1, self.var2 = var1, var2
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.negate = negate
+
+ def __repr__(self):
+ return "<IfEqualNode>"
+
+ def render(self, context):
+ try:
+ val1 = resolve_variable(self.var1, context)
+ except VariableDoesNotExist:
+ val1 = None
+ try:
+ val2 = resolve_variable(self.var2, context)
+ except VariableDoesNotExist:
+ val2 = None
+ if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
+ return self.nodelist_true.render(context)
+ return self.nodelist_false.render(context)
+
+class IfNode(Node):
+ def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
+ self.bool_exprs = bool_exprs
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.link_type = link_type
+
+ def __repr__(self):
+ return "<If node>"
+
+ def __iter__(self):
+ for node in self.nodelist_true:
+ yield node
+ for node in self.nodelist_false:
+ yield node
+
+ def get_nodes_by_type(self, nodetype):
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
+ nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
+ return nodes
+
+ def render(self, context):
+ if self.link_type == IfNode.LinkTypes.or_:
+ for ifnot, bool_expr in self.bool_exprs:
+ try:
+ value = bool_expr.resolve(context, True)
+ except VariableDoesNotExist:
+ value = None
+ if (value and not ifnot) or (ifnot and not value):
+ return self.nodelist_true.render(context)
+ return self.nodelist_false.render(context)
+ else:
+ for ifnot, bool_expr in self.bool_exprs:
+ try:
+ value = bool_expr.resolve(context, True)
+ except VariableDoesNotExist:
+ value = None
+ if not ((value and not ifnot) or (ifnot and not value)):
+ return self.nodelist_false.render(context)
+ return self.nodelist_true.render(context)
+
+ class LinkTypes:
+ and_ = 0,
+ or_ = 1
+
+class RegroupNode(Node):
+ def __init__(self, target, expression, var_name):
+ self.target, self.expression = target, expression
+ self.var_name = var_name
+
+ def render(self, context):
+ obj_list = self.target.resolve(context, True)
+ if obj_list == None: # target_var wasn't found in context; fail silently
+ context[self.var_name] = []
+ return ''
+ output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
+ for obj in obj_list:
+ grouper = self.expression.resolve(Context({'var': obj}), True)
+ # TODO: Is this a sensible way to determine equality?
+ if output and repr(output[-1]['grouper']) == repr(grouper):
+ output[-1]['list'].append(obj)
+ else:
+ output.append({'grouper': grouper, 'list': [obj]})
+ context[self.var_name] = output
+ return ''
+
+def include_is_allowed(filepath):
+ for root in settings.ALLOWED_INCLUDE_ROOTS:
+ if filepath.startswith(root):
+ return True
+ return False
+
+class SsiNode(Node):
+ def __init__(self, filepath, parsed):
+ self.filepath, self.parsed = filepath, parsed
+
+ def render(self, context):
+ if not include_is_allowed(self.filepath):
+ if settings.DEBUG:
+ return "[Didn't have permission to include file]"
+ else:
+ return '' # Fail silently for invalid includes.
+ try:
+ fp = open(self.filepath, 'r')
+ output = fp.read()
+ fp.close()
+ except IOError:
+ output = ''
+ if self.parsed:
+ try:
+ t = Template(output, name=self.filepath)
+ return t.render(context)
+ except TemplateSyntaxError, e:
+ if settings.DEBUG:
+ return "[Included template had syntax error: %s]" % e
+ else:
+ return '' # Fail silently for invalid included templates.
+ return output
+
+class LoadNode(Node):
+ def render(self, context):
+ return ''
+
+class NowNode(Node):
+ def __init__(self, format_string):
+ self.format_string = format_string
+
+ def render(self, context):
+ from datetime import datetime
+ from django.utils.dateformat import DateFormat
+ df = DateFormat(datetime.now())
+ return df.format(self.format_string)
+
+class SpacelessNode(Node):
+ def __init__(self, nodelist):
+ self.nodelist = nodelist
+
+ def render(self, context):
+ from django.utils.html import strip_spaces_between_tags
+ return strip_spaces_between_tags(self.nodelist.render(context).strip())
+
+class TemplateTagNode(Node):
+ mapping = {'openblock': BLOCK_TAG_START,
+ 'closeblock': BLOCK_TAG_END,
+ 'openvariable': VARIABLE_TAG_START,
+ 'closevariable': VARIABLE_TAG_END,
+ 'openbrace': SINGLE_BRACE_START,
+ 'closebrace': SINGLE_BRACE_END,
+ 'opencomment': COMMENT_TAG_START,
+ 'closecomment': COMMENT_TAG_END,
+ }
+
+ def __init__(self, tagtype):
+ self.tagtype = tagtype
+
+ def render(self, context):
+ return self.mapping.get(self.tagtype, '')
+
+class URLNode(Node):
+ def __init__(self, view_name, args, kwargs):
+ self.view_name = view_name
+ self.args = args
+ self.kwargs = kwargs
+
+ def render(self, context):
+ from django.core.urlresolvers import reverse, NoReverseMatch
+ args = [arg.resolve(context) for arg in self.args]
+ kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
+ try:
+ return reverse(self.view_name, args=args, kwargs=kwargs)
+ except NoReverseMatch:
+ try:
+ project_name = settings.SETTINGS_MODULE.split('.')[0]
+ return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs)
+ except NoReverseMatch:
+ return ''
+
+class WidthRatioNode(Node):
+ def __init__(self, val_expr, max_expr, max_width):
+ self.val_expr = val_expr
+ self.max_expr = max_expr
+ self.max_width = max_width
+
+ def render(self, context):
+ try:
+ value = self.val_expr.resolve(context)
+ maxvalue = self.max_expr.resolve(context)
+ except VariableDoesNotExist:
+ return ''
+ try:
+ value = float(value)
+ maxvalue = float(maxvalue)
+ ratio = (value / maxvalue) * int(self.max_width)
+ except (ValueError, ZeroDivisionError):
+ return ''
+ return str(int(round(ratio)))
+
+#@register.tag
+def comment(parser, token):
+ """
+ Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
+ """
+ parser.skip_past('endcomment')
+ return CommentNode()
+comment = register.tag(comment)
+
+#@register.tag
+def cycle(parser, token):
+ """
+ Cycle among the given strings each time this tag is encountered
+
+ Within a loop, cycles among the given strings each time through
+ the loop::
+
+ {% for o in some_list %}
+ <tr class="{% cycle row1,row2 %}">
+ ...
+ </tr>
+ {% endfor %}
+
+ Outside of a loop, give the values a unique name the first time you call
+ it, then use that name each sucessive time through::
+
+ <tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
+ <tr class="{% cycle rowcolors %}">...</tr>
+ <tr class="{% cycle rowcolors %}">...</tr>
+
+ You can use any number of values, seperated by commas. Make sure not to
+ put spaces between the values -- only commas.
+ """
+
+ # Note: This returns the exact same node on each {% cycle name %} call; that
+ # is, the node object returned from {% cycle a,b,c as name %} and the one
+ # returned from {% cycle name %} are the exact same object. This shouldn't
+ # cause problems (heh), but if it does, now you know.
+ #
+ # Ugly hack warning: this stuffs the named template dict into parser so
+ # that names are only unique within each template (as opposed to using
+ # a global variable, which would make cycle names have to be unique across
+ # *all* templates.
+
+ args = token.contents.split()
+ if len(args) < 2:
+ raise TemplateSyntaxError("'Cycle' statement requires at least two arguments")
+
+ elif len(args) == 2 and "," in args[1]:
+ # {% cycle a,b,c %}
+ cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
+ return CycleNode(cyclevars)
+ # {% cycle name %}
+
+ elif len(args) == 2:
+ name = args[1]
+ if not hasattr(parser, '_namedCycleNodes'):
+ raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name)
+ if not parser._namedCycleNodes.has_key(name):
+ raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
+ return parser._namedCycleNodes[name]
+
+ elif len(args) == 4:
+ # {% cycle a,b,c as name %}
+ if args[2] != 'as':
+ raise TemplateSyntaxError("Second 'cycle' argument must be 'as'")
+ cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
+ name = args[3]
+ node = CycleNode(cyclevars, name)
+
+ if not hasattr(parser, '_namedCycleNodes'):
+ parser._namedCycleNodes = {}
+
+ parser._namedCycleNodes[name] = node
+ return node
+
+ else:
+ raise TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
+cycle = register.tag(cycle)
+
+def debug(parser, token):
+ """
+ Output a whole load of debugging information, including the current context and imported modules.
+
+ Sample usage::
+
+ <pre>
+ {% debug %}
+ </pre>
+ """
+ return DebugNode()
+debug = register.tag(debug)
+
+#@register.tag(name="filter")
+def do_filter(parser, token):
+ """
+ Filter the contents of the blog through variable filters.
+
+ Filters can also be piped through each other, and they can have
+ arguments -- just like in variable syntax.
+
+ Sample usage::
+
+ {% filter escape|lower %}
+ This text will be HTML-escaped, and will appear in lowercase.
+ {% endfilter %}
+ """
+ _, rest = token.contents.split(None, 1)
+ filter_expr = parser.compile_filter("var|%s" % (rest))
+ nodelist = parser.parse(('endfilter',))
+ parser.delete_first_token()
+ return FilterNode(filter_expr, nodelist)
+filter = register.tag("filter", do_filter)
+
+#@register.tag
+def firstof(parser, token):
+ """
+ Outputs the first variable passed that is not False.
+
+ Outputs nothing if all the passed variables are False.
+
+ Sample usage::
+
+ {% firstof var1 var2 var3 %}
+
+ This is equivalent to::
+
+ {% if var1 %}
+ {{ var1 }}
+ {% else %}{% if var2 %}
+ {{ var2 }}
+ {% else %}{% if var3 %}
+ {{ var3 }}
+ {% endif %}{% endif %}{% endif %}
+
+ but obviously much cleaner!
+ """
+ bits = token.contents.split()[1:]
+ if len(bits) < 1:
+ raise TemplateSyntaxError, "'firstof' statement requires at least one argument"
+ return FirstOfNode(bits)
+firstof = register.tag(firstof)
+
+#@register.tag(name="for")
+def do_for(parser, token):
+ """
+ Loop over each item in an array.
+
+ For example, to display a list of athletes given ``athlete_list``::
+
+ <ul>
+ {% for athlete in athlete_list %}
+ <li>{{ athlete.name }}</li>
+ {% endfor %}
+ </ul>
+
+ You can also loop over a list in reverse by using
+ ``{% for obj in list reversed %}``.
+
+ The for loop sets a number of variables available within the loop:
+
+ ========================== ================================================
+ Variable Description
+ ========================== ================================================
+ ``forloop.counter`` The current iteration of the loop (1-indexed)
+ ``forloop.counter0`` The current iteration of the loop (0-indexed)
+ ``forloop.revcounter`` The number of iterations from the end of the
+ loop (1-indexed)
+ ``forloop.revcounter0`` The number of iterations from the end of the
+ loop (0-indexed)
+ ``forloop.first`` True if this is the first time through the loop
+ ``forloop.last`` True if this is the last time through the loop
+ ``forloop.parentloop`` For nested loops, this is the loop "above" the
+ current one
+ ========================== ================================================
+
+ """
+ bits = token.contents.split()
+ if len(bits) == 5 and bits[4] != 'reversed':
+ raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
+ if len(bits) not in (4, 5):
+ raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
+ if bits[2] != 'in':
+ raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
+ loopvar = bits[1]
+ sequence = parser.compile_filter(bits[3])
+ reversed = (len(bits) == 5)
+ nodelist_loop = parser.parse(('endfor',))
+ parser.delete_first_token()
+ return ForNode(loopvar, sequence, reversed, nodelist_loop)
+do_for = register.tag("for", do_for)
+
+def do_ifequal(parser, token, negate):
+ bits = list(token.split_contents())
+ if len(bits) != 3:
+ raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
+ end_tag = 'end' + bits[0]
+ nodelist_true = parser.parse(('else', end_tag))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse((end_tag,))
+ parser.delete_first_token()
+ else:
+ nodelist_false = NodeList()
+ return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
+
+#@register.tag
+def ifequal(parser, token):
+ """
+ Output the contents of the block if the two arguments equal each other.
+
+ Examples::
+
+ {% ifequal user.id comment.user_id %}
+ ...
+ {% endifequal %}
+
+ {% ifnotequal user.id comment.user_id %}
+ ...
+ {% else %}
+ ...
+ {% endifnotequal %}
+ """
+ return do_ifequal(parser, token, False)
+ifequal = register.tag(ifequal)
+
+#@register.tag
+def ifnotequal(parser, token):
+ """Output the contents of the block if the two arguments are not equal. See ifequal."""
+ return do_ifequal(parser, token, True)
+ifnotequal = register.tag(ifnotequal)
+
+#@register.tag(name="if")
+def do_if(parser, token):
+ """
+ The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
+ (i.e. exists, is not empty, and is not a false boolean value) the contents
+ of the block are output:
+
+ ::
+
+ {% if althlete_list %}
+ Number of athletes: {{ althete_list|count }}
+ {% else %}
+ No athletes.
+ {% endif %}
+
+ In the above, if ``athlete_list`` is not empty, the number of athletes will
+ be displayed by the ``{{ athlete_list|count }}`` variable.
+
+ As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
+ will be displayed if the test fails.
+
+ ``if`` tags may use ``or`` or ``not`` to test a number of variables or to
+ negate a given variable::
+
+ {% if not athlete_list %}
+ There are no athletes.
+ {% endif %}
+
+ {% if athlete_list or coach_list %}
+ There are some athletes or some coaches.
+ {% endif %}
+
+ {% if not athlete_list or coach_list %}
+ There are no athletes, or there are some coaches.
+ {% endif %}
+
+ For simplicity, ``if`` tags do not allow ``and`` clauses. Use nested ``if``
+ tags instead::
+
+ {% if athlete_list %}
+ {% if coach_list %}
+ Number of athletes: {{ athlete_list|count }}.
+ Number of coaches: {{ coach_list|count }}.
+ {% endif %}
+ {% endif %}
+ """
+ bits = token.contents.split()
+ del bits[0]
+ if not bits:
+ raise TemplateSyntaxError, "'if' statement requires at least one argument"
+ # bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
+ bitstr = ' '.join(bits)
+ boolpairs = bitstr.split(' and ')
+ boolvars = []
+ if len(boolpairs) == 1:
+ link_type = IfNode.LinkTypes.or_
+ boolpairs = bitstr.split(' or ')
+ else:
+ link_type = IfNode.LinkTypes.and_
+ if ' or ' in bitstr:
+ raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
+ for boolpair in boolpairs:
+ if ' ' in boolpair:
+ try:
+ not_, boolvar = boolpair.split()
+ except ValueError:
+ raise TemplateSyntaxError, "'if' statement improperly formatted"
+ if not_ != 'not':
+ raise TemplateSyntaxError, "Expected 'not' in if statement"
+ boolvars.append((True, parser.compile_filter(boolvar)))
+ else:
+ boolvars.append((False, parser.compile_filter(boolpair)))
+ nodelist_true = parser.parse(('else', 'endif'))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse(('endif',))
+ parser.delete_first_token()
+ else:
+ nodelist_false = NodeList()
+ return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
+do_if = register.tag("if", do_if)
+
+#@register.tag
+def ifchanged(parser, token):
+ """
+ Check if a value has changed from the last iteration of a loop.
+
+ The 'ifchanged' block tag is used within a loop. It has two possible uses.
+
+ 1. Checks its own rendered contents against its previous state and only
+ displays the content if it has changed. For example, this displays a list of
+ days, only displaying the month if it changes::
+
+ <h1>Archive for {{ year }}</h1>
+
+ {% for date in days %}
+ {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
+ <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
+ {% endfor %}
+
+ 2. If given a variable, check whether that variable has changed. For example, the
+ following shows the date every time it changes, but only shows the hour if both
+ the hour and the date have changed::
+
+ {% for date in days %}
+ {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
+ {% ifchanged date.hour date.date %}
+ {{ date.hour }}
+ {% endifchanged %}
+ {% endfor %}
+ """
+ bits = token.contents.split()
+ nodelist = parser.parse(('endifchanged',))
+ parser.delete_first_token()
+ return IfChangedNode(nodelist, *bits[1:])
+ifchanged = register.tag(ifchanged)
+
+#@register.tag
+def ssi(parser, token):
+ """
+ Output the contents of a given file into the page.
+
+ Like a simple "include" tag, the ``ssi`` tag includes the contents
+ of another file -- which must be specified using an absolute page --
+ in the current page::
+
+ {% ssi /home/html/ljworld.com/includes/right_generic.html %}
+
+ If the optional "parsed" parameter is given, the contents of the included
+ file are evaluated as template code, with the current context::
+
+ {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
+ """
+ bits = token.contents.split()
+ parsed = False
+ if len(bits) not in (2, 3):
+ raise TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included"
+ if len(bits) == 3:
+ if bits[2] == 'parsed':
+ parsed = True
+ else:
+ raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
+ return SsiNode(bits[1], parsed)
+ssi = register.tag(ssi)
+
+#@register.tag
+def load(parser, token):
+ """
+ Load a custom template tag set.
+
+ For example, to load the template tags in ``django/templatetags/news/photos.py``::
+
+ {% load news.photos %}
+ """
+ bits = token.contents.split()
+ for taglib in bits[1:]:
+ # add the library to the parser
+ try:
+ lib = get_library("django.templatetags.%s" % taglib.split('.')[-1])
+ parser.add_library(lib)
+ except InvalidTemplateLibrary, e:
+ raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
+ return LoadNode()
+load = register.tag(load)
+
+#@register.tag
+def now(parser, token):
+ """
+ Display the date, formatted according to the given string.
+
+ Uses the same format as PHP's ``date()`` function; see http://php.net/date
+ for all the possible values.
+
+ Sample usage::
+
+ It is {% now "jS F Y H:i" %}
+ """
+ bits = token.contents.split('"')
+ if len(bits) != 3:
+ raise TemplateSyntaxError, "'now' statement takes one argument"
+ format_string = bits[1]
+ return NowNode(format_string)
+now = register.tag(now)
+
+#@register.tag
+def regroup(parser, token):
+ """
+ Regroup a list of alike objects by a common attribute.
+
+ This complex tag is best illustrated by use of an example: say that
+ ``people`` is a list of ``Person`` objects that have ``first_name``,
+ ``last_name``, and ``gender`` attributes, and you'd like to display a list
+ that looks like:
+
+ * Male:
+ * George Bush
+ * Bill Clinton
+ * Female:
+ * Margaret Thatcher
+ * Colendeeza Rice
+ * Unknown:
+ * Pat Smith
+
+ The following snippet of template code would accomplish this dubious task::
+
+ {% regroup people by gender as grouped %}
+ <ul>
+ {% for group in grouped %}
+ <li>{{ group.grouper }}
+ <ul>
+ {% for item in group.list %}
+ <li>{{ item }}</li>
+ {% endfor %}
+ </ul>
+ {% endfor %}
+ </ul>
+
+ As you can see, ``{% regroup %}`` populates a variable with a list of
+ objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the
+ item that was grouped by; ``list`` contains the list of objects that share
+ that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female``
+ and ``Unknown``, and ``list`` is the list of people with those genders.
+
+ Note that `{% regroup %}`` does not work when the list to be grouped is not
+ sorted by the key you are grouping by! This means that if your list of
+ people was not sorted by gender, you'd need to make sure it is sorted before
+ using it, i.e.::
+
+ {% regroup people|dictsort:"gender" by gender as grouped %}
+
+ """
+ firstbits = token.contents.split(None, 3)
+ if len(firstbits) != 4:
+ raise TemplateSyntaxError, "'regroup' tag takes five arguments"
+ target = parser.compile_filter(firstbits[1])
+ if firstbits[2] != 'by':
+ raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
+ lastbits_reversed = firstbits[3][::-1].split(None, 2)
+ if lastbits_reversed[1][::-1] != 'as':
+ raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
+
+ expression = parser.compile_filter('var.%s' % lastbits_reversed[2][::-1])
+
+ var_name = lastbits_reversed[0][::-1]
+ return RegroupNode(target, expression, var_name)
+regroup = register.tag(regroup)
+
+def spaceless(parser, token):
+ """
+ Normalize whitespace between HTML tags to a single space. This includes tab
+ characters and newlines.
+
+ Example usage::
+
+ {% spaceless %}
+ <p>
+ <a href="foo/">Foo</a>
+ </p>
+ {% endspaceless %}
+
+ This example would return this HTML::
+
+ <p> <a href="foo/">Foo</a> </p>
+
+ Only space between *tags* is normalized -- not space between tags and text. In
+ this example, the space around ``Hello`` won't be stripped::
+
+ {% spaceless %}
+ <strong>
+ Hello
+ </strong>
+ {% endspaceless %}
+ """
+ nodelist = parser.parse(('endspaceless',))
+ parser.delete_first_token()
+ return SpacelessNode(nodelist)
+spaceless = register.tag(spaceless)
+
+#@register.tag
+def templatetag(parser, token):
+ """
+ Output one of the bits used to compose template tags.
+
+ Since the template system has no concept of "escaping", to display one of
+ the bits used in template tags, you must use the ``{% templatetag %}`` tag.
+
+ The argument tells which template bit to output:
+
+ ================== =======
+ Argument Outputs
+ ================== =======
+ ``openblock`` ``{%``
+ ``closeblock`` ``%}``
+ ``openvariable`` ``{{``
+ ``closevariable`` ``}}``
+ ``openbrace`` ``{``
+ ``closebrace`` ``}``
+ ``opencomment`` ``{#``
+ ``closecomment`` ``#}``
+ ================== =======
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'templatetag' statement takes one argument"
+ tag = bits[1]
+ if not TemplateTagNode.mapping.has_key(tag):
+ raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
+ (tag, TemplateTagNode.mapping.keys())
+ return TemplateTagNode(tag)
+templatetag = register.tag(templatetag)
+
+def url(parser, token):
+ """
+ Returns an absolute URL matching given view with its parameters.
+
+ This is a way to define links that aren't tied to a particular URL configuration::
+
+ {% url path.to.some_view arg1,arg2,name1=value1 %}
+
+ The first argument is a path to a view. It can be an absolute python path
+ or just ``app_name.view_name`` without the project name if the view is
+ located inside the project. Other arguments are comma-separated values
+ that will be filled in place of positional and keyword arguments in the
+ URL. All arguments for the URL should be present.
+
+ For example if you have a view ``app_name.client`` taking client's id and
+ the corresponding line in a URLconf looks like this::
+
+ ('^client/(\d+)/$', 'app_name.client')
+
+ and this app's URLconf is included into the project's URLconf under some
+ path::
+
+ ('^clients/', include('project_name.app_name.urls'))
+
+ then in a template you can create a link for a certain client like this::
+
+ {% url app_name.client client.id %}
+
+ The URL will look like ``/clients/client/123/``.
+ """
+ bits = token.contents.split(' ', 2)
+ if len(bits) < 2:
+ raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0]
+ args = []
+ kwargs = {}
+ if len(bits) > 2:
+ for arg in bits[2].split(','):
+ if '=' in arg:
+ k, v = arg.split('=', 1)
+ kwargs[k] = parser.compile_filter(v)
+ else:
+ args.append(parser.compile_filter(arg))
+ return URLNode(bits[1], args, kwargs)
+url = register.tag(url)
+
+#@register.tag
+def widthratio(parser, token):
+ """
+ For creating bar charts and such, this tag calculates the ratio of a given
+ value to a maximum value, and then applies that ratio to a constant.
+
+ For example::
+
+ <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
+
+ Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
+ the above example will be 88 pixels wide (because 175/200 = .875; .875 *
+ 100 = 87.5 which is rounded up to 88).
+ """
+ bits = token.contents.split()
+ if len(bits) != 4:
+ raise TemplateSyntaxError("widthratio takes three arguments")
+ tag, this_value_expr, max_value_expr, max_width = bits
+ try:
+ max_width = int(max_width)
+ except ValueError:
+ raise TemplateSyntaxError("widthratio final argument must be an integer")
+ return WidthRatioNode(parser.compile_filter(this_value_expr),
+ parser.compile_filter(max_value_expr), max_width)
+widthratio = register.tag(widthratio)
diff --git a/google_appengine/lib/django/django/template/defaulttags.pyc b/google_appengine/lib/django/django/template/defaulttags.pyc
new file mode 100644
index 0000000..d3d2589
--- /dev/null
+++ b/google_appengine/lib/django/django/template/defaulttags.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/template/loader.py b/google_appengine/lib/django/django/template/loader.py
new file mode 100755
index 0000000..03e6f8d
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loader.py
@@ -0,0 +1,118 @@
+# Wrapper for loading templates from storage of some sort (e.g. filesystem, database).
+#
+# This uses the TEMPLATE_LOADERS setting, which is a list of loaders to use.
+# Each loader is expected to have this interface:
+#
+# callable(name, dirs=[])
+#
+# name is the template name.
+# dirs is an optional list of directories to search instead of TEMPLATE_DIRS.
+#
+# The loader should return a tuple of (template_source, path). The path returned
+# might be shown to the user for debugging purposes, so it should identify where
+# the template was loaded from.
+#
+# Each loader should have an "is_usable" attribute set. This is a boolean that
+# specifies whether the loader can be used in this Python installation. Each
+# loader is responsible for setting this when it's initialized.
+#
+# For example, the eggs loader (which is capable of loading templates from
+# Python eggs) sets is_usable to False if the "pkg_resources" module isn't
+# installed, because pkg_resources is necessary to read eggs.
+
+from django.core.exceptions import ImproperlyConfigured
+from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
+from django.conf import settings
+
+template_source_loaders = None
+
+class LoaderOrigin(Origin):
+ def __init__(self, display_name, loader, name, dirs):
+ super(LoaderOrigin, self).__init__(display_name)
+ self.loader, self.loadname, self.dirs = loader, name, dirs
+
+ def reload(self):
+ return self.loader(self.loadname, self.dirs)[0]
+
+def make_origin(display_name, loader, name, dirs):
+ if settings.TEMPLATE_DEBUG:
+ return LoaderOrigin(display_name, loader, name, dirs)
+ else:
+ return None
+
+def find_template_source(name, dirs=None):
+ # Calculate template_source_loaders the first time the function is executed
+ # because putting this logic in the module-level namespace may cause
+ # circular import errors. See Django ticket #1292.
+ global template_source_loaders
+ if template_source_loaders is None:
+ template_source_loaders = []
+ for path in settings.TEMPLATE_LOADERS:
+ i = path.rfind('.')
+ module, attr = path[:i], path[i+1:]
+ try:
+ mod = __import__(module, globals(), locals(), [attr])
+ except ImportError, e:
+ raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
+ try:
+ func = getattr(mod, attr)
+ except AttributeError:
+ raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable template source loader' % (module, attr)
+ if not func.is_usable:
+ import warnings
+ warnings.warn("Your TEMPLATE_LOADERS setting includes %r, but your Python installation doesn't support that type of template loading. Consider removing that line from TEMPLATE_LOADERS." % path)
+ else:
+ template_source_loaders.append(func)
+ for loader in template_source_loaders:
+ try:
+ source, display_name = loader(name, dirs)
+ return (source, make_origin(display_name, loader, name, dirs))
+ except TemplateDoesNotExist:
+ pass
+ raise TemplateDoesNotExist, name
+
+def get_template(template_name):
+ """
+ Returns a compiled Template object for the given template name,
+ handling template inheritance recursively.
+ """
+ source, origin = find_template_source(template_name)
+ template = get_template_from_string(source, origin, template_name)
+ return template
+
+def get_template_from_string(source, origin=None, name=None):
+ """
+ Returns a compiled Template object for the given template code,
+ handling template inheritance recursively.
+ """
+ return Template(source, origin, name)
+
+def render_to_string(template_name, dictionary=None, context_instance=None):
+ """
+ Loads the given template_name and renders it with the given dictionary as
+ context. The template_name may be a string to load a single template using
+ get_template, or it may be a tuple to use select_template to find one of
+ the templates in the list. Returns a string.
+ """
+ dictionary = dictionary or {}
+ if isinstance(template_name, (list, tuple)):
+ t = select_template(template_name)
+ else:
+ t = get_template(template_name)
+ if context_instance:
+ context_instance.update(dictionary)
+ else:
+ context_instance = Context(dictionary)
+ return t.render(context_instance)
+
+def select_template(template_name_list):
+ "Given a list of template names, returns the first that can be loaded."
+ for template_name in template_name_list:
+ try:
+ return get_template(template_name)
+ except TemplateDoesNotExist:
+ continue
+ # If we get here, none of the templates could be loaded
+ raise TemplateDoesNotExist, ', '.join(template_name_list)
+
+add_to_builtins('django.template.loader_tags')
diff --git a/google_appengine/lib/django/django/template/loader.pyc b/google_appengine/lib/django/django/template/loader.pyc
new file mode 100644
index 0000000..f3a36b3
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loader.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/template/loader_tags.py b/google_appengine/lib/django/django/template/loader_tags.py
new file mode 100755
index 0000000..4439e0b
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loader_tags.py
@@ -0,0 +1,177 @@
+from django.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable
+from django.template import Library, Node
+from django.template.loader import get_template, get_template_from_string, find_template_source
+from django.conf import settings
+
+register = Library()
+
+class ExtendsError(Exception):
+ pass
+
+class BlockNode(Node):
+ def __init__(self, name, nodelist, parent=None):
+ self.name, self.nodelist, self.parent = name, nodelist, parent
+
+ def __repr__(self):
+ return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
+
+ def render(self, context):
+ context.push()
+ # Save context in case of block.super().
+ self.context = context
+ context['block'] = self
+ result = self.nodelist.render(context)
+ context.pop()
+ return result
+
+ def super(self):
+ if self.parent:
+ return self.parent.render(self.context)
+ return ''
+
+ def add_parent(self, nodelist):
+ if self.parent:
+ self.parent.add_parent(nodelist)
+ else:
+ self.parent = BlockNode(self.name, nodelist)
+
+class ExtendsNode(Node):
+ def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
+ self.nodelist = nodelist
+ self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
+ self.template_dirs = template_dirs
+
+ def get_parent(self, context):
+ if self.parent_name_expr:
+ self.parent_name = self.parent_name_expr.resolve(context)
+ parent = self.parent_name
+ if not parent:
+ error_msg = "Invalid template name in 'extends' tag: %r." % parent
+ if self.parent_name_expr:
+ error_msg += " Got this from the %r variable." % self.parent_name_expr #TODO nice repr.
+ raise TemplateSyntaxError, error_msg
+ if hasattr(parent, 'render'):
+ return parent # parent is a Template object
+ try:
+ source, origin = find_template_source(parent, self.template_dirs)
+ except TemplateDoesNotExist:
+ raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
+ else:
+ return get_template_from_string(source, origin, parent)
+
+ def render(self, context):
+ compiled_parent = self.get_parent(context)
+ parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
+ parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
+ for block_node in self.nodelist.get_nodes_by_type(BlockNode):
+ # Check for a BlockNode with this node's name, and replace it if found.
+ try:
+ parent_block = parent_blocks[block_node.name]
+ except KeyError:
+ # This BlockNode wasn't found in the parent template, but the
+ # parent block might be defined in the parent's *parent*, so we
+ # add this BlockNode to the parent's ExtendsNode nodelist, so
+ # it'll be checked when the parent node's render() is called.
+ if parent_is_child:
+ compiled_parent.nodelist[0].nodelist.append(block_node)
+ else:
+ # Keep any existing parents and add a new one. Used by BlockNode.
+ parent_block.parent = block_node.parent
+ parent_block.add_parent(parent_block.nodelist)
+ parent_block.nodelist = block_node.nodelist
+ return compiled_parent.render(context)
+
+class ConstantIncludeNode(Node):
+ def __init__(self, template_path):
+ try:
+ t = get_template(template_path)
+ self.template = t
+ except:
+ if settings.TEMPLATE_DEBUG:
+ raise
+ self.template = None
+
+ def render(self, context):
+ if self.template:
+ return self.template.render(context)
+ else:
+ return ''
+
+class IncludeNode(Node):
+ def __init__(self, template_name):
+ self.template_name = template_name
+
+ def render(self, context):
+ try:
+ template_name = resolve_variable(self.template_name, context)
+ t = get_template(template_name)
+ return t.render(context)
+ except TemplateSyntaxError, e:
+ if settings.TEMPLATE_DEBUG:
+ raise
+ return ''
+ except:
+ return '' # Fail silently for invalid included templates.
+
+def do_block(parser, token):
+ """
+ Define a block that can be overridden by child templates.
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
+ block_name = bits[1]
+ # Keep track of the names of BlockNodes found in this template, so we can
+ # check for duplication.
+ try:
+ if block_name in parser.__loaded_blocks:
+ raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
+ parser.__loaded_blocks.append(block_name)
+ except AttributeError: # parser.__loaded_blocks isn't a list yet
+ parser.__loaded_blocks = [block_name]
+ nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
+ parser.delete_first_token()
+ return BlockNode(block_name, nodelist)
+
+def do_extends(parser, token):
+ """
+ Signal that this template extends a parent template.
+
+ This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
+ uses the literal value "base" as the name of the parent template to extend,
+ or ``{% extends variable %}`` uses the value of ``variable`` as either the
+ name of the parent template to extend (if it evaluates to a string,) or as
+ the parent tempate itelf (if it evaluates to a Template object).
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
+ parent_name, parent_name_expr = None, None
+ if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
+ parent_name = bits[1][1:-1]
+ else:
+ parent_name_expr = parser.compile_filter(bits[1])
+ nodelist = parser.parse()
+ if nodelist.get_nodes_by_type(ExtendsNode):
+ raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
+ return ExtendsNode(nodelist, parent_name, parent_name_expr)
+
+def do_include(parser, token):
+ """
+ Loads a template and renders it with the current context.
+
+ Example::
+
+ {% include "foo/some_include" %}
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
+ path = bits[1]
+ if path[0] in ('"', "'") and path[-1] == path[0]:
+ return ConstantIncludeNode(path[1:-1])
+ return IncludeNode(bits[1])
+
+register.tag('block', do_block)
+register.tag('extends', do_extends)
+register.tag('include', do_include)
diff --git a/google_appengine/lib/django/django/template/loader_tags.pyc b/google_appengine/lib/django/django/template/loader_tags.pyc
new file mode 100644
index 0000000..a01318a
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loader_tags.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/template/loaders/__init__.py b/google_appengine/lib/django/django/template/loaders/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loaders/__init__.py
diff --git a/google_appengine/lib/django/django/template/loaders/__init__.pyc b/google_appengine/lib/django/django/template/loaders/__init__.pyc
new file mode 100644
index 0000000..d42a991
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loaders/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/template/loaders/app_directories.py b/google_appengine/lib/django/django/template/loaders/app_directories.py
new file mode 100755
index 0000000..c4e91df
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loaders/app_directories.py
@@ -0,0 +1,41 @@
+# Wrapper for loading templates from "template" directories in installed app packages.
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.template import TemplateDoesNotExist
+import os
+
+# At compile time, cache the directories to search.
+app_template_dirs = []
+for app in settings.INSTALLED_APPS:
+ i = app.rfind('.')
+ if i == -1:
+ m, a = app, None
+ else:
+ m, a = app[:i], app[i+1:]
+ try:
+ if a is None:
+ mod = __import__(m, {}, {}, [])
+ else:
+ mod = getattr(__import__(m, {}, {}, [a]), a)
+ except ImportError, e:
+ raise ImproperlyConfigured, 'ImportError %s: %s' % (app, e.args[0])
+ template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates')
+ if os.path.isdir(template_dir):
+ app_template_dirs.append(template_dir)
+
+# It won't change, so convert it to a tuple to save memory.
+app_template_dirs = tuple(app_template_dirs)
+
+def get_template_sources(template_name, template_dirs=None):
+ for template_dir in app_template_dirs:
+ yield os.path.join(template_dir, template_name)
+
+def load_template_source(template_name, template_dirs=None):
+ for filepath in get_template_sources(template_name, template_dirs):
+ try:
+ return (open(filepath).read(), filepath)
+ except IOError:
+ pass
+ raise TemplateDoesNotExist, template_name
+load_template_source.is_usable = True
diff --git a/google_appengine/lib/django/django/template/loaders/eggs.py b/google_appengine/lib/django/django/template/loaders/eggs.py
new file mode 100755
index 0000000..6184aea
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loaders/eggs.py
@@ -0,0 +1,25 @@
+# Wrapper for loading templates from eggs via pkg_resources.resource_string.
+
+try:
+ from pkg_resources import resource_string
+except ImportError:
+ resource_string = None
+
+from django.template import TemplateDoesNotExist
+from django.conf import settings
+
+def load_template_source(template_name, template_dirs=None):
+ """
+ Loads templates from Python eggs via pkg_resource.resource_string.
+
+ For every installed app, it tries to get the resource (app, template_name).
+ """
+ if resource_string is not None:
+ pkg_name = 'templates/' + template_name
+ for app in settings.INSTALLED_APPS:
+ try:
+ return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
+ except:
+ pass
+ raise TemplateDoesNotExist, template_name
+load_template_source.is_usable = resource_string is not None
diff --git a/google_appengine/lib/django/django/template/loaders/filesystem.py b/google_appengine/lib/django/django/template/loaders/filesystem.py
new file mode 100755
index 0000000..d01f54c
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loaders/filesystem.py
@@ -0,0 +1,25 @@
+# Wrapper for loading templates from the filesystem.
+
+from django.conf import settings
+from django.template import TemplateDoesNotExist
+import os
+
+def get_template_sources(template_name, template_dirs=None):
+ if not template_dirs:
+ template_dirs = settings.TEMPLATE_DIRS
+ for template_dir in template_dirs:
+ yield os.path.join(template_dir, template_name)
+
+def load_template_source(template_name, template_dirs=None):
+ tried = []
+ for filepath in get_template_sources(template_name, template_dirs):
+ try:
+ return (open(filepath).read(), filepath)
+ except IOError:
+ tried.append(filepath)
+ if tried:
+ error_msg = "Tried %s" % tried
+ else:
+ error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory."
+ raise TemplateDoesNotExist, error_msg
+load_template_source.is_usable = True
diff --git a/google_appengine/lib/django/django/template/loaders/filesystem.pyc b/google_appengine/lib/django/django/template/loaders/filesystem.pyc
new file mode 100644
index 0000000..6c5bfc2
--- /dev/null
+++ b/google_appengine/lib/django/django/template/loaders/filesystem.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/templatetags/__init__.py b/google_appengine/lib/django/django/templatetags/__init__.py
new file mode 100755
index 0000000..9204535
--- /dev/null
+++ b/google_appengine/lib/django/django/templatetags/__init__.py
@@ -0,0 +1,7 @@
+from django.conf import settings
+
+for a in settings.INSTALLED_APPS:
+ try:
+ __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
+ except ImportError:
+ pass
diff --git a/google_appengine/lib/django/django/templatetags/i18n.py b/google_appengine/lib/django/django/templatetags/i18n.py
new file mode 100755
index 0000000..bf6497f
--- /dev/null
+++ b/google_appengine/lib/django/django/templatetags/i18n.py
@@ -0,0 +1,246 @@
+from django.template import Node, resolve_variable
+from django.template import TemplateSyntaxError, TokenParser, Library
+from django.template import TOKEN_TEXT, TOKEN_VAR
+from django.utils import translation
+
+register = Library()
+
+class GetAvailableLanguagesNode(Node):
+ def __init__(self, variable):
+ self.variable = variable
+
+ def render(self, context):
+ from django.conf import settings
+ context[self.variable] = [(k, translation.gettext(v)) for k, v in settings.LANGUAGES]
+ return ''
+
+class GetCurrentLanguageNode(Node):
+ def __init__(self, variable):
+ self.variable = variable
+
+ def render(self, context):
+ context[self.variable] = translation.get_language()
+ return ''
+
+class GetCurrentLanguageBidiNode(Node):
+ def __init__(self, variable):
+ self.variable = variable
+
+ def render(self, context):
+ context[self.variable] = translation.get_language_bidi()
+ return ''
+
+class TranslateNode(Node):
+ def __init__(self, value, noop):
+ self.value = value
+ self.noop = noop
+
+ def render(self, context):
+ value = resolve_variable(self.value, context)
+ if self.noop:
+ return value
+ else:
+ return translation.gettext(value)
+
+class BlockTranslateNode(Node):
+ def __init__(self, extra_context, singular, plural=None, countervar=None, counter=None):
+ self.extra_context = extra_context
+ self.singular = singular
+ self.plural = plural
+ self.countervar = countervar
+ self.counter = counter
+
+ def render_token_list(self, tokens):
+ result = []
+ for token in tokens:
+ if token.token_type == TOKEN_TEXT:
+ result.append(token.contents)
+ elif token.token_type == TOKEN_VAR:
+ result.append('%%(%s)s' % token.contents)
+ return ''.join(result)
+
+ def render(self, context):
+ context.push()
+ for var,val in self.extra_context.items():
+ context[var] = val.resolve(context)
+ singular = self.render_token_list(self.singular)
+ if self.plural and self.countervar and self.counter:
+ count = self.counter.resolve(context)
+ context[self.countervar] = count
+ plural = self.render_token_list(self.plural)
+ result = translation.ngettext(singular, plural, count) % context
+ else:
+ result = translation.gettext(singular) % context
+ context.pop()
+ return result
+
+def do_get_available_languages(parser, token):
+ """
+ This will store a list of available languages
+ in the context.
+
+ Usage::
+
+ {% get_available_languages as languages %}
+ {% for language in languages %}
+ ...
+ {% endfor %}
+
+ This will just pull the LANGUAGES setting from
+ your setting file (or the default settings) and
+ put it into the named variable.
+ """
+ args = token.contents.split()
+ if len(args) != 3 or args[1] != 'as':
+ raise TemplateSyntaxError, "'get_available_languages' requires 'as variable' (got %r)" % args
+ return GetAvailableLanguagesNode(args[2])
+
+def do_get_current_language(parser, token):
+ """
+ This will store the current language in the context.
+
+ Usage::
+
+ {% get_current_language as language %}
+
+ This will fetch the currently active language and
+ put it's value into the ``language`` context
+ variable.
+ """
+ args = token.contents.split()
+ if len(args) != 3 or args[1] != 'as':
+ raise TemplateSyntaxError, "'get_current_language' requires 'as variable' (got %r)" % args
+ return GetCurrentLanguageNode(args[2])
+
+def do_get_current_language_bidi(parser, token):
+ """
+ This will store the current language layout in the context.
+
+ Usage::
+
+ {% get_current_language_bidi as bidi %}
+
+ This will fetch the currently active language's layout and
+ put it's value into the ``bidi`` context variable.
+ True indicates right-to-left layout, otherwise left-to-right
+ """
+ args = token.contents.split()
+ if len(args) != 3 or args[1] != 'as':
+ raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args
+ return GetCurrentLanguageBidiNode(args[2])
+
+def do_translate(parser, token):
+ """
+ This will mark a string for translation and will
+ translate the string for the current language.
+
+ Usage::
+
+ {% trans "this is a test" %}
+
+ This will mark the string for translation so it will
+ be pulled out by mark-messages.py into the .po files
+ and will run the string through the translation engine.
+
+ There is a second form::
+
+ {% trans "this is a test" noop %}
+
+ This will only mark for translation, but will return
+ the string unchanged. Use it when you need to store
+ values into forms that should be translated later on.
+
+ You can use variables instead of constant strings
+ to translate stuff you marked somewhere else::
+
+ {% trans variable %}
+
+ This will just try to translate the contents of
+ the variable ``variable``. Make sure that the string
+ in there is something that is in the .po file.
+ """
+ class TranslateParser(TokenParser):
+ def top(self):
+ value = self.value()
+ if self.more():
+ if self.tag() == 'noop':
+ noop = True
+ else:
+ raise TemplateSyntaxError, "only option for 'trans' is 'noop'"
+ else:
+ noop = False
+ return (value, noop)
+ value, noop = TranslateParser(token.contents).top()
+ return TranslateNode(value, noop)
+
+def do_block_translate(parser, token):
+ """
+ This will translate a block of text with parameters.
+
+ Usage::
+
+ {% blocktrans with foo|filter as bar and baz|filter as boo %}
+ This is {{ bar }} and {{ boo }}.
+ {% endblocktrans %}
+
+ Additionally, this supports pluralization::
+
+ {% blocktrans count var|length as count %}
+ There is {{ count }} object.
+ {% plural %}
+ There are {{ count }} objects.
+ {% endblocktrans %}
+
+ This is much like ngettext, only in template syntax.
+ """
+ class BlockTranslateParser(TokenParser):
+
+ def top(self):
+ countervar = None
+ counter = None
+ extra_context = {}
+ while self.more():
+ tag = self.tag()
+ if tag == 'with' or tag == 'and':
+ value = self.value()
+ if self.tag() != 'as':
+ raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'"
+ extra_context[self.tag()] = parser.compile_filter(value)
+ elif tag == 'count':
+ counter = parser.compile_filter(self.value())
+ if self.tag() != 'as':
+ raise TemplateSyntaxError, "counter specification in 'blocktrans' must be 'count value as variable'"
+ countervar = self.tag()
+ else:
+ raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag
+ return (countervar, counter, extra_context)
+
+ countervar, counter, extra_context = BlockTranslateParser(token.contents).top()
+
+ singular = []
+ plural = []
+ while parser.tokens:
+ token = parser.next_token()
+ if token.token_type in (TOKEN_VAR, TOKEN_TEXT):
+ singular.append(token)
+ else:
+ break
+ if countervar and counter:
+ if token.contents.strip() != 'plural':
+ raise TemplateSyntaxError, "'blocktrans' doesn't allow other block tags inside it"
+ while parser.tokens:
+ token = parser.next_token()
+ if token.token_type in (TOKEN_VAR, TOKEN_TEXT):
+ plural.append(token)
+ else:
+ break
+ if token.contents.strip() != 'endblocktrans':
+ raise TemplateSyntaxError, "'blocktrans' doesn't allow other block tags (seen %r) inside it" % token.contents
+
+ return BlockTranslateNode(extra_context, singular, plural, countervar, counter)
+
+register.tag('get_available_languages', do_get_available_languages)
+register.tag('get_current_language', do_get_current_language)
+register.tag('get_current_language_bidi', do_get_current_language_bidi)
+register.tag('trans', do_translate)
+register.tag('blocktrans', do_block_translate)
diff --git a/google_appengine/lib/django/django/test/__init__.py b/google_appengine/lib/django/django/test/__init__.py
new file mode 100755
index 0000000..554e72b
--- /dev/null
+++ b/google_appengine/lib/django/django/test/__init__.py
@@ -0,0 +1,6 @@
+"""
+Django Unit Test and Doctest framework.
+"""
+
+from django.test.client import Client
+from django.test.testcases import TestCase
diff --git a/google_appengine/lib/django/django/test/client.py b/google_appengine/lib/django/django/test/client.py
new file mode 100755
index 0000000..95d3b85
--- /dev/null
+++ b/google_appengine/lib/django/django/test/client.py
@@ -0,0 +1,256 @@
+import sys
+from cStringIO import StringIO
+from urlparse import urlparse
+from django.conf import settings
+from django.core.handlers.base import BaseHandler
+from django.core.handlers.wsgi import WSGIRequest
+from django.core.signals import got_request_exception
+from django.dispatch import dispatcher
+from django.http import urlencode, SimpleCookie
+from django.test import signals
+from django.utils.functional import curry
+
+BOUNDARY = 'BoUnDaRyStRiNg'
+MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
+
+class ClientHandler(BaseHandler):
+ """
+ A HTTP Handler that can be used for testing purposes.
+ Uses the WSGI interface to compose requests, but returns
+ the raw HttpResponse object
+ """
+ def __call__(self, environ):
+ from django.conf import settings
+ from django.core import signals
+
+ # Set up middleware if needed. We couldn't do this earlier, because
+ # settings weren't available.
+ if self._request_middleware is None:
+ self.load_middleware()
+
+ dispatcher.send(signal=signals.request_started)
+ try:
+ request = WSGIRequest(environ)
+ response = self.get_response(request)
+
+ # Apply response middleware
+ for middleware_method in self._response_middleware:
+ response = middleware_method(request, response)
+
+ finally:
+ dispatcher.send(signal=signals.request_finished)
+
+ return response
+
+def store_rendered_templates(store, signal, sender, template, context):
+ "A utility function for storing templates and contexts that are rendered"
+ store.setdefault('template',[]).append(template)
+ store.setdefault('context',[]).append(context)
+
+def encode_multipart(boundary, data):
+ """
+ A simple method for encoding multipart POST data from a dictionary of
+ form values.
+
+ The key will be used as the form data name; the value will be transmitted
+ as content. If the value is a file, the contents of the file will be sent
+ as an application/octet-stream; otherwise, str(value) will be sent.
+ """
+ lines = []
+ for (key, value) in data.items():
+ if isinstance(value, file):
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ '',
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s_file"; filename="%s"' % (key, value.name),
+ 'Content-Type: application/octet-stream',
+ '',
+ value.read()
+ ])
+ elif hasattr(value, '__iter__'):
+ for item in value:
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ '',
+ str(item)
+ ])
+ else:
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ '',
+ str(value)
+ ])
+
+ lines.extend([
+ '--' + boundary + '--',
+ '',
+ ])
+ return '\r\n'.join(lines)
+
+class Client:
+ """
+ A class that can act as a client for testing purposes.
+
+ It allows the user to compose GET and POST requests, and
+ obtain the response that the server gave to those requests.
+ The server Response objects are annotated with the details
+ of the contexts and templates that were rendered during the
+ process of serving the request.
+
+ Client objects are stateful - they will retain cookie (and
+ thus session) details for the lifetime of the Client instance.
+
+ This is not intended as a replacement for Twill/Selenium or
+ the like - it is here to allow testing against the
+ contexts and templates produced by a view, rather than the
+ HTML rendered to the end-user.
+ """
+ def __init__(self, **defaults):
+ self.handler = ClientHandler()
+ self.defaults = defaults
+ self.cookies = SimpleCookie()
+ self.session = {}
+ self.exc_info = None
+
+ def store_exc_info(self, *args, **kwargs):
+ """
+ Utility method that can be used to store exceptions when they are
+ generated by a view.
+ """
+ self.exc_info = sys.exc_info()
+
+ def request(self, **request):
+ """
+ The master request method. Composes the environment dictionary
+ and passes to the handler, returning the result of the handler.
+ Assumes defaults for the query environment, which can be overridden
+ using the arguments to the request.
+ """
+
+ environ = {
+ 'HTTP_COOKIE': self.cookies,
+ 'PATH_INFO': '/',
+ 'QUERY_STRING': '',
+ 'REQUEST_METHOD': 'GET',
+ 'SCRIPT_NAME': None,
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ 'SERVER_PROTOCOL': 'HTTP/1.1',
+ }
+ environ.update(self.defaults)
+ environ.update(request)
+
+ # Curry a data dictionary into an instance of
+ # the template renderer callback function
+ data = {}
+ on_template_render = curry(store_rendered_templates, data)
+ dispatcher.connect(on_template_render, signal=signals.template_rendered)
+
+ # Capture exceptions created by the handler
+ dispatcher.connect(self.store_exc_info, signal=got_request_exception)
+
+ response = self.handler(environ)
+
+ # Add any rendered template detail to the response
+ # If there was only one template rendered (the most likely case),
+ # flatten the list to a single element
+ for detail in ('template', 'context'):
+ if data.get(detail):
+ if len(data[detail]) == 1:
+ setattr(response, detail, data[detail][0]);
+ else:
+ setattr(response, detail, data[detail])
+ else:
+ setattr(response, detail, None)
+
+ # Look for a signalled exception and reraise it
+ if self.exc_info:
+ raise self.exc_info[1], None, self.exc_info[2]
+
+ # Update persistent cookie and session data
+ if response.cookies:
+ self.cookies.update(response.cookies)
+
+ if 'django.contrib.sessions' in settings.INSTALLED_APPS:
+ from django.contrib.sessions.middleware import SessionWrapper
+ cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
+ if cookie:
+ self.session = SessionWrapper(cookie.value)
+
+ return response
+
+ def get(self, path, data={}, **extra):
+ "Request a response from the server using GET."
+ r = {
+ 'CONTENT_LENGTH': None,
+ 'CONTENT_TYPE': 'text/html; charset=utf-8',
+ 'PATH_INFO': path,
+ 'QUERY_STRING': urlencode(data),
+ 'REQUEST_METHOD': 'GET',
+ }
+ r.update(extra)
+
+ return self.request(**r)
+
+ def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
+ "Request a response from the server using POST."
+
+ if content_type is MULTIPART_CONTENT:
+ post_data = encode_multipart(BOUNDARY, data)
+ else:
+ post_data = data
+
+ r = {
+ 'CONTENT_LENGTH': len(post_data),
+ 'CONTENT_TYPE': content_type,
+ 'PATH_INFO': path,
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': StringIO(post_data),
+ }
+ r.update(extra)
+
+ return self.request(**r)
+
+ def login(self, path, username, password, **extra):
+ """
+ A specialized sequence of GET and POST to log into a view that
+ is protected by a @login_required access decorator.
+
+ path should be the URL of the page that is login protected.
+
+ Returns the response from GETting the requested URL after
+ login is complete. Returns False if login process failed.
+ """
+ # First, GET the page that is login protected.
+ # This page will redirect to the login page.
+ response = self.get(path)
+ if response.status_code != 302:
+ return False
+
+ _, _, login_path, _, data, _= urlparse(response['Location'])
+ next = data.split('=')[1]
+
+ # Second, GET the login page; required to set up cookies
+ response = self.get(login_path, **extra)
+ if response.status_code != 200:
+ return False
+
+ # Last, POST the login data.
+ form_data = {
+ 'username': username,
+ 'password': password,
+ 'next' : next,
+ }
+ response = self.post(login_path, data=form_data, **extra)
+
+ # Login page should 302 redirect to the originally requested page
+ if (response.status_code != 302 or
+ urlparse(response['Location'])[2] != path):
+ return False
+
+ # Since we are logged in, request the actual page again
+ return self.get(path)
diff --git a/google_appengine/lib/django/django/test/doctest.py b/google_appengine/lib/django/django/test/doctest.py
new file mode 100755
index 0000000..3b364f0
--- /dev/null
+++ b/google_appengine/lib/django/django/test/doctest.py
@@ -0,0 +1,2669 @@
+# Module doctest.
+# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org).
+# Major enhancements and refactoring by:
+# Jim Fulton
+# Edward Loper
+
+# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
+
+r"""Module doctest -- a framework for running examples in docstrings.
+
+In simplest use, end each module M to be tested with:
+
+def _test():
+ import doctest
+ doctest.testmod()
+
+if __name__ == "__main__":
+ _test()
+
+Then running the module as a script will cause the examples in the
+docstrings to get executed and verified:
+
+python M.py
+
+This won't display anything unless an example fails, in which case the
+failing example(s) and the cause(s) of the failure(s) are printed to stdout
+(why not stderr? because stderr is a lame hack <0.2 wink>), and the final
+line of output is "Test failed.".
+
+Run it with the -v switch instead:
+
+python M.py -v
+
+and a detailed report of all examples tried is printed to stdout, along
+with assorted summaries at the end.
+
+You can force verbose mode by passing "verbose=True" to testmod, or prohibit
+it by passing "verbose=False". In either of those cases, sys.argv is not
+examined by testmod.
+
+There are a variety of other ways to run doctests, including integration
+with the unittest framework, and support for running non-Python text
+files containing doctests. There are also many ways to override parts
+of doctest's default behaviors. See the Library Reference Manual for
+details.
+"""
+
+__docformat__ = 'reStructuredText en'
+
+__all__ = [
+ # 0, Option Flags
+ 'register_optionflag',
+ 'DONT_ACCEPT_TRUE_FOR_1',
+ 'DONT_ACCEPT_BLANKLINE',
+ 'NORMALIZE_WHITESPACE',
+ 'ELLIPSIS',
+ 'IGNORE_EXCEPTION_DETAIL',
+ 'COMPARISON_FLAGS',
+ 'REPORT_UDIFF',
+ 'REPORT_CDIFF',
+ 'REPORT_NDIFF',
+ 'REPORT_ONLY_FIRST_FAILURE',
+ 'REPORTING_FLAGS',
+ # 1. Utility Functions
+ 'is_private',
+ # 2. Example & DocTest
+ 'Example',
+ 'DocTest',
+ # 3. Doctest Parser
+ 'DocTestParser',
+ # 4. Doctest Finder
+ 'DocTestFinder',
+ # 5. Doctest Runner
+ 'DocTestRunner',
+ 'OutputChecker',
+ 'DocTestFailure',
+ 'UnexpectedException',
+ 'DebugRunner',
+ # 6. Test Functions
+ 'testmod',
+ 'testfile',
+ 'run_docstring_examples',
+ # 7. Tester
+ 'Tester',
+ # 8. Unittest Support
+ 'DocTestSuite',
+ 'DocFileSuite',
+ 'set_unittest_reportflags',
+ # 9. Debugging Support
+ 'script_from_examples',
+ 'testsource',
+ 'debug_src',
+ 'debug',
+]
+
+import __future__
+
+import sys, traceback, inspect, linecache, os, re, types
+import unittest, difflib, pdb, tempfile
+import warnings
+from StringIO import StringIO
+
+# Don't whine about the deprecated is_private function in this
+# module's tests.
+warnings.filterwarnings("ignore", "is_private", DeprecationWarning,
+ __name__, 0)
+
+# There are 4 basic classes:
+# - Example: a <source, want> pair, plus an intra-docstring line number.
+# - DocTest: a collection of examples, parsed from a docstring, plus
+# info about where the docstring came from (name, filename, lineno).
+# - DocTestFinder: extracts DocTests from a given object's docstring and
+# its contained objects' docstrings.
+# - DocTestRunner: runs DocTest cases, and accumulates statistics.
+#
+# So the basic picture is:
+#
+# list of:
+# +------+ +---------+ +-------+
+# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results|
+# +------+ +---------+ +-------+
+# | Example |
+# | ... |
+# | Example |
+# +---------+
+
+# Option constants.
+
+OPTIONFLAGS_BY_NAME = {}
+def register_optionflag(name):
+ flag = 1 << len(OPTIONFLAGS_BY_NAME)
+ OPTIONFLAGS_BY_NAME[name] = flag
+ return flag
+
+DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1')
+DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
+NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
+ELLIPSIS = register_optionflag('ELLIPSIS')
+IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
+
+COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
+ DONT_ACCEPT_BLANKLINE |
+ NORMALIZE_WHITESPACE |
+ ELLIPSIS |
+ IGNORE_EXCEPTION_DETAIL)
+
+REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
+REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
+REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
+REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
+
+REPORTING_FLAGS = (REPORT_UDIFF |
+ REPORT_CDIFF |
+ REPORT_NDIFF |
+ REPORT_ONLY_FIRST_FAILURE)
+
+# Special string markers for use in `want` strings:
+BLANKLINE_MARKER = '<BLANKLINE>'
+ELLIPSIS_MARKER = '...'
+
+######################################################################
+## Table of Contents
+######################################################################
+# 1. Utility Functions
+# 2. Example & DocTest -- store test cases
+# 3. DocTest Parser -- extracts examples from strings
+# 4. DocTest Finder -- extracts test cases from objects
+# 5. DocTest Runner -- runs test cases
+# 6. Test Functions -- convenient wrappers for testing
+# 7. Tester Class -- for backwards compatibility
+# 8. Unittest Support
+# 9. Debugging Support
+# 10. Example Usage
+
+######################################################################
+## 1. Utility Functions
+######################################################################
+
+def is_private(prefix, base):
+ """prefix, base -> true iff name prefix + "." + base is "private".
+
+ Prefix may be an empty string, and base does not contain a period.
+ Prefix is ignored (although functions you write conforming to this
+ protocol may make use of it).
+ Return true iff base begins with an (at least one) underscore, but
+ does not both begin and end with (at least) two underscores.
+
+ >>> is_private("a.b", "my_func")
+ False
+ >>> is_private("____", "_my_func")
+ True
+ >>> is_private("someclass", "__init__")
+ False
+ >>> is_private("sometypo", "__init_")
+ True
+ >>> is_private("x.y.z", "_")
+ True
+ >>> is_private("_x.y.z", "__")
+ False
+ >>> is_private("", "") # senseless but consistent
+ False
+ """
+ warnings.warn("is_private is deprecated; it wasn't useful; "
+ "examine DocTestFinder.find() lists instead",
+ DeprecationWarning, stacklevel=2)
+ return base[:1] == "_" and not base[:2] == "__" == base[-2:]
+
+def _extract_future_flags(globs):
+ """
+ Return the compiler-flags associated with the future features that
+ have been imported into the given namespace (globs).
+ """
+ flags = 0
+ for fname in __future__.all_feature_names:
+ feature = globs.get(fname, None)
+ if feature is getattr(__future__, fname):
+ flags |= feature.compiler_flag
+ return flags
+
+def _normalize_module(module, depth=2):
+ """
+ Return the module specified by `module`. In particular:
+ - If `module` is a module, then return module.
+ - If `module` is a string, then import and return the
+ module with that name.
+ - If `module` is None, then return the calling module.
+ The calling module is assumed to be the module of
+ the stack frame at the given depth in the call stack.
+ """
+ if inspect.ismodule(module):
+ return module
+ elif isinstance(module, (str, unicode)):
+ return __import__(module, globals(), locals(), ["*"])
+ elif module is None:
+ return sys.modules[sys._getframe(depth).f_globals['__name__']]
+ else:
+ raise TypeError("Expected a module, string, or None")
+
+def _indent(s, indent=4):
+ """
+ Add the given number of space characters to the beginning every
+ non-blank line in `s`, and return the result.
+ """
+ # This regexp matches the start of non-blank lines:
+ return re.sub('(?m)^(?!$)', indent*' ', s)
+
+def _exception_traceback(exc_info):
+ """
+ Return a string containing a traceback message for the given
+ exc_info tuple (as returned by sys.exc_info()).
+ """
+ # Get a traceback message.
+ excout = StringIO()
+ exc_type, exc_val, exc_tb = exc_info
+ traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
+ return excout.getvalue()
+
+# Override some StringIO methods.
+class _SpoofOut(StringIO):
+ def getvalue(self):
+ result = StringIO.getvalue(self)
+ # If anything at all was written, make sure there's a trailing
+ # newline. There's no way for the expected output to indicate
+ # that a trailing newline is missing.
+ if result and not result.endswith("\n"):
+ result += "\n"
+ # Prevent softspace from screwing up the next test case, in
+ # case they used print with a trailing comma in an example.
+ if hasattr(self, "softspace"):
+ del self.softspace
+ return result
+
+ def truncate(self, size=None):
+ StringIO.truncate(self, size)
+ if hasattr(self, "softspace"):
+ del self.softspace
+
+# Worst-case linear-time ellipsis matching.
+def _ellipsis_match(want, got):
+ """
+ Essentially the only subtle case:
+ >>> _ellipsis_match('aa...aa', 'aaa')
+ False
+ """
+ if ELLIPSIS_MARKER not in want:
+ return want == got
+
+ # Find "the real" strings.
+ ws = want.split(ELLIPSIS_MARKER)
+ assert len(ws) >= 2
+
+ # Deal with exact matches possibly needed at one or both ends.
+ startpos, endpos = 0, len(got)
+ w = ws[0]
+ if w: # starts with exact match
+ if got.startswith(w):
+ startpos = len(w)
+ del ws[0]
+ else:
+ return False
+ w = ws[-1]
+ if w: # ends with exact match
+ if got.endswith(w):
+ endpos -= len(w)
+ del ws[-1]
+ else:
+ return False
+
+ if startpos > endpos:
+ # Exact end matches required more characters than we have, as in
+ # _ellipsis_match('aa...aa', 'aaa')
+ return False
+
+ # For the rest, we only need to find the leftmost non-overlapping
+ # match for each piece. If there's no overall match that way alone,
+ # there's no overall match period.
+ for w in ws:
+ # w may be '' at times, if there are consecutive ellipses, or
+ # due to an ellipsis at the start or end of `want`. That's OK.
+ # Search for an empty string succeeds, and doesn't change startpos.
+ startpos = got.find(w, startpos, endpos)
+ if startpos < 0:
+ return False
+ startpos += len(w)
+
+ return True
+
+def _comment_line(line):
+ "Return a commented form of the given line"
+ line = line.rstrip()
+ if line:
+ return '# '+line
+ else:
+ return '#'
+
+class _OutputRedirectingPdb(pdb.Pdb):
+ """
+ A specialized version of the python debugger that redirects stdout
+ to a given stream when interacting with the user. Stdout is *not*
+ redirected when traced code is executed.
+ """
+ def __init__(self, out):
+ self.__out = out
+ pdb.Pdb.__init__(self)
+
+ def trace_dispatch(self, *args):
+ # Redirect stdout to the given stream.
+ save_stdout = sys.stdout
+ sys.stdout = self.__out
+ # Call Pdb's trace dispatch method.
+ try:
+ return pdb.Pdb.trace_dispatch(self, *args)
+ finally:
+ sys.stdout = save_stdout
+
+# [XX] Normalize with respect to os.path.pardir?
+def _module_relative_path(module, path):
+ if not inspect.ismodule(module):
+ raise TypeError, 'Expected a module: %r' % module
+ if path.startswith('/'):
+ raise ValueError, 'Module-relative files may not have absolute paths'
+
+ # Find the base directory for the path.
+ if hasattr(module, '__file__'):
+ # A normal module/package
+ basedir = os.path.split(module.__file__)[0]
+ elif module.__name__ == '__main__':
+ # An interactive session.
+ if len(sys.argv)>0 and sys.argv[0] != '':
+ basedir = os.path.split(sys.argv[0])[0]
+ else:
+ basedir = os.curdir
+ else:
+ # A module w/o __file__ (this includes builtins)
+ raise ValueError("Can't resolve paths relative to the module " +
+ module + " (it has no __file__)")
+
+ # Combine the base directory and the path.
+ return os.path.join(basedir, *(path.split('/')))
+
+######################################################################
+## 2. Example & DocTest
+######################################################################
+## - An "example" is a <source, want> pair, where "source" is a
+## fragment of source code, and "want" is the expected output for
+## "source." The Example class also includes information about
+## where the example was extracted from.
+##
+## - A "doctest" is a collection of examples, typically extracted from
+## a string (such as an object's docstring). The DocTest class also
+## includes information about where the string was extracted from.
+
+class Example:
+ """
+ A single doctest example, consisting of source code and expected
+ output. `Example` defines the following attributes:
+
+ - source: A single Python statement, always ending with a newline.
+ The constructor adds a newline if needed.
+
+ - want: The expected output from running the source code (either
+ from stdout, or a traceback in case of exception). `want` ends
+ with a newline unless it's empty, in which case it's an empty
+ string. The constructor adds a newline if needed.
+
+ - exc_msg: The exception message generated by the example, if
+ the example is expected to generate an exception; or `None` if
+ it is not expected to generate an exception. This exception
+ message is compared against the return value of
+ `traceback.format_exception_only()`. `exc_msg` ends with a
+ newline unless it's `None`. The constructor adds a newline
+ if needed.
+
+ - lineno: The line number within the DocTest string containing
+ this Example where the Example begins. This line number is
+ zero-based, with respect to the beginning of the DocTest.
+
+ - indent: The example's indentation in the DocTest string.
+ I.e., the number of space characters that preceed the
+ example's first prompt.
+
+ - options: A dictionary mapping from option flags to True or
+ False, which is used to override default options for this
+ example. Any option flags not contained in this dictionary
+ are left at their default value (as specified by the
+ DocTestRunner's optionflags). By default, no options are set.
+ """
+ def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
+ options=None):
+ # Normalize inputs.
+ if not source.endswith('\n'):
+ source += '\n'
+ if want and not want.endswith('\n'):
+ want += '\n'
+ if exc_msg is not None and not exc_msg.endswith('\n'):
+ exc_msg += '\n'
+ # Store properties.
+ self.source = source
+ self.want = want
+ self.lineno = lineno
+ self.indent = indent
+ if options is None: options = {}
+ self.options = options
+ self.exc_msg = exc_msg
+
+class DocTest:
+ """
+ A collection of doctest examples that should be run in a single
+ namespace. Each `DocTest` defines the following attributes:
+
+ - examples: the list of examples.
+
+ - globs: The namespace (aka globals) that the examples should
+ be run in.
+
+ - name: A name identifying the DocTest (typically, the name of
+ the object whose docstring this DocTest was extracted from).
+
+ - filename: The name of the file that this DocTest was extracted
+ from, or `None` if the filename is unknown.
+
+ - lineno: The line number within filename where this DocTest
+ begins, or `None` if the line number is unavailable. This
+ line number is zero-based, with respect to the beginning of
+ the file.
+
+ - docstring: The string that the examples were extracted from,
+ or `None` if the string is unavailable.
+ """
+ def __init__(self, examples, globs, name, filename, lineno, docstring):
+ """
+ Create a new DocTest containing the given examples. The
+ DocTest's globals are initialized with a copy of `globs`.
+ """
+ assert not isinstance(examples, basestring), \
+ "DocTest no longer accepts str; use DocTestParser instead"
+ self.examples = examples
+ self.docstring = docstring
+ self.globs = globs.copy()
+ self.name = name
+ self.filename = filename
+ self.lineno = lineno
+
+ def __repr__(self):
+ if len(self.examples) == 0:
+ examples = 'no examples'
+ elif len(self.examples) == 1:
+ examples = '1 example'
+ else:
+ examples = '%d examples' % len(self.examples)
+ return ('<DocTest %s from %s:%s (%s)>' %
+ (self.name, self.filename, self.lineno, examples))
+
+
+ # This lets us sort tests by name:
+ def __cmp__(self, other):
+ if not isinstance(other, DocTest):
+ return -1
+ return cmp((self.name, self.filename, self.lineno, id(self)),
+ (other.name, other.filename, other.lineno, id(other)))
+
+######################################################################
+## 3. DocTestParser
+######################################################################
+
+class DocTestParser:
+ """
+ A class used to parse strings containing doctest examples.
+ """
+ # This regular expression is used to find doctest examples in a
+ # string. It defines three groups: `source` is the source code
+ # (including leading indentation and prompts); `indent` is the
+ # indentation of the first (PS1) line of the source code; and
+ # `want` is the expected output (including leading indentation).
+ _EXAMPLE_RE = re.compile(r'''
+ # Source consists of a PS1 line followed by zero or more PS2 lines.
+ (?P<source>
+ (?:^(?P<indent> [ ]*) >>> .*) # PS1 line
+ (?:\n [ ]* \.\.\. .*)*) # PS2 lines
+ \n?
+ # Want consists of any non-blank lines that do not start with PS1.
+ (?P<want> (?:(?![ ]*$) # Not a blank line
+ (?![ ]*>>>) # Not a line starting with PS1
+ .*$\n? # But any other line
+ )*)
+ ''', re.MULTILINE | re.VERBOSE)
+
+ # A regular expression for handling `want` strings that contain
+ # expected exceptions. It divides `want` into three pieces:
+ # - the traceback header line (`hdr`)
+ # - the traceback stack (`stack`)
+ # - the exception message (`msg`), as generated by
+ # traceback.format_exception_only()
+ # `msg` may have multiple lines. We assume/require that the
+ # exception message is the first non-indented line starting with a word
+ # character following the traceback header line.
+ _EXCEPTION_RE = re.compile(r"""
+ # Grab the traceback header. Different versions of Python have
+ # said different things on the first traceback line.
+ ^(?P<hdr> Traceback\ \(
+ (?: most\ recent\ call\ last
+ | innermost\ last
+ ) \) :
+ )
+ \s* $ # toss trailing whitespace on the header.
+ (?P<stack> .*?) # don't blink: absorb stuff until...
+ ^ (?P<msg> \w+ .*) # a line *starts* with alphanum.
+ """, re.VERBOSE | re.MULTILINE | re.DOTALL)
+
+ # A callable returning a true value iff its argument is a blank line
+ # or contains a single comment.
+ _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match
+
+ def parse(self, string, name='<string>'):
+ """
+ Divide the given string into examples and intervening text,
+ and return them as a list of alternating Examples and strings.
+ Line numbers for the Examples are 0-based. The optional
+ argument `name` is a name identifying this string, and is only
+ used for error messages.
+ """
+ string = string.expandtabs()
+ # If all lines begin with the same indentation, then strip it.
+ min_indent = self._min_indent(string)
+ if min_indent > 0:
+ string = '\n'.join([l[min_indent:] for l in string.split('\n')])
+
+ output = []
+ charno, lineno = 0, 0
+ # Find all doctest examples in the string:
+ for m in self._EXAMPLE_RE.finditer(string):
+ # Add the pre-example text to `output`.
+ output.append(string[charno:m.start()])
+ # Update lineno (lines before this example)
+ lineno += string.count('\n', charno, m.start())
+ # Extract info from the regexp match.
+ (source, options, want, exc_msg) = \
+ self._parse_example(m, name, lineno)
+ # Create an Example, and add it to the list.
+ if not self._IS_BLANK_OR_COMMENT(source):
+ output.append( Example(source, want, exc_msg,
+ lineno=lineno,
+ indent=min_indent+len(m.group('indent')),
+ options=options) )
+ # Update lineno (lines inside this example)
+ lineno += string.count('\n', m.start(), m.end())
+ # Update charno.
+ charno = m.end()
+ # Add any remaining post-example text to `output`.
+ output.append(string[charno:])
+ return output
+
+ def get_doctest(self, string, globs, name, filename, lineno):
+ """
+ Extract all doctest examples from the given string, and
+ collect them into a `DocTest` object.
+
+ `globs`, `name`, `filename`, and `lineno` are attributes for
+ the new `DocTest` object. See the documentation for `DocTest`
+ for more information.
+ """
+ return DocTest(self.get_examples(string, name), globs,
+ name, filename, lineno, string)
+
+ def get_examples(self, string, name='<string>'):
+ """
+ Extract all doctest examples from the given string, and return
+ them as a list of `Example` objects. Line numbers are
+ 0-based, because it's most common in doctests that nothing
+ interesting appears on the same line as opening triple-quote,
+ and so the first interesting line is called \"line 1\" then.
+
+ The optional argument `name` is a name identifying this
+ string, and is only used for error messages.
+ """
+ return [x for x in self.parse(string, name)
+ if isinstance(x, Example)]
+
+ def _parse_example(self, m, name, lineno):
+ """
+ Given a regular expression match from `_EXAMPLE_RE` (`m`),
+ return a pair `(source, want)`, where `source` is the matched
+ example's source code (with prompts and indentation stripped);
+ and `want` is the example's expected output (with indentation
+ stripped).
+
+ `name` is the string's name, and `lineno` is the line number
+ where the example starts; both are used for error messages.
+ """
+ # Get the example's indentation level.
+ indent = len(m.group('indent'))
+
+ # Divide source into lines; check that they're properly
+ # indented; and then strip their indentation & prompts.
+ source_lines = m.group('source').split('\n')
+ self._check_prompt_blank(source_lines, indent, name, lineno)
+ self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno)
+ source = '\n'.join([sl[indent+4:] for sl in source_lines])
+
+ # Divide want into lines; check that it's properly indented; and
+ # then strip the indentation. Spaces before the last newline should
+ # be preserved, so plain rstrip() isn't good enough.
+ want = m.group('want')
+ want_lines = want.split('\n')
+ if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
+ del want_lines[-1] # forget final newline & spaces after it
+ self._check_prefix(want_lines, ' '*indent, name,
+ lineno + len(source_lines))
+ want = '\n'.join([wl[indent:] for wl in want_lines])
+
+ # If `want` contains a traceback message, then extract it.
+ m = self._EXCEPTION_RE.match(want)
+ if m:
+ exc_msg = m.group('msg')
+ else:
+ exc_msg = None
+
+ # Extract options from the source.
+ options = self._find_options(source, name, lineno)
+
+ return source, options, want, exc_msg
+
+ # This regular expression looks for option directives in the
+ # source code of an example. Option directives are comments
+ # starting with "doctest:". Warning: this may give false
+ # positives for string-literals that contain the string
+ # "#doctest:". Eliminating these false positives would require
+ # actually parsing the string; but we limit them by ignoring any
+ # line containing "#doctest:" that is *followed* by a quote mark.
+ _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$',
+ re.MULTILINE)
+
+ def _find_options(self, source, name, lineno):
+ """
+ Return a dictionary containing option overrides extracted from
+ option directives in the given source string.
+
+ `name` is the string's name, and `lineno` is the line number
+ where the example starts; both are used for error messages.
+ """
+ options = {}
+ # (note: with the current regexp, this will match at most once:)
+ for m in self._OPTION_DIRECTIVE_RE.finditer(source):
+ option_strings = m.group(1).replace(',', ' ').split()
+ for option in option_strings:
+ if (option[0] not in '+-' or
+ option[1:] not in OPTIONFLAGS_BY_NAME):
+ raise ValueError('line %r of the doctest for %s '
+ 'has an invalid option: %r' %
+ (lineno+1, name, option))
+ flag = OPTIONFLAGS_BY_NAME[option[1:]]
+ options[flag] = (option[0] == '+')
+ if options and self._IS_BLANK_OR_COMMENT(source):
+ raise ValueError('line %r of the doctest for %s has an option '
+ 'directive on a line with no example: %r' %
+ (lineno, name, source))
+ return options
+
+ # This regular expression finds the indentation of every non-blank
+ # line in a string.
+ _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE)
+
+ def _min_indent(self, s):
+ "Return the minimum indentation of any non-blank line in `s`"
+ indents = [len(indent) for indent in self._INDENT_RE.findall(s)]
+ if len(indents) > 0:
+ return min(indents)
+ else:
+ return 0
+
+ def _check_prompt_blank(self, lines, indent, name, lineno):
+ """
+ Given the lines of a source string (including prompts and
+ leading indentation), check to make sure that every prompt is
+ followed by a space character. If any line is not followed by
+ a space character, then raise ValueError.
+ """
+ for i, line in enumerate(lines):
+ if len(line) >= indent+4 and line[indent+3] != ' ':
+ raise ValueError('line %r of the docstring for %s '
+ 'lacks blank after %s: %r' %
+ (lineno+i+1, name,
+ line[indent:indent+3], line))
+
+ def _check_prefix(self, lines, prefix, name, lineno):
+ """
+ Check that every line in the given list starts with the given
+ prefix; if any line does not, then raise a ValueError.
+ """
+ for i, line in enumerate(lines):
+ if line and not line.startswith(prefix):
+ raise ValueError('line %r of the docstring for %s has '
+ 'inconsistent leading whitespace: %r' %
+ (lineno+i+1, name, line))
+
+
+######################################################################
+## 4. DocTest Finder
+######################################################################
+
+class DocTestFinder:
+ """
+ A class used to extract the DocTests that are relevant to a given
+ object, from its docstring and the docstrings of its contained
+ objects. Doctests can currently be extracted from the following
+ object types: modules, functions, classes, methods, staticmethods,
+ classmethods, and properties.
+ """
+
+ def __init__(self, verbose=False, parser=DocTestParser(),
+ recurse=True, _namefilter=None, exclude_empty=True):
+ """
+ Create a new doctest finder.
+
+ The optional argument `parser` specifies a class or
+ function that should be used to create new DocTest objects (or
+ objects that implement the same interface as DocTest). The
+ signature for this factory function should match the signature
+ of the DocTest constructor.
+
+ If the optional argument `recurse` is false, then `find` will
+ only examine the given object, and not any contained objects.
+
+ If the optional argument `exclude_empty` is false, then `find`
+ will include tests for objects with empty docstrings.
+ """
+ self._parser = parser
+ self._verbose = verbose
+ self._recurse = recurse
+ self._exclude_empty = exclude_empty
+ # _namefilter is undocumented, and exists only for temporary backward-
+ # compatibility support of testmod's deprecated isprivate mess.
+ self._namefilter = _namefilter
+
+ def find(self, obj, name=None, module=None, globs=None,
+ extraglobs=None):
+ """
+ Return a list of the DocTests that are defined by the given
+ object's docstring, or by any of its contained objects'
+ docstrings.
+
+ The optional parameter `module` is the module that contains
+ the given object. If the module is not specified or is None, then
+ the test finder will attempt to automatically determine the
+ correct module. The object's module is used:
+
+ - As a default namespace, if `globs` is not specified.
+ - To prevent the DocTestFinder from extracting DocTests
+ from objects that are imported from other modules.
+ - To find the name of the file containing the object.
+ - To help find the line number of the object within its
+ file.
+
+ Contained objects whose module does not match `module` are ignored.
+
+ If `module` is False, no attempt to find the module will be made.
+ This is obscure, of use mostly in tests: if `module` is False, or
+ is None but cannot be found automatically, then all objects are
+ considered to belong to the (non-existent) module, so all contained
+ objects will (recursively) be searched for doctests.
+
+ The globals for each DocTest is formed by combining `globs`
+ and `extraglobs` (bindings in `extraglobs` override bindings
+ in `globs`). A new copy of the globals dictionary is created
+ for each DocTest. If `globs` is not specified, then it
+ defaults to the module's `__dict__`, if specified, or {}
+ otherwise. If `extraglobs` is not specified, then it defaults
+ to {}.
+
+ """
+ # If name was not specified, then extract it from the object.
+ if name is None:
+ name = getattr(obj, '__name__', None)
+ if name is None:
+ raise ValueError("DocTestFinder.find: name must be given "
+ "when obj.__name__ doesn't exist: %r" %
+ (type(obj),))
+
+ # Find the module that contains the given object (if obj is
+ # a module, then module=obj.). Note: this may fail, in which
+ # case module will be None.
+ if module is False:
+ module = None
+ elif module is None:
+ module = inspect.getmodule(obj)
+
+ # Read the module's source code. This is used by
+ # DocTestFinder._find_lineno to find the line number for a
+ # given object's docstring.
+ try:
+ file = inspect.getsourcefile(obj) or inspect.getfile(obj)
+ source_lines = linecache.getlines(file)
+ if not source_lines:
+ source_lines = None
+ except TypeError:
+ source_lines = None
+
+ # Initialize globals, and merge in extraglobs.
+ if globs is None:
+ if module is None:
+ globs = {}
+ else:
+ globs = module.__dict__.copy()
+ else:
+ globs = globs.copy()
+ if extraglobs is not None:
+ globs.update(extraglobs)
+
+ # Recursively explore `obj`, extracting DocTests.
+ tests = []
+ self._find(tests, obj, name, module, source_lines, globs, {})
+ return tests
+
+ def _filter(self, obj, prefix, base):
+ """
+ Return true if the given object should not be examined.
+ """
+ return (self._namefilter is not None and
+ self._namefilter(prefix, base))
+
+ def _from_module(self, module, object):
+ """
+ Return true if the given object is defined in the given
+ module.
+ """
+ if module is None:
+ return True
+ elif inspect.isfunction(object):
+ return module.__dict__ is object.func_globals
+ elif inspect.isclass(object):
+ return module.__name__ == object.__module__
+ elif inspect.getmodule(object) is not None:
+ return module is inspect.getmodule(object)
+ elif hasattr(object, '__module__'):
+ return module.__name__ == object.__module__
+ elif isinstance(object, property):
+ return True # [XX] no way not be sure.
+ else:
+ raise ValueError("object must be a class or function")
+
+ def _find(self, tests, obj, name, module, source_lines, globs, seen):
+ """
+ Find tests for the given object and any contained objects, and
+ add them to `tests`.
+ """
+ if self._verbose:
+ print 'Finding tests in %s' % name
+
+ # If we've already processed this object, then ignore it.
+ if id(obj) in seen:
+ return
+ seen[id(obj)] = 1
+
+ # Find a test for this object, and add it to the list of tests.
+ test = self._get_test(obj, name, module, globs, source_lines)
+ if test is not None:
+ tests.append(test)
+
+ # Look for tests in a module's contained objects.
+ if inspect.ismodule(obj) and self._recurse:
+ for valname, val in obj.__dict__.items():
+ # Check if this contained object should be ignored.
+ if self._filter(val, name, valname):
+ continue
+ valname = '%s.%s' % (name, valname)
+ # Recurse to functions & classes.
+ if ((inspect.isfunction(val) or inspect.isclass(val)) and
+ self._from_module(module, val)):
+ self._find(tests, val, valname, module, source_lines,
+ globs, seen)
+
+ # Look for tests in a module's __test__ dictionary.
+ if inspect.ismodule(obj) and self._recurse:
+ for valname, val in getattr(obj, '__test__', {}).items():
+ if not isinstance(valname, basestring):
+ raise ValueError("DocTestFinder.find: __test__ keys "
+ "must be strings: %r" %
+ (type(valname),))
+ if not (inspect.isfunction(val) or inspect.isclass(val) or
+ inspect.ismethod(val) or inspect.ismodule(val) or
+ isinstance(val, basestring)):
+ raise ValueError("DocTestFinder.find: __test__ values "
+ "must be strings, functions, methods, "
+ "classes, or modules: %r" %
+ (type(val),))
+ valname = '%s.__test__.%s' % (name, valname)
+ self._find(tests, val, valname, module, source_lines,
+ globs, seen)
+
+ # Look for tests in a class's contained objects.
+ if inspect.isclass(obj) and self._recurse:
+ for valname, val in obj.__dict__.items():
+ # Check if this contained object should be ignored.
+ if self._filter(val, name, valname):
+ continue
+ # Special handling for staticmethod/classmethod.
+ if isinstance(val, staticmethod):
+ val = getattr(obj, valname)
+ if isinstance(val, classmethod):
+ val = getattr(obj, valname).im_func
+
+ # Recurse to methods, properties, and nested classes.
+ if ((inspect.isfunction(val) or inspect.isclass(val) or
+ isinstance(val, property)) and
+ self._from_module(module, val)):
+ valname = '%s.%s' % (name, valname)
+ self._find(tests, val, valname, module, source_lines,
+ globs, seen)
+
+ def _get_test(self, obj, name, module, globs, source_lines):
+ """
+ Return a DocTest for the given object, if it defines a docstring;
+ otherwise, return None.
+ """
+ # Extract the object's docstring. If it doesn't have one,
+ # then return None (no test for this object).
+ if isinstance(obj, basestring):
+ docstring = obj
+ else:
+ try:
+ if obj.__doc__ is None:
+ docstring = ''
+ else:
+ docstring = obj.__doc__
+ if not isinstance(docstring, basestring):
+ docstring = str(docstring)
+ except (TypeError, AttributeError):
+ docstring = ''
+
+ # Find the docstring's location in the file.
+ lineno = self._find_lineno(obj, source_lines)
+
+ # Don't bother if the docstring is empty.
+ if self._exclude_empty and not docstring:
+ return None
+
+ # Return a DocTest for this object.
+ if module is None:
+ filename = None
+ else:
+ filename = getattr(module, '__file__', module.__name__)
+ if filename[-4:] in (".pyc", ".pyo"):
+ filename = filename[:-1]
+ return self._parser.get_doctest(docstring, globs, name,
+ filename, lineno)
+
+ def _find_lineno(self, obj, source_lines):
+ """
+ Return a line number of the given object's docstring. Note:
+ this method assumes that the object has a docstring.
+ """
+ lineno = None
+
+ # Find the line number for modules.
+ if inspect.ismodule(obj):
+ lineno = 0
+
+ # Find the line number for classes.
+ # Note: this could be fooled if a class is defined multiple
+ # times in a single file.
+ if inspect.isclass(obj):
+ if source_lines is None:
+ return None
+ pat = re.compile(r'^\s*class\s*%s\b' %
+ getattr(obj, '__name__', '-'))
+ for i, line in enumerate(source_lines):
+ if pat.match(line):
+ lineno = i
+ break
+
+ # Find the line number for functions & methods.
+ if inspect.ismethod(obj): obj = obj.im_func
+ if inspect.isfunction(obj): obj = obj.func_code
+ if inspect.istraceback(obj): obj = obj.tb_frame
+ if inspect.isframe(obj): obj = obj.f_code
+ if inspect.iscode(obj):
+ lineno = getattr(obj, 'co_firstlineno', None)-1
+
+ # Find the line number where the docstring starts. Assume
+ # that it's the first line that begins with a quote mark.
+ # Note: this could be fooled by a multiline function
+ # signature, where a continuation line begins with a quote
+ # mark.
+ if lineno is not None:
+ if source_lines is None:
+ return lineno+1
+ pat = re.compile('(^|.*:)\s*\w*("|\')')
+ for lineno in range(lineno, len(source_lines)):
+ if pat.match(source_lines[lineno]):
+ return lineno
+
+ # We couldn't find the line number.
+ return None
+
+######################################################################
+## 5. DocTest Runner
+######################################################################
+
+class DocTestRunner:
+ """
+ A class used to run DocTest test cases, and accumulate statistics.
+ The `run` method is used to process a single DocTest case. It
+ returns a tuple `(f, t)`, where `t` is the number of test cases
+ tried, and `f` is the number of test cases that failed.
+
+ >>> tests = DocTestFinder().find(_TestClass)
+ >>> runner = DocTestRunner(verbose=False)
+ >>> for test in tests:
+ ... print runner.run(test)
+ (0, 2)
+ (0, 1)
+ (0, 2)
+ (0, 2)
+
+ The `summarize` method prints a summary of all the test cases that
+ have been run by the runner, and returns an aggregated `(f, t)`
+ tuple:
+
+ >>> runner.summarize(verbose=1)
+ 4 items passed all tests:
+ 2 tests in _TestClass
+ 2 tests in _TestClass.__init__
+ 2 tests in _TestClass.get
+ 1 tests in _TestClass.square
+ 7 tests in 4 items.
+ 7 passed and 0 failed.
+ Test passed.
+ (0, 7)
+
+ The aggregated number of tried examples and failed examples is
+ also available via the `tries` and `failures` attributes:
+
+ >>> runner.tries
+ 7
+ >>> runner.failures
+ 0
+
+ The comparison between expected outputs and actual outputs is done
+ by an `OutputChecker`. This comparison may be customized with a
+ number of option flags; see the documentation for `testmod` for
+ more information. If the option flags are insufficient, then the
+ comparison may also be customized by passing a subclass of
+ `OutputChecker` to the constructor.
+
+ The test runner's display output can be controlled in two ways.
+ First, an output function (`out) can be passed to
+ `TestRunner.run`; this function will be called with strings that
+ should be displayed. It defaults to `sys.stdout.write`. If
+ capturing the output is not sufficient, then the display output
+ can be also customized by subclassing DocTestRunner, and
+ overriding the methods `report_start`, `report_success`,
+ `report_unexpected_exception`, and `report_failure`.
+ """
+ # This divider string is used to separate failure messages, and to
+ # separate sections of the summary.
+ DIVIDER = "*" * 70
+
+ def __init__(self, checker=None, verbose=None, optionflags=0):
+ """
+ Create a new test runner.
+
+ Optional keyword arg `checker` is the `OutputChecker` that
+ should be used to compare the expected outputs and actual
+ outputs of doctest examples.
+
+ Optional keyword arg 'verbose' prints lots of stuff if true,
+ only failures if false; by default, it's true iff '-v' is in
+ sys.argv.
+
+ Optional argument `optionflags` can be used to control how the
+ test runner compares expected output to actual output, and how
+ it displays failures. See the documentation for `testmod` for
+ more information.
+ """
+ self._checker = checker or OutputChecker()
+ if verbose is None:
+ verbose = '-v' in sys.argv
+ self._verbose = verbose
+ self.optionflags = optionflags
+ self.original_optionflags = optionflags
+
+ # Keep track of the examples we've run.
+ self.tries = 0
+ self.failures = 0
+ self._name2ft = {}
+
+ # Create a fake output target for capturing doctest output.
+ self._fakeout = _SpoofOut()
+
+ #/////////////////////////////////////////////////////////////////
+ # Reporting methods
+ #/////////////////////////////////////////////////////////////////
+
+ def report_start(self, out, test, example):
+ """
+ Report that the test runner is about to process the given
+ example. (Only displays a message if verbose=True)
+ """
+ if self._verbose:
+ if example.want:
+ out('Trying:\n' + _indent(example.source) +
+ 'Expecting:\n' + _indent(example.want))
+ else:
+ out('Trying:\n' + _indent(example.source) +
+ 'Expecting nothing\n')
+
+ def report_success(self, out, test, example, got):
+ """
+ Report that the given example ran successfully. (Only
+ displays a message if verbose=True)
+ """
+ if self._verbose:
+ out("ok\n")
+
+ def report_failure(self, out, test, example, got):
+ """
+ Report that the given example failed.
+ """
+ out(self._failure_header(test, example) +
+ self._checker.output_difference(example, got, self.optionflags))
+
+ def report_unexpected_exception(self, out, test, example, exc_info):
+ """
+ Report that the given example raised an unexpected exception.
+ """
+ out(self._failure_header(test, example) +
+ 'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
+
+ def _failure_header(self, test, example):
+ out = [self.DIVIDER]
+ if test.filename:
+ if test.lineno is not None and example.lineno is not None:
+ lineno = test.lineno + example.lineno + 1
+ else:
+ lineno = '?'
+ out.append('File "%s", line %s, in %s' %
+ (test.filename, lineno, test.name))
+ else:
+ out.append('Line %s, in %s' % (example.lineno+1, test.name))
+ out.append('Failed example:')
+ source = example.source
+ out.append(_indent(source))
+ return '\n'.join(out)
+
+ #/////////////////////////////////////////////////////////////////
+ # DocTest Running
+ #/////////////////////////////////////////////////////////////////
+
+ def __run(self, test, compileflags, out):
+ """
+ Run the examples in `test`. Write the outcome of each example
+ with one of the `DocTestRunner.report_*` methods, using the
+ writer function `out`. `compileflags` is the set of compiler
+ flags that should be used to execute examples. Return a tuple
+ `(f, t)`, where `t` is the number of examples tried, and `f`
+ is the number of examples that failed. The examples are run
+ in the namespace `test.globs`.
+ """
+ # Keep track of the number of failures and tries.
+ failures = tries = 0
+
+ # Save the option flags (since option directives can be used
+ # to modify them).
+ original_optionflags = self.optionflags
+
+ SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
+
+ check = self._checker.check_output
+
+ # Process each example.
+ for examplenum, example in enumerate(test.examples):
+
+ # If REPORT_ONLY_FIRST_FAILURE is set, then suppress
+ # reporting after the first failure.
+ quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and
+ failures > 0)
+
+ # Merge in the example's options.
+ self.optionflags = original_optionflags
+ if example.options:
+ for (optionflag, val) in example.options.items():
+ if val:
+ self.optionflags |= optionflag
+ else:
+ self.optionflags &= ~optionflag
+
+ # Record that we started this example.
+ tries += 1
+ if not quiet:
+ self.report_start(out, test, example)
+
+ # Use a special filename for compile(), so we can retrieve
+ # the source code during interactive debugging (see
+ # __patched_linecache_getlines).
+ filename = '<doctest %s[%d]>' % (test.name, examplenum)
+
+ # Run the example in the given context (globs), and record
+ # any exception that gets raised. (But don't intercept
+ # keyboard interrupts.)
+ try:
+ # Don't blink! This is where the user's code gets run.
+ exec compile(example.source, filename, "single",
+ compileflags, 1) in test.globs
+ self.debugger.set_continue() # ==== Example Finished ====
+ exception = None
+ except KeyboardInterrupt:
+ raise
+ except:
+ exception = sys.exc_info()
+ self.debugger.set_continue() # ==== Example Finished ====
+
+ got = self._fakeout.getvalue() # the actual output
+ self._fakeout.truncate(0)
+ outcome = FAILURE # guilty until proved innocent or insane
+
+ # If the example executed without raising any exceptions,
+ # verify its output.
+ if exception is None:
+ if check(example.want, got, self.optionflags):
+ outcome = SUCCESS
+
+ # The example raised an exception: check if it was expected.
+ else:
+ exc_info = sys.exc_info()
+ exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
+ if not quiet:
+ got += _exception_traceback(exc_info)
+
+ # If `example.exc_msg` is None, then we weren't expecting
+ # an exception.
+ if example.exc_msg is None:
+ outcome = BOOM
+
+ # We expected an exception: see whether it matches.
+ elif check(example.exc_msg, exc_msg, self.optionflags):
+ outcome = SUCCESS
+
+ # Another chance if they didn't care about the detail.
+ elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
+ m1 = re.match(r'[^:]*:', example.exc_msg)
+ m2 = re.match(r'[^:]*:', exc_msg)
+ if m1 and m2 and check(m1.group(0), m2.group(0),
+ self.optionflags):
+ outcome = SUCCESS
+
+ # Report the outcome.
+ if outcome is SUCCESS:
+ if not quiet:
+ self.report_success(out, test, example, got)
+ elif outcome is FAILURE:
+ if not quiet:
+ self.report_failure(out, test, example, got)
+ failures += 1
+ elif outcome is BOOM:
+ if not quiet:
+ self.report_unexpected_exception(out, test, example,
+ exc_info)
+ failures += 1
+ else:
+ assert False, ("unknown outcome", outcome)
+
+ # Restore the option flags (in case they were modified)
+ self.optionflags = original_optionflags
+
+ # Record and return the number of failures and tries.
+ self.__record_outcome(test, failures, tries)
+ return failures, tries
+
+ def __record_outcome(self, test, f, t):
+ """
+ Record the fact that the given DocTest (`test`) generated `f`
+ failures out of `t` tried examples.
+ """
+ f2, t2 = self._name2ft.get(test.name, (0,0))
+ self._name2ft[test.name] = (f+f2, t+t2)
+ self.failures += f
+ self.tries += t
+
+ __LINECACHE_FILENAME_RE = re.compile(r'<doctest '
+ r'(?P<name>[\w\.]+)'
+ r'\[(?P<examplenum>\d+)\]>$')
+ def __patched_linecache_getlines(self, filename, module_globals=None):
+ m = self.__LINECACHE_FILENAME_RE.match(filename)
+ if m and m.group('name') == self.test.name:
+ example = self.test.examples[int(m.group('examplenum'))]
+ return example.source.splitlines(True)
+ else:
+ if sys.version_info < (2, 5, 0):
+ return self.save_linecache_getlines(filename)
+ else:
+ return self.save_linecache_getlines(filename, module_globals)
+
+ def run(self, test, compileflags=None, out=None, clear_globs=True):
+ """
+ Run the examples in `test`, and display the results using the
+ writer function `out`.
+
+ The examples are run in the namespace `test.globs`. If
+ `clear_globs` is true (the default), then this namespace will
+ be cleared after the test runs, to help with garbage
+ collection. If you would like to examine the namespace after
+ the test completes, then use `clear_globs=False`.
+
+ `compileflags` gives the set of flags that should be used by
+ the Python compiler when running the examples. If not
+ specified, then it will default to the set of future-import
+ flags that apply to `globs`.
+
+ The output of each example is checked using
+ `DocTestRunner.check_output`, and the results are formatted by
+ the `DocTestRunner.report_*` methods.
+ """
+ self.test = test
+
+ if compileflags is None:
+ compileflags = _extract_future_flags(test.globs)
+
+ save_stdout = sys.stdout
+ if out is None:
+ out = save_stdout.write
+ sys.stdout = self._fakeout
+
+ # Patch pdb.set_trace to restore sys.stdout during interactive
+ # debugging (so it's not still redirected to self._fakeout).
+ # Note that the interactive output will go to *our*
+ # save_stdout, even if that's not the real sys.stdout; this
+ # allows us to write test cases for the set_trace behavior.
+ save_set_trace = pdb.set_trace
+ self.debugger = _OutputRedirectingPdb(save_stdout)
+ self.debugger.reset()
+ pdb.set_trace = self.debugger.set_trace
+
+ # Patch linecache.getlines, so we can see the example's source
+ # when we're inside the debugger.
+ self.save_linecache_getlines = linecache.getlines
+ linecache.getlines = self.__patched_linecache_getlines
+
+ try:
+ return self.__run(test, compileflags, out)
+ finally:
+ sys.stdout = save_stdout
+ pdb.set_trace = save_set_trace
+ linecache.getlines = self.save_linecache_getlines
+ if clear_globs:
+ test.globs.clear()
+
+ #/////////////////////////////////////////////////////////////////
+ # Summarization
+ #/////////////////////////////////////////////////////////////////
+ def summarize(self, verbose=None):
+ """
+ Print a summary of all the test cases that have been run by
+ this DocTestRunner, and return a tuple `(f, t)`, where `f` is
+ the total number of failed examples, and `t` is the total
+ number of tried examples.
+
+ The optional `verbose` argument controls how detailed the
+ summary is. If the verbosity is not specified, then the
+ DocTestRunner's verbosity is used.
+ """
+ if verbose is None:
+ verbose = self._verbose
+ notests = []
+ passed = []
+ failed = []
+ totalt = totalf = 0
+ for x in self._name2ft.items():
+ name, (f, t) = x
+ assert f <= t
+ totalt += t
+ totalf += f
+ if t == 0:
+ notests.append(name)
+ elif f == 0:
+ passed.append( (name, t) )
+ else:
+ failed.append(x)
+ if verbose:
+ if notests:
+ print len(notests), "items had no tests:"
+ notests.sort()
+ for thing in notests:
+ print " ", thing
+ if passed:
+ print len(passed), "items passed all tests:"
+ passed.sort()
+ for thing, count in passed:
+ print " %3d tests in %s" % (count, thing)
+ if failed:
+ print self.DIVIDER
+ print len(failed), "items had failures:"
+ failed.sort()
+ for thing, (f, t) in failed:
+ print " %3d of %3d in %s" % (f, t, thing)
+ if verbose:
+ print totalt, "tests in", len(self._name2ft), "items."
+ print totalt - totalf, "passed and", totalf, "failed."
+ if totalf:
+ print "***Test Failed***", totalf, "failures."
+ elif verbose:
+ print "Test passed."
+ return totalf, totalt
+
+ #/////////////////////////////////////////////////////////////////
+ # Backward compatibility cruft to maintain doctest.master.
+ #/////////////////////////////////////////////////////////////////
+ def merge(self, other):
+ d = self._name2ft
+ for name, (f, t) in other._name2ft.items():
+ if name in d:
+ print "*** DocTestRunner.merge: '" + name + "' in both" \
+ " testers; summing outcomes."
+ f2, t2 = d[name]
+ f = f + f2
+ t = t + t2
+ d[name] = f, t
+
+class OutputChecker:
+ """
+ A class used to check the whether the actual output from a doctest
+ example matches the expected output. `OutputChecker` defines two
+ methods: `check_output`, which compares a given pair of outputs,
+ and returns true if they match; and `output_difference`, which
+ returns a string describing the differences between two outputs.
+ """
+ def check_output(self, want, got, optionflags):
+ """
+ Return True iff the actual output from an example (`got`)
+ matches the expected output (`want`). These strings are
+ always considered to match if they are identical; but
+ depending on what option flags the test runner is using,
+ several non-exact match types are also possible. See the
+ documentation for `TestRunner` for more information about
+ option flags.
+ """
+ # Handle the common case first, for efficiency:
+ # if they're string-identical, always return true.
+ if got == want:
+ return True
+
+ # The values True and False replaced 1 and 0 as the return
+ # value for boolean comparisons in Python 2.3.
+ if not (optionflags & DONT_ACCEPT_TRUE_FOR_1):
+ if (got,want) == ("True\n", "1\n"):
+ return True
+ if (got,want) == ("False\n", "0\n"):
+ return True
+
+ # <BLANKLINE> can be used as a special sequence to signify a
+ # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used.
+ if not (optionflags & DONT_ACCEPT_BLANKLINE):
+ # Replace <BLANKLINE> in want with a blank line.
+ want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER),
+ '', want)
+ # If a line in got contains only spaces, then remove the
+ # spaces.
+ got = re.sub('(?m)^\s*?$', '', got)
+ if got == want:
+ return True
+
+ # This flag causes doctest to ignore any differences in the
+ # contents of whitespace strings. Note that this can be used
+ # in conjunction with the ELLIPSIS flag.
+ if optionflags & NORMALIZE_WHITESPACE:
+ got = ' '.join(got.split())
+ want = ' '.join(want.split())
+ if got == want:
+ return True
+
+ # The ELLIPSIS flag says to let the sequence "..." in `want`
+ # match any substring in `got`.
+ if optionflags & ELLIPSIS:
+ if _ellipsis_match(want, got):
+ return True
+
+ # We didn't find any match; return false.
+ return False
+
+ # Should we do a fancy diff?
+ def _do_a_fancy_diff(self, want, got, optionflags):
+ # Not unless they asked for a fancy diff.
+ if not optionflags & (REPORT_UDIFF |
+ REPORT_CDIFF |
+ REPORT_NDIFF):
+ return False
+
+ # If expected output uses ellipsis, a meaningful fancy diff is
+ # too hard ... or maybe not. In two real-life failures Tim saw,
+ # a diff was a major help anyway, so this is commented out.
+ # [todo] _ellipsis_match() knows which pieces do and don't match,
+ # and could be the basis for a kick-ass diff in this case.
+ ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want:
+ ## return False
+
+ # ndiff does intraline difference marking, so can be useful even
+ # for 1-line differences.
+ if optionflags & REPORT_NDIFF:
+ return True
+
+ # The other diff types need at least a few lines to be helpful.
+ return want.count('\n') > 2 and got.count('\n') > 2
+
+ def output_difference(self, example, got, optionflags):
+ """
+ Return a string describing the differences between the
+ expected output for a given example (`example`) and the actual
+ output (`got`). `optionflags` is the set of option flags used
+ to compare `want` and `got`.
+ """
+ want = example.want
+ # If <BLANKLINE>s are being used, then replace blank lines
+ # with <BLANKLINE> in the actual output string.
+ if not (optionflags & DONT_ACCEPT_BLANKLINE):
+ got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got)
+
+ # Check if we should use diff.
+ if self._do_a_fancy_diff(want, got, optionflags):
+ # Split want & got into lines.
+ want_lines = want.splitlines(True) # True == keep line ends
+ got_lines = got.splitlines(True)
+ # Use difflib to find their differences.
+ if optionflags & REPORT_UDIFF:
+ diff = difflib.unified_diff(want_lines, got_lines, n=2)
+ diff = list(diff)[2:] # strip the diff header
+ kind = 'unified diff with -expected +actual'
+ elif optionflags & REPORT_CDIFF:
+ diff = difflib.context_diff(want_lines, got_lines, n=2)
+ diff = list(diff)[2:] # strip the diff header
+ kind = 'context diff with expected followed by actual'
+ elif optionflags & REPORT_NDIFF:
+ engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK)
+ diff = list(engine.compare(want_lines, got_lines))
+ kind = 'ndiff with -expected +actual'
+ else:
+ assert 0, 'Bad diff option'
+ # Remove trailing whitespace on diff output.
+ diff = [line.rstrip() + '\n' for line in diff]
+ return 'Differences (%s):\n' % kind + _indent(''.join(diff))
+
+ # If we're not using diff, then simply list the expected
+ # output followed by the actual output.
+ if want and got:
+ return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got))
+ elif want:
+ return 'Expected:\n%sGot nothing\n' % _indent(want)
+ elif got:
+ return 'Expected nothing\nGot:\n%s' % _indent(got)
+ else:
+ return 'Expected nothing\nGot nothing\n'
+
+class DocTestFailure(Exception):
+ """A DocTest example has failed in debugging mode.
+
+ The exception instance has variables:
+
+ - test: the DocTest object being run
+
+ - excample: the Example object that failed
+
+ - got: the actual output
+ """
+ def __init__(self, test, example, got):
+ self.test = test
+ self.example = example
+ self.got = got
+
+ def __str__(self):
+ return str(self.test)
+
+class UnexpectedException(Exception):
+ """A DocTest example has encountered an unexpected exception
+
+ The exception instance has variables:
+
+ - test: the DocTest object being run
+
+ - excample: the Example object that failed
+
+ - exc_info: the exception info
+ """
+ def __init__(self, test, example, exc_info):
+ self.test = test
+ self.example = example
+ self.exc_info = exc_info
+
+ def __str__(self):
+ return str(self.test)
+
+class DebugRunner(DocTestRunner):
+ r"""Run doc tests but raise an exception as soon as there is a failure.
+
+ If an unexpected exception occurs, an UnexpectedException is raised.
+ It contains the test, the example, and the original exception:
+
+ >>> runner = DebugRunner(verbose=False)
+ >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
+ ... {}, 'foo', 'foo.py', 0)
+ >>> try:
+ ... runner.run(test)
+ ... except UnexpectedException, failure:
+ ... pass
+
+ >>> failure.test is test
+ True
+
+ >>> failure.example.want
+ '42\n'
+
+ >>> exc_info = failure.exc_info
+ >>> raise exc_info[0], exc_info[1], exc_info[2]
+ Traceback (most recent call last):
+ ...
+ KeyError
+
+ We wrap the original exception to give the calling application
+ access to the test and example information.
+
+ If the output doesn't match, then a DocTestFailure is raised:
+
+ >>> test = DocTestParser().get_doctest('''
+ ... >>> x = 1
+ ... >>> x
+ ... 2
+ ... ''', {}, 'foo', 'foo.py', 0)
+
+ >>> try:
+ ... runner.run(test)
+ ... except DocTestFailure, failure:
+ ... pass
+
+ DocTestFailure objects provide access to the test:
+
+ >>> failure.test is test
+ True
+
+ As well as to the example:
+
+ >>> failure.example.want
+ '2\n'
+
+ and the actual output:
+
+ >>> failure.got
+ '1\n'
+
+ If a failure or error occurs, the globals are left intact:
+
+ >>> del test.globs['__builtins__']
+ >>> test.globs
+ {'x': 1}
+
+ >>> test = DocTestParser().get_doctest('''
+ ... >>> x = 2
+ ... >>> raise KeyError
+ ... ''', {}, 'foo', 'foo.py', 0)
+
+ >>> runner.run(test)
+ Traceback (most recent call last):
+ ...
+ UnexpectedException: <DocTest foo from foo.py:0 (2 examples)>
+
+ >>> del test.globs['__builtins__']
+ >>> test.globs
+ {'x': 2}
+
+ But the globals are cleared if there is no error:
+
+ >>> test = DocTestParser().get_doctest('''
+ ... >>> x = 2
+ ... ''', {}, 'foo', 'foo.py', 0)
+
+ >>> runner.run(test)
+ (0, 1)
+
+ >>> test.globs
+ {}
+
+ """
+
+ def run(self, test, compileflags=None, out=None, clear_globs=True):
+ r = DocTestRunner.run(self, test, compileflags, out, False)
+ if clear_globs:
+ test.globs.clear()
+ return r
+
+ def report_unexpected_exception(self, out, test, example, exc_info):
+ raise UnexpectedException(test, example, exc_info)
+
+ def report_failure(self, out, test, example, got):
+ raise DocTestFailure(test, example, got)
+
+######################################################################
+## 6. Test Functions
+######################################################################
+# These should be backwards compatible.
+
+# For backward compatibility, a global instance of a DocTestRunner
+# class, updated by testmod.
+master = None
+
+def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
+ report=True, optionflags=0, extraglobs=None,
+ raise_on_error=False, exclude_empty=False):
+ """m=None, name=None, globs=None, verbose=None, isprivate=None,
+ report=True, optionflags=0, extraglobs=None, raise_on_error=False,
+ exclude_empty=False
+
+ Test examples in docstrings in functions and classes reachable
+ from module m (or the current module if m is not supplied), starting
+ with m.__doc__. Unless isprivate is specified, private names
+ are not skipped.
+
+ Also test examples reachable from dict m.__test__ if it exists and is
+ not None. m.__test__ maps names to functions, classes and strings;
+ function and class docstrings are tested even if the name is private;
+ strings are tested directly, as if they were docstrings.
+
+ Return (#failures, #tests).
+
+ See doctest.__doc__ for an overview.
+
+ Optional keyword arg "name" gives the name of the module; by default
+ use m.__name__.
+
+ Optional keyword arg "globs" gives a dict to be used as the globals
+ when executing examples; by default, use m.__dict__. A copy of this
+ dict is actually used for each docstring, so that each docstring's
+ examples start with a clean slate.
+
+ Optional keyword arg "extraglobs" gives a dictionary that should be
+ merged into the globals that are used to execute examples. By
+ default, no extra globals are used. This is new in 2.4.
+
+ Optional keyword arg "verbose" prints lots of stuff if true, prints
+ only failures if false; by default, it's true iff "-v" is in sys.argv.
+
+ Optional keyword arg "report" prints a summary at the end when true,
+ else prints nothing at the end. In verbose mode, the summary is
+ detailed, else very brief (in fact, empty if all tests passed).
+
+ Optional keyword arg "optionflags" or's together module constants,
+ and defaults to 0. This is new in 2.3. Possible values (see the
+ docs for details):
+
+ DONT_ACCEPT_TRUE_FOR_1
+ DONT_ACCEPT_BLANKLINE
+ NORMALIZE_WHITESPACE
+ ELLIPSIS
+ IGNORE_EXCEPTION_DETAIL
+ REPORT_UDIFF
+ REPORT_CDIFF
+ REPORT_NDIFF
+ REPORT_ONLY_FIRST_FAILURE
+
+ Optional keyword arg "raise_on_error" raises an exception on the
+ first unexpected exception or failure. This allows failures to be
+ post-mortem debugged.
+
+ Deprecated in Python 2.4:
+ Optional keyword arg "isprivate" specifies a function used to
+ determine whether a name is private. The default function is
+ treat all functions as public. Optionally, "isprivate" can be
+ set to doctest.is_private to skip over functions marked as private
+ using the underscore naming convention; see its docs for details.
+
+ Advanced tomfoolery: testmod runs methods of a local instance of
+ class doctest.Tester, then merges the results into (or creates)
+ global Tester instance doctest.master. Methods of doctest.master
+ can be called directly too, if you want to do something unusual.
+ Passing report=0 to testmod is especially useful then, to delay
+ displaying a summary. Invoke doctest.master.summarize(verbose)
+ when you're done fiddling.
+ """
+ global master
+
+ if isprivate is not None:
+ warnings.warn("the isprivate argument is deprecated; "
+ "examine DocTestFinder.find() lists instead",
+ DeprecationWarning)
+
+ # If no module was given, then use __main__.
+ if m is None:
+ # DWA - m will still be None if this wasn't invoked from the command
+ # line, in which case the following TypeError is about as good an error
+ # as we should expect
+ m = sys.modules.get('__main__')
+
+ # Check that we were actually given a module.
+ if not inspect.ismodule(m):
+ raise TypeError("testmod: module required; %r" % (m,))
+
+ # If no name was given, then use the module's name.
+ if name is None:
+ name = m.__name__
+
+ # Find, parse, and run all tests in the given module.
+ finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty)
+
+ if raise_on_error:
+ runner = DebugRunner(verbose=verbose, optionflags=optionflags)
+ else:
+ runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
+
+ for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
+ runner.run(test)
+
+ if report:
+ runner.summarize()
+
+ if master is None:
+ master = runner
+ else:
+ master.merge(runner)
+
+ return runner.failures, runner.tries
+
+def testfile(filename, module_relative=True, name=None, package=None,
+ globs=None, verbose=None, report=True, optionflags=0,
+ extraglobs=None, raise_on_error=False, parser=DocTestParser()):
+ """
+ Test examples in the given file. Return (#failures, #tests).
+
+ Optional keyword arg "module_relative" specifies how filenames
+ should be interpreted:
+
+ - If "module_relative" is True (the default), then "filename"
+ specifies a module-relative path. By default, this path is
+ relative to the calling module's directory; but if the
+ "package" argument is specified, then it is relative to that
+ package. To ensure os-independence, "filename" should use
+ "/" characters to separate path segments, and should not
+ be an absolute path (i.e., it may not begin with "/").
+
+ - If "module_relative" is False, then "filename" specifies an
+ os-specific path. The path may be absolute or relative (to
+ the current working directory).
+
+ Optional keyword arg "name" gives the name of the test; by default
+ use the file's basename.
+
+ Optional keyword argument "package" is a Python package or the
+ name of a Python package whose directory should be used as the
+ base directory for a module relative filename. If no package is
+ specified, then the calling module's directory is used as the base
+ directory for module relative filenames. It is an error to
+ specify "package" if "module_relative" is False.
+
+ Optional keyword arg "globs" gives a dict to be used as the globals
+ when executing examples; by default, use {}. A copy of this dict
+ is actually used for each docstring, so that each docstring's
+ examples start with a clean slate.
+
+ Optional keyword arg "extraglobs" gives a dictionary that should be
+ merged into the globals that are used to execute examples. By
+ default, no extra globals are used.
+
+ Optional keyword arg "verbose" prints lots of stuff if true, prints
+ only failures if false; by default, it's true iff "-v" is in sys.argv.
+
+ Optional keyword arg "report" prints a summary at the end when true,
+ else prints nothing at the end. In verbose mode, the summary is
+ detailed, else very brief (in fact, empty if all tests passed).
+
+ Optional keyword arg "optionflags" or's together module constants,
+ and defaults to 0. Possible values (see the docs for details):
+
+ DONT_ACCEPT_TRUE_FOR_1
+ DONT_ACCEPT_BLANKLINE
+ NORMALIZE_WHITESPACE
+ ELLIPSIS
+ IGNORE_EXCEPTION_DETAIL
+ REPORT_UDIFF
+ REPORT_CDIFF
+ REPORT_NDIFF
+ REPORT_ONLY_FIRST_FAILURE
+
+ Optional keyword arg "raise_on_error" raises an exception on the
+ first unexpected exception or failure. This allows failures to be
+ post-mortem debugged.
+
+ Optional keyword arg "parser" specifies a DocTestParser (or
+ subclass) that should be used to extract tests from the files.
+
+ Advanced tomfoolery: testmod runs methods of a local instance of
+ class doctest.Tester, then merges the results into (or creates)
+ global Tester instance doctest.master. Methods of doctest.master
+ can be called directly too, if you want to do something unusual.
+ Passing report=0 to testmod is especially useful then, to delay
+ displaying a summary. Invoke doctest.master.summarize(verbose)
+ when you're done fiddling.
+ """
+ global master
+
+ if package and not module_relative:
+ raise ValueError("Package may only be specified for module-"
+ "relative paths.")
+
+ # Relativize the path
+ if module_relative:
+ package = _normalize_module(package)
+ filename = _module_relative_path(package, filename)
+
+ # If no name was given, then use the file's name.
+ if name is None:
+ name = os.path.basename(filename)
+
+ # Assemble the globals.
+ if globs is None:
+ globs = {}
+ else:
+ globs = globs.copy()
+ if extraglobs is not None:
+ globs.update(extraglobs)
+
+ if raise_on_error:
+ runner = DebugRunner(verbose=verbose, optionflags=optionflags)
+ else:
+ runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
+
+ # Read the file, convert it to a test, and run it.
+ s = open(filename).read()
+ test = parser.get_doctest(s, globs, name, filename, 0)
+ runner.run(test)
+
+ if report:
+ runner.summarize()
+
+ if master is None:
+ master = runner
+ else:
+ master.merge(runner)
+
+ return runner.failures, runner.tries
+
+def run_docstring_examples(f, globs, verbose=False, name="NoName",
+ compileflags=None, optionflags=0):
+ """
+ Test examples in the given object's docstring (`f`), using `globs`
+ as globals. Optional argument `name` is used in failure messages.
+ If the optional argument `verbose` is true, then generate output
+ even if there are no failures.
+
+ `compileflags` gives the set of flags that should be used by the
+ Python compiler when running the examples. If not specified, then
+ it will default to the set of future-import flags that apply to
+ `globs`.
+
+ Optional keyword arg `optionflags` specifies options for the
+ testing and output. See the documentation for `testmod` for more
+ information.
+ """
+ # Find, parse, and run all tests in the given module.
+ finder = DocTestFinder(verbose=verbose, recurse=False)
+ runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
+ for test in finder.find(f, name, globs=globs):
+ runner.run(test, compileflags=compileflags)
+
+######################################################################
+## 7. Tester
+######################################################################
+# This is provided only for backwards compatibility. It's not
+# actually used in any way.
+
+class Tester:
+ def __init__(self, mod=None, globs=None, verbose=None,
+ isprivate=None, optionflags=0):
+
+ warnings.warn("class Tester is deprecated; "
+ "use class doctest.DocTestRunner instead",
+ DeprecationWarning, stacklevel=2)
+ if mod is None and globs is None:
+ raise TypeError("Tester.__init__: must specify mod or globs")
+ if mod is not None and not inspect.ismodule(mod):
+ raise TypeError("Tester.__init__: mod must be a module; %r" %
+ (mod,))
+ if globs is None:
+ globs = mod.__dict__
+ self.globs = globs
+
+ self.verbose = verbose
+ self.isprivate = isprivate
+ self.optionflags = optionflags
+ self.testfinder = DocTestFinder(_namefilter=isprivate)
+ self.testrunner = DocTestRunner(verbose=verbose,
+ optionflags=optionflags)
+
+ def runstring(self, s, name):
+ test = DocTestParser().get_doctest(s, self.globs, name, None, None)
+ if self.verbose:
+ print "Running string", name
+ (f,t) = self.testrunner.run(test)
+ if self.verbose:
+ print f, "of", t, "examples failed in string", name
+ return (f,t)
+
+ def rundoc(self, object, name=None, module=None):
+ f = t = 0
+ tests = self.testfinder.find(object, name, module=module,
+ globs=self.globs)
+ for test in tests:
+ (f2, t2) = self.testrunner.run(test)
+ (f,t) = (f+f2, t+t2)
+ return (f,t)
+
+ def rundict(self, d, name, module=None):
+ import new
+ m = new.module(name)
+ m.__dict__.update(d)
+ if module is None:
+ module = False
+ return self.rundoc(m, name, module)
+
+ def run__test__(self, d, name):
+ import new
+ m = new.module(name)
+ m.__test__ = d
+ return self.rundoc(m, name)
+
+ def summarize(self, verbose=None):
+ return self.testrunner.summarize(verbose)
+
+ def merge(self, other):
+ self.testrunner.merge(other.testrunner)
+
+######################################################################
+## 8. Unittest Support
+######################################################################
+
+_unittest_reportflags = 0
+
+def set_unittest_reportflags(flags):
+ """Sets the unittest option flags.
+
+ The old flag is returned so that a runner could restore the old
+ value if it wished to:
+
+ >>> old = _unittest_reportflags
+ >>> set_unittest_reportflags(REPORT_NDIFF |
+ ... REPORT_ONLY_FIRST_FAILURE) == old
+ True
+
+ >>> import doctest
+ >>> doctest._unittest_reportflags == (REPORT_NDIFF |
+ ... REPORT_ONLY_FIRST_FAILURE)
+ True
+
+ Only reporting flags can be set:
+
+ >>> set_unittest_reportflags(ELLIPSIS)
+ Traceback (most recent call last):
+ ...
+ ValueError: ('Only reporting flags allowed', 8)
+
+ >>> set_unittest_reportflags(old) == (REPORT_NDIFF |
+ ... REPORT_ONLY_FIRST_FAILURE)
+ True
+ """
+ global _unittest_reportflags
+
+ if (flags & REPORTING_FLAGS) != flags:
+ raise ValueError("Only reporting flags allowed", flags)
+ old = _unittest_reportflags
+ _unittest_reportflags = flags
+ return old
+
+
+class DocTestCase(unittest.TestCase):
+
+ def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
+ checker=None, runner=DocTestRunner):
+
+ unittest.TestCase.__init__(self)
+ self._dt_optionflags = optionflags
+ self._dt_checker = checker
+ self._dt_test = test
+ self._dt_setUp = setUp
+ self._dt_tearDown = tearDown
+ self._dt_runner = runner
+
+ def setUp(self):
+ test = self._dt_test
+
+ if self._dt_setUp is not None:
+ self._dt_setUp(test)
+
+ def tearDown(self):
+ test = self._dt_test
+
+ if self._dt_tearDown is not None:
+ self._dt_tearDown(test)
+
+ test.globs.clear()
+
+ def runTest(self):
+ test = self._dt_test
+ old = sys.stdout
+ new = StringIO()
+ optionflags = self._dt_optionflags
+
+ if not (optionflags & REPORTING_FLAGS):
+ # The option flags don't include any reporting flags,
+ # so add the default reporting flags
+ optionflags |= _unittest_reportflags
+
+ runner = self._dt_runner(optionflags=optionflags,
+ checker=self._dt_checker, verbose=False)
+
+ try:
+ runner.DIVIDER = "-"*70
+ failures, tries = runner.run(
+ test, out=new.write, clear_globs=False)
+ finally:
+ sys.stdout = old
+
+ if failures:
+ raise self.failureException(self.format_failure(new.getvalue()))
+
+ def format_failure(self, err):
+ test = self._dt_test
+ if test.lineno is None:
+ lineno = 'unknown line number'
+ else:
+ lineno = '%s' % test.lineno
+ lname = '.'.join(test.name.split('.')[-1:])
+ return ('Failed doctest test for %s\n'
+ ' File "%s", line %s, in %s\n\n%s'
+ % (test.name, test.filename, lineno, lname, err)
+ )
+
+ def debug(self):
+ r"""Run the test case without results and without catching exceptions
+
+ The unit test framework includes a debug method on test cases
+ and test suites to support post-mortem debugging. The test code
+ is run in such a way that errors are not caught. This way a
+ caller can catch the errors and initiate post-mortem debugging.
+
+ The DocTestCase provides a debug method that raises
+ UnexpectedException errors if there is an unexepcted
+ exception:
+
+ >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
+ ... {}, 'foo', 'foo.py', 0)
+ >>> case = DocTestCase(test)
+ >>> try:
+ ... case.debug()
+ ... except UnexpectedException, failure:
+ ... pass
+
+ The UnexpectedException contains the test, the example, and
+ the original exception:
+
+ >>> failure.test is test
+ True
+
+ >>> failure.example.want
+ '42\n'
+
+ >>> exc_info = failure.exc_info
+ >>> raise exc_info[0], exc_info[1], exc_info[2]
+ Traceback (most recent call last):
+ ...
+ KeyError
+
+ If the output doesn't match, then a DocTestFailure is raised:
+
+ >>> test = DocTestParser().get_doctest('''
+ ... >>> x = 1
+ ... >>> x
+ ... 2
+ ... ''', {}, 'foo', 'foo.py', 0)
+ >>> case = DocTestCase(test)
+
+ >>> try:
+ ... case.debug()
+ ... except DocTestFailure, failure:
+ ... pass
+
+ DocTestFailure objects provide access to the test:
+
+ >>> failure.test is test
+ True
+
+ As well as to the example:
+
+ >>> failure.example.want
+ '2\n'
+
+ and the actual output:
+
+ >>> failure.got
+ '1\n'
+
+ """
+
+ self.setUp()
+ runner = DebugRunner(optionflags=self._dt_optionflags,
+ checker=self._dt_checker, verbose=False)
+ runner.run(self._dt_test)
+ self.tearDown()
+
+ def id(self):
+ return self._dt_test.name
+
+ def __repr__(self):
+ name = self._dt_test.name.split('.')
+ return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
+
+ __str__ = __repr__
+
+ def shortDescription(self):
+ return "Doctest: " + self._dt_test.name
+
+def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
+ test_class=DocTestCase, **options):
+ """
+ Convert doctest tests for a module to a unittest test suite.
+
+ This converts each documentation string in a module that
+ contains doctest tests to a unittest test case. If any of the
+ tests in a doc string fail, then the test case fails. An exception
+ is raised showing the name of the file containing the test and a
+ (sometimes approximate) line number.
+
+ The `module` argument provides the module to be tested. The argument
+ can be either a module or a module name.
+
+ If no argument is given, the calling module is used.
+
+ A number of options may be provided as keyword arguments:
+
+ setUp
+ A set-up function. This is called before running the
+ tests in each file. The setUp function will be passed a DocTest
+ object. The setUp function can access the test globals as the
+ globs attribute of the test passed.
+
+ tearDown
+ A tear-down function. This is called after running the
+ tests in each file. The tearDown function will be passed a DocTest
+ object. The tearDown function can access the test globals as the
+ globs attribute of the test passed.
+
+ globs
+ A dictionary containing initial global variables for the tests.
+
+ optionflags
+ A set of doctest option flags expressed as an integer.
+ """
+
+ if test_finder is None:
+ test_finder = DocTestFinder()
+
+ module = _normalize_module(module)
+ tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
+ if globs is None:
+ globs = module.__dict__
+ if not tests:
+ # Why do we want to do this? Because it reveals a bug that might
+ # otherwise be hidden.
+ raise ValueError(module, "has no tests")
+
+ tests.sort()
+ suite = unittest.TestSuite()
+ for test in tests:
+ if len(test.examples) == 0:
+ continue
+ if not test.filename:
+ filename = module.__file__
+ if filename[-4:] in (".pyc", ".pyo"):
+ filename = filename[:-1]
+ test.filename = filename
+ suite.addTest(test_class(test, **options))
+
+ return suite
+
+class DocFileCase(DocTestCase):
+
+ def id(self):
+ return '_'.join(self._dt_test.name.split('.'))
+
+ def __repr__(self):
+ return self._dt_test.filename
+ __str__ = __repr__
+
+ def format_failure(self, err):
+ return ('Failed doctest test for %s\n File "%s", line 0\n\n%s'
+ % (self._dt_test.name, self._dt_test.filename, err)
+ )
+
+def DocFileTest(path, module_relative=True, package=None,
+ globs=None, parser=DocTestParser(), **options):
+ if globs is None:
+ globs = {}
+
+ if package and not module_relative:
+ raise ValueError("Package may only be specified for module-"
+ "relative paths.")
+
+ # Relativize the path.
+ if module_relative:
+ package = _normalize_module(package)
+ path = _module_relative_path(package, path)
+
+ # Find the file and read it.
+ name = os.path.basename(path)
+ doc = open(path).read()
+
+ # Convert it to a test, and wrap it in a DocFileCase.
+ test = parser.get_doctest(doc, globs, name, path, 0)
+ return DocFileCase(test, **options)
+
+def DocFileSuite(*paths, **kw):
+ """A unittest suite for one or more doctest files.
+
+ The path to each doctest file is given as a string; the
+ interpretation of that string depends on the keyword argument
+ "module_relative".
+
+ A number of options may be provided as keyword arguments:
+
+ module_relative
+ If "module_relative" is True, then the given file paths are
+ interpreted as os-independent module-relative paths. By
+ default, these paths are relative to the calling module's
+ directory; but if the "package" argument is specified, then
+ they are relative to that package. To ensure os-independence,
+ "filename" should use "/" characters to separate path
+ segments, and may not be an absolute path (i.e., it may not
+ begin with "/").
+
+ If "module_relative" is False, then the given file paths are
+ interpreted as os-specific paths. These paths may be absolute
+ or relative (to the current working directory).
+
+ package
+ A Python package or the name of a Python package whose directory
+ should be used as the base directory for module relative paths.
+ If "package" is not specified, then the calling module's
+ directory is used as the base directory for module relative
+ filenames. It is an error to specify "package" if
+ "module_relative" is False.
+
+ setUp
+ A set-up function. This is called before running the
+ tests in each file. The setUp function will be passed a DocTest
+ object. The setUp function can access the test globals as the
+ globs attribute of the test passed.
+
+ tearDown
+ A tear-down function. This is called after running the
+ tests in each file. The tearDown function will be passed a DocTest
+ object. The tearDown function can access the test globals as the
+ globs attribute of the test passed.
+
+ globs
+ A dictionary containing initial global variables for the tests.
+
+ optionflags
+ A set of doctest option flags expressed as an integer.
+
+ parser
+ A DocTestParser (or subclass) that should be used to extract
+ tests from the files.
+ """
+ suite = unittest.TestSuite()
+
+ # We do this here so that _normalize_module is called at the right
+ # level. If it were called in DocFileTest, then this function
+ # would be the caller and we might guess the package incorrectly.
+ if kw.get('module_relative', True):
+ kw['package'] = _normalize_module(kw.get('package'))
+
+ for path in paths:
+ suite.addTest(DocFileTest(path, **kw))
+
+ return suite
+
+######################################################################
+## 9. Debugging Support
+######################################################################
+
+def script_from_examples(s):
+ r"""Extract script from text with examples.
+
+ Converts text with examples to a Python script. Example input is
+ converted to regular code. Example output and all other words
+ are converted to comments:
+
+ >>> text = '''
+ ... Here are examples of simple math.
+ ...
+ ... Python has super accurate integer addition
+ ...
+ ... >>> 2 + 2
+ ... 5
+ ...
+ ... And very friendly error messages:
+ ...
+ ... >>> 1/0
+ ... To Infinity
+ ... And
+ ... Beyond
+ ...
+ ... You can use logic if you want:
+ ...
+ ... >>> if 0:
+ ... ... blah
+ ... ... blah
+ ... ...
+ ...
+ ... Ho hum
+ ... '''
+
+ >>> print script_from_examples(text)
+ # Here are examples of simple math.
+ #
+ # Python has super accurate integer addition
+ #
+ 2 + 2
+ # Expected:
+ ## 5
+ #
+ # And very friendly error messages:
+ #
+ 1/0
+ # Expected:
+ ## To Infinity
+ ## And
+ ## Beyond
+ #
+ # You can use logic if you want:
+ #
+ if 0:
+ blah
+ blah
+ #
+ # Ho hum
+ """
+ output = []
+ for piece in DocTestParser().parse(s):
+ if isinstance(piece, Example):
+ # Add the example's source code (strip trailing NL)
+ output.append(piece.source[:-1])
+ # Add the expected output:
+ want = piece.want
+ if want:
+ output.append('# Expected:')
+ output += ['## '+l for l in want.split('\n')[:-1]]
+ else:
+ # Add non-example text.
+ output += [_comment_line(l)
+ for l in piece.split('\n')[:-1]]
+
+ # Trim junk on both ends.
+ while output and output[-1] == '#':
+ output.pop()
+ while output and output[0] == '#':
+ output.pop(0)
+ # Combine the output, and return it.
+ return '\n'.join(output)
+
+def testsource(module, name):
+ """Extract the test sources from a doctest docstring as a script.
+
+ Provide the module (or dotted name of the module) containing the
+ test to be debugged and the name (within the module) of the object
+ with the doc string with tests to be debugged.
+ """
+ module = _normalize_module(module)
+ tests = DocTestFinder().find(module)
+ test = [t for t in tests if t.name == name]
+ if not test:
+ raise ValueError(name, "not found in tests")
+ test = test[0]
+ testsrc = script_from_examples(test.docstring)
+ return testsrc
+
+def debug_src(src, pm=False, globs=None):
+ """Debug a single doctest docstring, in argument `src`'"""
+ testsrc = script_from_examples(src)
+ debug_script(testsrc, pm, globs)
+
+def debug_script(src, pm=False, globs=None):
+ "Debug a test script. `src` is the script, as a string."
+ import pdb
+
+ # Note that tempfile.NameTemporaryFile() cannot be used. As the
+ # docs say, a file so created cannot be opened by name a second time
+ # on modern Windows boxes, and execfile() needs to open it.
+ srcfilename = tempfile.mktemp(".py", "doctestdebug")
+ f = open(srcfilename, 'w')
+ f.write(src)
+ f.close()
+
+ try:
+ if globs:
+ globs = globs.copy()
+ else:
+ globs = {}
+
+ if pm:
+ try:
+ execfile(srcfilename, globs, globs)
+ except:
+ print sys.exc_info()[1]
+ pdb.post_mortem(sys.exc_info()[2])
+ else:
+ # Note that %r is vital here. '%s' instead can, e.g., cause
+ # backslashes to get treated as metacharacters on Windows.
+ pdb.run("execfile(%r)" % srcfilename, globs, globs)
+
+ finally:
+ os.remove(srcfilename)
+
+def debug(module, name, pm=False):
+ """Debug a single doctest docstring.
+
+ Provide the module (or dotted name of the module) containing the
+ test to be debugged and the name (within the module) of the object
+ with the docstring with tests to be debugged.
+ """
+ module = _normalize_module(module)
+ testsrc = testsource(module, name)
+ debug_script(testsrc, pm, module.__dict__)
+
+######################################################################
+## 10. Example Usage
+######################################################################
+class _TestClass:
+ """
+ A pointless class, for sanity-checking of docstring testing.
+
+ Methods:
+ square()
+ get()
+
+ >>> _TestClass(13).get() + _TestClass(-12).get()
+ 1
+ >>> hex(_TestClass(13).square().get())
+ '0xa9'
+ """
+
+ def __init__(self, val):
+ """val -> _TestClass object with associated value val.
+
+ >>> t = _TestClass(123)
+ >>> print t.get()
+ 123
+ """
+
+ self.val = val
+
+ def square(self):
+ """square() -> square TestClass's associated value
+
+ >>> _TestClass(13).square().get()
+ 169
+ """
+
+ self.val = self.val ** 2
+ return self
+
+ def get(self):
+ """get() -> return TestClass's associated value.
+
+ >>> x = _TestClass(-42)
+ >>> print x.get()
+ -42
+ """
+
+ return self.val
+
+__test__ = {"_TestClass": _TestClass,
+ "string": r"""
+ Example of a string object, searched as-is.
+ >>> x = 1; y = 2
+ >>> x + y, x * y
+ (3, 2)
+ """,
+
+ "bool-int equivalence": r"""
+ In 2.2, boolean expressions displayed
+ 0 or 1. By default, we still accept
+ them. This can be disabled by passing
+ DONT_ACCEPT_TRUE_FOR_1 to the new
+ optionflags argument.
+ >>> 4 == 4
+ 1
+ >>> 4 == 4
+ True
+ >>> 4 > 4
+ 0
+ >>> 4 > 4
+ False
+ """,
+
+ "blank lines": r"""
+ Blank lines can be marked with <BLANKLINE>:
+ >>> print 'foo\n\nbar\n'
+ foo
+ <BLANKLINE>
+ bar
+ <BLANKLINE>
+ """,
+
+ "ellipsis": r"""
+ If the ellipsis flag is used, then '...' can be used to
+ elide substrings in the desired output:
+ >>> print range(1000) #doctest: +ELLIPSIS
+ [0, 1, 2, ..., 999]
+ """,
+
+ "whitespace normalization": r"""
+ If the whitespace normalization flag is used, then
+ differences in whitespace are ignored.
+ >>> print range(30) #doctest: +NORMALIZE_WHITESPACE
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
+ 27, 28, 29]
+ """,
+ }
+
+def _test():
+ r = unittest.TextTestRunner()
+ r.run(DocTestSuite())
+
+if __name__ == "__main__":
+ _test()
diff --git a/google_appengine/lib/django/django/test/signals.py b/google_appengine/lib/django/django/test/signals.py
new file mode 100755
index 0000000..40748ff
--- /dev/null
+++ b/google_appengine/lib/django/django/test/signals.py
@@ -0,0 +1 @@
+template_rendered = object() \ No newline at end of file
diff --git a/google_appengine/lib/django/django/test/simple.py b/google_appengine/lib/django/django/test/simple.py
new file mode 100755
index 0000000..200f150
--- /dev/null
+++ b/google_appengine/lib/django/django/test/simple.py
@@ -0,0 +1,88 @@
+import unittest, doctest
+from django.conf import settings
+from django.test.utils import setup_test_environment, teardown_test_environment
+from django.test.utils import create_test_db, destroy_test_db
+from django.test.testcases import OutputChecker, DocTestRunner
+
+# The module name for tests outside models.py
+TEST_MODULE = 'tests'
+
+doctestOutputChecker = OutputChecker()
+
+def build_suite(app_module):
+ "Create a complete Django test suite for the provided application module"
+ suite = unittest.TestSuite()
+
+ # Load unit and doctests in the models.py file
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(app_module))
+ try:
+ suite.addTest(doctest.DocTestSuite(app_module,
+ checker=doctestOutputChecker,
+ runner=DocTestRunner))
+ except ValueError:
+ # No doc tests in models.py
+ pass
+
+ # Check to see if a separate 'tests' module exists parallel to the
+ # models module
+ try:
+ app_path = app_module.__name__.split('.')[:-1]
+ test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
+
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_module))
+ try:
+ suite.addTest(doctest.DocTestSuite(test_module,
+ checker=doctestOutputChecker,
+ runner=DocTestRunner))
+ except ValueError:
+ # No doc tests in tests.py
+ pass
+ except ImportError, e:
+ # Couldn't import tests.py. Was it due to a missing file, or
+ # due to an import error in a tests.py that actually exists?
+ import os.path
+ from imp import find_module
+ try:
+ mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
+ except ImportError:
+ # 'tests' module doesn't exist. Move on.
+ pass
+ else:
+ # The module exists, so there must be an import error in the
+ # test module itself. We don't need the module; close the file
+ # handle returned by find_module.
+ mod[0].close()
+ raise
+
+ return suite
+
+def run_tests(module_list, verbosity=1, extra_tests=[]):
+ """
+ Run the unit tests for all the modules in the provided list.
+ This testrunner will search each of the modules in the provided list,
+ looking for doctests and unittests in models.py or tests.py within
+ the module. A list of 'extra' tests may also be provided; these tests
+ will be added to the test suite.
+
+ Returns the number of tests that failed.
+ """
+ setup_test_environment()
+
+ settings.DEBUG = False
+ suite = unittest.TestSuite()
+
+ for module in module_list:
+ suite.addTest(build_suite(module))
+
+ for test in extra_tests:
+ suite.addTest(test)
+
+ old_name = settings.DATABASE_NAME
+ create_test_db(verbosity)
+ result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
+ destroy_test_db(old_name, verbosity)
+
+ teardown_test_environment()
+
+ return len(result.failures)
+ \ No newline at end of file
diff --git a/google_appengine/lib/django/django/test/testcases.py b/google_appengine/lib/django/django/test/testcases.py
new file mode 100755
index 0000000..2bfb9a7
--- /dev/null
+++ b/google_appengine/lib/django/django/test/testcases.py
@@ -0,0 +1,50 @@
+import re, doctest, unittest
+from django.db import transaction
+from django.core import management
+from django.db.models import get_apps
+
+normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
+
+class OutputChecker(doctest.OutputChecker):
+ def check_output(self, want, got, optionflags):
+ ok = doctest.OutputChecker.check_output(self, want, got, optionflags)
+
+ # Doctest does an exact string comparison of output, which means long
+ # integers aren't equal to normal integers ("22L" vs. "22"). The
+ # following code normalizes long integers so that they equal normal
+ # integers.
+ if not ok:
+ return normalize_long_ints(want) == normalize_long_ints(got)
+ return ok
+
+class DocTestRunner(doctest.DocTestRunner):
+ def __init__(self, *args, **kwargs):
+ doctest.DocTestRunner.__init__(self, *args, **kwargs)
+ self.optionflags = doctest.ELLIPSIS
+
+ def report_unexpected_exception(self, out, test, example, exc_info):
+ doctest.DocTestRunner.report_unexpected_exception(self,out,test,example,exc_info)
+
+ # Rollback, in case of database errors. Otherwise they'd have
+ # side effects on other tests.
+ from django.db import transaction
+ transaction.rollback_unless_managed()
+
+class TestCase(unittest.TestCase):
+ def install_fixtures(self):
+ """If the Test Case class has a 'fixtures' member, clear the database and
+ install the named fixtures at the start of each test.
+
+ """
+ management.flush(verbosity=0, interactive=False)
+ if hasattr(self, 'fixtures'):
+ management.load_data(self.fixtures, verbosity=0)
+
+ def run(self, result=None):
+ """Wrapper around default run method so that user-defined Test Cases
+ automatically call install_fixtures without having to include a call to
+ super().
+
+ """
+ self.install_fixtures()
+ super(TestCase, self).run(result)
diff --git a/google_appengine/lib/django/django/test/utils.py b/google_appengine/lib/django/django/test/utils.py
new file mode 100755
index 0000000..9939e36
--- /dev/null
+++ b/google_appengine/lib/django/django/test/utils.py
@@ -0,0 +1,110 @@
+import sys, time
+from django.conf import settings
+from django.db import connection, transaction, backend
+from django.core import management
+from django.dispatch import dispatcher
+from django.test import signals
+from django.template import Template
+
+# The prefix to put on the default database name when creating
+# the test database.
+TEST_DATABASE_PREFIX = 'test_'
+
+def instrumented_test_render(self, context):
+ """An instrumented Template render method, providing a signal
+ that can be intercepted by the test system Client
+
+ """
+ dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
+ return self.nodelist.render(context)
+
+def setup_test_environment():
+ """Perform any global pre-test setup. This involves:
+
+ - Installing the instrumented test renderer
+
+ """
+ Template.original_render = Template.render
+ Template.render = instrumented_test_render
+
+def teardown_test_environment():
+ """Perform any global post-test teardown. This involves:
+
+ - Restoring the original test renderer
+
+ """
+ Template.render = Template.original_render
+ del Template.original_render
+
+def _set_autocommit(connection):
+ "Make sure a connection is in autocommit mode."
+ if hasattr(connection.connection, "autocommit"):
+ connection.connection.autocommit(True)
+ elif hasattr(connection.connection, "set_isolation_level"):
+ connection.connection.set_isolation_level(0)
+
+def create_test_db(verbosity=1, autoclobber=False):
+ if verbosity >= 1:
+ print "Creating test database..."
+ # If we're using SQLite, it's more convenient to test against an
+ # in-memory database.
+ if settings.DATABASE_ENGINE == "sqlite3":
+ TEST_DATABASE_NAME = ":memory:"
+ else:
+ if settings.TEST_DATABASE_NAME:
+ TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
+ else:
+ TEST_DATABASE_NAME = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
+
+ # Create the test database and connect to it. We need to autocommit
+ # if the database supports it because PostgreSQL doesn't allow
+ # CREATE/DROP DATABASE statements within transactions.
+ cursor = connection.cursor()
+ _set_autocommit(connection)
+ try:
+ cursor.execute("CREATE DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
+ except Exception, e:
+ sys.stderr.write("Got an error creating the test database: %s\n" % e)
+ if not autoclobber:
+ confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
+ if autoclobber or confirm == 'yes':
+ try:
+ if verbosity >= 1:
+ print "Destroying old test database..."
+ cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
+ if verbosity >= 1:
+ print "Creating test database..."
+ cursor.execute("CREATE DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
+ except Exception, e:
+ sys.stderr.write("Got an error recreating the test database: %s\n" % e)
+ sys.exit(2)
+ else:
+ print "Tests cancelled."
+ sys.exit(1)
+
+ connection.close()
+ settings.DATABASE_NAME = TEST_DATABASE_NAME
+
+ management.syncdb(verbosity, interactive=False)
+
+ # Get a cursor (even though we don't need one yet). This has
+ # the side effect of initializing the test database.
+ cursor = connection.cursor()
+
+def destroy_test_db(old_database_name, verbosity=1):
+ # Unless we're using SQLite, remove the test database to clean up after
+ # ourselves. Connect to the previous database (not the test database)
+ # to do so, because it's not allowed to delete a database while being
+ # connected to it.
+ if verbosity >= 1:
+ print "Destroying test database..."
+ connection.close()
+ TEST_DATABASE_NAME = settings.DATABASE_NAME
+ settings.DATABASE_NAME = old_database_name
+
+ if settings.DATABASE_ENGINE != "sqlite3":
+ cursor = connection.cursor()
+ _set_autocommit(connection)
+ time.sleep(1) # To avoid "database is being accessed by other users" errors.
+ cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
+ connection.close()
diff --git a/google_appengine/lib/django/django/utils/__init__.py b/google_appengine/lib/django/django/utils/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/__init__.py
diff --git a/google_appengine/lib/django/django/utils/__init__.pyc b/google_appengine/lib/django/django/utils/__init__.pyc
new file mode 100644
index 0000000..9396240
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/_threading_local.py b/google_appengine/lib/django/django/utils/_threading_local.py
new file mode 100755
index 0000000..bf9a257
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/_threading_local.py
@@ -0,0 +1,240 @@
+"""Thread-local objects
+
+(Note that this module provides a Python version of thread
+ threading.local class. Depending on the version of Python you're
+ using, there may be a faster one available. You should always import
+ the local class from threading.)
+
+Thread-local objects support the management of thread-local data.
+If you have data that you want to be local to a thread, simply create
+a thread-local object and use its attributes:
+
+ >>> mydata = local()
+ >>> mydata.number = 42
+ >>> mydata.number
+ 42
+
+You can also access the local-object's dictionary:
+
+ >>> mydata.__dict__
+ {'number': 42}
+ >>> mydata.__dict__.setdefault('widgets', [])
+ []
+ >>> mydata.widgets
+ []
+
+What's important about thread-local objects is that their data are
+local to a thread. If we access the data in a different thread:
+
+ >>> log = []
+ >>> def f():
+ ... items = mydata.__dict__.items()
+ ... items.sort()
+ ... log.append(items)
+ ... mydata.number = 11
+ ... log.append(mydata.number)
+
+ >>> import threading
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+ >>> log
+ [[], 11]
+
+we get different data. Furthermore, changes made in the other thread
+don't affect data seen in this thread:
+
+ >>> mydata.number
+ 42
+
+Of course, values you get from a local object, including a __dict__
+attribute, are for whatever thread was current at the time the
+attribute was read. For that reason, you generally don't want to save
+these values across threads, as they apply only to the thread they
+came from.
+
+You can create custom local objects by subclassing the local class:
+
+ >>> class MyLocal(local):
+ ... number = 2
+ ... initialized = False
+ ... def __init__(self, **kw):
+ ... if self.initialized:
+ ... raise SystemError('__init__ called too many times')
+ ... self.initialized = True
+ ... self.__dict__.update(kw)
+ ... def squared(self):
+ ... return self.number ** 2
+
+This can be useful to support default values, methods and
+initialization. Note that if you define an __init__ method, it will be
+called each time the local object is used in a separate thread. This
+is necessary to initialize each thread's dictionary.
+
+Now if we create a local object:
+
+ >>> mydata = MyLocal(color='red')
+
+Now we have a default number:
+
+ >>> mydata.number
+ 2
+
+an initial color:
+
+ >>> mydata.color
+ 'red'
+ >>> del mydata.color
+
+And a method that operates on the data:
+
+ >>> mydata.squared()
+ 4
+
+As before, we can access the data in a separate thread:
+
+ >>> log = []
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+ >>> log
+ [[('color', 'red'), ('initialized', True)], 11]
+
+without affecting this thread's data:
+
+ >>> mydata.number
+ 2
+ >>> mydata.color
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'MyLocal' object has no attribute 'color'
+
+Note that subclasses can define slots, but they are not thread
+local. They are shared across threads:
+
+ >>> class MyLocal(local):
+ ... __slots__ = 'number'
+
+ >>> mydata = MyLocal()
+ >>> mydata.number = 42
+ >>> mydata.color = 'red'
+
+So, the separate thread:
+
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+
+affects what we see:
+
+ >>> mydata.number
+ 11
+
+>>> del mydata
+"""
+
+# Threading import is at end
+
+class _localbase(object):
+ __slots__ = '_local__key', '_local__args', '_local__lock'
+
+ def __new__(cls, *args, **kw):
+ self = object.__new__(cls)
+ key = '_local__key', 'thread.local.' + str(id(self))
+ object.__setattr__(self, '_local__key', key)
+ object.__setattr__(self, '_local__args', (args, kw))
+ object.__setattr__(self, '_local__lock', RLock())
+
+ if args or kw and (cls.__init__ is object.__init__):
+ raise TypeError("Initialization arguments are not supported")
+
+ # We need to create the thread dict in anticipation of
+ # __init__ being called, to make sure we don't call it
+ # again ourselves.
+ dict = object.__getattribute__(self, '__dict__')
+ currentThread().__dict__[key] = dict
+
+ return self
+
+def _patch(self):
+ key = object.__getattribute__(self, '_local__key')
+ d = currentThread().__dict__.get(key)
+ if d is None:
+ d = {}
+ currentThread().__dict__[key] = d
+ object.__setattr__(self, '__dict__', d)
+
+ # we have a new instance dict, so call out __init__ if we have
+ # one
+ cls = type(self)
+ if cls.__init__ is not object.__init__:
+ args, kw = object.__getattribute__(self, '_local__args')
+ cls.__init__(self, *args, **kw)
+ else:
+ object.__setattr__(self, '__dict__', d)
+
+class local(_localbase):
+
+ def __getattribute__(self, name):
+ lock = object.__getattribute__(self, '_local__lock')
+ lock.acquire()
+ try:
+ _patch(self)
+ return object.__getattribute__(self, name)
+ finally:
+ lock.release()
+
+ def __setattr__(self, name, value):
+ lock = object.__getattribute__(self, '_local__lock')
+ lock.acquire()
+ try:
+ _patch(self)
+ return object.__setattr__(self, name, value)
+ finally:
+ lock.release()
+
+ def __delattr__(self, name):
+ lock = object.__getattribute__(self, '_local__lock')
+ lock.acquire()
+ try:
+ _patch(self)
+ return object.__delattr__(self, name)
+ finally:
+ lock.release()
+
+
+ def __del__():
+ threading_enumerate = enumerate
+ __getattribute__ = object.__getattribute__
+
+ def __del__(self):
+ key = __getattribute__(self, '_local__key')
+
+ try:
+ threads = list(threading_enumerate())
+ except:
+ # if enumerate fails, as it seems to do during
+ # shutdown, we'll skip cleanup under the assumption
+ # that there is nothing to clean up
+ return
+
+ for thread in threads:
+ try:
+ __dict__ = thread.__dict__
+ except AttributeError:
+ # Thread is dying, rest in peace
+ continue
+
+ if key in __dict__:
+ try:
+ del __dict__[key]
+ except KeyError:
+ pass # didn't have anything in this thread
+
+ return __del__
+ __del__ = __del__()
+
+try:
+ from threading import currentThread, enumerate, RLock
+except ImportError:
+ from dummy_threading import currentThread, enumerate, RLock
diff --git a/google_appengine/lib/django/django/utils/autoreload.py b/google_appengine/lib/django/django/utils/autoreload.py
new file mode 100755
index 0000000..e05b7fa
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/autoreload.py
@@ -0,0 +1,94 @@
+# Autoreloading launcher.
+# Borrowed from Peter Hunt and the CherryPy project (http://www.cherrypy.org).
+# Some taken from Ian Bicking's Paste (http://pythonpaste.org/).
+#
+# Portions copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of the CherryPy Team 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.
+
+import os, sys, time
+
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
+
+# This import does nothing, but it's necessary to avoid some race conditions
+# in the threading module. See http://code.djangoproject.com/ticket/2330 .
+try:
+ import threading
+except ImportError:
+ pass
+
+
+RUN_RELOADER = True
+
+def reloader_thread():
+ mtimes = {}
+ win = (sys.platform == "win32")
+ while RUN_RELOADER:
+ for filename in filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())):
+ if filename.endswith(".pyc") or filename.endswith("*.pyo"):
+ filename = filename[:-1]
+ if not os.path.exists(filename):
+ continue # File might be in an egg, so it can't be reloaded.
+ stat = os.stat(filename)
+ mtime = stat.st_mtime
+ if win:
+ mtime -= stat.st_ctime
+ if filename not in mtimes:
+ mtimes[filename] = mtime
+ continue
+ if mtime != mtimes[filename]:
+ sys.exit(3) # force reload
+ time.sleep(1)
+
+def restart_with_reloader():
+ while True:
+ args = [sys.executable] + sys.argv
+ if sys.platform == "win32":
+ args = ['"%s"' % arg for arg in args]
+ new_environ = os.environ.copy()
+ new_environ["RUN_MAIN"] = 'true'
+ exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)
+ if exit_code != 3:
+ return exit_code
+
+def main(main_func, args=None, kwargs=None):
+ if os.environ.get("RUN_MAIN") == "true":
+ if args is None:
+ args = ()
+ if kwargs is None:
+ kwargs = {}
+ thread.start_new_thread(main_func, args, kwargs)
+ try:
+ reloader_thread()
+ except KeyboardInterrupt:
+ pass
+ else:
+ try:
+ sys.exit(restart_with_reloader())
+ except KeyboardInterrupt:
+ pass
diff --git a/google_appengine/lib/django/django/utils/cache.py b/google_appengine/lib/django/django/utils/cache.py
new file mode 100755
index 0000000..5eba302
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/cache.py
@@ -0,0 +1,166 @@
+"""
+This module contains helper functions for controlling caching. It does so by
+managing the "Vary" header of responses. It includes functions to patch the
+header of response objects directly and decorators that change functions to do
+that header-patching themselves.
+
+For information on the Vary header, see:
+
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
+
+Essentially, the "Vary" HTTP header defines which headers a cache should take
+into account when building its cache key. Requests with the same path but
+different header content for headers named in "Vary" need to get different
+cache keys to prevent delivery of wrong content.
+
+A example: i18n middleware would need to distinguish caches by the
+"Accept-language" header.
+"""
+
+import datetime, md5, re
+from django.conf import settings
+from django.core.cache import cache
+
+cc_delim_re = re.compile(r'\s*,\s*')
+
+def patch_cache_control(response, **kwargs):
+ """
+ This function patches the Cache-Control header by adding all
+ keyword arguments to it. The transformation is as follows:
+
+ * All keyword parameter names are turned to lowercase, and underscores
+ are converted to hyphens.
+ * If the value of a parameter is True (exactly True, not just a
+ true value), only the parameter name is added to the header.
+ * All other parameters are added with their value, after applying
+ str() to it.
+ """
+ def dictitem(s):
+ t = s.split('=',1)
+ if len(t) > 1:
+ return (t[0].lower().replace('-', '_'), t[1])
+ else:
+ return (t[0].lower().replace('-', '_'), True)
+
+ def dictvalue(t):
+ if t[1] == True:
+ return t[0]
+ else:
+ return t[0] + '=' + str(t[1])
+
+ if response.has_header('Cache-Control'):
+ cc = cc_delim_re.split(response['Cache-Control'])
+ cc = dict([dictitem(el) for el in cc])
+ else:
+ cc = {}
+ for (k,v) in kwargs.items():
+ cc[k.replace('_', '-')] = v
+ cc = ', '.join([dictvalue(el) for el in cc.items()])
+ response['Cache-Control'] = cc
+
+vary_delim_re = re.compile(r',\s*')
+
+def patch_response_headers(response, cache_timeout=None):
+ """
+ Adds some useful headers to the given HttpResponse object:
+ ETag, Last-Modified, Expires and Cache-Control
+
+ Each header is only added if it isn't already set.
+
+ cache_timeout is in seconds. The CACHE_MIDDLEWARE_SECONDS setting is used
+ by default.
+ """
+ if cache_timeout is None:
+ cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
+ now = datetime.datetime.utcnow()
+ if not response.has_header('ETag'):
+ response['ETag'] = md5.new(response.content).hexdigest()
+ if not response.has_header('Last-Modified'):
+ response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
+ if not response.has_header('Expires'):
+ expires = now + datetime.timedelta(0, cache_timeout)
+ response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
+ if cache_timeout < 0:
+ cache_timeout = 0 # Can't have max-age negative
+ patch_cache_control(response, max_age=cache_timeout)
+
+def add_never_cache_headers(response):
+ """
+ Add headers to a response to indicate that
+ a page should never be cached.
+ """
+ patch_response_headers(response, cache_timeout=-1)
+
+def patch_vary_headers(response, newheaders):
+ """
+ Adds (or updates) the "Vary" header in the given HttpResponse object.
+ newheaders is a list of header names that should be in "Vary". Existing
+ headers in "Vary" aren't removed.
+ """
+ # Note that we need to keep the original order intact, because cache
+ # implementations may rely on the order of the Vary contents in, say,
+ # computing an MD5 hash.
+ vary = []
+ if response.has_header('Vary'):
+ vary = vary_delim_re.split(response['Vary'])
+ oldheaders = dict([(el.lower(), 1) for el in vary])
+ for newheader in newheaders:
+ if not newheader.lower() in oldheaders:
+ vary.append(newheader)
+ response['Vary'] = ', '.join(vary)
+
+def _generate_cache_key(request, headerlist, key_prefix):
+ "Returns a cache key from the headers given in the header list."
+ ctx = md5.new()
+ for header in headerlist:
+ value = request.META.get(header, None)
+ if value is not None:
+ ctx.update(value)
+ return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, request.path, ctx.hexdigest())
+
+def get_cache_key(request, key_prefix=None):
+ """
+ Returns a cache key based on the request path. It can be used in the
+ request phase because it pulls the list of headers to take into account
+ from the global path registry and uses those to build a cache key to check
+ against.
+
+ If there is no headerlist stored, the page needs to be rebuilt, so this
+ function returns None.
+ """
+ if key_prefix is None:
+ key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+ cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
+ headerlist = cache.get(cache_key, None)
+ if headerlist is not None:
+ return _generate_cache_key(request, headerlist, key_prefix)
+ else:
+ return None
+
+def learn_cache_key(request, response, cache_timeout=None, key_prefix=None):
+ """
+ Learns what headers to take into account for some request path from the
+ response object. It stores those headers in a global path registry so that
+ later access to that path will know what headers to take into account
+ without building the response object itself. The headers are named in the
+ Vary header of the response, but we want to prevent response generation.
+
+ The list of headers to use for cache key generation is stored in the same
+ cache as the pages themselves. If the cache ages some data out of the
+ cache, this just means that we have to build the response once to get at
+ the Vary header and so at the list of headers to use for the cache key.
+ """
+ if key_prefix is None:
+ key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+ if cache_timeout is None:
+ cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
+ cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
+ if response.has_header('Vary'):
+ headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])]
+ cache.set(cache_key, headerlist, cache_timeout)
+ return _generate_cache_key(request, headerlist, key_prefix)
+ else:
+ # if there is no Vary header, we still need a cache key
+ # for the request.path
+ cache.set(cache_key, [], cache_timeout)
+ return _generate_cache_key(request, [], key_prefix)
diff --git a/google_appengine/lib/django/django/utils/daemonize.py b/google_appengine/lib/django/django/utils/daemonize.py
new file mode 100755
index 0000000..9671b8d
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/daemonize.py
@@ -0,0 +1,55 @@
+import os
+import sys
+
+if os.name == 'posix':
+ def become_daemon(our_home_dir='.', out_log='/dev/null', err_log='/dev/null'):
+ "Robustly turn into a UNIX daemon, running in our_home_dir."
+ # First fork
+ try:
+ if os.fork() > 0:
+ sys.exit(0) # kill off parent
+ except OSError, e:
+ sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
+ sys.exit(1)
+ os.setsid()
+ os.chdir(our_home_dir)
+ os.umask(0)
+
+ # Second fork
+ try:
+ if os.fork() > 0:
+ sys.exit(0)
+ except OSError, e:
+ sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
+ sys.exit(1)
+
+ si = open('/dev/null', 'r')
+ so = open(out_log, 'a+', 0)
+ se = open(err_log, 'a+', 0)
+ os.dup2(si.fileno(), sys.stdin.fileno())
+ os.dup2(so.fileno(), sys.stdout.fileno())
+ os.dup2(se.fileno(), sys.stderr.fileno())
+else:
+ def become_daemon(our_home_dir='.', out_log=None, err_log=None):
+ """
+ If we're not running under a POSIX system, just simulate the daemon
+ mode by doing redirections and directory changing.
+ """
+ os.chdir(our_home_dir)
+ os.umask(0)
+ sys.stdin.close()
+ sys.stdout.close()
+ sys.stderr.close()
+ if err_log:
+ sys.stderr = open(err_log, 'a', 0)
+ else:
+ sys.stderr = NullDevice()
+ if out_log:
+ sys.stdout = open(out_log, 'a', 0)
+ else:
+ sys.stdout = NullDevice()
+
+ class NullDevice:
+ "A writeable object that writes to nowhere -- like /dev/null."
+ def write(self, s):
+ pass
diff --git a/google_appengine/lib/django/django/utils/datastructures.py b/google_appengine/lib/django/django/utils/datastructures.py
new file mode 100755
index 0000000..7b7fa2b
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/datastructures.py
@@ -0,0 +1,262 @@
+class MergeDict(object):
+ """
+ A simple class for creating new "virtual" dictionaries that actualy look
+ up values in more than one dictionary, passed in the constructor.
+ """
+ def __init__(self, *dicts):
+ self.dicts = dicts
+
+ def __getitem__(self, key):
+ for dict in self.dicts:
+ try:
+ return dict[key]
+ except KeyError:
+ pass
+ raise KeyError
+
+ def __contains__(self, key):
+ return self.has_key(key)
+
+ def __copy__(self):
+ return self.__class__(*self.dicts)
+
+ def get(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def getlist(self, key):
+ for dict in self.dicts:
+ try:
+ return dict.getlist(key)
+ except KeyError:
+ pass
+ raise KeyError
+
+ def items(self):
+ item_list = []
+ for dict in self.dicts:
+ item_list.extend(dict.items())
+ return item_list
+
+ def has_key(self, key):
+ for dict in self.dicts:
+ if dict.has_key(key):
+ return True
+ return False
+
+ def copy(self):
+ """ returns a copy of this object"""
+ return self.__copy__()
+
+class SortedDict(dict):
+ "A dictionary that keeps its keys in the order in which they're inserted."
+ def __init__(self, data=None):
+ if data is None: data = {}
+ dict.__init__(self, data)
+ self.keyOrder = data.keys()
+
+ def __setitem__(self, key, value):
+ dict.__setitem__(self, key, value)
+ if key not in self.keyOrder:
+ self.keyOrder.append(key)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+ self.keyOrder.remove(key)
+
+ def __iter__(self):
+ for k in self.keyOrder:
+ yield k
+
+ def items(self):
+ return zip(self.keyOrder, self.values())
+
+ def keys(self):
+ return self.keyOrder[:]
+
+ def values(self):
+ return [dict.__getitem__(self, k) for k in self.keyOrder]
+
+ def update(self, dict):
+ for k, v in dict.items():
+ self.__setitem__(k, v)
+
+ def setdefault(self, key, default):
+ if key not in self.keyOrder:
+ self.keyOrder.append(key)
+ return dict.setdefault(self, key, default)
+
+ def value_for_index(self, index):
+ "Returns the value of the item at the given zero-based index."
+ return self[self.keyOrder[index]]
+
+ def copy(self):
+ "Returns a copy of this object."
+ # This way of initializing the copy means it works for subclasses, too.
+ obj = self.__class__(self)
+ obj.keyOrder = self.keyOrder
+ return obj
+
+class MultiValueDictKeyError(KeyError):
+ pass
+
+class MultiValueDict(dict):
+ """
+ A subclass of dictionary customized to handle multiple values for the same key.
+
+ >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
+ >>> d['name']
+ 'Simon'
+ >>> d.getlist('name')
+ ['Adrian', 'Simon']
+ >>> d.get('lastname', 'nonexistent')
+ 'nonexistent'
+ >>> d.setlist('lastname', ['Holovaty', 'Willison'])
+
+ This class exists to solve the irritating problem raised by cgi.parse_qs,
+ which returns a list for every key, even though most Web forms submit
+ single name-value pairs.
+ """
+ def __init__(self, key_to_list_mapping=()):
+ dict.__init__(self, key_to_list_mapping)
+
+ def __repr__(self):
+ return "<MultiValueDict: %s>" % dict.__repr__(self)
+
+ def __getitem__(self, key):
+ """
+ Returns the last data value for this key, or [] if it's an empty list;
+ raises KeyError if not found.
+ """
+ try:
+ list_ = dict.__getitem__(self, key)
+ except KeyError:
+ raise MultiValueDictKeyError, "Key %r not found in %r" % (key, self)
+ try:
+ return list_[-1]
+ except IndexError:
+ return []
+
+ def __setitem__(self, key, value):
+ dict.__setitem__(self, key, [value])
+
+ def __copy__(self):
+ return self.__class__(dict.items(self))
+
+ def __deepcopy__(self, memo=None):
+ import copy
+ if memo is None: memo = {}
+ result = self.__class__()
+ memo[id(self)] = result
+ for key, value in dict.items(self):
+ dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo))
+ return result
+
+ def get(self, key, default=None):
+ "Returns the default value if the requested data doesn't exist"
+ try:
+ val = self[key]
+ except KeyError:
+ return default
+ if val == []:
+ return default
+ return val
+
+ def getlist(self, key):
+ "Returns an empty list if the requested data doesn't exist"
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ return []
+
+ def setlist(self, key, list_):
+ dict.__setitem__(self, key, list_)
+
+ def setdefault(self, key, default=None):
+ if key not in self:
+ self[key] = default
+ return self[key]
+
+ def setlistdefault(self, key, default_list=()):
+ if key not in self:
+ self.setlist(key, default_list)
+ return self.getlist(key)
+
+ def appendlist(self, key, value):
+ "Appends an item to the internal list associated with key"
+ self.setlistdefault(key, [])
+ dict.__setitem__(self, key, self.getlist(key) + [value])
+
+ def items(self):
+ """
+ Returns a list of (key, value) pairs, where value is the last item in
+ the list associated with the key.
+ """
+ return [(key, self[key]) for key in self.keys()]
+
+ def lists(self):
+ "Returns a list of (key, list) pairs."
+ return dict.items(self)
+
+ def values(self):
+ "Returns a list of the last value on every key list."
+ return [self[key] for key in self.keys()]
+
+ def copy(self):
+ "Returns a copy of this object."
+ return self.__deepcopy__()
+
+ def update(self, *args, **kwargs):
+ "update() extends rather than replaces existing key lists. Also accepts keyword args."
+ if len(args) > 1:
+ raise TypeError, "update expected at most 1 arguments, got %d", len(args)
+ if args:
+ other_dict = args[0]
+ if isinstance(other_dict, MultiValueDict):
+ for key, value_list in other_dict.lists():
+ self.setlistdefault(key, []).extend(value_list)
+ else:
+ try:
+ for key, value in other_dict.items():
+ self.setlistdefault(key, []).append(value)
+ except TypeError:
+ raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
+ for key, value in kwargs.iteritems():
+ self.setlistdefault(key, []).append(value)
+
+class DotExpandedDict(dict):
+ """
+ A special dictionary constructor that takes a dictionary in which the keys
+ may contain dots to specify inner dictionaries. It's confusing, but this
+ example should make sense.
+
+ >>> d = DotExpandedDict({'person.1.firstname': ['Simon'],
+ 'person.1.lastname': ['Willison'],
+ 'person.2.firstname': ['Adrian'],
+ 'person.2.lastname': ['Holovaty']})
+ >>> d
+ {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']},
+ '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
+ >>> d['person']
+ {'1': {'firstname': ['Simon'], 'lastname': ['Willison'],
+ '2': {'firstname': ['Adrian'], 'lastname': ['Holovaty']}
+ >>> d['person']['1']
+ {'firstname': ['Simon'], 'lastname': ['Willison']}
+
+ # Gotcha: Results are unpredictable if the dots are "uneven":
+ >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
+ >>> {'c': 1}
+ """
+ def __init__(self, key_to_list_mapping):
+ for k, v in key_to_list_mapping.items():
+ current = self
+ bits = k.split('.')
+ for bit in bits[:-1]:
+ current = current.setdefault(bit, {})
+ # Now assign value to current position
+ try:
+ current[bits[-1]] = v
+ except TypeError: # Special-case if current isn't a dict.
+ current = {bits[-1] : v}
diff --git a/google_appengine/lib/django/django/utils/dateformat.py b/google_appengine/lib/django/django/utils/dateformat.py
new file mode 100755
index 0000000..a558e3a
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/dateformat.py
@@ -0,0 +1,262 @@
+"""
+PHP date() style date formatting
+See http://www.php.net/date for format strings
+
+Usage:
+>>> import datetime
+>>> d = datetime.datetime.now()
+>>> df = DateFormat(d)
+>>> print df.format('jS F Y H:i')
+7th October 2003 11:39
+>>>
+"""
+
+from django.utils.dates import MONTHS, MONTHS_3, MONTHS_AP, WEEKDAYS
+from django.utils.tzinfo import LocalTimezone
+from django.utils.translation import gettext as _
+from calendar import isleap, monthrange
+import re, time
+
+re_formatchars = re.compile(r'(?<!\\)([aAbBdDfFgGhHiIjlLmMnNOPrsStTUwWyYzZ])')
+re_escaped = re.compile(r'\\(.)')
+
+class Formatter(object):
+ def format(self, formatstr):
+ pieces = []
+ for i, piece in enumerate(re_formatchars.split(formatstr)):
+ if i % 2:
+ pieces.append(str(getattr(self, piece)()))
+ elif piece:
+ pieces.append(re_escaped.sub(r'\1', piece))
+ return ''.join(pieces)
+
+class TimeFormat(Formatter):
+ def __init__(self, t):
+ self.data = t
+
+ def a(self):
+ "'a.m.' or 'p.m.'"
+ if self.data.hour > 11:
+ return _('p.m.')
+ return _('a.m.')
+
+ def A(self):
+ "'AM' or 'PM'"
+ if self.data.hour > 11:
+ return _('PM')
+ return _('AM')
+
+ def B(self):
+ "Swatch Internet time"
+ raise NotImplementedError
+
+ def f(self):
+ """
+ Time, in 12-hour hours and minutes, with minutes left off if they're zero.
+ Examples: '1', '1:30', '2:05', '2'
+ Proprietary extension.
+ """
+ if self.data.minute == 0:
+ return self.g()
+ return '%s:%s' % (self.g(), self.i())
+
+ def g(self):
+ "Hour, 12-hour format without leading zeros; i.e. '1' to '12'"
+ if self.data.hour == 0:
+ return 12
+ if self.data.hour > 12:
+ return self.data.hour - 12
+ return self.data.hour
+
+ def G(self):
+ "Hour, 24-hour format without leading zeros; i.e. '0' to '23'"
+ return self.data.hour
+
+ def h(self):
+ "Hour, 12-hour format; i.e. '01' to '12'"
+ return '%02d' % self.g()
+
+ def H(self):
+ "Hour, 24-hour format; i.e. '00' to '23'"
+ return '%02d' % self.G()
+
+ def i(self):
+ "Minutes; i.e. '00' to '59'"
+ return '%02d' % self.data.minute
+
+ def P(self):
+ """
+ Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
+ if they're zero and the strings 'midnight' and 'noon' if appropriate.
+ Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
+ Proprietary extension.
+ """
+ if self.data.minute == 0 and self.data.hour == 0:
+ return _('midnight')
+ if self.data.minute == 0 and self.data.hour == 12:
+ return _('noon')
+ return '%s %s' % (self.f(), self.a())
+
+ def s(self):
+ "Seconds; i.e. '00' to '59'"
+ return '%02d' % self.data.second
+
+class DateFormat(TimeFormat):
+ year_days = [None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
+
+ def __init__(self, dt):
+ # Accepts either a datetime or date object.
+ self.data = dt
+ self.timezone = getattr(dt, 'tzinfo', None)
+ if hasattr(self.data, 'hour') and not self.timezone:
+ self.timezone = LocalTimezone(dt)
+
+ def b(self):
+ "Month, textual, 3 letters, lowercase; e.g. 'jan'"
+ return MONTHS_3[self.data.month]
+
+ def d(self):
+ "Day of the month, 2 digits with leading zeros; i.e. '01' to '31'"
+ return '%02d' % self.data.day
+
+ def D(self):
+ "Day of the week, textual, 3 letters; e.g. 'Fri'"
+ return WEEKDAYS[self.data.weekday()][0:3]
+
+ def F(self):
+ "Month, textual, long; e.g. 'January'"
+ return MONTHS[self.data.month]
+
+ def I(self):
+ "'1' if Daylight Savings Time, '0' otherwise."
+ if self.timezone.dst(self.data):
+ return '1'
+ else:
+ return '0'
+
+ def j(self):
+ "Day of the month without leading zeros; i.e. '1' to '31'"
+ return self.data.day
+
+ def l(self):
+ "Day of the week, textual, long; e.g. 'Friday'"
+ return WEEKDAYS[self.data.weekday()]
+
+ def L(self):
+ "Boolean for whether it is a leap year; i.e. True or False"
+ return isleap(self.data.year)
+
+ def m(self):
+ "Month; i.e. '01' to '12'"
+ return '%02d' % self.data.month
+
+ def M(self):
+ "Month, textual, 3 letters; e.g. 'Jan'"
+ return MONTHS_3[self.data.month].title()
+
+ def n(self):
+ "Month without leading zeros; i.e. '1' to '12'"
+ return self.data.month
+
+ def N(self):
+ "Month abbreviation in Associated Press style. Proprietary extension."
+ return MONTHS_AP[self.data.month]
+
+ def O(self):
+ "Difference to Greenwich time in hours; e.g. '+0200'"
+ tz = self.timezone.utcoffset(self.data)
+ return "%+03d%02d" % (tz.seconds // 3600, (tz.seconds // 60) % 60)
+
+ def r(self):
+ "RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
+ return self.format('D, j M Y H:i:s O')
+
+ def S(self):
+ "English ordinal suffix for the day of the month, 2 characters; i.e. 'st', 'nd', 'rd' or 'th'"
+ if self.data.day in (11, 12, 13): # Special case
+ return 'th'
+ last = self.data.day % 10
+ if last == 1:
+ return 'st'
+ if last == 2:
+ return 'nd'
+ if last == 3:
+ return 'rd'
+ return 'th'
+
+ def t(self):
+ "Number of days in the given month; i.e. '28' to '31'"
+ return '%02d' % monthrange(self.data.year, self.data.month)[1]
+
+ def T(self):
+ "Time zone of this machine; e.g. 'EST' or 'MDT'"
+ name = self.timezone.tzname(self.data)
+ if name is None:
+ name = self.format('O')
+ return name
+
+ def U(self):
+ "Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
+ off = self.timezone.utcoffset(self.data)
+ return int(time.mktime(self.data.timetuple())) + off.seconds * 60
+
+ def w(self):
+ "Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)"
+ return (self.data.weekday() + 1) % 7
+
+ def W(self):
+ "ISO-8601 week number of year, weeks starting on Monday"
+ # Algorithm from http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt
+ week_number = None
+ jan1_weekday = self.data.replace(month=1, day=1).weekday() + 1
+ weekday = self.data.weekday() + 1
+ day_of_year = self.z()
+ if day_of_year <= (8 - jan1_weekday) and jan1_weekday > 4:
+ if jan1_weekday == 5 or (jan1_weekday == 6 and isleap(self.data.year-1)):
+ week_number = 53
+ else:
+ week_number = 52
+ else:
+ if isleap(self.data.year):
+ i = 366
+ else:
+ i = 365
+ if (i - day_of_year) < (4 - weekday):
+ week_number = 1
+ else:
+ j = day_of_year + (7 - weekday) + (jan1_weekday - 1)
+ week_number = j / 7
+ if jan1_weekday > 4:
+ week_number -= 1
+ return week_number
+
+ def y(self):
+ "Year, 2 digits; e.g. '99'"
+ return str(self.data.year)[2:]
+
+ def Y(self):
+ "Year, 4 digits; e.g. '1999'"
+ return self.data.year
+
+ def z(self):
+ "Day of the year; i.e. '0' to '365'"
+ doy = self.year_days[self.data.month] + self.data.day
+ if self.L() and self.data.month > 2:
+ doy += 1
+ return doy
+
+ def Z(self):
+ """Time zone offset in seconds (i.e. '-43200' to '43200'). The offset
+ for timezones west of UTC is always negative, and for those east of UTC
+ is always positive."""
+ return self.timezone.utcoffset(self.data).seconds
+
+def format(value, format_string):
+ "Convenience function"
+ df = DateFormat(value)
+ return df.format(format_string)
+
+def time_format(value, format_string):
+ "Convenience function"
+ tf = TimeFormat(value)
+ return tf.format(format_string)
diff --git a/google_appengine/lib/django/django/utils/dates.py b/google_appengine/lib/django/django/utils/dates.py
new file mode 100755
index 0000000..111f32e
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/dates.py
@@ -0,0 +1,29 @@
+"Commonly-used date structures"
+
+from django.utils.translation import gettext_lazy as _
+
+WEEKDAYS = {
+ 0:_('Monday'), 1:_('Tuesday'), 2:_('Wednesday'), 3:_('Thursday'), 4:_('Friday'),
+ 5:_('Saturday'), 6:_('Sunday')
+}
+WEEKDAYS_REV = {
+ 'monday':0, 'tuesday':1, 'wednesday':2, 'thursday':3, 'friday':4,
+ 'saturday':5, 'sunday':6
+}
+MONTHS = {
+ 1:_('January'), 2:_('February'), 3:_('March'), 4:_('April'), 5:_('May'), 6:_('June'),
+ 7:_('July'), 8:_('August'), 9:_('September'), 10:_('October'), 11:_('November'),
+ 12:_('December')
+}
+MONTHS_3 = {
+ 1:_('jan'), 2:_('feb'), 3:_('mar'), 4:_('apr'), 5:_('may'), 6:_('jun'),
+ 7:_('jul'), 8:_('aug'), 9:_('sep'), 10:_('oct'), 11:_('nov'), 12:_('dec')
+}
+MONTHS_3_REV = {
+ 'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, 'jul':7, 'aug':8,
+ 'sep':9, 'oct':10, 'nov':11, 'dec':12
+}
+MONTHS_AP = { # month names in Associated Press style
+ 1:_('Jan.'), 2:_('Feb.'), 3:_('March'), 4:_('April'), 5:_('May'), 6:_('June'), 7:_('July'),
+ 8:_('Aug.'), 9:_('Sept.'), 10:_('Oct.'), 11:_('Nov.'), 12:_('Dec.')
+}
diff --git a/google_appengine/lib/django/django/utils/decorators.py b/google_appengine/lib/django/django/utils/decorators.py
new file mode 100755
index 0000000..1c6cc8c
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/decorators.py
@@ -0,0 +1,33 @@
+"Functions that help with dynamically creating decorators for views."
+
+def decorator_from_middleware(middleware_class):
+ """
+ Given a middleware class (not an instance), returns a view decorator. This
+ lets you use middleware functionality on a per-view basis.
+ """
+ def _decorator_from_middleware(view_func, *args, **kwargs):
+ middleware = middleware_class(*args, **kwargs)
+ def _wrapped_view(request, *args, **kwargs):
+ if hasattr(middleware, 'process_request'):
+ result = middleware.process_request(request)
+ if result is not None:
+ return result
+ if hasattr(middleware, 'process_view'):
+ result = middleware.process_view(request, view_func, *args, **kwargs)
+ if result is not None:
+ return result
+ try:
+ response = view_func(request, *args, **kwargs)
+ except Exception, e:
+ if hasattr(middleware, 'process_exception'):
+ result = middleware.process_exception(request, e)
+ if result is not None:
+ return result
+ raise
+ if hasattr(middleware, 'process_response'):
+ result = middleware.process_response(request, response)
+ if result is not None:
+ return result
+ return response
+ return _wrapped_view
+ return _decorator_from_middleware
diff --git a/google_appengine/lib/django/django/utils/feedgenerator.py b/google_appengine/lib/django/django/utils/feedgenerator.py
new file mode 100755
index 0000000..9397789
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/feedgenerator.py
@@ -0,0 +1,273 @@
+"""
+Syndication feed generation library -- used for generating RSS, etc.
+
+Sample usage:
+
+>>> feed = feedgenerator.Rss201rev2Feed(
+... title=u"Poynter E-Media Tidbits",
+... link=u"http://www.poynter.org/column.asp?id=31",
+... description=u"A group weblog by the sharpest minds in online media/journalism/publishing.",
+... language=u"en",
+... )
+>>> feed.add_item(title="Hello", link=u"http://www.holovaty.com/test/", description="Testing.")
+>>> fp = open('test.rss', 'w')
+>>> feed.write(fp, 'utf-8')
+>>> fp.close()
+
+For definitions of the different versions of RSS, see:
+http://diveintomark.org/archives/2004/02/04/incompatible-rss
+"""
+
+from django.utils.xmlutils import SimplerXMLGenerator
+import datetime, re, time
+import email.Utils
+
+def rfc2822_date(date):
+ return email.Utils.formatdate(time.mktime(date.timetuple()))
+
+def rfc3339_date(date):
+ return date.strftime('%Y-%m-%dT%H:%M:%SZ')
+
+def get_tag_uri(url, date):
+ "Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
+ tag = re.sub('^http://', '', url)
+ if date is not None:
+ tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
+ tag = re.sub('#', '/', tag)
+ return 'tag:' + tag
+
+class SyndicationFeed(object):
+ "Base class for all syndication feeds. Subclasses should provide write()"
+ def __init__(self, title, link, description, language=None, author_email=None,
+ author_name=None, author_link=None, subtitle=None, categories=None,
+ feed_url=None, feed_copyright=None):
+ self.feed = {
+ 'title': title,
+ 'link': link,
+ 'description': description,
+ 'language': language,
+ 'author_email': author_email,
+ 'author_name': author_name,
+ 'author_link': author_link,
+ 'subtitle': subtitle,
+ 'categories': categories or (),
+ 'feed_url': feed_url,
+ 'feed_copyright': feed_copyright,
+ }
+ self.items = []
+
+ def add_item(self, title, link, description, author_email=None,
+ author_name=None, author_link=None, pubdate=None, comments=None,
+ unique_id=None, enclosure=None, categories=(), item_copyright=None):
+ """
+ Adds an item to the feed. All args are expected to be Python Unicode
+ objects except pubdate, which is a datetime.datetime object, and
+ enclosure, which is an instance of the Enclosure class.
+ """
+ self.items.append({
+ 'title': title,
+ 'link': link,
+ 'description': description,
+ 'author_email': author_email,
+ 'author_name': author_name,
+ 'author_link': author_link,
+ 'pubdate': pubdate,
+ 'comments': comments,
+ 'unique_id': unique_id,
+ 'enclosure': enclosure,
+ 'categories': categories or (),
+ 'item_copyright': item_copyright,
+ })
+
+ def num_items(self):
+ return len(self.items)
+
+ def write(self, outfile, encoding):
+ """
+ Outputs the feed in the given encoding to outfile, which is a file-like
+ object. Subclasses should override this.
+ """
+ raise NotImplementedError
+
+ def writeString(self, encoding):
+ """
+ Returns the feed in the given encoding as a string.
+ """
+ from StringIO import StringIO
+ s = StringIO()
+ self.write(s, encoding)
+ return s.getvalue()
+
+ def latest_post_date(self):
+ """
+ Returns the latest item's pubdate. If none of them have a pubdate,
+ this returns the current date/time.
+ """
+ updates = [i['pubdate'] for i in self.items if i['pubdate'] is not None]
+ if len(updates) > 0:
+ updates.sort()
+ return updates[-1]
+ else:
+ return datetime.datetime.now()
+
+class Enclosure(object):
+ "Represents an RSS enclosure"
+ def __init__(self, url, length, mime_type):
+ "All args are expected to be Python Unicode objects"
+ self.url, self.length, self.mime_type = url, length, mime_type
+
+class RssFeed(SyndicationFeed):
+ mime_type = 'application/rss+xml'
+ def write(self, outfile, encoding):
+ handler = SimplerXMLGenerator(outfile, encoding)
+ handler.startDocument()
+ handler.startElement(u"rss", {u"version": self._version})
+ handler.startElement(u"channel", {})
+ handler.addQuickElement(u"title", self.feed['title'])
+ handler.addQuickElement(u"link", self.feed['link'])
+ handler.addQuickElement(u"description", self.feed['description'])
+ if self.feed['language'] is not None:
+ handler.addQuickElement(u"language", self.feed['language'])
+ for cat in self.feed['categories']:
+ handler.addQuickElement(u"category", cat)
+ if self.feed['feed_copyright'] is not None:
+ handler.addQuickElement(u"copyright", self.feed['feed_copyright'])
+ self.write_items(handler)
+ self.endChannelElement(handler)
+ handler.endElement(u"rss")
+
+ def endChannelElement(self, handler):
+ handler.endElement(u"channel")
+
+class RssUserland091Feed(RssFeed):
+ _version = u"0.91"
+ def write_items(self, handler):
+ for item in self.items:
+ handler.startElement(u"item", {})
+ handler.addQuickElement(u"title", item['title'])
+ handler.addQuickElement(u"link", item['link'])
+ if item['description'] is not None:
+ handler.addQuickElement(u"description", item['description'])
+ handler.endElement(u"item")
+
+class Rss201rev2Feed(RssFeed):
+ # Spec: http://blogs.law.harvard.edu/tech/rss
+ _version = u"2.0"
+ def write_items(self, handler):
+ for item in self.items:
+ handler.startElement(u"item", {})
+ handler.addQuickElement(u"title", item['title'])
+ handler.addQuickElement(u"link", item['link'])
+ if item['description'] is not None:
+ handler.addQuickElement(u"description", item['description'])
+
+ # Author information.
+ if item["author_name"] and item["author_email"]:
+ handler.addQuickElement(u"author", "%s (%s)" % \
+ (item['author_email'], item['author_name']))
+ elif item["author_email"]:
+ handler.addQuickElement(u"author", item["author_email"])
+
+ if item['pubdate'] is not None:
+ handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('ascii'))
+ if item['comments'] is not None:
+ handler.addQuickElement(u"comments", item['comments'])
+ if item['unique_id'] is not None:
+ handler.addQuickElement(u"guid", item['unique_id'])
+
+ # Enclosure.
+ if item['enclosure'] is not None:
+ handler.addQuickElement(u"enclosure", '',
+ {u"url": item['enclosure'].url, u"length": item['enclosure'].length,
+ u"type": item['enclosure'].mime_type})
+
+ # Categories.
+ for cat in item['categories']:
+ handler.addQuickElement(u"category", cat)
+
+ handler.endElement(u"item")
+
+class Atom1Feed(SyndicationFeed):
+ # Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html
+ mime_type = 'application/atom+xml'
+ ns = u"http://www.w3.org/2005/Atom"
+ def write(self, outfile, encoding):
+ handler = SimplerXMLGenerator(outfile, encoding)
+ handler.startDocument()
+ if self.feed['language'] is not None:
+ handler.startElement(u"feed", {u"xmlns": self.ns, u"xml:lang": self.feed['language']})
+ else:
+ handler.startElement(u"feed", {u"xmlns": self.ns})
+ handler.addQuickElement(u"title", self.feed['title'])
+ handler.addQuickElement(u"link", "", {u"rel": u"alternate", u"href": self.feed['link']})
+ if self.feed['feed_url'] is not None:
+ handler.addQuickElement(u"link", "", {u"rel": u"self", u"href": self.feed['feed_url']})
+ handler.addQuickElement(u"id", self.feed['link'])
+ handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('ascii'))
+ if self.feed['author_name'] is not None:
+ handler.startElement(u"author", {})
+ handler.addQuickElement(u"name", self.feed['author_name'])
+ if self.feed['author_email'] is not None:
+ handler.addQuickElement(u"email", self.feed['author_email'])
+ if self.feed['author_link'] is not None:
+ handler.addQuickElement(u"uri", self.feed['author_link'])
+ handler.endElement(u"author")
+ if self.feed['subtitle'] is not None:
+ handler.addQuickElement(u"subtitle", self.feed['subtitle'])
+ for cat in self.feed['categories']:
+ handler.addQuickElement(u"category", "", {u"term": cat})
+ if self.feed['feed_copyright'] is not None:
+ handler.addQuickElement(u"rights", self.feed['feed_copyright'])
+ self.write_items(handler)
+ handler.endElement(u"feed")
+
+ def write_items(self, handler):
+ for item in self.items:
+ handler.startElement(u"entry", {})
+ handler.addQuickElement(u"title", item['title'])
+ handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
+ if item['pubdate'] is not None:
+ handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('ascii'))
+
+ # Author information.
+ if item['author_name'] is not None:
+ handler.startElement(u"author", {})
+ handler.addQuickElement(u"name", item['author_name'])
+ if item['author_email'] is not None:
+ handler.addQuickElement(u"email", item['author_email'])
+ if item['author_link'] is not None:
+ handler.addQuickElement(u"uri", item['author_link'])
+ handler.endElement(u"author")
+
+ # Unique ID.
+ if item['unique_id'] is not None:
+ unique_id = item['unique_id']
+ else:
+ unique_id = get_tag_uri(item['link'], item['pubdate'])
+ handler.addQuickElement(u"id", unique_id)
+
+ # Summary.
+ if item['description'] is not None:
+ handler.addQuickElement(u"summary", item['description'], {u"type": u"html"})
+
+ # Enclosure.
+ if item['enclosure'] is not None:
+ handler.addQuickElement(u"link", '',
+ {u"rel": u"enclosure",
+ u"href": item['enclosure'].url,
+ u"length": item['enclosure'].length,
+ u"type": item['enclosure'].mime_type})
+
+ # Categories.
+ for cat in item['categories']:
+ handler.addQuickElement(u"category", u"", {u"term": cat})
+
+ # Rights.
+ if item['item_copyright'] is not None:
+ handler.addQuickElement(u"rights", item['item_copyright'])
+
+ handler.endElement(u"entry")
+
+# This isolates the decision of what the system default is, so calling code can
+# do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".
+DefaultFeed = Rss201rev2Feed
diff --git a/google_appengine/lib/django/django/utils/functional.py b/google_appengine/lib/django/django/utils/functional.py
new file mode 100755
index 0000000..e3c0a3c
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/functional.py
@@ -0,0 +1,54 @@
+def curry(_curried_func, *args, **kwargs):
+ def _curried(*moreargs, **morekwargs):
+ return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
+ return _curried
+
+class Promise:
+ """
+ This is just a base class for the proxy class created in
+ the closure of the lazy function. It can be used to recognize
+ promises in code.
+ """
+ pass
+
+def lazy(func, *resultclasses):
+ """
+ Turns any callable into a lazy evaluated callable. You need to give result
+ classes or types -- at least one is needed so that the automatic forcing of
+ the lazy evaluation code is triggered. Results are not memoized; the
+ function is evaluated on every access.
+ """
+ class __proxy__(Promise):
+ # This inner class encapsulates the code that should be evaluated
+ # lazily. On calling of one of the magic methods it will force
+ # the evaluation and store the result. Afterwards, the result
+ # is delivered directly. So the result is memoized.
+ def __init__(self, args, kw):
+ self.__func = func
+ self.__args = args
+ self.__kw = kw
+ self.__dispatch = {}
+ for resultclass in resultclasses:
+ self.__dispatch[resultclass] = {}
+ for (k, v) in resultclass.__dict__.items():
+ setattr(self, k, self.__promise__(resultclass, k, v))
+
+ def __promise__(self, klass, funcname, func):
+ # Builds a wrapper around some magic method and registers that magic
+ # method for the given type and method name.
+ def __wrapper__(*args, **kw):
+ # Automatically triggers the evaluation of a lazy value and
+ # applies the given magic method of the result type.
+ res = self.__func(*self.__args, **self.__kw)
+ return self.__dispatch[type(res)][funcname](res, *args, **kw)
+
+ if not self.__dispatch.has_key(klass):
+ self.__dispatch[klass] = {}
+ self.__dispatch[klass][funcname] = func
+ return __wrapper__
+
+ def __wrapper__(*args, **kw):
+ # Creates the proxy object, instead of the actual value.
+ return __proxy__(args, kw)
+
+ return __wrapper__
diff --git a/google_appengine/lib/django/django/utils/functional.pyc b/google_appengine/lib/django/django/utils/functional.pyc
new file mode 100644
index 0000000..3318ce6
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/functional.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/html.py b/google_appengine/lib/django/django/utils/html.py
new file mode 100755
index 0000000..a0d1e82
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/html.py
@@ -0,0 +1,115 @@
+"HTML utilities suitable for global use."
+
+import re, string
+
+# Configuration for urlize() function
+LEADING_PUNCTUATION = ['(', '<', '&lt;']
+TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;']
+
+# list of possible strings used for bullets in bulleted lists
+DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
+
+unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
+word_split_re = re.compile(r'(\s+)')
+punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
+ ('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]),
+ '|'.join([re.escape(x) for x in TRAILING_PUNCTUATION])))
+simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
+link_target_attribute_re = re.compile(r'(<a [^>]*?)target=[^\s>]+')
+html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
+hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
+trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
+del x # Temporary variable
+
+def escape(html):
+ "Returns the given HTML with ampersands, quotes and carets encoded"
+ if not isinstance(html, basestring):
+ html = str(html)
+ return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
+
+def linebreaks(value):
+ "Converts newlines into <p> and <br />s"
+ value = re.sub(r'\r\n|\r|\n', '\n', value) # normalize newlines
+ paras = re.split('\n{2,}', value)
+ paras = ['<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
+ return '\n\n'.join(paras)
+
+def strip_tags(value):
+ "Returns the given HTML with all tags stripped"
+ return re.sub(r'<[^>]*?>', '', value)
+
+def strip_spaces_between_tags(value):
+ "Returns the given HTML with spaces between tags normalized to a single space"
+ return re.sub(r'>\s+<', '> <', value)
+
+def strip_entities(value):
+ "Returns the given HTML with all entities (&something;) stripped"
+ return re.sub(r'&(?:\w+|#\d);', '', value)
+
+def fix_ampersands(value):
+ "Returns the given HTML with all unencoded ampersands encoded correctly"
+ return unencoded_ampersands_re.sub('&amp;', value)
+
+def urlize(text, trim_url_limit=None, nofollow=False):
+ """
+ Converts any URLs in text into clickable links. Works on http://, https:// and
+ www. links. Links can have trailing punctuation (periods, commas, close-parens)
+ and leading punctuation (opening parens) and it'll still do the right thing.
+
+ If trim_url_limit is not None, the URLs in link text will be limited to
+ trim_url_limit characters.
+
+ If nofollow is True, the URLs in link text will get a rel="nofollow" attribute.
+ """
+ trim_url = lambda x, limit=trim_url_limit: limit is not None and (x[:limit] + (len(x) >=limit and '...' or '')) or x
+ words = word_split_re.split(text)
+ nofollow_attr = nofollow and ' rel="nofollow"' or ''
+ for i, word in enumerate(words):
+ match = punctuation_re.match(word)
+ if match:
+ lead, middle, trail = match.groups()
+ if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
+ len(middle) > 0 and middle[0] in string.letters + string.digits and \
+ (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
+ middle = '<a href="http://%s"%s>%s</a>' % (middle, nofollow_attr, trim_url(middle))
+ if middle.startswith('http://') or middle.startswith('https://'):
+ middle = '<a href="%s"%s>%s</a>' % (middle, nofollow_attr, trim_url(middle))
+ if '@' in middle and not middle.startswith('www.') and not ':' in middle \
+ and simple_email_re.match(middle):
+ middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
+ if lead + middle + trail != word:
+ words[i] = lead + middle + trail
+ return ''.join(words)
+
+def clean_html(text):
+ """
+ Cleans the given HTML. Specifically, it does the following:
+ * Converts <b> and <i> to <strong> and <em>.
+ * Encodes all ampersands correctly.
+ * Removes all "target" attributes from <a> tags.
+ * Removes extraneous HTML, such as presentational tags that open and
+ immediately close and <br clear="all">.
+ * Converts hard-coded bullets into HTML unordered lists.
+ * Removes stuff like "<p>&nbsp;&nbsp;</p>", but only if it's at the
+ bottom of the text.
+ """
+ from django.utils.text import normalize_newlines
+ text = normalize_newlines(text)
+ text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text)
+ text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text)
+ text = fix_ampersands(text)
+ # Remove all target="" attributes from <a> tags.
+ text = link_target_attribute_re.sub('\\1', text)
+ # Trim stupid HTML such as <br clear="all">.
+ text = html_gunk_re.sub('', text)
+ # Convert hard-coded bullets into HTML unordered lists.
+ def replace_p_tags(match):
+ s = match.group().replace('</p>', '</li>')
+ for d in DOTS:
+ s = s.replace('<p>%s' % d, '<li>')
+ return '<ul>\n%s\n</ul>' % s
+ text = hard_coded_bullets_re.sub(replace_p_tags, text)
+ # Remove stuff like "<p>&nbsp;&nbsp;</p>", but only if it's at the bottom of the text.
+ text = trailing_empty_content_re.sub('', text)
+ return text
+
diff --git a/google_appengine/lib/django/django/utils/html.pyc b/google_appengine/lib/django/django/utils/html.pyc
new file mode 100644
index 0000000..13c3678
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/html.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/images.py b/google_appengine/lib/django/django/utils/images.py
new file mode 100755
index 0000000..122c6ae
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/images.py
@@ -0,0 +1,22 @@
+"""
+Utility functions for handling images.
+
+Requires PIL, as you might imagine.
+"""
+
+import ImageFile
+
+def get_image_dimensions(path):
+ """Returns the (width, height) of an image at a given path."""
+ p = ImageFile.Parser()
+ fp = open(path, 'rb')
+ while 1:
+ data = fp.read(1024)
+ if not data:
+ break
+ p.feed(data)
+ if p.image:
+ return p.image.size
+ break
+ fp.close()
+ return None
diff --git a/google_appengine/lib/django/django/utils/itercompat.py b/google_appengine/lib/django/django/utils/itercompat.py
new file mode 100755
index 0000000..370988b
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/itercompat.py
@@ -0,0 +1,31 @@
+"""
+Providing iterator functions that are not in all version of Python we support.
+Where possible, we try to use the system-native version and only fall back to
+these implementations if necessary.
+"""
+
+import itertools
+
+def compat_tee(iterable):
+ """Return two independent iterators from a single iterable.
+
+ Based on http://www.python.org/doc/2.3.5/lib/itertools-example.html
+ """
+ # Note: Using a dictionary and a list as the default arguments here is
+ # deliberate and safe in this instance.
+ def gen(next, data={}, cnt=[0]):
+ dpop = data.pop
+ for i in itertools.count():
+ if i == cnt[0]:
+ item = data[i] = next()
+ cnt[0] += 1
+ else:
+ item = dpop(i)
+ yield item
+ next = iter(iterable).next
+ return gen(next), gen(next)
+
+if hasattr(itertools, 'tee'):
+ tee = itertools.tee
+else:
+ tee = compat_tee
diff --git a/google_appengine/lib/django/django/utils/simplejson/__init__.py b/google_appengine/lib/django/django/utils/simplejson/__init__.py
new file mode 100755
index 0000000..15b7173
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/__init__.py
@@ -0,0 +1,252 @@
+r"""
+A simple, fast, extensible JSON encoder and decoder
+
+JSON (JavaScript Object Notation) <http://json.org> is a subset of
+JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
+interchange format.
+
+simplejson exposes an API familiar to uses of the standard library
+marshal and pickle modules.
+
+Encoding basic Python object hierarchies::
+
+ >>> import simplejson
+ >>> simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
+ '["foo", {"bar": ["baz", null, 1.0, 2]}]'
+ >>> print simplejson.dumps("\"foo\bar")
+ "\"foo\bar"
+ >>> print simplejson.dumps(u'\u1234')
+ "\u1234"
+ >>> print simplejson.dumps('\\')
+ "\\"
+ >>> print simplejson.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+ {"a": 0, "b": 0, "c": 0}
+ >>> from StringIO import StringIO
+ >>> io = StringIO()
+ >>> simplejson.dump(['streaming API'], io)
+ >>> io.getvalue()
+ '["streaming API"]'
+
+Compact encoding::
+
+ >>> import simplejson
+ >>> simplejson.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
+ '[1,2,3,{"4":5,"6":7}]'
+
+Pretty printing::
+
+ >>> import simplejson
+ >>> print simplejson.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
+ {
+ "4": 5,
+ "6": 7
+ }
+
+Decoding JSON::
+
+ >>> import simplejson
+ >>> simplejson.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
+ [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
+ >>> simplejson.loads('"\\"foo\\bar"')
+ u'"foo\x08ar'
+ >>> from StringIO import StringIO
+ >>> io = StringIO('["streaming API"]')
+ >>> simplejson.load(io)
+ [u'streaming API']
+
+Specializing JSON object decoding::
+
+ >>> import simplejson
+ >>> def as_complex(dct):
+ ... if '__complex__' in dct:
+ ... return complex(dct['real'], dct['imag'])
+ ... return dct
+ ...
+ >>> simplejson.loads('{"__complex__": true, "real": 1, "imag": 2}',
+ ... object_hook=as_complex)
+ (1+2j)
+
+Extending JSONEncoder::
+
+ >>> import simplejson
+ >>> class ComplexEncoder(simplejson.JSONEncoder):
+ ... def default(self, obj):
+ ... if isinstance(obj, complex):
+ ... return [obj.real, obj.imag]
+ ... return simplejson.JSONEncoder.default(self, obj)
+ ...
+ >>> dumps(2 + 1j, cls=ComplexEncoder)
+ '[2.0, 1.0]'
+ >>> ComplexEncoder().encode(2 + 1j)
+ '[2.0, 1.0]'
+ >>> list(ComplexEncoder().iterencode(2 + 1j))
+ ['[', '2.0', ', ', '1.0', ']']
+
+
+Note that the JSON produced by this module's default settings
+is a subset of YAML, so it may be used as a serializer for that as well.
+"""
+__version__ = '1.5'
+__all__ = [
+ 'dump', 'dumps', 'load', 'loads',
+ 'JSONDecoder', 'JSONEncoder',
+]
+
+from django.utils.simplejson.decoder import JSONDecoder
+from django.utils.simplejson.encoder import JSONEncoder
+
+def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, **kw):
+ """
+ Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
+ ``.write()``-supporting file-like object).
+
+ If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If ``ensure_ascii`` is ``False``, then the some chunks written to ``fp``
+ may be ``unicode`` instances, subject to normal Python ``str`` to
+ ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
+ understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
+ to cause an error.
+
+ If ``check_circular`` is ``False``, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
+ in strict compliance of the JSON specification, instead of using the
+ JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+ If ``indent`` is a non-negative integer, then JSON array elements and object
+ members will be pretty-printed with that indent level. An indent level
+ of 0 will only insert newlines. ``None`` is the most compact representation.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg.
+ """
+ if cls is None:
+ cls = JSONEncoder
+ iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ **kw).iterencode(obj)
+ # could accelerate with writelines in some versions of Python, at
+ # a debuggability cost
+ for chunk in iterable:
+ fp.write(chunk)
+
+def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=None, **kw):
+ """
+ Serialize ``obj`` to a JSON formatted ``str``.
+
+ If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If ``ensure_ascii`` is ``False``, then the return value will be a
+ ``unicode`` instance subject to normal Python ``str`` to ``unicode``
+ coercion rules instead of being escaped to an ASCII ``str``.
+
+ If ``check_circular`` is ``False``, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
+ strict compliance of the JSON specification, instead of using the
+ JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+ If ``indent`` is a non-negative integer, then JSON array elements and
+ object members will be pretty-printed with that indent level. An indent
+ level of 0 will only insert newlines. ``None`` is the most compact
+ representation.
+
+ If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+ then it will be used instead of the default ``(', ', ': ')`` separators.
+ ``(',', ':')`` is the most compact JSON representation.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg.
+ """
+ if cls is None:
+ cls = JSONEncoder
+ return cls(
+ skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ separators=separators,
+ **kw).encode(obj)
+
+def load(fp, encoding=None, cls=None, object_hook=None, **kw):
+ """
+ Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
+ a JSON document) to a Python object.
+
+ If the contents of ``fp`` is encoded with an ASCII based encoding other
+ than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
+ be specified. Encodings that are not ASCII based (such as UCS-2) are
+ not allowed, and should be wrapped with
+ ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
+ object and passed to ``loads()``
+
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg.
+ """
+ if cls is None:
+ cls = JSONDecoder
+ if object_hook is not None:
+ kw['object_hook'] = object_hook
+ return cls(encoding=encoding, **kw).decode(fp.read())
+
+def loads(s, encoding=None, cls=None, object_hook=None, **kw):
+ """
+ Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
+ document) to a Python object.
+
+ If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
+ other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
+ must be specified. Encodings that are not ASCII based (such as UCS-2)
+ are not allowed and should be decoded to ``unicode`` first.
+
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg.
+ """
+ if cls is None:
+ cls = JSONDecoder
+ if object_hook is not None:
+ kw['object_hook'] = object_hook
+ return cls(encoding=encoding, **kw).decode(s)
+
+def read(s):
+ """
+ json-py API compatibility hook. Use loads(s) instead.
+ """
+ import warnings
+ warnings.warn("simplejson.loads(s) should be used instead of read(s)",
+ DeprecationWarning)
+ return loads(s)
+
+def write(obj):
+ """
+ json-py API compatibility hook. Use dumps(s) instead.
+ """
+ import warnings
+ warnings.warn("simplejson.dumps(s) should be used instead of write(s)",
+ DeprecationWarning)
+ return dumps(obj)
+
+
diff --git a/google_appengine/lib/django/django/utils/simplejson/__init__.pyc b/google_appengine/lib/django/django/utils/simplejson/__init__.pyc
new file mode 100644
index 0000000..a31b07f
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/simplejson/decoder.py b/google_appengine/lib/django/django/utils/simplejson/decoder.py
new file mode 100755
index 0000000..66f68a2
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/decoder.py
@@ -0,0 +1,273 @@
+"""
+Implementation of JSONDecoder
+"""
+import re
+
+from django.utils.simplejson.scanner import Scanner, pattern
+
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+
+def _floatconstants():
+ import struct
+ import sys
+ _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+ if sys.byteorder != 'big':
+ _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
+ nan, inf = struct.unpack('dd', _BYTES)
+ return nan, inf, -inf
+
+NaN, PosInf, NegInf = _floatconstants()
+
+def linecol(doc, pos):
+ lineno = doc.count('\n', 0, pos) + 1
+ if lineno == 1:
+ colno = pos
+ else:
+ colno = pos - doc.rindex('\n', 0, pos)
+ return lineno, colno
+
+def errmsg(msg, doc, pos, end=None):
+ lineno, colno = linecol(doc, pos)
+ if end is None:
+ return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos)
+ endlineno, endcolno = linecol(doc, end)
+ return '%s: line %d column %d - line %d column %d (char %d - %d)' % (
+ msg, lineno, colno, endlineno, endcolno, pos, end)
+
+_CONSTANTS = {
+ '-Infinity': NegInf,
+ 'Infinity': PosInf,
+ 'NaN': NaN,
+ 'true': True,
+ 'false': False,
+ 'null': None,
+}
+
+def JSONConstant(match, context, c=_CONSTANTS):
+ return c[match.group(0)], None
+pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant)
+
+def JSONNumber(match, context):
+ match = JSONNumber.regex.match(match.string, *match.span())
+ integer, frac, exp = match.groups()
+ if frac or exp:
+ res = float(integer + (frac or '') + (exp or ''))
+ else:
+ res = int(integer)
+ return res, None
+pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber)
+
+STRINGCHUNK = re.compile(r'(.*?)(["\\])', FLAGS)
+BACKSLASH = {
+ '"': u'"', '\\': u'\\', '/': u'/',
+ 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
+}
+
+DEFAULT_ENCODING = "utf-8"
+
+def scanstring(s, end, encoding=None, _b=BACKSLASH, _m=STRINGCHUNK.match):
+ if encoding is None:
+ encoding = DEFAULT_ENCODING
+ chunks = []
+ _append = chunks.append
+ begin = end - 1
+ while 1:
+ chunk = _m(s, end)
+ if chunk is None:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
+ end = chunk.end()
+ content, terminator = chunk.groups()
+ if content:
+ if not isinstance(content, unicode):
+ content = unicode(content, encoding)
+ _append(content)
+ if terminator == '"':
+ break
+ try:
+ esc = s[end]
+ except IndexError:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
+ if esc != 'u':
+ try:
+ m = _b[esc]
+ except KeyError:
+ raise ValueError(
+ errmsg("Invalid \\escape: %r" % (esc,), s, end))
+ end += 1
+ else:
+ esc = s[end + 1:end + 5]
+ try:
+ m = unichr(int(esc, 16))
+ if len(esc) != 4 or not esc.isalnum():
+ raise ValueError
+ except ValueError:
+ raise ValueError(errmsg("Invalid \\uXXXX escape", s, end))
+ end += 5
+ _append(m)
+ return u''.join(chunks), end
+
+def JSONString(match, context):
+ encoding = getattr(context, 'encoding', None)
+ return scanstring(match.string, match.end(), encoding)
+pattern(r'"')(JSONString)
+
+WHITESPACE = re.compile(r'\s*', FLAGS)
+
+def JSONObject(match, context, _w=WHITESPACE.match):
+ pairs = {}
+ s = match.string
+ end = _w(s, match.end()).end()
+ nextchar = s[end:end + 1]
+ # trivial empty object
+ if nextchar == '}':
+ return pairs, end + 1
+ if nextchar != '"':
+ raise ValueError(errmsg("Expecting property name", s, end))
+ end += 1
+ encoding = getattr(context, 'encoding', None)
+ iterscan = JSONScanner.iterscan
+ while True:
+ key, end = scanstring(s, end, encoding)
+ end = _w(s, end).end()
+ if s[end:end + 1] != ':':
+ raise ValueError(errmsg("Expecting : delimiter", s, end))
+ end = _w(s, end + 1).end()
+ try:
+ value, end = iterscan(s, idx=end, context=context).next()
+ except StopIteration:
+ raise ValueError(errmsg("Expecting object", s, end))
+ pairs[key] = value
+ end = _w(s, end).end()
+ nextchar = s[end:end + 1]
+ end += 1
+ if nextchar == '}':
+ break
+ if nextchar != ',':
+ raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
+ end = _w(s, end).end()
+ nextchar = s[end:end + 1]
+ end += 1
+ if nextchar != '"':
+ raise ValueError(errmsg("Expecting property name", s, end - 1))
+ object_hook = getattr(context, 'object_hook', None)
+ if object_hook is not None:
+ pairs = object_hook(pairs)
+ return pairs, end
+pattern(r'{')(JSONObject)
+
+def JSONArray(match, context, _w=WHITESPACE.match):
+ values = []
+ s = match.string
+ end = _w(s, match.end()).end()
+ # look-ahead for trivial empty array
+ nextchar = s[end:end + 1]
+ if nextchar == ']':
+ return values, end + 1
+ iterscan = JSONScanner.iterscan
+ while True:
+ try:
+ value, end = iterscan(s, idx=end, context=context).next()
+ except StopIteration:
+ raise ValueError(errmsg("Expecting object", s, end))
+ values.append(value)
+ end = _w(s, end).end()
+ nextchar = s[end:end + 1]
+ end += 1
+ if nextchar == ']':
+ break
+ if nextchar != ',':
+ raise ValueError(errmsg("Expecting , delimiter", s, end))
+ end = _w(s, end).end()
+ return values, end
+pattern(r'\[')(JSONArray)
+
+ANYTHING = [
+ JSONObject,
+ JSONArray,
+ JSONString,
+ JSONConstant,
+ JSONNumber,
+]
+
+JSONScanner = Scanner(ANYTHING)
+
+class JSONDecoder(object):
+ """
+ Simple JSON <http://json.org> decoder
+
+ Performs the following translations in decoding:
+
+ +---------------+-------------------+
+ | JSON | Python |
+ +===============+===================+
+ | object | dict |
+ +---------------+-------------------+
+ | array | list |
+ +---------------+-------------------+
+ | string | unicode |
+ +---------------+-------------------+
+ | number (int) | int, long |
+ +---------------+-------------------+
+ | number (real) | float |
+ +---------------+-------------------+
+ | true | True |
+ +---------------+-------------------+
+ | false | False |
+ +---------------+-------------------+
+ | null | None |
+ +---------------+-------------------+
+
+ It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
+ their corresponding ``float`` values, which is outside the JSON spec.
+ """
+
+ _scanner = Scanner(ANYTHING)
+ __all__ = ['__init__', 'decode', 'raw_decode']
+
+ def __init__(self, encoding=None, object_hook=None):
+ """
+ ``encoding`` determines the encoding used to interpret any ``str``
+ objects decoded by this instance (utf-8 by default). It has no
+ effect when decoding ``unicode`` objects.
+
+ Note that currently only encodings that are a superset of ASCII work,
+ strings of other encodings should be passed in as ``unicode``.
+
+ ``object_hook``, if specified, will be called with the result
+ of every JSON object decoded and its return value will be used in
+ place of the given ``dict``. This can be used to provide custom
+ deserializations (e.g. to support JSON-RPC class hinting).
+ """
+ self.encoding = encoding
+ self.object_hook = object_hook
+
+ def decode(self, s, _w=WHITESPACE.match):
+ """
+ Return the Python representation of ``s`` (a ``str`` or ``unicode``
+ instance containing a JSON document)
+ """
+ obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+ end = _w(s, end).end()
+ if end != len(s):
+ raise ValueError(errmsg("Extra data", s, end, len(s)))
+ return obj
+
+ def raw_decode(self, s, **kw):
+ """
+ Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
+ with a JSON document) and return a 2-tuple of the Python
+ representation and the index in ``s`` where the document ended.
+
+ This can be used to decode a JSON document from a string that may
+ have extraneous data at the end.
+ """
+ kw.setdefault('context', self)
+ try:
+ obj, end = self._scanner.iterscan(s, **kw).next()
+ except StopIteration:
+ raise ValueError("No JSON object could be decoded")
+ return obj, end
+
+__all__ = ['JSONDecoder']
diff --git a/google_appengine/lib/django/django/utils/simplejson/decoder.pyc b/google_appengine/lib/django/django/utils/simplejson/decoder.pyc
new file mode 100644
index 0000000..67334a0
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/decoder.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/simplejson/encoder.py b/google_appengine/lib/django/django/utils/simplejson/encoder.py
new file mode 100755
index 0000000..c83c687
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/encoder.py
@@ -0,0 +1,331 @@
+"""
+Implementation of JSONEncoder
+"""
+import re
+
+ESCAPE = re.compile(r'[\x00-\x19\\"\b\f\n\r\t]')
+ESCAPE_ASCII = re.compile(r'([\\"/]|[^\ -~])')
+ESCAPE_DCT = {
+ # escape all forward slashes to prevent </script> attack
+ '/': '\\/',
+ '\\': '\\\\',
+ '"': '\\"',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+}
+for i in range(0x20):
+ ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+
+# assume this produces an infinity on all machines (probably not guaranteed)
+INFINITY = float('1e66666')
+
+def floatstr(o, allow_nan=True):
+ # Check for specials. Note that this type of test is processor- and/or
+ # platform-specific, so do tests which don't depend on the internals.
+
+ if o != o:
+ text = 'NaN'
+ elif o == INFINITY:
+ text = 'Infinity'
+ elif o == -INFINITY:
+ text = '-Infinity'
+ else:
+ return str(o)
+
+ if not allow_nan:
+ raise ValueError("Out of range float values are not JSON compliant: %r"
+ % (o,))
+
+ return text
+
+
+def encode_basestring(s):
+ """
+ Return a JSON representation of a Python string
+ """
+ def replace(match):
+ return ESCAPE_DCT[match.group(0)]
+ return '"' + ESCAPE.sub(replace, s) + '"'
+
+def encode_basestring_ascii(s):
+ def replace(match):
+ s = match.group(0)
+ try:
+ return ESCAPE_DCT[s]
+ except KeyError:
+ return '\\u%04x' % (ord(s),)
+ return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+class JSONEncoder(object):
+ """
+ Extensible JSON <http://json.org> encoder for Python data structures.
+
+ Supports the following objects and types by default:
+
+ +-------------------+---------------+
+ | Python | JSON |
+ +===================+===============+
+ | dict | object |
+ +-------------------+---------------+
+ | list, tuple | array |
+ +-------------------+---------------+
+ | str, unicode | string |
+ +-------------------+---------------+
+ | int, long, float | number |
+ +-------------------+---------------+
+ | True | true |
+ +-------------------+---------------+
+ | False | false |
+ +-------------------+---------------+
+ | None | null |
+ +-------------------+---------------+
+
+ To extend this to recognize other objects, subclass and implement a
+ ``.default()`` method with another method that returns a serializable
+ object for ``o`` if possible, otherwise it should call the superclass
+ implementation (to raise ``TypeError``).
+ """
+ __all__ = ['__init__', 'default', 'encode', 'iterencode']
+ item_separator = ', '
+ key_separator = ': '
+ def __init__(self, skipkeys=False, ensure_ascii=True,
+ check_circular=True, allow_nan=True, sort_keys=False,
+ indent=None, separators=None):
+ """
+ Constructor for JSONEncoder, with sensible defaults.
+
+ If skipkeys is False, then it is a TypeError to attempt
+ encoding of keys that are not str, int, long, float or None. If
+ skipkeys is True, such items are simply skipped.
+
+ If ensure_ascii is True, the output is guaranteed to be str
+ objects with all incoming unicode characters escaped. If
+ ensure_ascii is false, the output will be unicode object.
+
+ If check_circular is True, then lists, dicts, and custom encoded
+ objects will be checked for circular references during encoding to
+ prevent an infinite recursion (which would cause an OverflowError).
+ Otherwise, no such check takes place.
+
+ If allow_nan is True, then NaN, Infinity, and -Infinity will be
+ encoded as such. This behavior is not JSON specification compliant,
+ but is consistent with most JavaScript based encoders and decoders.
+ Otherwise, it will be a ValueError to encode such floats.
+
+ If sort_keys is True, then the output of dictionaries will be
+ sorted by key; this is useful for regression tests to ensure
+ that JSON serializations can be compared on a day-to-day basis.
+
+ If indent is a non-negative integer, then JSON array
+ elements and object members will be pretty-printed with that
+ indent level. An indent level of 0 will only insert newlines.
+ None is the most compact representation.
+
+ If specified, separators should be a (item_separator, key_separator)
+ tuple. The default is (', ', ': '). To get the most compact JSON
+ representation you should specify (',', ':') to eliminate whitespace.
+ """
+
+ self.skipkeys = skipkeys
+ self.ensure_ascii = ensure_ascii
+ self.check_circular = check_circular
+ self.allow_nan = allow_nan
+ self.sort_keys = sort_keys
+ self.indent = indent
+ self.current_indent_level = 0
+ if separators is not None:
+ self.item_separator, self.key_separator = separators
+
+ def _newline_indent(self):
+ return '\n' + (' ' * (self.indent * self.current_indent_level))
+
+ def _iterencode_list(self, lst, markers=None):
+ if not lst:
+ yield '[]'
+ return
+ if markers is not None:
+ markerid = id(lst)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = lst
+ yield '['
+ if self.indent is not None:
+ self.current_indent_level += 1
+ newline_indent = self._newline_indent()
+ separator = self.item_separator + newline_indent
+ yield newline_indent
+ else:
+ newline_indent = None
+ separator = self.item_separator
+ first = True
+ for value in lst:
+ if first:
+ first = False
+ else:
+ yield separator
+ for chunk in self._iterencode(value, markers):
+ yield chunk
+ if newline_indent is not None:
+ self.current_indent_level -= 1
+ yield self._newline_indent()
+ yield ']'
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode_dict(self, dct, markers=None):
+ if not dct:
+ yield '{}'
+ return
+ if markers is not None:
+ markerid = id(dct)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = dct
+ yield '{'
+ key_separator = self.key_separator
+ if self.indent is not None:
+ self.current_indent_level += 1
+ newline_indent = self._newline_indent()
+ item_separator = self.item_separator + newline_indent
+ yield newline_indent
+ else:
+ newline_indent = None
+ item_separator = self.item_separator
+ first = True
+ if self.ensure_ascii:
+ encoder = encode_basestring_ascii
+ else:
+ encoder = encode_basestring
+ allow_nan = self.allow_nan
+ if self.sort_keys:
+ keys = dct.keys()
+ keys.sort()
+ items = [(k, dct[k]) for k in keys]
+ else:
+ items = dct.iteritems()
+ for key, value in items:
+ if isinstance(key, basestring):
+ pass
+ # JavaScript is weakly typed for these, so it makes sense to
+ # also allow them. Many encoders seem to do something like this.
+ elif isinstance(key, float):
+ key = floatstr(key, allow_nan)
+ elif isinstance(key, (int, long)):
+ key = str(key)
+ elif key is True:
+ key = 'true'
+ elif key is False:
+ key = 'false'
+ elif key is None:
+ key = 'null'
+ elif self.skipkeys:
+ continue
+ else:
+ raise TypeError("key %r is not a string" % (key,))
+ if first:
+ first = False
+ else:
+ yield item_separator
+ yield encoder(key)
+ yield key_separator
+ for chunk in self._iterencode(value, markers):
+ yield chunk
+ if newline_indent is not None:
+ self.current_indent_level -= 1
+ yield self._newline_indent()
+ yield '}'
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode(self, o, markers=None):
+ if isinstance(o, basestring):
+ if self.ensure_ascii:
+ encoder = encode_basestring_ascii
+ else:
+ encoder = encode_basestring
+ yield encoder(o)
+ elif o is None:
+ yield 'null'
+ elif o is True:
+ yield 'true'
+ elif o is False:
+ yield 'false'
+ elif isinstance(o, (int, long)):
+ yield str(o)
+ elif isinstance(o, float):
+ yield floatstr(o, self.allow_nan)
+ elif isinstance(o, (list, tuple)):
+ for chunk in self._iterencode_list(o, markers):
+ yield chunk
+ elif isinstance(o, dict):
+ for chunk in self._iterencode_dict(o, markers):
+ yield chunk
+ else:
+ if markers is not None:
+ markerid = id(o)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = o
+ for chunk in self._iterencode_default(o, markers):
+ yield chunk
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode_default(self, o, markers=None):
+ newobj = self.default(o)
+ return self._iterencode(newobj, markers)
+
+ def default(self, o):
+ """
+ Implement this method in a subclass such that it returns
+ a serializable object for ``o``, or calls the base implementation
+ (to raise a ``TypeError``).
+
+ For example, to support arbitrary iterators, you could
+ implement default like this::
+
+ def default(self, o):
+ try:
+ iterable = iter(o)
+ except TypeError:
+ pass
+ else:
+ return list(iterable)
+ return JSONEncoder.default(self, o)
+ """
+ raise TypeError("%r is not JSON serializable" % (o,))
+
+ def encode(self, o):
+ """
+ Return a JSON string representation of a Python data structure.
+
+ >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+ '{"foo":["bar", "baz"]}'
+ """
+ # This doesn't pass the iterator directly to ''.join() because it
+ # sucks at reporting exceptions. It's going to do this internally
+ # anyway because it uses PySequence_Fast or similar.
+ chunks = list(self.iterencode(o))
+ return ''.join(chunks)
+
+ def iterencode(self, o):
+ """
+ Encode the given object and yield each string
+ representation as available.
+
+ For example::
+
+ for chunk in JSONEncoder().iterencode(bigobject):
+ mysocket.write(chunk)
+ """
+ if self.check_circular:
+ markers = {}
+ else:
+ markers = None
+ return self._iterencode(o, markers)
+
+__all__ = ['JSONEncoder']
diff --git a/google_appengine/lib/django/django/utils/simplejson/encoder.pyc b/google_appengine/lib/django/django/utils/simplejson/encoder.pyc
new file mode 100644
index 0000000..a18d9eb
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/encoder.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/simplejson/jsonfilter.py b/google_appengine/lib/django/django/utils/simplejson/jsonfilter.py
new file mode 100755
index 0000000..d02ae20
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/jsonfilter.py
@@ -0,0 +1,40 @@
+from django.utils import simplejson
+import cgi
+
+class JSONFilter(object):
+ def __init__(self, app, mime_type='text/x-json'):
+ self.app = app
+ self.mime_type = mime_type
+
+ def __call__(self, environ, start_response):
+ # Read JSON POST input to jsonfilter.json if matching mime type
+ response = {'status': '200 OK', 'headers': []}
+ def json_start_response(status, headers):
+ response['status'] = status
+ response['headers'].extend(headers)
+ environ['jsonfilter.mime_type'] = self.mime_type
+ if environ.get('REQUEST_METHOD', '') == 'POST':
+ if environ.get('CONTENT_TYPE', '') == self.mime_type:
+ args = [_ for _ in [environ.get('CONTENT_LENGTH')] if _]
+ data = environ['wsgi.input'].read(*map(int, args))
+ environ['jsonfilter.json'] = simplejson.loads(data)
+ res = simplejson.dumps(self.app(environ, json_start_response))
+ jsonp = cgi.parse_qs(environ.get('QUERY_STRING', '')).get('jsonp')
+ if jsonp:
+ content_type = 'text/javascript'
+ res = ''.join(jsonp + ['(', res, ')'])
+ elif 'Opera' in environ.get('HTTP_USER_AGENT', ''):
+ # Opera has bunk XMLHttpRequest support for most mime types
+ content_type = 'text/plain'
+ else:
+ content_type = self.mime_type
+ headers = [
+ ('Content-type', content_type),
+ ('Content-length', len(res)),
+ ]
+ headers.extend(response['headers'])
+ start_response(response['status'], headers)
+ return [res]
+
+def factory(app, global_conf, **kw):
+ return JSONFilter(app, **kw)
diff --git a/google_appengine/lib/django/django/utils/simplejson/scanner.py b/google_appengine/lib/django/django/utils/simplejson/scanner.py
new file mode 100755
index 0000000..64f4999
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/scanner.py
@@ -0,0 +1,63 @@
+"""
+Iterator based sre token scanner
+"""
+import sre_parse, sre_compile, sre_constants
+from sre_constants import BRANCH, SUBPATTERN
+from re import VERBOSE, MULTILINE, DOTALL
+import re
+
+__all__ = ['Scanner', 'pattern']
+
+FLAGS = (VERBOSE | MULTILINE | DOTALL)
+class Scanner(object):
+ def __init__(self, lexicon, flags=FLAGS):
+ self.actions = [None]
+ # combine phrases into a compound pattern
+ s = sre_parse.Pattern()
+ s.flags = flags
+ p = []
+ for idx, token in enumerate(lexicon):
+ phrase = token.pattern
+ try:
+ subpattern = sre_parse.SubPattern(s,
+ [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))])
+ except sre_constants.error:
+ raise
+ p.append(subpattern)
+ self.actions.append(token)
+
+ p = sre_parse.SubPattern(s, [(BRANCH, (None, p))])
+ self.scanner = sre_compile.compile(p)
+
+
+ def iterscan(self, string, idx=0, context=None):
+ """
+ Yield match, end_idx for each match
+ """
+ match = self.scanner.scanner(string, idx).match
+ actions = self.actions
+ lastend = idx
+ end = len(string)
+ while True:
+ m = match()
+ if m is None:
+ break
+ matchbegin, matchend = m.span()
+ if lastend == matchend:
+ break
+ action = actions[m.lastindex]
+ if action is not None:
+ rval, next_pos = action(m, context)
+ if next_pos is not None and next_pos != matchend:
+ # "fast forward" the scanner
+ matchend = next_pos
+ match = self.scanner.scanner(string, matchend).match
+ yield rval, matchend
+ lastend = matchend
+
+def pattern(pattern, flags=FLAGS):
+ def decorator(fn):
+ fn.pattern = pattern
+ fn.regex = re.compile(pattern, flags)
+ return fn
+ return decorator
diff --git a/google_appengine/lib/django/django/utils/simplejson/scanner.pyc b/google_appengine/lib/django/django/utils/simplejson/scanner.pyc
new file mode 100644
index 0000000..8286f79
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/simplejson/scanner.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/stopwords.py b/google_appengine/lib/django/django/utils/stopwords.py
new file mode 100755
index 0000000..dea5660
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/stopwords.py
@@ -0,0 +1,42 @@
+# Performance note: I benchmarked this code using a set instead of
+# a list for the stopwords and was surprised to find that the list
+# performed /better/ than the set - maybe because it's only a small
+# list.
+
+stopwords = '''
+i
+a
+an
+are
+as
+at
+be
+by
+for
+from
+how
+in
+is
+it
+of
+on
+or
+that
+the
+this
+to
+was
+what
+when
+where
+'''.split()
+
+def strip_stopwords(sentence):
+ "Removes stopwords - also normalizes whitespace"
+ words = sentence.split()
+ sentence = []
+ for word in words:
+ if word.lower() not in stopwords:
+ sentence.append(word)
+ return ' '.join(sentence)
+
diff --git a/google_appengine/lib/django/django/utils/synch.py b/google_appengine/lib/django/django/utils/synch.py
new file mode 100755
index 0000000..6fcd813
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/synch.py
@@ -0,0 +1,88 @@
+"""
+Synchronization primitives:
+
+ - reader-writer lock (preference to writers)
+
+(Contributed to Django by eugene@lazutkin.com)
+"""
+
+try:
+ import threading
+except ImportError:
+ import dummy_threading as threading
+
+class RWLock:
+ """
+ Classic implementation of reader-writer lock with preference to writers.
+
+ Readers can access a resource simultaneously.
+ Writers get an exclusive access.
+
+ API is self-descriptive:
+ reader_enters()
+ reader_leaves()
+ writer_enters()
+ writer_leaves()
+ """
+
+ def __init__(self):
+ self.mutex = threading.RLock()
+ self.can_read = threading.Semaphore(0)
+ self.can_write = threading.Semaphore(0)
+ self.active_readers = 0
+ self.active_writers = 0
+ self.waiting_readers = 0
+ self.waiting_writers = 0
+
+ def reader_enters(self):
+ self.mutex.acquire()
+ try:
+ if self.active_writers == 0 and self.waiting_writers == 0:
+ self.active_readers += 1
+ self.can_read.release()
+ else:
+ self.waiting_readers += 1
+ finally:
+ self.mutex.release()
+ self.can_read.acquire()
+
+ def reader_leaves(self):
+ self.mutex.acquire()
+ try:
+ self.active_readers -= 1
+ if self.active_readers == 0 and self.waiting_writers != 0:
+ self.active_writers += 1
+ self.waiting_writers -= 1
+ self.can_write.release()
+ finally:
+ self.mutex.release()
+
+ def writer_enters(self):
+ self.mutex.acquire()
+ try:
+ if self.active_writers == 0 and self.waiting_writers == 0 and self.active_readers == 0:
+ self.active_writers += 1
+ self.can_write.release()
+ else:
+ self.waiting_writers += 1
+ finally:
+ self.mutex.release()
+ self.can_write.acquire()
+
+ def writer_leaves(self):
+ self.mutex.acquire()
+ try:
+ self.active_writers -= 1
+ if self.waiting_writers != 0:
+ self.active_writers += 1
+ self.waiting_writers -= 1
+ self.can_write.release()
+ elif self.waiting_readers != 0:
+ t = self.waiting_readers
+ self.waiting_readers = 0
+ self.active_readers += t
+ while t > 0:
+ self.can_read.release()
+ t -= 1
+ finally:
+ self.mutex.release()
diff --git a/google_appengine/lib/django/django/utils/termcolors.py b/google_appengine/lib/django/django/utils/termcolors.py
new file mode 100755
index 0000000..17a600f
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/termcolors.py
@@ -0,0 +1,68 @@
+"""
+termcolors.py
+"""
+
+color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
+foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
+background = dict([(color_names[x], '4%s' % x) for x in range(8)])
+del color_names
+
+RESET = '0'
+opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
+
+def colorize(text='', opts=(), **kwargs):
+ """
+ Returns your text, enclosed in ANSI graphics codes.
+
+ Depends on the keyword arguments 'fg' and 'bg', and the contents of
+ the opts tuple/list.
+
+ Returns the RESET code if no parameters are given.
+
+ Valid colors:
+ 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
+
+ Valid options:
+ 'bold'
+ 'underscore'
+ 'blink'
+ 'reverse'
+ 'conceal'
+ 'noreset' - string will not be auto-terminated with the RESET code
+
+ Examples:
+ colorize('hello', fg='red', bg='blue', opts=('blink',))
+ colorize()
+ colorize('goodbye', opts=('underscore',))
+ print colorize('first line', fg='red', opts=('noreset',))
+ print 'this should be red too'
+ print colorize('and so should this')
+ print 'this should not be red'
+ """
+ text = str(text)
+ code_list = []
+ if text == '' and len(opts) == 1 and opts[0] == 'reset':
+ return '\x1b[%sm' % RESET
+ for k, v in kwargs.iteritems():
+ if k == 'fg':
+ code_list.append(foreground[v])
+ elif k == 'bg':
+ code_list.append(background[v])
+ for o in opts:
+ if o in opt_dict:
+ code_list.append(opt_dict[o])
+ if 'noreset' not in opts:
+ text = text + '\x1b[%sm' % RESET
+ return ('\x1b[%sm' % ';'.join(code_list)) + text
+
+def make_style(opts=(), **kwargs):
+ """
+ Returns a function with default parameters for colorize()
+
+ Example:
+ bold_red = make_style(opts=('bold',), fg='red')
+ print bold_red('hello')
+ KEYWORD = make_style(fg='yellow')
+ COMMENT = make_style(fg='blue', opts=('bold',))
+ """
+ return lambda text: colorize(text, opts, **kwargs)
diff --git a/google_appengine/lib/django/django/utils/text.py b/google_appengine/lib/django/django/utils/text.py
new file mode 100755
index 0000000..faf8705
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/text.py
@@ -0,0 +1,204 @@
+import re
+
+from django.conf import settings
+
+# Capitalizes the first letter of a string.
+capfirst = lambda x: x and x[0].upper() + x[1:]
+
+def wrap(text, width):
+ """
+ A word-wrap function that preserves existing line breaks and most spaces in
+ the text. Expects that existing line breaks are posix newlines.
+ """
+ def _generator():
+ it = iter(text.split(' '))
+ word = it.next()
+ yield word
+ pos = len(word) - word.rfind('\n') - 1
+ for word in it:
+ if "\n" in word:
+ lines = word.split('\n')
+ else:
+ lines = (word,)
+ pos += len(lines[0]) + 1
+ if pos > width:
+ yield '\n'
+ pos = len(lines[-1])
+ else:
+ yield ' '
+ if len(lines) > 1:
+ pos = len(lines[-1])
+ yield word
+ return "".join(_generator())
+
+def truncate_words(s, num):
+ "Truncates a string after a certain number of words."
+ length = int(num)
+ words = s.split()
+ if len(words) > length:
+ words = words[:length]
+ if not words[-1].endswith('...'):
+ words.append('...')
+ return ' '.join(words)
+
+def truncate_html_words(s, num):
+ """
+ Truncates html to a certain number of words (not counting tags and comments).
+ Closes opened tags if they were correctly closed in the given html.
+ """
+ length = int(num)
+ if length <= 0:
+ return ''
+ html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input')
+ # Set up regular expressions
+ re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)')
+ re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
+ # Count non-HTML words and keep note of open tags
+ pos = 0
+ ellipsis_pos = 0
+ words = 0
+ open_tags = []
+ while words <= length:
+ m = re_words.search(s, pos)
+ if not m:
+ # Checked through whole string
+ break
+ pos = m.end(0)
+ if m.group(1):
+ # It's an actual non-HTML word
+ words += 1
+ if words == length:
+ ellipsis_pos = pos
+ continue
+ # Check for tag
+ tag = re_tag.match(m.group(0))
+ if not tag or ellipsis_pos:
+ # Don't worry about non tags or tags after our truncate point
+ continue
+ closing_tag, tagname, self_closing = tag.groups()
+ tagname = tagname.lower() # Element names are always case-insensitive
+ if self_closing or tagname in html4_singlets:
+ pass
+ elif closing_tag:
+ # Check for match in open tags list
+ try:
+ i = open_tags.index(tagname)
+ except ValueError:
+ pass
+ else:
+ # SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags
+ open_tags = open_tags[i+1:]
+ else:
+ # Add it to the start of the open tags list
+ open_tags.insert(0, tagname)
+ if words <= length:
+ # Don't try to close tags if we don't need to truncate
+ return s
+ out = s[:ellipsis_pos] + ' ...'
+ # Close any tags still open
+ for tag in open_tags:
+ out += '</%s>' % tag
+ # Return string
+ return out
+
+def get_valid_filename(s):
+ """
+ Returns the given string converted to a string that can be used for a clean
+ filename. Specifically, leading and trailing spaces are removed; other
+ spaces are converted to underscores; and all non-filename-safe characters
+ are removed.
+ >>> get_valid_filename("john's portrait in 2004.jpg")
+ 'johns_portrait_in_2004.jpg'
+ """
+ s = s.strip().replace(' ', '_')
+ return re.sub(r'[^-A-Za-z0-9_.]', '', s)
+
+def get_text_list(list_, last_word='or'):
+ """
+ >>> get_text_list(['a', 'b', 'c', 'd'])
+ 'a, b, c or d'
+ >>> get_text_list(['a', 'b', 'c'], 'and')
+ 'a, b and c'
+ >>> get_text_list(['a', 'b'], 'and')
+ 'a and b'
+ >>> get_text_list(['a'])
+ 'a'
+ >>> get_text_list([])
+ ''
+ """
+ if len(list_) == 0: return ''
+ if len(list_) == 1: return list_[0]
+ return '%s %s %s' % (', '.join([str(i) for i in list_][:-1]), last_word, list_[-1])
+
+def normalize_newlines(text):
+ return re.sub(r'\r\n|\r|\n', '\n', text)
+
+def recapitalize(text):
+ "Recapitalizes text, placing caps after end-of-sentence punctuation."
+# capwords = ()
+ text = text.lower()
+ capsRE = re.compile(r'(?:^|(?<=[\.\?\!] ))([a-z])')
+ text = capsRE.sub(lambda x: x.group(1).upper(), text)
+# for capword in capwords:
+# capwordRE = re.compile(r'\b%s\b' % capword, re.I)
+# text = capwordRE.sub(capword, text)
+ return text
+
+def phone2numeric(phone):
+ "Converts a phone number with letters into its numeric equivalent."
+ letters = re.compile(r'[A-PR-Y]', re.I)
+ char2number = lambda m: {'a': '2', 'c': '2', 'b': '2', 'e': '3',
+ 'd': '3', 'g': '4', 'f': '3', 'i': '4', 'h': '4', 'k': '5',
+ 'j': '5', 'm': '6', 'l': '5', 'o': '6', 'n': '6', 'p': '7',
+ 's': '7', 'r': '7', 'u': '8', 't': '8', 'w': '9', 'v': '8',
+ 'y': '9', 'x': '9'}.get(m.group(0).lower())
+ return letters.sub(char2number, phone)
+
+# From http://www.xhaus.com/alan/python/httpcomp.html#gzip
+# Used with permission.
+def compress_string(s):
+ import cStringIO, gzip
+ zbuf = cStringIO.StringIO()
+ zfile = gzip.GzipFile(mode='wb', compresslevel=6, fileobj=zbuf)
+ zfile.write(s)
+ zfile.close()
+ return zbuf.getvalue()
+
+ustring_re = re.compile(u"([\u0080-\uffff])")
+
+def javascript_quote(s, quote_double_quotes=False):
+
+ def fix(match):
+ return r"\u%04x" % ord(match.group(1))
+
+ if type(s) == str:
+ s = s.decode(settings.DEFAULT_CHARSET)
+ elif type(s) != unicode:
+ raise TypeError, s
+ s = s.replace('\\', '\\\\')
+ s = s.replace('\r', '\\r')
+ s = s.replace('\n', '\\n')
+ s = s.replace('\t', '\\t')
+ s = s.replace("'", "\\'")
+ if quote_double_quotes:
+ s = s.replace('"', '&quot;')
+ return str(ustring_re.sub(fix, s))
+
+smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)')
+def smart_split(text):
+ """
+ Generator that splits a string by spaces, leaving quoted phrases together.
+ Supports both single and double quotes, and supports escaping quotes with
+ backslashes. In the output, strings will keep their initial and trailing
+ quote marks.
+ >>> list(smart_split('This is "a person\'s" test.'))
+ ['This', 'is', '"a person\'s"', 'test.']
+ """
+ for bit in smart_split_re.finditer(text):
+ bit = bit.group(0)
+ if bit[0] == '"':
+ yield '"' + bit[1:-1].replace('\\"', '"').replace('\\\\', '\\') + '"'
+ elif bit[0] == "'":
+ yield "'" + bit[1:-1].replace("\\'", "'").replace("\\\\", "\\") + "'"
+ else:
+ yield bit
diff --git a/google_appengine/lib/django/django/utils/text.pyc b/google_appengine/lib/django/django/utils/text.pyc
new file mode 100644
index 0000000..4bf67a2
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/text.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/timesince.py b/google_appengine/lib/django/django/utils/timesince.py
new file mode 100755
index 0000000..e69c45c
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/timesince.py
@@ -0,0 +1,57 @@
+import datetime, math, time
+from django.utils.tzinfo import LocalTimezone
+from django.utils.translation import ngettext
+
+def timesince(d, now=None):
+ """
+ Takes two datetime objects and returns the time between then and now
+ as a nicely formatted string, e.g "10 minutes"
+ Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
+ """
+ chunks = (
+ (60 * 60 * 24 * 365, lambda n: ngettext('year', 'years', n)),
+ (60 * 60 * 24 * 30, lambda n: ngettext('month', 'months', n)),
+ (60 * 60 * 24 * 7, lambda n : ngettext('week', 'weeks', n)),
+ (60 * 60 * 24, lambda n : ngettext('day', 'days', n)),
+ (60 * 60, lambda n: ngettext('hour', 'hours', n)),
+ (60, lambda n: ngettext('minute', 'minutes', n))
+ )
+ # Convert datetime.date to datetime.datetime for comparison
+ if d.__class__ is not datetime.datetime:
+ d = datetime.datetime(d.year, d.month, d.day)
+ if now:
+ t = now.timetuple()
+ else:
+ t = time.localtime()
+ if d.tzinfo:
+ tz = LocalTimezone(d)
+ else:
+ tz = None
+ now = datetime.datetime(t[0], t[1], t[2], t[3], t[4], t[5], tzinfo=tz)
+
+ # ignore microsecond part of 'd' since we removed it from 'now'
+ delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
+ since = delta.days * 24 * 60 * 60 + delta.seconds
+ for i, (seconds, name) in enumerate(chunks):
+ count = since / seconds
+ if count != 0:
+ break
+ if count < 0:
+ return '%d milliseconds' % math.floor((now - d).microseconds / 1000)
+ s = '%d %s' % (count, name(count))
+ if i + 1 < len(chunks):
+ # Now get the second item
+ seconds2, name2 = chunks[i + 1]
+ count2 = (since - (seconds * count)) / seconds2
+ if count2 != 0:
+ s += ', %d %s' % (count2, name2(count2))
+ return s
+
+def timeuntil(d, now=None):
+ """
+ Like timesince, but returns a string measuring the time until
+ the given time.
+ """
+ if now == None:
+ now = datetime.datetime.now()
+ return timesince(now, d)
diff --git a/google_appengine/lib/django/django/utils/timesince.pyc b/google_appengine/lib/django/django/utils/timesince.pyc
new file mode 100644
index 0000000..9c7e63d
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/timesince.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/translation/__init__.py b/google_appengine/lib/django/django/utils/translation/__init__.py
new file mode 100755
index 0000000..276e59f
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/translation/__init__.py
@@ -0,0 +1,8 @@
+from django.conf import settings
+
+if settings.USE_I18N:
+ from trans_real import *
+else:
+ from trans_null import *
+
+del settings
diff --git a/google_appengine/lib/django/django/utils/translation/__init__.pyc b/google_appengine/lib/django/django/utils/translation/__init__.pyc
new file mode 100644
index 0000000..68b9e6b
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/translation/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/translation/trans_null.py b/google_appengine/lib/django/django/utils/translation/trans_null.py
new file mode 100755
index 0000000..75ad573
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/translation/trans_null.py
@@ -0,0 +1,30 @@
+# These are versions of the functions in django.utils.translation.trans_real
+# that don't actually do anything. This is purely for performance, so that
+# settings.USE_I18N = False can use this module rather than trans_real.py.
+
+from django.conf import settings
+
+def ngettext(singular, plural, number):
+ if number == 1: return singular
+ return plural
+ngettext_lazy = ngettext
+
+gettext = gettext_noop = gettext_lazy = _ = lambda x: x
+string_concat = lambda *strings: ''.join([str(el) for el in strings])
+activate = lambda x: None
+deactivate = install = lambda: None
+get_language = lambda: settings.LANGUAGE_CODE
+get_language_bidi = lambda: settings.LANGUAGE_CODE in settings.LANGUAGES_BIDI
+get_date_formats = lambda: (settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT)
+get_partial_date_formats = lambda: (settings.YEAR_MONTH_FORMAT, settings.MONTH_DAY_FORMAT)
+check_for_language = lambda x: True
+
+def to_locale(language):
+ p = language.find('-')
+ if p >= 0:
+ return language[:p].lower()+'_'+language[p+1:].upper()
+ else:
+ return language.lower()
+
+def get_language_from_request(request):
+ return settings.LANGUAGE_CODE
diff --git a/google_appengine/lib/django/django/utils/translation/trans_real.py b/google_appengine/lib/django/django/utils/translation/trans_real.py
new file mode 100755
index 0000000..e576c57
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/translation/trans_real.py
@@ -0,0 +1,524 @@
+"Translation helper functions"
+
+import locale
+import os
+import re
+import sys
+import gettext as gettext_module
+from cStringIO import StringIO
+from django.utils.functional import lazy
+
+try:
+ import threading
+ hasThreads = True
+except ImportError:
+ hasThreads = False
+
+if hasThreads:
+ currentThread = threading.currentThread
+else:
+ def currentThread():
+ return 'no threading'
+
+# Translations are cached in a dictionary for every language+app tuple.
+# The active translations are stored by threadid to make them thread local.
+_translations = {}
+_active = {}
+
+# The default translation is based on the settings file.
+_default = None
+
+# This is a cache for normalised accept-header languages to prevent multiple
+# file lookups when checking the same locale on repeated requests.
+_accepted = {}
+
+# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
+accept_language_re = re.compile(r'''
+ ([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
+ (?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
+ (?:\s*,\s*|$) # Multiple accepts per header.
+ ''', re.VERBOSE)
+
+def to_locale(language, to_lower=False):
+ "Turns a language name (en-us) into a locale name (en_US)."
+ p = language.find('-')
+ if p >= 0:
+ if to_lower:
+ return language[:p].lower()+'_'+language[p+1:].lower()
+ else:
+ return language[:p].lower()+'_'+language[p+1:].upper()
+ else:
+ return language.lower()
+
+def to_language(locale):
+ "Turns a locale name (en_US) into a language name (en-us)."
+ p = locale.find('_')
+ if p >= 0:
+ return locale[:p].lower()+'-'+locale[p+1:].lower()
+ else:
+ return locale.lower()
+
+class DjangoTranslation(gettext_module.GNUTranslations):
+ """
+ This class sets up the GNUTranslations context with regard to output
+ charset. Django uses a defined DEFAULT_CHARSET as the output charset on
+ Python 2.4. With Python 2.3, use DjangoTranslation23.
+ """
+ def __init__(self, *args, **kw):
+ from django.conf import settings
+ gettext_module.GNUTranslations.__init__(self, *args, **kw)
+ # Starting with Python 2.4, there's a function to define
+ # the output charset. Before 2.4, the output charset is
+ # identical with the translation file charset.
+ try:
+ self.set_output_charset(settings.DEFAULT_CHARSET)
+ except AttributeError:
+ pass
+ self.django_output_charset = settings.DEFAULT_CHARSET
+ self.__language = '??'
+
+ def merge(self, other):
+ self._catalog.update(other._catalog)
+
+ def set_language(self, language):
+ self.__language = language
+
+ def language(self):
+ return self.__language
+
+ def __repr__(self):
+ return "<DjangoTranslation lang:%s>" % self.__language
+
+class DjangoTranslation23(DjangoTranslation):
+ """
+ Compatibility class that is only used with Python 2.3.
+ Python 2.3 doesn't support set_output_charset on translation objects and
+ needs this wrapper class to make sure input charsets from translation files
+ are correctly translated to output charsets.
+
+ With a full switch to Python 2.4, this can be removed from the source.
+ """
+ def gettext(self, msgid):
+ res = self.ugettext(msgid)
+ return res.encode(self.django_output_charset)
+
+ def ngettext(self, msgid1, msgid2, n):
+ res = self.ungettext(msgid1, msgid2, n)
+ return res.encode(self.django_output_charset)
+
+def translation(language):
+ """
+ Returns a translation object.
+
+ This translation object will be constructed out of multiple GNUTranslations
+ objects by merging their catalogs. It will construct a object for the
+ requested language and add a fallback to the default language, if it's
+ different from the requested language.
+ """
+ global _translations
+
+ t = _translations.get(language, None)
+ if t is not None:
+ return t
+
+ from django.conf import settings
+
+ # set up the right translation class
+ klass = DjangoTranslation
+ if sys.version_info < (2, 4):
+ klass = DjangoTranslation23
+
+ globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale')
+
+ if settings.SETTINGS_MODULE is not None:
+ parts = settings.SETTINGS_MODULE.split('.')
+ project = __import__(parts[0], {}, {}, [])
+ projectpath = os.path.join(os.path.dirname(project.__file__), 'locale')
+ else:
+ projectpath = None
+
+ def _fetch(lang, fallback=None):
+
+ global _translations
+
+ loc = to_locale(lang)
+
+ res = _translations.get(lang, None)
+ if res is not None:
+ return res
+
+ def _translation(path):
+ try:
+ t = gettext_module.translation('django', path, [loc], klass)
+ t.set_language(lang)
+ return t
+ except IOError, e:
+ return None
+
+ res = _translation(globalpath)
+
+ def _merge(path):
+ t = _translation(path)
+ if t is not None:
+ if res is None:
+ return t
+ else:
+ res.merge(t)
+ return res
+
+ if hasattr(settings, 'LOCALE_PATHS'):
+ for localepath in settings.LOCALE_PATHS:
+ if os.path.isdir(localepath):
+ res = _merge(localepath)
+
+ if projectpath and os.path.isdir(projectpath):
+ res = _merge(projectpath)
+
+ for appname in settings.INSTALLED_APPS:
+ p = appname.rfind('.')
+ if p >= 0:
+ app = getattr(__import__(appname[:p], {}, {}, [appname[p+1:]]), appname[p+1:])
+ else:
+ app = __import__(appname, {}, {}, [])
+
+ apppath = os.path.join(os.path.dirname(app.__file__), 'locale')
+
+ if os.path.isdir(apppath):
+ res = _merge(apppath)
+
+ if res is None:
+ if fallback is not None:
+ res = fallback
+ else:
+ return gettext_module.NullTranslations()
+ _translations[lang] = res
+ return res
+
+ default_translation = _fetch(settings.LANGUAGE_CODE)
+ current_translation = _fetch(language, fallback=default_translation)
+
+ return current_translation
+
+def activate(language):
+ """
+ Fetches the translation object for a given tuple of application name and
+ language and installs it as the current translation object for the current
+ thread.
+ """
+ _active[currentThread()] = translation(language)
+
+def deactivate():
+ """
+ Deinstalls the currently active translation object so that further _ calls
+ will resolve against the default translation object, again.
+ """
+ global _active
+ if _active.has_key(currentThread()):
+ del _active[currentThread()]
+
+def get_language():
+ "Returns the currently selected language."
+ t = _active.get(currentThread(), None)
+ if t is not None:
+ try:
+ return to_language(t.language())
+ except AttributeError:
+ pass
+ # If we don't have a real translation object, assume it's the default language.
+ from django.conf import settings
+ return settings.LANGUAGE_CODE
+
+def get_language_bidi():
+ """
+ Returns selected language's BiDi layout.
+ False = left-to-right layout
+ True = right-to-left layout
+ """
+ from django.conf import settings
+ return get_language() in settings.LANGUAGES_BIDI
+
+def catalog():
+ """
+ This function returns the current active catalog for further processing.
+ This can be used if you need to modify the catalog or want to access the
+ whole message catalog instead of just translating one string.
+ """
+ global _default, _active
+ t = _active.get(currentThread(), None)
+ if t is not None:
+ return t
+ if _default is None:
+ from django.conf import settings
+ _default = translation(settings.LANGUAGE_CODE)
+ return _default
+
+def gettext(message):
+ """
+ This function will be patched into the builtins module to provide the _
+ helper function. It will use the current thread as a discriminator to find
+ the translation object to use. If no current translation is activated, the
+ message will be run through the default translation object.
+ """
+ global _default, _active
+ t = _active.get(currentThread(), None)
+ if t is not None:
+ return t.gettext(message)
+ if _default is None:
+ from django.conf import settings
+ _default = translation(settings.LANGUAGE_CODE)
+ return _default.gettext(message)
+
+def gettext_noop(message):
+ """
+ Marks strings for translation but doesn't translate them now. This can be
+ used to store strings in global variables that should stay in the base
+ language (because they might be used externally) and will be translated later.
+ """
+ return message
+
+def ngettext(singular, plural, number):
+ """
+ Returns the translation of either the singular or plural, based on the number.
+ """
+ global _default, _active
+
+ t = _active.get(currentThread(), None)
+ if t is not None:
+ return t.ngettext(singular, plural, number)
+ if _default is None:
+ from django.conf import settings
+ _default = translation(settings.LANGUAGE_CODE)
+ return _default.ngettext(singular, plural, number)
+
+gettext_lazy = lazy(gettext, str)
+ngettext_lazy = lazy(ngettext, str)
+
+def check_for_language(lang_code):
+ """
+ Checks whether there is a global language file for the given language code.
+ This is used to decide whether a user-provided language is available. This is
+ only used for language codes from either the cookies or session.
+ """
+ from django.conf import settings
+ globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale')
+ if gettext_module.find('django', globalpath, [to_locale(lang_code)]) is not None:
+ return True
+ else:
+ return False
+
+def get_language_from_request(request):
+ """
+ Analyzes the request to find what language the user wants the system to show.
+ Only languages listed in settings.LANGUAGES are taken into account. If the user
+ requests a sublanguage where we have a main language, we send out the main language.
+ """
+ global _accepted
+ from django.conf import settings
+ globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale')
+ supported = dict(settings.LANGUAGES)
+
+ if hasattr(request, 'session'):
+ lang_code = request.session.get('django_language', None)
+ if lang_code in supported and lang_code is not None and check_for_language(lang_code):
+ return lang_code
+
+ lang_code = request.COOKIES.get('django_language')
+ if lang_code and lang_code in supported and check_for_language(lang_code):
+ return lang_code
+
+ accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
+ for lang, unused in parse_accept_lang_header(accept):
+ if lang == '*':
+ break
+
+ # We have a very restricted form for our language files (no encoding
+ # specifier, since they all must be UTF-8 and only one possible
+ # language each time. So we avoid the overhead of gettext.find() and
+ # look up the MO file manually.
+
+ normalized = locale.locale_alias.get(to_locale(lang, True))
+ if not normalized:
+ continue
+
+ # Remove the default encoding from locale_alias
+ normalized = normalized.split('.')[0]
+
+ if normalized in _accepted:
+ # We've seen this locale before and have an MO file for it, so no
+ # need to check again.
+ return _accepted[normalized]
+
+ for lang in (normalized, normalized.split('_')[0]):
+ if lang not in supported:
+ continue
+ langfile = os.path.join(globalpath, lang, 'LC_MESSAGES',
+ 'django.mo')
+ if os.path.exists(langfile):
+ _accepted[normalized] = lang
+ return lang
+
+ return settings.LANGUAGE_CODE
+
+def get_date_formats():
+ """
+ This function checks whether translation files provide a translation for some
+ technical message ID to store date and time formats. If it doesn't contain
+ one, the formats provided in the settings will be used.
+ """
+ from django.conf import settings
+ date_format = _('DATE_FORMAT')
+ datetime_format = _('DATETIME_FORMAT')
+ time_format = _('TIME_FORMAT')
+ if date_format == 'DATE_FORMAT':
+ date_format = settings.DATE_FORMAT
+ if datetime_format == 'DATETIME_FORMAT':
+ datetime_format = settings.DATETIME_FORMAT
+ if time_format == 'TIME_FORMAT':
+ time_format = settings.TIME_FORMAT
+ return date_format, datetime_format, time_format
+
+def get_partial_date_formats():
+ """
+ This function checks whether translation files provide a translation for some
+ technical message ID to store partial date formats. If it doesn't contain
+ one, the formats provided in the settings will be used.
+ """
+ from django.conf import settings
+ year_month_format = _('YEAR_MONTH_FORMAT')
+ month_day_format = _('MONTH_DAY_FORMAT')
+ if year_month_format == 'YEAR_MONTH_FORMAT':
+ year_month_format = settings.YEAR_MONTH_FORMAT
+ if month_day_format == 'MONTH_DAY_FORMAT':
+ month_day_format = settings.MONTH_DAY_FORMAT
+ return year_month_format, month_day_format
+
+def install():
+ """
+ Installs the gettext function as the default translation function under
+ the name '_'.
+ """
+ __builtins__['_'] = gettext
+
+dot_re = re.compile(r'\S')
+def blankout(src, char):
+ """
+ Changes every non-whitespace character to the given char.
+ Used in the templatize function.
+ """
+ return dot_re.sub(char, src)
+
+inline_re = re.compile(r"""^\s*trans\s+((?:".*?")|(?:'.*?'))\s*""")
+block_re = re.compile(r"""^\s*blocktrans(?:\s+|$)""")
+endblock_re = re.compile(r"""^\s*endblocktrans$""")
+plural_re = re.compile(r"""^\s*plural$""")
+constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")
+def templatize(src):
+ """
+ Turns a Django template into something that is understood by xgettext. It
+ does so by translating the Django translation tags into standard gettext
+ function invocations.
+ """
+ from django.template import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK
+ out = StringIO()
+ intrans = False
+ inplural = False
+ singular = []
+ plural = []
+ for t in Lexer(src, None).tokenize():
+ if intrans:
+ if t.token_type == TOKEN_BLOCK:
+ endbmatch = endblock_re.match(t.contents)
+ pluralmatch = plural_re.match(t.contents)
+ if endbmatch:
+ if inplural:
+ out.write(' ngettext(%r,%r,count) ' % (''.join(singular), ''.join(plural)))
+ for part in singular:
+ out.write(blankout(part, 'S'))
+ for part in plural:
+ out.write(blankout(part, 'P'))
+ else:
+ out.write(' gettext(%r) ' % ''.join(singular))
+ for part in singular:
+ out.write(blankout(part, 'S'))
+ intrans = False
+ inplural = False
+ singular = []
+ plural = []
+ elif pluralmatch:
+ inplural = True
+ else:
+ raise SyntaxError, "Translation blocks must not include other block tags: %s" % t.contents
+ elif t.token_type == TOKEN_VAR:
+ if inplural:
+ plural.append('%%(%s)s' % t.contents)
+ else:
+ singular.append('%%(%s)s' % t.contents)
+ elif t.token_type == TOKEN_TEXT:
+ if inplural:
+ plural.append(t.contents)
+ else:
+ singular.append(t.contents)
+ else:
+ if t.token_type == TOKEN_BLOCK:
+ imatch = inline_re.match(t.contents)
+ bmatch = block_re.match(t.contents)
+ cmatches = constant_re.findall(t.contents)
+ if imatch:
+ g = imatch.group(1)
+ if g[0] == '"': g = g.strip('"')
+ elif g[0] == "'": g = g.strip("'")
+ out.write(' gettext(%r) ' % g)
+ elif bmatch:
+ intrans = True
+ inplural = False
+ singular = []
+ plural = []
+ elif cmatches:
+ for cmatch in cmatches:
+ out.write(' _(%s) ' % cmatch)
+ else:
+ out.write(blankout(t.contents, 'B'))
+ elif t.token_type == TOKEN_VAR:
+ parts = t.contents.split('|')
+ cmatch = constant_re.match(parts[0])
+ if cmatch:
+ out.write(' _(%s) ' % cmatch.group(1))
+ for p in parts[1:]:
+ if p.find(':_(') >= 0:
+ out.write(' %s ' % p.split(':',1)[1])
+ else:
+ out.write(blankout(p, 'F'))
+ else:
+ out.write(blankout(t.contents, 'X'))
+ return out.getvalue()
+
+def string_concat(*strings):
+ """"
+ lazy variant of string concatenation, needed for translations that are
+ constructed from multiple parts. Handles lazy strings and non-strings by
+ first turning all arguments to strings, before joining them.
+ """
+ return ''.join([str(el) for el in strings])
+
+string_concat = lazy(string_concat, str)
+
+def parse_accept_lang_header(lang_string):
+ """
+ Parses the lang_string, which is the body of an HTTP Accept-Language
+ header, and returns a list of (lang, q-value), ordered by 'q' values.
+
+ Any format errors in lang_string results in an empty list being returned.
+ """
+ result = []
+ pieces = accept_language_re.split(lang_string)
+ if pieces[-1]:
+ return []
+ for i in range(0, len(pieces) - 1, 3):
+ first, lang, priority = pieces[i : i + 3]
+ if first:
+ return []
+ priority = priority and float(priority) or 1.0
+ result.append((lang, priority))
+ result.sort(lambda x, y: -cmp(x[1], y[1]))
+ return result
+
diff --git a/google_appengine/lib/django/django/utils/translation/trans_real.pyc b/google_appengine/lib/django/django/utils/translation/trans_real.pyc
new file mode 100644
index 0000000..336bfcc
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/translation/trans_real.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/tzinfo.py b/google_appengine/lib/django/django/utils/tzinfo.py
new file mode 100755
index 0000000..cc9f028
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/tzinfo.py
@@ -0,0 +1,52 @@
+"Implementation of tzinfo classes for use with datetime.datetime."
+
+import time
+from datetime import timedelta, tzinfo
+
+class FixedOffset(tzinfo):
+ "Fixed offset in minutes east from UTC."
+ def __init__(self, offset):
+ self.__offset = timedelta(minutes=offset)
+ self.__name = "%+03d%02d" % (offset // 60, offset % 60)
+
+ def __repr__(self):
+ return self.__name
+
+ def utcoffset(self, dt):
+ return self.__offset
+
+ def tzname(self, dt):
+ return self.__name
+
+ def dst(self, dt):
+ return timedelta(0)
+
+class LocalTimezone(tzinfo):
+ "Proxy timezone information from time module."
+ def __init__(self, dt):
+ tzinfo.__init__(self, dt)
+ self._tzname = time.tzname[self._isdst(dt)]
+
+ def __repr__(self):
+ return self._tzname
+
+ def utcoffset(self, dt):
+ if self._isdst(dt):
+ return timedelta(seconds=-time.altzone)
+ else:
+ return timedelta(seconds=-time.timezone)
+
+ def dst(self, dt):
+ if self._isdst(dt):
+ return timedelta(seconds=-time.altzone) - timedelta(seconds=-time.timezone)
+ else:
+ return timedelta(0)
+
+ def tzname(self, dt):
+ return time.tzname[self._isdst(dt)]
+
+ def _isdst(self, dt):
+ tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1)
+ stamp = time.mktime(tt)
+ tt = time.localtime(stamp)
+ return tt.tm_isdst > 0
diff --git a/google_appengine/lib/django/django/utils/tzinfo.pyc b/google_appengine/lib/django/django/utils/tzinfo.pyc
new file mode 100644
index 0000000..a547270
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/tzinfo.pyc
Binary files differ
diff --git a/google_appengine/lib/django/django/utils/xmlutils.py b/google_appengine/lib/django/django/utils/xmlutils.py
new file mode 100755
index 0000000..a1eb5fb
--- /dev/null
+++ b/google_appengine/lib/django/django/utils/xmlutils.py
@@ -0,0 +1,14 @@
+"""
+Utilities for XML generation/parsing.
+"""
+
+from xml.sax.saxutils import XMLGenerator
+
+class SimplerXMLGenerator(XMLGenerator):
+ def addQuickElement(self, name, contents=None, attrs=None):
+ "Convenience method for adding an element with no children"
+ if attrs is None: attrs = {}
+ self.startElement(name, attrs)
+ if contents is not None:
+ self.characters(contents)
+ self.endElement(name)
diff --git a/google_appengine/lib/django/django/views/__init__.py b/google_appengine/lib/django/django/views/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/views/__init__.py
diff --git a/google_appengine/lib/django/django/views/debug.py b/google_appengine/lib/django/django/views/debug.py
new file mode 100755
index 0000000..77b6c2f
--- /dev/null
+++ b/google_appengine/lib/django/django/views/debug.py
@@ -0,0 +1,661 @@
+from django.conf import settings
+from django.template import Template, Context, TemplateDoesNotExist
+from django.utils.html import escape
+from django.http import HttpResponseServerError, HttpResponseNotFound
+import os, re
+
+HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
+
+def linebreak_iter(template_source):
+ yield 0
+ p = template_source.find('\n')
+ while p >= 0:
+ yield p+1
+ p = template_source.find('\n', p+1)
+ yield len(template_source) + 1
+
+def get_template_exception_info(exc_type, exc_value, tb):
+ origin, (start, end) = exc_value.source
+ template_source = origin.reload()
+ context_lines = 10
+ line = 0
+ upto = 0
+ source_lines = []
+ before = during = after = ""
+ for num, next in enumerate(linebreak_iter(template_source)):
+ if start >= upto and end <= next:
+ line = num
+ before = escape(template_source[upto:start])
+ during = escape(template_source[start:end])
+ after = escape(template_source[end:next])
+ source_lines.append( (num, escape(template_source[upto:next])) )
+ upto = next
+ total = len(source_lines)
+
+ top = max(1, line - context_lines)
+ bottom = min(total, line + 1 + context_lines)
+
+ template_info = {
+ 'message': exc_value.args[0],
+ 'source_lines': source_lines[top:bottom],
+ 'before': before,
+ 'during': during,
+ 'after': after,
+ 'top': top,
+ 'bottom': bottom,
+ 'total': total,
+ 'line': line,
+ 'name': origin.name,
+ }
+ exc_info = hasattr(exc_value, 'exc_info') and exc_value.exc_info or (exc_type, exc_value, tb)
+ return exc_info + (template_info,)
+
+def get_safe_settings():
+ "Returns a dictionary of the settings module, with sensitive settings blurred out."
+ settings_dict = {}
+ for k in dir(settings):
+ if k.isupper():
+ if HIDDEN_SETTINGS.search(k):
+ settings_dict[k] = '********************'
+ else:
+ settings_dict[k] = getattr(settings, k)
+ return settings_dict
+
+def technical_500_response(request, exc_type, exc_value, tb):
+ """
+ Create a technical server error response. The last three arguments are
+ the values returned from sys.exc_info() and friends.
+ """
+ template_info = None
+ template_does_not_exist = False
+ loader_debug_info = None
+ if issubclass(exc_type, TemplateDoesNotExist):
+ from django.template.loader import template_source_loaders
+ template_does_not_exist = True
+ loader_debug_info = []
+ for loader in template_source_loaders:
+ try:
+ source_list_func = getattr(__import__(loader.__module__, {}, {}, ['get_template_sources']), 'get_template_sources')
+ # NOTE: This assumes exc_value is the name of the template that
+ # the loader attempted to load.
+ template_list = [{'name': t, 'exists': os.path.exists(t)} \
+ for t in source_list_func(str(exc_value))]
+ except (ImportError, AttributeError):
+ template_list = []
+ loader_debug_info.append({
+ 'loader': loader.__module__ + '.' + loader.__name__,
+ 'templates': template_list,
+ })
+ if settings.TEMPLATE_DEBUG and hasattr(exc_value, 'source'):
+ exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb)
+ frames = []
+ while tb is not None:
+ filename = tb.tb_frame.f_code.co_filename
+ function = tb.tb_frame.f_code.co_name
+ lineno = tb.tb_lineno - 1
+ pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7)
+ if pre_context_lineno:
+ frames.append({
+ 'tb': tb,
+ 'filename': filename,
+ 'function': function,
+ 'lineno': lineno + 1,
+ 'vars': tb.tb_frame.f_locals.items(),
+ 'id': id(tb),
+ 'pre_context': pre_context,
+ 'context_line': context_line,
+ 'post_context': post_context,
+ 'pre_context_lineno': pre_context_lineno + 1,
+ })
+ tb = tb.tb_next
+
+ if not frames:
+ frames = [{
+ 'filename': '&lt;unknown&gt;',
+ 'function': '?',
+ 'lineno': '?',
+ }]
+ t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
+ c = Context({
+ 'exception_type': exc_type.__name__,
+ 'exception_value': exc_value,
+ 'frames': frames,
+ 'lastframe': frames[-1],
+ 'request': request,
+ 'request_protocol': request.is_secure() and "https" or "http",
+ 'settings': get_safe_settings(),
+ 'template_info': template_info,
+ 'template_does_not_exist': template_does_not_exist,
+ 'loader_debug_info': loader_debug_info,
+ })
+ return HttpResponseServerError(t.render(c), mimetype='text/html')
+
+def technical_404_response(request, exception):
+ "Create a technical 404 error response. The exception should be the Http404."
+ try:
+ tried = exception.args[0]['tried']
+ except (IndexError, TypeError):
+ tried = []
+ else:
+ if not tried:
+ # tried exists but is an empty list. The URLconf must've been empty.
+ return empty_urlconf(request)
+
+ t = Template(TECHNICAL_404_TEMPLATE, name='Technical 404 template')
+ c = Context({
+ 'root_urlconf': settings.ROOT_URLCONF,
+ 'urlpatterns': tried,
+ 'reason': str(exception),
+ 'request': request,
+ 'request_protocol': request.is_secure() and "https" or "http",
+ 'settings': get_safe_settings(),
+ })
+ return HttpResponseNotFound(t.render(c), mimetype='text/html')
+
+def empty_urlconf(request):
+ "Create an empty URLconf 404 error response."
+ t = Template(EMPTY_URLCONF_TEMPLATE, name='Empty URLConf template')
+ c = Context({
+ 'project_name': settings.SETTINGS_MODULE.split('.')[0]
+ })
+ return HttpResponseNotFound(t.render(c), mimetype='text/html')
+
+def _get_lines_from_file(filename, lineno, context_lines):
+ """
+ Returns context_lines before and after lineno from file.
+ Returns (pre_context_lineno, pre_context, context_line, post_context).
+ """
+ try:
+ source = open(filename).readlines()
+ lower_bound = max(0, lineno - context_lines)
+ upper_bound = lineno + context_lines
+
+ pre_context = [line.strip('\n') for line in source[lower_bound:lineno]]
+ context_line = source[lineno].strip('\n')
+ post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]]
+
+ return lower_bound, pre_context, context_line, post_context
+ except (OSError, IOError):
+ return None, [], None, []
+
+#
+# Templates are embedded in the file so that we know the error handler will
+# always work even if the template loader is broken.
+#
+
+TECHNICAL_500_TEMPLATE = """
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ <title>{{ exception_type }} at {{ request.path|escape }}</title>
+ <style type="text/css">
+ html * { padding:0; margin:0; }
+ body * { padding:10px 20px; }
+ body * * { padding:0; }
+ body { font:small sans-serif; }
+ body>div { border-bottom:1px solid #ddd; }
+ h1 { font-weight:normal; }
+ h2 { margin-bottom:.8em; }
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
+ h3 { margin:1em 0 .5em 0; }
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
+ table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
+ thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; }
+ tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
+ table.vars { margin:5px 0 2px 40px; }
+ table.vars td, table.req td { font-family:monospace; }
+ table td.code { width:100%; }
+ table td.code div { overflow:hidden; }
+ table.source th { color:#666; }
+ table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
+ ul.traceback { list-style-type:none; }
+ ul.traceback li.frame { margin-bottom:1em; }
+ div.context { margin: 10px 0; }
+ div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; }
+ div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
+ div.context ol.context-line li { color:black; background-color:#ccc; }
+ div.context ol.context-line li span { float: right; }
+ div.commands { margin-left: 40px; }
+ div.commands a { color:black; text-decoration:none; }
+ #summary { background: #ffc; }
+ #summary h2 { font-weight: normal; color: #666; }
+ #explanation { background:#eee; }
+ #template, #template-not-exist { background:#f6f6f6; }
+ #template-not-exist ul { margin: 0 0 0 20px; }
+ #traceback { background:#eee; }
+ #requestinfo { background:#f6f6f6; padding-left:120px; }
+ #summary table { border:none; background:transparent; }
+ #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
+ #requestinfo h3 { margin-bottom:-1em; }
+ .error { background: #ffc; }
+ .specific { color:#cc3300; font-weight:bold; }
+ </style>
+ <script type="text/javascript">
+ //<!--
+ function getElementsByClassName(oElm, strTagName, strClassName){
+ // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
+ var arrElements = (strTagName == "*" && document.all)? document.all :
+ oElm.getElementsByTagName(strTagName);
+ var arrReturnElements = new Array();
+ strClassName = strClassName.replace(/\-/g, "\\-");
+ var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
+ var oElement;
+ for(var i=0; i<arrElements.length; i++){
+ oElement = arrElements[i];
+ if(oRegExp.test(oElement.className)){
+ arrReturnElements.push(oElement);
+ }
+ }
+ return (arrReturnElements)
+ }
+ function hideAll(elems) {
+ for (var e = 0; e < elems.length; e++) {
+ elems[e].style.display = 'none';
+ }
+ }
+ window.onload = function() {
+ hideAll(getElementsByClassName(document, 'table', 'vars'));
+ hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
+ hideAll(getElementsByClassName(document, 'ol', 'post-context'));
+ hideAll(getElementsByClassName(document, 'div', 'pastebin'));
+ }
+ function toggle() {
+ for (var i = 0; i < arguments.length; i++) {
+ var e = document.getElementById(arguments[i]);
+ if (e) {
+ e.style.display = e.style.display == 'none' ? 'block' : 'none';
+ }
+ }
+ return false;
+ }
+ function varToggle(link, id) {
+ toggle('v' + id);
+ var s = link.getElementsByTagName('span')[0];
+ var uarr = String.fromCharCode(0x25b6);
+ var darr = String.fromCharCode(0x25bc);
+ s.innerHTML = s.innerHTML == uarr ? darr : uarr;
+ return false;
+ }
+ function switchPastebinFriendly(link) {
+ s1 = "Switch to copy-and-paste view";
+ s2 = "Switch back to interactive view";
+ link.innerHTML = link.innerHTML == s1 ? s2 : s1;
+ toggle('browserTraceback', 'pastebinTraceback');
+ return false;
+ }
+ //-->
+ </script>
+</head>
+<body>
+
+<div id="summary">
+ <h1>{{ exception_type }} at {{ request.path|escape }}</h1>
+ <h2>{{ exception_value|escape }}</h2>
+ <table class="meta">
+ <tr>
+ <th>Request Method:</th>
+ <td>{{ request.META.REQUEST_METHOD }}</td>
+ </tr>
+ <tr>
+ <th>Request URL:</th>
+ <td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path|escape }}</td>
+ </tr>
+ <tr>
+ <th>Exception Type:</th>
+ <td>{{ exception_type }}</td>
+ </tr>
+ <tr>
+ <th>Exception Value:</th>
+ <td>{{ exception_value|escape }}</td>
+ </tr>
+ <tr>
+ <th>Exception Location:</th>
+ <td>{{ lastframe.filename }} in {{ lastframe.function }}, line {{ lastframe.lineno }}</td>
+ </tr>
+ </table>
+</div>
+{% if template_does_not_exist %}
+<div id="template-not-exist">
+ <h2>Template-loader postmortem</h2>
+ {% if loader_debug_info %}
+ <p>Django tried loading these templates, in this order:</p>
+ <ul>
+ {% for loader in loader_debug_info %}
+ <li>Using loader <code>{{ loader.loader }}</code>:
+ <ul>{% for t in loader.templates %}<li><code>{{ t.name }}</code> (File {% if t.exists %}exists{% else %}does not exist{% endif %})</li>{% endfor %}</ul>
+ </li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <p>Django couldn't find any templates because your <code>TEMPLATE_LOADERS</code> setting is empty!</p>
+ {% endif %}
+</div>
+{% endif %}
+{% if template_info %}
+<div id="template">
+ <h2>Template error</h2>
+ <p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
+ <h3>{{ template_info.message|escape }}</h3>
+ <table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}">
+ {% for source_line in template_info.source_lines %}
+ {% ifequal source_line.0 template_info.line %}
+ <tr class="error"><th>{{ source_line.0 }}</th>
+ <td>{{ template_info.before }}<span class="specific">{{ template_info.during }}</span>{{ template_info.after }}</td></tr>
+ {% else %}
+ <tr><th>{{ source_line.0 }}</th>
+ <td>{{ source_line.1 }}</td></tr>
+ {% endifequal %}
+ {% endfor %}
+ </table>
+</div>
+{% endif %}
+<div id="traceback">
+ <h2>Traceback <span>(innermost last)</span></h2>
+ <div class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></div>
+ <br/>
+ <div id="browserTraceback">
+ <ul class="traceback">
+ {% for frame in frames %}
+ <li class="frame">
+ <code>{{ frame.filename }}</code> in <code>{{ frame.function }}</code>
+
+ {% if frame.context_line %}
+ <div class="context" id="c{{ frame.id }}">
+ {% if frame.pre_context %}
+ <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
+ {% endif %}
+ <ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line|escape }} <span>...</span></li></ol>
+ {% if frame.post_context %}
+ <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
+ {% endif %}
+ </div>
+ {% endif %}
+
+ {% if frame.vars %}
+ <div class="commands">
+ <a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Local vars</a>
+ </div>
+ <table class="vars" id="v{{ frame.id }}">
+ <thead>
+ <tr>
+ <th>Variable</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for var in frame.vars|dictsort:"0" %}
+ <tr>
+ <td>{{ var.0 }}</td>
+ <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ <div id="pastebinTraceback" class="pastebin">
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <code>
+Traceback (most recent call last):<br/>
+{% for frame in frames %}
+ File "{{ frame.filename }}" in {{ frame.function }}<br/>
+ {% if frame.context_line %}
+ &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
+ {% endif %}
+{% endfor %}<br/>
+&nbsp;&nbsp;{{ exception_type }} at {{ request.path|escape }}<br/>
+&nbsp;&nbsp;{{ exception_value|escape }}</code>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
+
+<div id="requestinfo">
+ <h2>Request information</h2>
+
+ <h3 id="get-info">GET</h3>
+ {% if request.GET %}
+ <table class="req">
+ <thead>
+ <tr>
+ <th>Variable</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for var in request.GET.items %}
+ <tr>
+ <td>{{ var.0 }}</td>
+ <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% else %}
+ <p>No GET data</p>
+ {% endif %}
+
+ <h3 id="post-info">POST</h3>
+ {% if request.POST %}
+ <table class="req">
+ <thead>
+ <tr>
+ <th>Variable</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for var in request.POST.items %}
+ <tr>
+ <td>{{ var.0 }}</td>
+ <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% else %}
+ <p>No POST data</p>
+ {% endif %}
+
+ <h3 id="cookie-info">COOKIES</h3>
+ {% if request.COOKIES %}
+ <table class="req">
+ <thead>
+ <tr>
+ <th>Variable</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for var in request.COOKIES.items %}
+ <tr>
+ <td>{{ var.0 }}</td>
+ <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% else %}
+ <p>No cookie data</p>
+ {% endif %}
+
+ <h3 id="meta-info">META</h3>
+ <table class="req">
+ <thead>
+ <tr>
+ <th>Variable</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for var in request.META.items|dictsort:"0" %}
+ <tr>
+ <td>{{ var.0 }}</td>
+ <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+
+ <h3 id="settings-info">Settings</h3>
+ <h4>Using settings module <code>{{ settings.SETTINGS_MODULE }}</code></h4>
+ <table class="req">
+ <thead>
+ <tr>
+ <th>Setting</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for var in settings.items|dictsort:"0" %}
+ <tr>
+ <td>{{ var.0 }}</td>
+ <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+
+</div>
+
+<div id="explanation">
+ <p>
+ You're seeing this error because you have <code>DEBUG = True</code> in your
+ Django settings file. Change that to <code>False</code>, and Django will
+ display a standard 500 page.
+ </p>
+</div>
+
+</body>
+</html>
+"""
+
+TECHNICAL_404_TEMPLATE = """
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>Page not found at {{ request.path|escape }}</title>
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ <style type="text/css">
+ html * { padding:0; margin:0; }
+ body * { padding:10px 20px; }
+ body * * { padding:0; }
+ body { font:small sans-serif; background:#eee; }
+ body>div { border-bottom:1px solid #ddd; }
+ h1 { font-weight:normal; margin-bottom:.4em; }
+ h1 span { font-size:60%; color:#666; font-weight:normal; }
+ table { border:none; border-collapse: collapse; width:100%; }
+ td, th { vertical-align:top; padding:2px 3px; }
+ th { width:12em; text-align:right; color:#666; padding-right:.5em; }
+ #info { background:#f6f6f6; }
+ #info ol { margin: 0.5em 4em; }
+ #info ol li { font-family: monospace; }
+ #summary { background: #ffc; }
+ #explanation { background:#eee; border-bottom: 0px none; }
+ </style>
+</head>
+<body>
+ <div id="summary">
+ <h1>Page not found <span>(404)</span></h1>
+ <table class="meta">
+ <tr>
+ <th>Request Method:</th>
+ <td>{{ request.META.REQUEST_METHOD }}</td>
+ </tr>
+ <tr>
+ <th>Request URL:</th>
+ <td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path|escape }}</td>
+ </tr>
+ </table>
+ </div>
+ <div id="info">
+ {% if urlpatterns %}
+ <p>
+ Using the URLconf defined in <code>{{ settings.ROOT_URLCONF }}</code>,
+ Django tried these URL patterns, in this order:
+ </p>
+ <ol>
+ {% for pattern in urlpatterns %}
+ <li>{{ pattern|escape }}</li>
+ {% endfor %}
+ </ol>
+ <p>The current URL, <code>{{ request.path|escape }}</code>, didn't match any of these.</p>
+ {% else %}
+ <p>{{ reason|escape }}</p>
+ {% endif %}
+ </div>
+
+ <div id="explanation">
+ <p>
+ You're seeing this error because you have <code>DEBUG = True</code> in
+ your Django settings file. Change that to <code>False</code>, and Django
+ will display a standard 404 page.
+ </p>
+ </div>
+</body>
+</html>
+"""
+
+EMPTY_URLCONF_TEMPLATE = """
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html lang="en"><head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <meta name="robots" content="NONE,NOARCHIVE"><title>Welcome to Django</title>
+ <style type="text/css">
+ html * { padding:0; margin:0; }
+ body * { padding:10px 20px; }
+ body * * { padding:0; }
+ body { font:small sans-serif; }
+ body>div { border-bottom:1px solid #ddd; }
+ h1 { font-weight:normal; }
+ h2 { margin-bottom:.8em; }
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
+ h3 { margin:1em 0 .5em 0; }
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
+ table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
+ thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; }
+ tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
+ ul { margin-left: 2em; margin-top: 1em; }
+ #summary { background: #e0ebff; }
+ #summary h2 { font-weight: normal; color: #666; }
+ #explanation { background:#eee; }
+ #instructions { background:#f6f6f6; }
+ #summary table { border:none; background:transparent; }
+ </style>
+</head>
+
+<body>
+<div id="summary">
+ <h1>It worked!</h1>
+ <h2>Congratulations on your first Django-powered page.</h2>
+</div>
+
+<div id="instructions">
+ <p>Of course, you haven't actually done any work yet. Here's what to do next:</p>
+ <ul>
+ <li>If you plan to use a database, edit the <code>DATABASE_*</code> settings in <code>{{ project_name }}/settings.py</code>.</li>
+ <li>Start your first app by running <code>python {{ project_name }}/manage.py startapp [appname]</code>.</li>
+ </ul>
+</div>
+
+<div id="explanation">
+ <p>
+ You're seeing this message because you have <code>DEBUG = True</code> in your
+ Django settings file and you haven't configured any URLs. Get to work!
+ </p>
+</div>
+</body></html>
+"""
diff --git a/google_appengine/lib/django/django/views/decorators/__init__.py b/google_appengine/lib/django/django/views/decorators/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/views/decorators/__init__.py
diff --git a/google_appengine/lib/django/django/views/decorators/cache.py b/google_appengine/lib/django/django/views/decorators/cache.py
new file mode 100755
index 0000000..b04cc23
--- /dev/null
+++ b/google_appengine/lib/django/django/views/decorators/cache.py
@@ -0,0 +1,42 @@
+"""
+Decorator for views that tries getting the page from the cache and
+populates the cache if the page isn't in the cache yet.
+
+The cache is keyed by the URL and some data from the headers. Additionally
+there is the key prefix that is used to distinguish different cache areas
+in a multi-site setup. You could use the sites.get_current().domain, for
+example, as that is unique across a Django project.
+
+Additionally, all headers from the response's Vary header will be taken into
+account on caching -- just like the middleware does.
+"""
+
+from django.utils.decorators import decorator_from_middleware
+from django.utils.cache import patch_cache_control, add_never_cache_headers
+from django.middleware.cache import CacheMiddleware
+
+cache_page = decorator_from_middleware(CacheMiddleware)
+
+def cache_control(**kwargs):
+
+ def _cache_controller(viewfunc):
+
+ def _cache_controlled(request, *args, **kw):
+ response = viewfunc(request, *args, **kw)
+ patch_cache_control(response, **kwargs)
+ return response
+
+ return _cache_controlled
+
+ return _cache_controller
+
+def never_cache(view_func):
+ """
+ Decorator that adds headers to a response so that it will
+ never be cached.
+ """
+ def _wrapped_view_func(request, *args, **kwargs):
+ response = view_func(request, *args, **kwargs)
+ add_never_cache_headers(response)
+ return response
+ return _wrapped_view_func
diff --git a/google_appengine/lib/django/django/views/decorators/gzip.py b/google_appengine/lib/django/django/views/decorators/gzip.py
new file mode 100755
index 0000000..dc6edad
--- /dev/null
+++ b/google_appengine/lib/django/django/views/decorators/gzip.py
@@ -0,0 +1,6 @@
+"Decorator for views that gzips pages if the client supports it."
+
+from django.utils.decorators import decorator_from_middleware
+from django.middleware.gzip import GZipMiddleware
+
+gzip_page = decorator_from_middleware(GZipMiddleware)
diff --git a/google_appengine/lib/django/django/views/decorators/http.py b/google_appengine/lib/django/django/views/decorators/http.py
new file mode 100755
index 0000000..9feb8c0
--- /dev/null
+++ b/google_appengine/lib/django/django/views/decorators/http.py
@@ -0,0 +1,34 @@
+"""
+Decorators for views based on HTTP headers.
+"""
+
+from django.utils.decorators import decorator_from_middleware
+from django.middleware.http import ConditionalGetMiddleware
+from django.http import HttpResponseNotAllowed
+
+conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
+
+def require_http_methods(request_method_list):
+ """
+ Decorator to make a view only accept particular request methods. Usage::
+
+ @require_http_methods(["GET", "POST"])
+ def my_view(request):
+ # I can assume now that only GET or POST requests make it this far
+ # ...
+
+ Note that request methods should be in uppercase.
+ """
+ def decorator(func):
+ def inner(request, *args, **kwargs):
+ if request.method not in request_method_list:
+ return HttpResponseNotAllowed(request_method_list)
+ return func(request, *args, **kwargs)
+ return inner
+ return decorator
+
+require_GET = require_http_methods(["GET"])
+require_GET.__doc__ = "Decorator to require that a view only accept the GET method."
+
+require_POST = require_http_methods(["POST"])
+require_POST.__doc__ = "Decorator to require that a view only accept the POST method." \ No newline at end of file
diff --git a/google_appengine/lib/django/django/views/decorators/vary.py b/google_appengine/lib/django/django/views/decorators/vary.py
new file mode 100755
index 0000000..9b49c45
--- /dev/null
+++ b/google_appengine/lib/django/django/views/decorators/vary.py
@@ -0,0 +1,35 @@
+from django.utils.cache import patch_vary_headers
+
+def vary_on_headers(*headers):
+ """
+ A view decorator that adds the specified headers to the Vary header of the
+ response. Usage:
+
+ @vary_on_headers('Cookie', 'Accept-language')
+ def index(request):
+ ...
+
+ Note that the header names are not case-sensitive.
+ """
+ def decorator(func):
+ def inner_func(*args, **kwargs):
+ response = func(*args, **kwargs)
+ patch_vary_headers(response, headers)
+ return response
+ return inner_func
+ return decorator
+
+def vary_on_cookie(func):
+ """
+ A view decorator that adds "Cookie" to the Vary header of a response. This
+ indicates that a page's contents depends on cookies. Usage:
+
+ @vary_on_cookie
+ def index(request):
+ ...
+ """
+ def inner_func(*args, **kwargs):
+ response = func(*args, **kwargs)
+ patch_vary_headers(response, ('Cookie',))
+ return response
+ return inner_func
diff --git a/google_appengine/lib/django/django/views/defaults.py b/google_appengine/lib/django/django/views/defaults.py
new file mode 100755
index 0000000..701aeba
--- /dev/null
+++ b/google_appengine/lib/django/django/views/defaults.py
@@ -0,0 +1,89 @@
+from django.core.exceptions import ObjectDoesNotExist
+from django.template import Context, RequestContext, loader
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.sites.models import Site
+from django import http
+
+def shortcut(request, content_type_id, object_id):
+ "Redirect to an object's page based on a content-type ID and an object ID."
+ # Look up the object, making sure it's got a get_absolute_url() function.
+ try:
+ content_type = ContentType.objects.get(pk=content_type_id)
+ obj = content_type.get_object_for_this_type(pk=object_id)
+ except ObjectDoesNotExist:
+ raise http.Http404, "Content type %s object %s doesn't exist" % (content_type_id, object_id)
+ try:
+ absurl = obj.get_absolute_url()
+ except AttributeError:
+ raise http.Http404, "%s objects don't have get_absolute_url() methods" % content_type.name
+
+ # Try to figure out the object's domain, so we can do a cross-site redirect
+ # if necessary.
+
+ # If the object actually defines a domain, we're done.
+ if absurl.startswith('http://'):
+ return http.HttpResponseRedirect(absurl)
+
+ object_domain = None
+
+ # Otherwise, we need to introspect the object's relationships for a
+ # relation to the Site object
+ opts = obj._meta
+
+ # First, look for an many-to-many relationship to sites
+ for field in opts.many_to_many:
+ if field.rel.to is Site:
+ try:
+ object_domain = getattr(obj, field.name).all()[0].domain
+ except IndexError:
+ pass
+ if object_domain is not None:
+ break
+
+ # Next look for a many-to-one relationship to site
+ if object_domain is None:
+ for field in obj._meta.fields:
+ if field.rel and field.rel.to is Site:
+ try:
+ object_domain = getattr(obj, field.name).domain
+ except Site.DoesNotExist:
+ pass
+ if object_domain is not None:
+ break
+
+ # Fall back to the current site (if possible)
+ if object_domain is None:
+ try:
+ object_domain = Site.objects.get_current().domain
+ except Site.DoesNotExist:
+ pass
+
+ # If all that malarkey found an object domain, use it; otherwise fall back
+ # to whatever get_absolute_url() returned.
+ if object_domain is not None:
+ return http.HttpResponseRedirect('http://%s%s' % (object_domain, absurl))
+ else:
+ return http.HttpResponseRedirect(absurl)
+
+def page_not_found(request, template_name='404.html'):
+ """
+ Default 404 handler, which looks for the requested URL in the redirects
+ table, redirects if found, and displays 404 page if not redirected.
+
+ Templates: `404.html`
+ Context:
+ request_path
+ The path of the requested URL (e.g., '/app/pages/bad_page/')
+ """
+ t = loader.get_template(template_name) # You need to create a 404.html template.
+ return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path})))
+
+def server_error(request, template_name='500.html'):
+ """
+ 500 error handler.
+
+ Templates: `500.html`
+ Context: None
+ """
+ t = loader.get_template(template_name) # You need to create a 500.html template.
+ return http.HttpResponseServerError(t.render(Context({})))
diff --git a/google_appengine/lib/django/django/views/generic/__init__.py b/google_appengine/lib/django/django/views/generic/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/google_appengine/lib/django/django/views/generic/__init__.py
diff --git a/google_appengine/lib/django/django/views/generic/create_update.py b/google_appengine/lib/django/django/views/generic/create_update.py
new file mode 100755
index 0000000..28987f7
--- /dev/null
+++ b/google_appengine/lib/django/django/views/generic/create_update.py
@@ -0,0 +1,200 @@
+from django.core.xheaders import populate_xheaders
+from django.template import loader
+from django import oldforms
+from django.db.models import FileField
+from django.contrib.auth.views import redirect_to_login
+from django.template import RequestContext
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
+from django.utils.translation import gettext
+
+def create_object(request, model, template_name=None,
+ template_loader=loader, extra_context=None, post_save_redirect=None,
+ login_required=False, follow=None, context_processors=None):
+ """
+ Generic object-creation function.
+
+ Templates: ``<app_label>/<model_name>_form.html``
+ Context:
+ form
+ the form wrapper for the object
+ """
+ if extra_context is None: extra_context = {}
+ if login_required and not request.user.is_authenticated():
+ return redirect_to_login(request.path)
+
+ manipulator = model.AddManipulator(follow=follow)
+ if request.POST:
+ # If data was POSTed, we're trying to create a new object
+ new_data = request.POST.copy()
+
+ if model._meta.has_field_type(FileField):
+ new_data.update(request.FILES)
+
+ # Check for errors
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+
+ if not errors:
+ # No errors -- this means we can save the data!
+ new_object = manipulator.save(new_data)
+
+ if request.user.is_authenticated():
+ request.user.message_set.create(message=gettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
+
+ # Redirect to the new object: first by trying post_save_redirect,
+ # then by obj.get_absolute_url; fail if neither works.
+ if post_save_redirect:
+ return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
+ elif hasattr(new_object, 'get_absolute_url'):
+ return HttpResponseRedirect(new_object.get_absolute_url())
+ else:
+ raise ImproperlyConfigured("No URL to redirect to from generic create view.")
+ else:
+ # No POST, so we want a brand new form without any data or errors
+ errors = {}
+ new_data = manipulator.flatten_data()
+
+ # Create the FormWrapper, template, context, response
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
+ if not template_name:
+ template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ 'form': form,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ return HttpResponse(t.render(c))
+
+def update_object(request, model, object_id=None, slug=None,
+ slug_field=None, template_name=None, template_loader=loader,
+ extra_context=None, post_save_redirect=None,
+ login_required=False, follow=None, context_processors=None,
+ template_object_name='object'):
+ """
+ Generic object-update function.
+
+ Templates: ``<app_label>/<model_name>_form.html``
+ Context:
+ form
+ the form wrapper for the object
+ object
+ the original object being edited
+ """
+ if extra_context is None: extra_context = {}
+ if login_required and not request.user.is_authenticated():
+ return redirect_to_login(request.path)
+
+ # Look up the object to be edited
+ lookup_kwargs = {}
+ if object_id:
+ lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+ elif slug and slug_field:
+ lookup_kwargs['%s__exact' % slug_field] = slug
+ else:
+ raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
+ try:
+ object = model.objects.get(**lookup_kwargs)
+ except ObjectDoesNotExist:
+ raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
+
+ manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
+
+ if request.POST:
+ new_data = request.POST.copy()
+ if model._meta.has_field_type(FileField):
+ new_data.update(request.FILES)
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+ if not errors:
+ object = manipulator.save(new_data)
+
+ if request.user.is_authenticated():
+ request.user.message_set.create(message=gettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
+
+ # Do a post-after-redirect so that reload works, etc.
+ if post_save_redirect:
+ return HttpResponseRedirect(post_save_redirect % object.__dict__)
+ elif hasattr(object, 'get_absolute_url'):
+ return HttpResponseRedirect(object.get_absolute_url())
+ else:
+ raise ImproperlyConfigured("No URL to redirect to from generic create view.")
+ else:
+ errors = {}
+ # This makes sure the form acurate represents the fields of the place.
+ new_data = manipulator.flatten_data()
+
+ form = oldforms.FormWrapper(manipulator, new_data, errors)
+ if not template_name:
+ template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ 'form': form,
+ template_object_name: object,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ response = HttpResponse(t.render(c))
+ populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
+ return response
+
+def delete_object(request, model, post_delete_redirect,
+ object_id=None, slug=None, slug_field=None, template_name=None,
+ template_loader=loader, extra_context=None,
+ login_required=False, context_processors=None, template_object_name='object'):
+ """
+ Generic object-delete function.
+
+ The given template will be used to confirm deletetion if this view is
+ fetched using GET; for safty, deletion will only be performed if this
+ view is POSTed.
+
+ Templates: ``<app_label>/<model_name>_confirm_delete.html``
+ Context:
+ object
+ the original object being deleted
+ """
+ if extra_context is None: extra_context = {}
+ if login_required and not request.user.is_authenticated():
+ return redirect_to_login(request.path)
+
+ # Look up the object to be edited
+ lookup_kwargs = {}
+ if object_id:
+ lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+ elif slug and slug_field:
+ lookup_kwargs['%s__exact' % slug_field] = slug
+ else:
+ raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field")
+ try:
+ object = model._default_manager.get(**lookup_kwargs)
+ except ObjectDoesNotExist:
+ raise Http404, "No %s found for %s" % (model._meta.app_label, lookup_kwargs)
+
+ if request.method == 'POST':
+ object.delete()
+ if request.user.is_authenticated():
+ request.user.message_set.create(message=gettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
+ return HttpResponseRedirect(post_delete_redirect)
+ else:
+ if not template_name:
+ template_name = "%s/%s_confirm_delete.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ template_object_name: object,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ response = HttpResponse(t.render(c))
+ populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
+ return response
diff --git a/google_appengine/lib/django/django/views/generic/date_based.py b/google_appengine/lib/django/django/views/generic/date_based.py
new file mode 100755
index 0000000..d13c029
--- /dev/null
+++ b/google_appengine/lib/django/django/views/generic/date_based.py
@@ -0,0 +1,344 @@
+from django.template import loader, RequestContext
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.xheaders import populate_xheaders
+from django.db.models.fields import DateTimeField
+from django.http import Http404, HttpResponse
+import datetime, time
+
+def archive_index(request, queryset, date_field, num_latest=15,
+ template_name=None, template_loader=loader,
+ extra_context=None, allow_empty=False, context_processors=None,
+ mimetype=None, allow_future=False):
+ """
+ Generic top-level archive of date-based objects.
+
+ Templates: ``<app_label>/<model_name>_archive.html``
+ Context:
+ date_list
+ List of years
+ latest
+ Latest N (defaults to 15) objects by date
+ """
+ if extra_context is None: extra_context = {}
+ model = queryset.model
+ if not allow_future:
+ queryset = queryset.filter(**{'%s__lte' % date_field: datetime.datetime.now()})
+ date_list = queryset.dates(date_field, 'year')[::-1]
+ if not date_list and not allow_empty:
+ raise Http404, "No %s available" % model._meta.verbose_name
+
+ if date_list and num_latest:
+ latest = queryset.order_by('-'+date_field)[:num_latest]
+ else:
+ latest = None
+
+ if not template_name:
+ template_name = "%s/%s_archive.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ 'date_list' : date_list,
+ 'latest' : latest,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ return HttpResponse(t.render(c), mimetype=mimetype)
+
+def archive_year(request, year, queryset, date_field, template_name=None,
+ template_loader=loader, extra_context=None, allow_empty=False,
+ context_processors=None, template_object_name='object', mimetype=None,
+ make_object_list=False, allow_future=False):
+ """
+ Generic yearly archive view.
+
+ Templates: ``<app_label>/<model_name>_archive_year.html``
+ Context:
+ date_list
+ List of months in this year with objects
+ year
+ This year
+ object_list
+ List of objects published in the given month
+ (Only available if make_object_list argument is True)
+ """
+ if extra_context is None: extra_context = {}
+ model = queryset.model
+ now = datetime.datetime.now()
+
+ lookup_kwargs = {'%s__year' % date_field: year}
+
+ # Only bother to check current date if the year isn't in the past and future objects aren't requested.
+ if int(year) >= now.year and not allow_future:
+ lookup_kwargs['%s__lte' % date_field] = now
+ date_list = queryset.filter(**lookup_kwargs).dates(date_field, 'month')
+ if not date_list and not allow_empty:
+ raise Http404
+ if make_object_list:
+ object_list = queryset.filter(**lookup_kwargs).order_by(date_field)
+ else:
+ object_list = []
+ if not template_name:
+ template_name = "%s/%s_archive_year.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ 'date_list': date_list,
+ 'year': year,
+ '%s_list' % template_object_name: object_list,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ return HttpResponse(t.render(c), mimetype=mimetype)
+
+def archive_month(request, year, month, queryset, date_field,
+ month_format='%b', template_name=None, template_loader=loader,
+ extra_context=None, allow_empty=False, context_processors=None,
+ template_object_name='object', mimetype=None, allow_future=False):
+ """
+ Generic monthly archive view.
+
+ Templates: ``<app_label>/<model_name>_archive_month.html``
+ Context:
+ month:
+ (date) this month
+ next_month:
+ (date) the first day of the next month, or None if the next month is in the future
+ previous_month:
+ (date) the first day of the previous month
+ object_list:
+ list of objects published in the given month
+ """
+ if extra_context is None: extra_context = {}
+ try:
+ date = datetime.date(*time.strptime(year+month, '%Y'+month_format)[:3])
+ except ValueError:
+ raise Http404
+
+ model = queryset.model
+ now = datetime.datetime.now()
+
+ # Calculate first and last day of month, for use in a date-range lookup.
+ first_day = date.replace(day=1)
+ if first_day.month == 12:
+ last_day = first_day.replace(year=first_day.year + 1, month=1)
+ else:
+ last_day = first_day.replace(month=first_day.month + 1)
+ lookup_kwargs = {'%s__range' % date_field: (first_day, last_day)}
+
+ # Only bother to check current date if the month isn't in the past and future objects are requested.
+ if last_day >= now.date() and not allow_future:
+ lookup_kwargs['%s__lte' % date_field] = now
+ object_list = queryset.filter(**lookup_kwargs)
+ if not object_list and not allow_empty:
+ raise Http404
+
+ # Calculate the next month, if applicable.
+ if allow_future:
+ next_month = last_day + datetime.timedelta(days=1)
+ elif last_day < datetime.date.today():
+ next_month = last_day + datetime.timedelta(days=1)
+ else:
+ next_month = None
+
+ if not template_name:
+ template_name = "%s/%s_archive_month.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ '%s_list' % template_object_name: object_list,
+ 'month': date,
+ 'next_month': next_month,
+ 'previous_month': first_day - datetime.timedelta(days=1),
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ return HttpResponse(t.render(c), mimetype=mimetype)
+
+def archive_week(request, year, week, queryset, date_field,
+ template_name=None, template_loader=loader,
+ extra_context=None, allow_empty=True, context_processors=None,
+ template_object_name='object', mimetype=None, allow_future=False):
+ """
+ Generic weekly archive view.
+
+ Templates: ``<app_label>/<model_name>_archive_week.html``
+ Context:
+ week:
+ (date) this week
+ object_list:
+ list of objects published in the given week
+ """
+ if extra_context is None: extra_context = {}
+ try:
+ date = datetime.date(*time.strptime(year+'-0-'+week, '%Y-%w-%U')[:3])
+ except ValueError:
+ raise Http404
+
+ model = queryset.model
+ now = datetime.datetime.now()
+
+ # Calculate first and last day of week, for use in a date-range lookup.
+ first_day = date
+ last_day = date + datetime.timedelta(days=7)
+ lookup_kwargs = {'%s__range' % date_field: (first_day, last_day)}
+
+ # Only bother to check current date if the week isn't in the past and future objects aren't requested.
+ if last_day >= now.date() and not allow_future:
+ lookup_kwargs['%s__lte' % date_field] = now
+ object_list = queryset.filter(**lookup_kwargs)
+ if not object_list and not allow_empty:
+ raise Http404
+ if not template_name:
+ template_name = "%s/%s_archive_week.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ '%s_list' % template_object_name: object_list,
+ 'week': date,
+ })
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ return HttpResponse(t.render(c), mimetype=mimetype)
+
+def archive_day(request, year, month, day, queryset, date_field,
+ month_format='%b', day_format='%d', template_name=None,
+ template_loader=loader, extra_context=None, allow_empty=False,
+ context_processors=None, template_object_name='object',
+ mimetype=None, allow_future=False):
+ """
+ Generic daily archive view.
+
+ Templates: ``<app_label>/<model_name>_archive_day.html``
+ Context:
+ object_list:
+ list of objects published that day
+ day:
+ (datetime) the day
+ previous_day
+ (datetime) the previous day
+ next_day
+ (datetime) the next day, or None if the current day is today
+ """
+ if extra_context is None: extra_context = {}
+ try:
+ date = datetime.date(*time.strptime(year+month+day, '%Y'+month_format+day_format)[:3])
+ except ValueError:
+ raise Http404
+
+ model = queryset.model
+ now = datetime.datetime.now()
+
+ if isinstance(model._meta.get_field(date_field), DateTimeField):
+ lookup_kwargs = {'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max))}
+ else:
+ lookup_kwargs = {date_field: date}
+
+ # Only bother to check current date if the date isn't in the past and future objects aren't requested.
+ if date >= now.date() and not allow_future:
+ lookup_kwargs['%s__lte' % date_field] = now
+ object_list = queryset.filter(**lookup_kwargs)
+ if not allow_empty and not object_list:
+ raise Http404
+
+ # Calculate the next day, if applicable.
+ if allow_future:
+ next_day = date + datetime.timedelta(days=1)
+ elif date < datetime.date.today():
+ next_day = date + datetime.timedelta(days=1)
+ else:
+ next_day = None
+
+ if not template_name:
+ template_name = "%s/%s_archive_day.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ '%s_list' % template_object_name: object_list,
+ 'day': date,
+ 'previous_day': date - datetime.timedelta(days=1),
+ 'next_day': next_day,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ return HttpResponse(t.render(c), mimetype=mimetype)
+
+def archive_today(request, **kwargs):
+ """
+ Generic daily archive view for today. Same as archive_day view.
+ """
+ today = datetime.date.today()
+ kwargs.update({
+ 'year': str(today.year),
+ 'month': today.strftime('%b').lower(),
+ 'day': str(today.day),
+ })
+ return archive_day(request, **kwargs)
+
+def object_detail(request, year, month, day, queryset, date_field,
+ month_format='%b', day_format='%d', object_id=None, slug=None,
+ slug_field=None, template_name=None, template_name_field=None,
+ template_loader=loader, extra_context=None, context_processors=None,
+ template_object_name='object', mimetype=None, allow_future=False):
+ """
+ Generic detail view from year/month/day/slug or year/month/day/id structure.
+
+ Templates: ``<app_label>/<model_name>_detail.html``
+ Context:
+ object:
+ the object to be detailed
+ """
+ if extra_context is None: extra_context = {}
+ try:
+ date = datetime.date(*time.strptime(year+month+day, '%Y'+month_format+day_format)[:3])
+ except ValueError:
+ raise Http404
+
+ model = queryset.model
+ now = datetime.datetime.now()
+
+ if isinstance(model._meta.get_field(date_field), DateTimeField):
+ lookup_kwargs = {'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max))}
+ else:
+ lookup_kwargs = {date_field: date}
+
+ # Only bother to check current date if the date isn't in the past and future objects aren't requested.
+ if date >= now.date() and not allow_future:
+ lookup_kwargs['%s__lte' % date_field] = now
+ if object_id:
+ lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+ elif slug and slug_field:
+ lookup_kwargs['%s__exact' % slug_field] = slug
+ else:
+ raise AttributeError, "Generic detail view must be called with either an object_id or a slug/slugfield"
+ try:
+ obj = queryset.get(**lookup_kwargs)
+ except ObjectDoesNotExist:
+ raise Http404, "No %s found for" % model._meta.verbose_name
+ if not template_name:
+ template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
+ if template_name_field:
+ template_name_list = [getattr(obj, template_name_field), template_name]
+ t = template_loader.select_template(template_name_list)
+ else:
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ template_object_name: obj,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ response = HttpResponse(t.render(c), mimetype=mimetype)
+ populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
+ return response
diff --git a/google_appengine/lib/django/django/views/generic/list_detail.py b/google_appengine/lib/django/django/views/generic/list_detail.py
new file mode 100755
index 0000000..16d5520
--- /dev/null
+++ b/google_appengine/lib/django/django/views/generic/list_detail.py
@@ -0,0 +1,131 @@
+from django.template import loader, RequestContext
+from django.http import Http404, HttpResponse
+from django.core.xheaders import populate_xheaders
+from django.core.paginator import ObjectPaginator, InvalidPage
+from django.core.exceptions import ObjectDoesNotExist
+
+def object_list(request, queryset, paginate_by=None, page=None,
+ allow_empty=False, template_name=None, template_loader=loader,
+ extra_context=None, context_processors=None, template_object_name='object',
+ mimetype=None):
+ """
+ Generic list of objects.
+
+ Templates: ``<app_label>/<model_name>_list.html``
+ Context:
+ object_list
+ list of objects
+ is_paginated
+ are the results paginated?
+ results_per_page
+ number of objects per page (if paginated)
+ has_next
+ is there a next page?
+ has_previous
+ is there a prev page?
+ page
+ the current page
+ next
+ the next page
+ previous
+ the previous page
+ pages
+ number of pages, total
+ hits
+ number of objects, total
+ last_on_page
+ the result number of the last of object in the
+ object_list (1-indexed)
+ first_on_page
+ the result number of the first object in the
+ object_list (1-indexed)
+ """
+ if extra_context is None: extra_context = {}
+ queryset = queryset._clone()
+ if paginate_by:
+ paginator = ObjectPaginator(queryset, paginate_by)
+ if not page:
+ page = request.GET.get('page', 1)
+ try:
+ page = int(page)
+ object_list = paginator.get_page(page - 1)
+ except (InvalidPage, ValueError):
+ if page == 1 and allow_empty:
+ object_list = []
+ else:
+ raise Http404
+ c = RequestContext(request, {
+ '%s_list' % template_object_name: object_list,
+ 'is_paginated': paginator.pages > 1,
+ 'results_per_page': paginate_by,
+ 'has_next': paginator.has_next_page(page - 1),
+ 'has_previous': paginator.has_previous_page(page - 1),
+ 'page': page,
+ 'next': page + 1,
+ 'previous': page - 1,
+ 'last_on_page': paginator.last_on_page(page - 1),
+ 'first_on_page': paginator.first_on_page(page - 1),
+ 'pages': paginator.pages,
+ 'hits' : paginator.hits,
+ }, context_processors)
+ else:
+ c = RequestContext(request, {
+ '%s_list' % template_object_name: queryset,
+ 'is_paginated': False
+ }, context_processors)
+ if not allow_empty and len(queryset) == 0:
+ raise Http404
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ if not template_name:
+ model = queryset.model
+ template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ return HttpResponse(t.render(c), mimetype=mimetype)
+
+def object_detail(request, queryset, object_id=None, slug=None,
+ slug_field=None, template_name=None, template_name_field=None,
+ template_loader=loader, extra_context=None,
+ context_processors=None, template_object_name='object',
+ mimetype=None):
+ """
+ Generic detail of an object.
+
+ Templates: ``<app_label>/<model_name>_detail.html``
+ Context:
+ object
+ the object
+ """
+ if extra_context is None: extra_context = {}
+ model = queryset.model
+ if object_id:
+ queryset = queryset.filter(pk=object_id)
+ elif slug and slug_field:
+ queryset = queryset.filter(**{slug_field: slug})
+ else:
+ raise AttributeError, "Generic detail view must be called with either an object_id or a slug/slug_field."
+ try:
+ obj = queryset.get()
+ except ObjectDoesNotExist:
+ raise Http404, "No %s found matching the query" % (model._meta.verbose_name)
+ if not template_name:
+ template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
+ if template_name_field:
+ template_name_list = [getattr(obj, template_name_field), template_name]
+ t = template_loader.select_template(template_name_list)
+ else:
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ template_object_name: obj,
+ }, context_processors)
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ response = HttpResponse(t.render(c), mimetype=mimetype)
+ populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
+ return response
diff --git a/google_appengine/lib/django/django/views/generic/simple.py b/google_appengine/lib/django/django/views/generic/simple.py
new file mode 100755
index 0000000..355bd25
--- /dev/null
+++ b/google_appengine/lib/django/django/views/generic/simple.py
@@ -0,0 +1,35 @@
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.http import HttpResponse, HttpResponsePermanentRedirect, HttpResponseGone
+
+def direct_to_template(request, template, extra_context={}, **kwargs):
+ """
+ Render a given template with any extra URL parameters in the context as
+ ``{{ params }}``.
+ """
+ dictionary = {'params': kwargs}
+ for key, value in extra_context.items():
+ if callable(value):
+ dictionary[key] = value()
+ else:
+ dictionary[key] = value
+ return render_to_response(template, dictionary, context_instance=RequestContext(request))
+
+def redirect_to(request, url, **kwargs):
+ """
+ Redirect to a given URL.
+
+ The given url may contain dict-style string formatting, which will be
+ interpolated against the params in the URL. For example, to redirect from
+ ``/foo/<id>/`` to ``/bar/<id>/``, you could use the following URLconf::
+
+ urlpatterns = patterns('',
+ ('^foo/(?P<id>\d+)/$', 'django.views.generic.simple.redirect_to', {'url' : '/bar/%(id)s/'}),
+ )
+
+ If the given url is ``None``, a HttpResponseGone (410) will be issued.
+ """
+ if url is not None:
+ return HttpResponsePermanentRedirect(url % kwargs)
+ else:
+ return HttpResponseGone()
diff --git a/google_appengine/lib/django/django/views/i18n.py b/google_appengine/lib/django/django/views/i18n.py
new file mode 100755
index 0000000..0a19cfe
--- /dev/null
+++ b/google_appengine/lib/django/django/views/i18n.py
@@ -0,0 +1,172 @@
+from django import http
+from django.utils.translation import check_for_language, activate, to_locale, get_language
+from django.utils.text import javascript_quote
+from django.conf import settings
+import os
+import gettext as gettext_module
+
+def set_language(request):
+ """
+ Redirect to a given url while setting the chosen language in the
+ session or cookie. The url and the language code need to be
+ specified in the GET parameters.
+ """
+ lang_code = request.GET.get('language', None)
+ next = request.GET.get('next', None)
+ if not next:
+ next = request.META.get('HTTP_REFERER', None)
+ if not next:
+ next = '/'
+ response = http.HttpResponseRedirect(next)
+ if lang_code and check_for_language(lang_code):
+ if hasattr(request, 'session'):
+ request.session['django_language'] = lang_code
+ else:
+ response.set_cookie('django_language', lang_code)
+ return response
+
+NullSource = """
+/* gettext identity library */
+
+function gettext(msgid) { return msgid; }
+function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; }
+function gettext_noop(msgid) { return msgid; }
+"""
+
+LibHead = """
+/* gettext library */
+
+var catalog = new Array();
+"""
+
+LibFoot = """
+
+function gettext(msgid) {
+ var value = catalog[msgid];
+ if (typeof(value) == 'undefined') {
+ return msgid;
+ } else {
+ return (typeof(value) == 'string') ? value : value[0];
+ }
+}
+
+function ngettext(singular, plural, count) {
+ value = catalog[singular];
+ if (typeof(value) == 'undefined') {
+ return (count == 1) ? singular : plural;
+ } else {
+ return value[pluralidx(count)];
+ }
+}
+
+function gettext_noop(msgid) { return msgid; }
+"""
+
+SimplePlural = """
+function pluralidx(count) { return (count == 1) ? 0 : 1; }
+"""
+
+InterPolate = r"""
+function interpolate(fmt, obj, named) {
+ if (named) {
+ return fmt.replace(/%\(\w+\)s/, function(match){return String(obj[match.slice(2,-2)])});
+ } else {
+ return fmt.replace(/%s/, function(match){return String(obj.shift())});
+ }
+}
+"""
+
+def null_javascript_catalog(request, domain=None, packages=None):
+ """
+ Returns "identity" versions of the JavaScript i18n functions -- i.e.,
+ versions that don't actually do anything.
+ """
+ return http.HttpResponse(NullSource + InterPolate, 'text/javascript')
+
+def javascript_catalog(request, domain='djangojs', packages=None):
+ """
+ Returns the selected language catalog as a javascript library.
+
+ Receives the list of packages to check for translations in the
+ packages parameter either from an infodict or as a +-delimited
+ string from the request. Default is 'django.conf'.
+
+ Additionally you can override the gettext domain for this view,
+ but usually you don't want to do that, as JavaScript messages
+ go to the djangojs domain. But this might be needed if you
+ deliver your JavaScript source from Django templates.
+ """
+ if request.GET:
+ if request.GET.has_key('language'):
+ if check_for_language(request.GET['language']):
+ activate(request.GET['language'])
+ if packages is None:
+ packages = ['django.conf']
+ if type(packages) in (str, unicode):
+ packages = packages.split('+')
+ packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS]
+ default_locale = to_locale(settings.LANGUAGE_CODE)
+ locale = to_locale(get_language())
+ t = {}
+ paths = []
+ # first load all english languages files for defaults
+ for package in packages:
+ p = __import__(package, {}, {}, [''])
+ path = os.path.join(os.path.dirname(p.__file__), 'locale')
+ paths.append(path)
+ catalog = gettext_module.translation(domain, path, ['en'])
+ t.update(catalog._catalog)
+ # next load the settings.LANGUAGE_CODE translations if it isn't english
+ if default_locale != 'en':
+ for path in paths:
+ try:
+ catalog = gettext_module.translation(domain, path, [default_locale])
+ except IOError:
+ catalog = None
+ if catalog is not None:
+ t.update(catalog._catalog)
+ # last load the currently selected language, if it isn't identical to the default.
+ if locale != default_locale:
+ for path in paths:
+ try:
+ catalog = gettext_module.translation(domain, path, [locale])
+ except IOError:
+ catalog = None
+ if catalog is not None:
+ t.update(catalog._catalog)
+ src = [LibHead]
+ plural = None
+ if t.has_key(''):
+ for l in t[''].split('\n'):
+ if l.startswith('Plural-Forms:'):
+ plural = l.split(':',1)[1].strip()
+ if plural is not None:
+ # this should actually be a compiled function of a typical plural-form:
+ # 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;
+ plural = [el.strip() for el in plural.split(';') if el.strip().startswith('plural=')][0].split('=',1)[1]
+ src.append('function pluralidx(n) {\n return %s;\n}\n' % plural)
+ else:
+ src.append(SimplePlural)
+ csrc = []
+ pdict = {}
+ for k, v in t.items():
+ if k == '':
+ continue
+ if type(k) in (str, unicode):
+ csrc.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(v)))
+ elif type(k) == tuple:
+ if not pdict.has_key(k[0]):
+ pdict[k[0]] = k[1]
+ else:
+ pdict[k[0]] = max(k[1], pdict[k[0]])
+ csrc.append("catalog['%s'][%d] = '%s';\n" % (javascript_quote(k[0]), k[1], javascript_quote(v)))
+ else:
+ raise TypeError, k
+ csrc.sort()
+ for k,v in pdict.items():
+ src.append("catalog['%s'] = [%s];\n" % (javascript_quote(k), ','.join(["''"]*(v+1))))
+ src.extend(csrc)
+ src.append(LibFoot)
+ src.append(InterPolate)
+ src = ''.join(src)
+ return http.HttpResponse(src, 'text/javascript')
diff --git a/google_appengine/lib/django/django/views/static.py b/google_appengine/lib/django/django/views/static.py
new file mode 100755
index 0000000..3ec4ca1
--- /dev/null
+++ b/google_appengine/lib/django/django/views/static.py
@@ -0,0 +1,125 @@
+from django.template import loader
+from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
+from django.template import Template, Context, TemplateDoesNotExist
+import mimetypes
+import os
+import posixpath
+import re
+import rfc822
+import stat
+import urllib
+
+def serve(request, path, document_root=None, show_indexes=False):
+ """
+ Serve static files below a given point in the directory structure.
+
+ To use, put a URL pattern such as::
+
+ (r'^(?P<path>.*)$', 'django.views.static.serve', {'document_root' : '/path/to/my/files/'})
+
+ in your URLconf. You must provide the ``document_root`` param. You may
+ also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
+ of the directory. This index view will use the template hardcoded below,
+ but if you'd like to override it, you can create a template called
+ ``static/directory_index``.
+ """
+
+ # Clean up given path to only allow serving files below document_root.
+ path = posixpath.normpath(urllib.unquote(path))
+ newpath = ''
+ for part in path.split('/'):
+ if not part:
+ # strip empty path components
+ continue
+ drive, part = os.path.splitdrive(part)
+ head, part = os.path.split(part)
+ if part in (os.curdir, os.pardir):
+ # strip '.' amd '..' in path
+ continue
+ newpath = os.path.join(newpath, part).replace('\\', '/')
+ if newpath and path != newpath:
+ return HttpResponseRedirect(newpath)
+ fullpath = os.path.join(document_root, newpath)
+ if os.path.isdir(fullpath):
+ if show_indexes:
+ return directory_index(newpath, fullpath)
+ raise Http404, "Directory indexes are not allowed here."
+ if not os.path.exists(fullpath):
+ raise Http404, '"%s" does not exist' % fullpath
+ # Respect the If-Modified-Since header.
+ statobj = os.stat(fullpath)
+ if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
+ statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
+ return HttpResponseNotModified()
+ mimetype = mimetypes.guess_type(fullpath)[0]
+ contents = open(fullpath, 'rb').read()
+ response = HttpResponse(contents, mimetype=mimetype)
+ response["Last-Modified"] = rfc822.formatdate(statobj[stat.ST_MTIME])
+ return response
+
+DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Language" content="en-us" />
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ <title>Index of {{ directory|escape }}</title>
+ </head>
+ <body>
+ <h1>Index of {{ directory|escape }}</h1>
+ <ul>
+ {% for f in file_list %}
+ <li><a href="{{ f|urlencode }}">{{ f|escape }}</a></li>
+ {% endfor %}
+ </ul>
+ </body>
+</html>
+"""
+
+def directory_index(path, fullpath):
+ try:
+ t = loader.get_template('static/directory_index')
+ except TemplateDoesNotExist:
+ t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
+ files = []
+ for f in os.listdir(fullpath):
+ if not f.startswith('.'):
+ if os.path.isdir(os.path.join(fullpath, f)):
+ f += '/'
+ files.append(f)
+ c = Context({
+ 'directory' : path + '/',
+ 'file_list' : files,
+ })
+ return HttpResponse(t.render(c))
+
+def was_modified_since(header=None, mtime=0, size=0):
+ """
+ Was something modified since the user last downloaded it?
+
+ header
+ This is the value of the If-Modified-Since header. If this is None,
+ I'll just return True.
+
+ mtime
+ This is the modification time of the item we're talking about.
+
+ size
+ This is the size of the item we're talking about.
+ """
+ try:
+ if header is None:
+ raise ValueError
+ matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
+ re.IGNORECASE)
+ header_mtime = rfc822.mktime_tz(rfc822.parsedate_tz(
+ matches.group(1)))
+ header_len = matches.group(3)
+ if header_len and int(header_len) != size:
+ raise ValueError
+ if mtime > header_mtime:
+ raise ValueError
+ except (AttributeError, ValueError):
+ return True
+ return False
diff --git a/google_appengine/lib/django/docs/add_ons.txt b/google_appengine/lib/django/docs/add_ons.txt
new file mode 100644
index 0000000..9809cf4
--- /dev/null
+++ b/google_appengine/lib/django/docs/add_ons.txt
@@ -0,0 +1,206 @@
+============================
+The "django.contrib" add-ons
+============================
+
+Django aims to follow Python's `"batteries included" philosophy`_. It ships
+with a variety of extra, optional tools that solve common Web-development
+problems.
+
+This code lives in ``django/contrib`` in the Django distribution. Here's a
+rundown of the packages in ``contrib``:
+
+.. _"batteries included" philosophy: http://docs.python.org/tut/node12.html#batteries-included
+
+admin
+=====
+
+The automatic Django administrative interface. For more information, see
+`Tutorial 2`_.
+
+.. _Tutorial 2: ../tutorial2/
+
+auth
+====
+
+Django's authentication framework.
+
+See the `authentication documentation`_.
+
+.. _authentication documentation: ../authentication/
+
+comments
+========
+
+A simple yet flexible comments system. This is not yet documented.
+
+contenttypes
+============
+
+A light framework for hooking into "types" of content, where each installed
+Django model is a separate content type. This is not yet documented.
+
+csrf
+====
+
+A middleware for preventing Cross Site Request Forgeries
+
+See the `csrf documentation`_.
+
+.. _csrf documentation: ../csrf/
+
+formtools
+=========
+
+A set of high-level abstractions for Django forms (django.newforms).
+
+django.contrib.formtools.preview
+--------------------------------
+
+An abstraction of the following workflow:
+
+"Display an HTML form, force a preview, then do something with the submission."
+
+Full documentation for this feature does not yet exist, but you can read the
+code and docstrings in ``django/contrib/formtools/preview.py`` for a start.
+
+humanize
+========
+
+A set of Django template filters useful for adding a "human touch" to data.
+To activate these filters, add ``'django.contrib.humanize'`` to your
+``INSTALLED_APPS`` setting. Once you've done that, use ``{% load humanize %}``
+in a template, and you'll have access to these filters:
+
+apnumber
+--------
+
+For numbers 1-9, returns the number spelled out. Otherwise, returns the
+number. This follows Associated Press style.
+
+Examples:
+
+ * ``1`` becomes ``'one'``.
+ * ``2`` becomes ``'two'``.
+ * ``10`` becomes ``10``.
+
+You can pass in either an integer or a string representation of an integer.
+
+intcomma
+--------
+
+Converts an integer to a string containing commas every three digits.
+
+Examples:
+
+ * ``4500`` becomes ``'4,500'``.
+ * ``45000`` becomes ``'45,000'``.
+ * ``450000`` becomes ``'450,000'``.
+ * ``4500000`` becomes ``'4,500,000'``.
+
+You can pass in either an integer or a string representation of an integer.
+
+intword
+-------
+
+Converts a large integer to a friendly text representation. Works best for
+numbers over 1 million.
+
+Examples:
+
+ * ``1000000`` becomes ``'1.0 million'``.
+ * ``1200000`` becomes ``'1.2 million'``.
+ * ``1200000000`` becomes ``'1.2 billion'``.
+
+Values up to 1000000000000000 (one quadrillion) are supported.
+
+You can pass in either an integer or a string representation of an integer.
+
+ordinal
+-------
+
+Converts an integer to its ordinal as a string.
+
+Examples:
+
+ * ``1`` becomes ``'1st'``.
+ * ``2`` becomes ``'2nd'``.
+ * ``3`` becomes ``'3rd'``.
+
+You can pass in either an integer or a string representation of an integer.
+
+flatpages
+=========
+
+A framework for managing simple "flat" HTML content in a database.
+
+See the `flatpages documentation`_.
+
+.. _flatpages documentation: ../flatpages/
+
+localflavor
+===========
+
+A collection of various Django snippets that are useful only for a particular
+country or culture. For example, ``django.contrib.localflavor.usa.forms``
+contains a ``USZipCodeField`` that you can use to validate U.S. zip codes.
+
+markup
+======
+
+A collection of template filters that implement these common markup languages:
+
+ * `Textile`_
+ * `Markdown`_
+ * `ReST (ReStructured Text)`_
+
+For documentation, read the source code in django/contrib/markup/templatetags/markup.py.
+
+.. _Textile: http://en.wikipedia.org/wiki/Textile_%28markup_language%29
+.. _Markdown: http://en.wikipedia.org/wiki/Markdown
+.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText
+
+redirects
+=========
+
+A framework for managing redirects.
+
+See the `redirects documentation`_.
+
+.. _redirects documentation: ../redirects/
+
+sites
+=====
+
+A light framework that lets you operate multiple Web sites off of the same
+database and Django installation. It gives you hooks for associating objects to
+one or more sites.
+
+See the `sites documentation`_.
+
+.. _sites documentation: ../sites/
+
+sitemaps
+========
+
+A framework for generating Google sitemap XML files.
+
+See the `sitemaps documentation`_.
+
+.. _sitemaps documentation: ../sitemaps/
+
+syndication
+===========
+
+A framework for generating syndication feeds, in RSS and Atom, quite easily.
+
+See the `syndication documentation`_.
+
+.. _syndication documentation: ../syndication/
+
+Other add-ons
+=============
+
+If you have an idea for functionality to include in ``contrib``, let us know!
+Code it up, and post it to the `django-users mailing list`_.
+
+.. _django-users mailing list: http://groups.google.com/group/django-users
diff --git a/google_appengine/lib/django/docs/admin_css.txt b/google_appengine/lib/django/docs/admin_css.txt
new file mode 100644
index 0000000..5822e26
--- /dev/null
+++ b/google_appengine/lib/django/docs/admin_css.txt
@@ -0,0 +1,173 @@
+======================================
+Customizing the Django admin interface
+======================================
+
+Django's dynamic admin interface gives you a fully-functional admin for free
+with no hand-coding required. The dynamic admin is designed to be
+production-ready, not just a starting point, so you can use it as-is on a real
+site. While the underlying format of the admin pages is built in to Django, you
+can customize the look and feel by editing the admin stylesheet and images.
+
+Here's a quick and dirty overview some of the main styles and classes used in
+the Django admin CSS.
+
+Modules
+=======
+
+The ``.module`` class is a basic building block for grouping content in the
+admin. It's generally applied to a ``div`` or a ``fieldset``. It wraps the content
+group in a box and applies certain styles to the elements within. An ``h2``
+within a ``div.module`` will align to the top of the ``div`` as a header for the
+whole group.
+
+.. image:: http://media.djangoproject.com/img/doc/admincss/module.gif
+ :alt: Example use of module class on admin homepage
+
+Column Types
+============
+
+.. admonition:: Note
+
+ All admin pages (except the dashboard) are fluid-width. All fixed-width
+ classes from previous Django versions have been removed.
+
+The base template for each admin page has a block that defines the column
+structure for the page. This sets a class on the page content area
+(``div#content``) so everything on the page knows how wide it should be. There
+are three column types available.
+
+colM
+ This is the default column setting for all pages. The "M" stands for "main".
+ Assumes that all content on the page is in one main column
+ (``div#content-main``).
+colMS
+ This is for pages with one main column and a sidebar on the right. The "S"
+ stands for "sidebar". Assumes that main content is in ``div#content-main``
+ and sidebar content is in ``div#content-related``. This is used on the main
+ admin page.
+colSM
+ Same as above, with the sidebar on the left. The source order of the columns
+ doesn't matter.
+
+For instance, you could stick this in a template to make a two-column page with
+the sidebar on the right::
+
+ {% block coltype %}colMS{% endblock %}
+
+Text Styles
+===========
+
+Font Sizes
+----------
+
+Most HTML elements (headers, lists, etc.) have base font sizes in the stylesheet
+based on context. There are three classes are available for forcing text to a
+certain size in any context.
+
+small
+ 11px
+tiny
+ 10px
+mini
+ 9px (use sparingly)
+
+Font Styles and Alignment
+-------------------------
+
+There are also a few styles for styling text.
+
+.quiet
+ Sets font color to light gray. Good for side notes in instructions. Combine
+ with ``.small`` or ``.tiny`` for sheer excitement.
+.help
+ This is a custom class for blocks of inline help text explaining the
+ function of form elements. It makes text smaller and gray, and when applied
+ to ``p`` elements within ``.form-row`` elements (see Form Styles below),
+ it will offset the text to align with the form field. Use this for help
+ text, instead of ``small quiet``. It works on other elements, but try to
+ put the class on a ``p`` whenever you can.
+.align-left
+ It aligns the text left. Only works on block elements containing inline
+ elements.
+.align-right
+ Are you paying attention?
+.nowrap
+ Keeps text and inline objects from wrapping. Comes in handy for table
+ headers you want to stay on one line.
+
+Floats and Clears
+-----------------
+
+float-left
+ floats left
+float-right
+ floats right
+clear
+ clears all
+
+Object Tools
+============
+
+Certain actions which apply directly to an object are used in form and
+changelist pages. These appear in a "toolbar" row above the form or changelist,
+to the right of the page. The tools are wrapped in a ``ul`` with the class
+``object-tools``. There are two custom tool types which can be defined with an
+additional class on the ``a`` for that tool. These are ``.addlink`` and
+``.viewsitelink``.
+
+Example from a changelist page::
+
+ <ul class="object-tools">
+ <li><a href="/stories/add/" class="addlink">Add redirect</a></li>
+ </ul>
+
+.. image:: http://media.djangoproject.com/img/doc/admincss/objecttools_01.gif
+ :alt: Object tools on a changelist page
+
+and from a form page::
+
+ <ul class="object-tools">
+ <li><a href="/history/303/152383/">History</a></li>
+ <li><a href="/r/303/152383/" class="viewsitelink">View on site</a></li>
+ </ul>
+
+.. image:: http://media.djangoproject.com/img/doc/admincss/objecttools_02.gif
+ :alt: Object tools on a form page
+
+Form Styles
+===========
+
+Fieldsets
+---------
+
+Admin forms are broken up into groups by ``fieldset`` elements. Each form fieldset
+should have a class ``.module``. Each fieldset should have a header ``h2`` within the
+fieldset at the top (except the first group in the form, and in some cases where the
+group of fields doesn't have a logical label).
+
+Each fieldset can also take extra classes in addition to ``.module`` to apply
+appropriate formatting to the group of fields.
+
+.aligned
+ This will align the labels and inputs side by side on the same line.
+.wide
+ Used in combination with ``.aligned`` to widen the space available for the
+ labels.
+
+Form Rows
+---------
+
+Each row of the form (within the ``fieldset``) should be enclosed in a ``div``
+with class ``form-row``. If the field in the row is required, a class of
+``required`` should also be added to the ``div.form-row``.
+
+.. image:: http://media.djangoproject.com/img/doc/admincss/formrow.gif
+ :alt: Example use of form-row class
+
+Labels
+------
+
+Form labels should always precede the field, except in the case
+of checkboxes and radio buttons, where the ``input`` should come first. Any
+explanation or help text should follow the ``label`` in a ``p`` with class
+``.help``.
diff --git a/google_appengine/lib/django/docs/apache_auth.txt b/google_appengine/lib/django/docs/apache_auth.txt
new file mode 100644
index 0000000..583cb96
--- /dev/null
+++ b/google_appengine/lib/django/docs/apache_auth.txt
@@ -0,0 +1,71 @@
+=========================================================
+Authenticating against Django's user database from Apache
+=========================================================
+
+Since keeping multiple authentication databases in sync is a common problem when
+dealing with Apache, you can configuring Apache to authenticate against Django's
+`authentication system`_ directly. For example, you could:
+
+ * Serve static/media files directly from Apache only to authenticated users.
+
+ * Authenticate access to a Subversion_ repository against Django users with
+ a certain permission.
+
+ * Allow certain users to connect to a WebDAV share created with mod_dav_.
+
+Configuring Apache
+==================
+
+To check against Django's authorization database from a Apache configuration
+file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along
+with the standard ``Auth*`` and ``Require`` directives::
+
+ <Location /example/>
+ AuthType basic
+ AuthName "example.com"
+ Require valid-user
+
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ PythonAuthenHandler django.contrib.auth.handlers.modpython
+ </Location>
+
+By default, the authentication handler will limit access to the ``/example/``
+location to users marked as staff members. You can use a set of
+``PythonOption`` directives to modify this behavior:
+
+ ================================ =========================================
+ ``PythonOption`` Explanation
+ ================================ =========================================
+ ``DjangoRequireStaffStatus`` If set to ``on`` only "staff" users (i.e.
+ those with the ``is_staff`` flag set)
+ will be allowed.
+
+ Defaults to ``on``.
+
+ ``DjangoRequireSuperuserStatus`` If set to ``on`` only superusers (i.e.
+ those with the ``is_superuser`` flag set)
+ will be allowed.
+
+ Defaults to ``off``.
+
+ ``DjangoPermissionName`` The name of a permission to require for
+ access. See `custom permissions`_ for
+ more information.
+
+ By default no specific permission will be
+ required.
+ ================================ =========================================
+
+Note that sometimes ``SetEnv`` doesn't play well in this mod_python
+configuration, for reasons unknown. If you're having problems getting
+mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using
+``PythonOption`` instead of ``SetEnv``. Therefore, these two Apache directives
+are equivalent::
+
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ PythonOption DJANGO_SETTINGS_MODULE mysite.settings
+
+.. _authentication system: ../authentication/
+.. _Subversion: http://subversion.tigris.org/
+.. _mod_dav: http://httpd.apache.org/docs/2.0/mod/mod_dav.html
+.. _custom permissions: ../authentication/#custom-permissions
diff --git a/google_appengine/lib/django/docs/api_stability.txt b/google_appengine/lib/django/docs/api_stability.txt
new file mode 100644
index 0000000..508336e
--- /dev/null
+++ b/google_appengine/lib/django/docs/api_stability.txt
@@ -0,0 +1,123 @@
+=============
+API stability
+=============
+
+Although Django has not reached a 1.0 release, the bulk of Django's public APIs are
+stable as of the 0.95 release. This document explains which APIs will and will not
+change before the 1.0 release.
+
+What "stable" means
+===================
+
+In this context, stable means:
+
+ - All the public APIs -- everything documented in the linked documents, and
+ all methods that don't begin with an underscore -- will not be moved or
+ renamed without providing backwards-compatible aliases.
+
+ - If new features are added to these APIs -- which is quite possible --
+ they will not break or change the meaning of existing methods. In other
+ words, "stable" does not (necessarily) mean "complete."
+
+ - If, for some reason, an API declared stable must be removed or replaced, it
+ will be declared deprecated but will remain in the API until at least
+ version 1.1. Warnings will be issued when the deprecated method is
+ called.
+
+ - We'll only break backwards compatibility of these APIs if a bug or
+ security hole makes it completely unavoidable.
+
+Stable APIs
+===========
+
+These APIs are stable:
+
+ - `Caching`_.
+
+ - `Custom template tags and libraries`_ (with the possible exception for a
+ small change in the way templates are registered and loaded).
+
+ - `Database lookup`_ (with the exception of validation; see below).
+
+ - `django-admin utility`_.
+
+ - `FastCGI integration`_.
+
+ - `Flatpages`_.
+
+ - `Generic views`_.
+
+ - `Internationalization`_.
+
+ - `Legacy database integration`_.
+
+ - `Model definition`_ (with the exception of generic relations; see below).
+
+ - `mod_python integration`_.
+
+ - `Redirects`_.
+
+ - `Request/response objects`_.
+
+ - `Sending email`_.
+
+ - `Sessions`_.
+
+ - `Settings`_.
+
+ - `Syndication`_.
+
+ - `Template language`_ (with the exception of some possible disambiguation
+ of how tag arguments are passed to tags and filters).
+
+ - `Transactions`_.
+
+ - `URL dispatch`_.
+
+You'll notice that this list comprises the bulk of Django's APIs. That's right
+-- most of the changes planned between now and Django 1.0 are either under the
+hood, feature additions, or changes to a few select bits. A good estimate is
+that 90% of Django can be considered forwards-compatible at this point.
+
+That said, these APIs should *not* be considered stable, and are likely to
+change:
+
+ - `Forms and validation`_ will most likely be completely rewritten to
+ deemphasize Manipulators in favor of validation-aware models.
+
+ - `Serialization`_ is under heavy development; changes are likely.
+
+ - The `authentication`_ framework is changing to be far more flexible, and
+ API changes may be necessary.
+
+ - Generic relations will most likely be moved out of core and into the
+ content-types contrib package to avoid core dependancies on optional
+ components.
+
+ - The comments framework, which is yet undocumented, will likely get a complete
+ rewrite before Django 1.0. Even if the change isn't quite that drastic,
+ there will at least be moderate changes.
+
+.. _caching: ../cache/
+.. _custom template tags and libraries: ../templates_python/
+.. _database lookup: ../db_api/
+.. _django-admin utility: ../django_admin/
+.. _fastcgi integration: ../fastcgi/
+.. _flatpages: ../flatpages/
+.. _generic views: ../generic_views/
+.. _internationalization: ../i18n/
+.. _legacy database integration: ../legacy_databases/
+.. _model definition: ../model_api/
+.. _mod_python integration: ../modpython/
+.. _redirects: ../redirects/
+.. _request/response objects: ../request_response/
+.. _sending email: ../email/
+.. _sessions: ../sessions/
+.. _settings: ../settings/
+.. _syndication: ../syndication/
+.. _template language: ../templates/
+.. _transactions: ../transactions/
+.. _url dispatch: ../url_dispatch/
+.. _forms and validation: ../forms/
+.. _serialization: ../serialization/
+.. _authentication: ../authentication/
diff --git a/google_appengine/lib/django/docs/authentication.txt b/google_appengine/lib/django/docs/authentication.txt
new file mode 100644
index 0000000..aff336f
--- /dev/null
+++ b/google_appengine/lib/django/docs/authentication.txt
@@ -0,0 +1,1024 @@
+=============================
+User authentication in Django
+=============================
+
+Django comes with a user authentication system. It handles user accounts,
+groups, permissions and cookie-based user sessions. This document explains how
+things work.
+
+Overview
+========
+
+The auth system consists of:
+
+ * Users
+ * Permissions: Binary (yes/no) flags designating whether a user may perform
+ a certain task.
+ * Groups: A generic way of applying labels and permissions to more than one
+ user.
+ * Messages: A simple way to queue messages for given users.
+
+Installation
+============
+
+Authentication support is bundled as a Django application in
+``django.contrib.auth``. To install it, do the following:
+
+ 1. Put ``'django.contrib.auth'`` in your ``INSTALLED_APPS`` setting.
+ 2. Run the command ``manage.py syncdb``.
+
+Note that the default ``settings.py`` file created by
+``django-admin.py startproject`` includes ``'django.contrib.auth'`` in
+``INSTALLED_APPS`` for convenience. If your ``INSTALLED_APPS`` already contains
+``'django.contrib.auth'``, feel free to run ``manage.py syncdb`` again; you
+can run that command as many times as you'd like, and each time it'll only
+install what's needed.
+
+The ``syncdb`` command creates the necessary database tables, creates
+permission objects for all installed apps that need 'em, and prompts you to
+create a superuser account the first time you run it.
+
+Once you've taken those steps, that's it.
+
+Users
+=====
+
+Users are represented by a standard Django model, which lives in
+`django/contrib/auth/models.py`_.
+
+.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py
+
+API reference
+-------------
+
+Fields
+~~~~~~
+
+``User`` objects have the following fields:
+
+ * ``username`` -- Required. 30 characters or fewer. Alphanumeric characters
+ only (letters, digits and underscores).
+ * ``first_name`` -- Optional. 30 characters or fewer.
+ * ``last_name`` -- Optional. 30 characters or fewer.
+ * ``email`` -- Optional. E-mail address.
+ * ``password`` -- Required. A hash of, and metadata about, the password.
+ (Django doesn't store the raw password.) Raw passwords can be arbitrarily
+ long and can contain any character. See the "Passwords" section below.
+ * ``is_staff`` -- Boolean. Designates whether this user can access the
+ admin site.
+ * ``is_active`` -- Boolean. Designates whether this account can be used
+ to log in. Set this flag to ``False`` instead of deleting accounts.
+ * ``is_superuser`` -- Boolean. Designates that this user has all permissions
+ without explicitly assigning them.
+ * ``last_login`` -- A datetime of the user's last login. Is set to the
+ current date/time by default.
+ * ``date_joined`` -- A datetime designating when the account was created.
+ Is set to the current date/time by default when the account is created.
+
+Methods
+~~~~~~~
+
+``User`` objects have two many-to-many fields: ``groups`` and
+``user_permissions``. ``User`` objects can access their related
+objects in the same way as any other `Django model`_::
+
+ myuser.groups = [group_list]
+ myuser.groups.add(group, group,...)
+ myuser.groups.remove(group, group,...)
+ myuser.groups.clear()
+ myuser.user_permissions = [permission_list]
+ myuser.user_permissions.add(permission, permission, ...)
+ myuser.user_permissions.remove(permission, permission, ...]
+ myuser.user_permissions.clear()
+
+In addition to those automatic API methods, ``User`` objects have the following
+custom methods:
+
+ * ``is_anonymous()`` -- Always returns ``False``. This is a way of
+ differentiating ``User`` and ``AnonymousUser`` objects. Generally, you
+ should prefer using ``is_authenticated()`` to this method.
+
+ * ``is_authenticated()`` -- Always returns ``True``. This is a way to
+ tell if the user has been authenticated. This does not imply any
+ permissions, and doesn't check if the user is active - it only indicates
+ that the user has provided a valid username and password.
+
+ * ``get_full_name()`` -- Returns the ``first_name`` plus the ``last_name``,
+ with a space in between.
+
+ * ``set_password(raw_password)`` -- Sets the user's password to the given
+ raw string, taking care of the password hashing. Doesn't save the
+ ``User`` object.
+
+ * ``check_password(raw_password)`` -- Returns ``True`` if the given raw
+ string is the correct password for the user. (This takes care of the
+ password hashing in making the comparison.)
+
+ * ``get_group_permissions()`` -- Returns a list of permission strings that
+ the user has, through his/her groups.
+
+ * ``get_all_permissions()`` -- Returns a list of permission strings that
+ the user has, both through group and user permissions.
+
+ * ``has_perm(perm)`` -- Returns ``True`` if the user has the specified
+ permission, where perm is in the format ``"package.codename"``.
+ If the user is inactive, this method will always return ``False``.
+
+ * ``has_perms(perm_list)`` -- Returns ``True`` if the user has each of the
+ specified permissions, where each perm is in the format
+ ``"package.codename"``. If the user is inactive, this method will
+ always return ``False``.
+
+ * ``has_module_perms(package_name)`` -- Returns ``True`` if the user has
+ any permissions in the given package (the Django app label).
+ If the user is inactive, this method will always return ``False``.
+
+ * ``get_and_delete_messages()`` -- Returns a list of ``Message`` objects in
+ the user's queue and deletes the messages from the queue.
+
+ * ``email_user(subject, message, from_email=None)`` -- Sends an e-mail to
+ the user. If ``from_email`` is ``None``, Django uses the
+ `DEFAULT_FROM_EMAIL`_ setting.
+
+ * ``get_profile()`` -- Returns a site-specific profile for this user.
+ Raises ``django.contrib.auth.models.SiteProfileNotAvailable`` if the current site
+ doesn't allow profiles.
+
+.. _Django model: ../model_api/
+.. _DEFAULT_FROM_EMAIL: ../settings/#default-from-email
+
+Manager functions
+~~~~~~~~~~~~~~~~~
+
+The ``User`` model has a custom manager that has the following helper functions:
+
+ * ``create_user(username, email, password)`` -- Creates, saves and returns
+ a ``User``. The ``username``, ``email`` and ``password`` are set as
+ given, and the ``User`` gets ``is_active=True``.
+
+ See _`Creating users` for example usage.
+
+ * ``make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')``
+ Returns a random password with the given length and given string of
+ allowed characters. (Note that the default value of ``allowed_chars``
+ doesn't contain ``"I"`` or letters that look like it, to avoid user
+ confusion.
+
+Basic usage
+-----------
+
+Creating users
+~~~~~~~~~~~~~~
+
+The most basic way to create users is to use the ``create_user`` helper
+function that comes with Django::
+
+ >>> from django.contrib.auth.models import User
+ >>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
+
+ # At this point, user is a User object ready to be saved
+ # to the database. You can continue to change its attributes
+ # if you want to change other fields.
+ >>> user.is_staff = True
+ >>> user.save()
+
+Changing passwords
+~~~~~~~~~~~~~~~~~~
+
+Change a password with ``set_password()``::
+
+ >>> from django.contrib.auth.models import User
+ >>> u = User.objects.get(username__exact='john')
+ >>> u.set_password('new password')
+ >>> u.save()
+
+Don't set the ``password`` attribute directly unless you know what you're
+doing. This is explained in the next section.
+
+Passwords
+---------
+
+The ``password`` attribute of a ``User`` object is a string in this format::
+
+ hashtype$salt$hash
+
+That's hashtype, salt and hash, separated by the dollar-sign character.
+
+Hashtype is either ``sha1`` (default) or ``md5`` -- the algorithm used to
+perform a one-way hash of the password. Salt is a random string used to salt
+the raw password to create the hash.
+
+For example::
+
+ sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4
+
+The ``User.set_password()`` and ``User.check_password()`` functions handle
+the setting and checking of these values behind the scenes.
+
+Previous Django versions, such as 0.90, used simple MD5 hashes without password
+salts. For backwards compatibility, those are still supported; they'll be
+converted automatically to the new style the first time ``check_password()``
+works correctly for a given user.
+
+Anonymous users
+---------------
+
+``django.contrib.auth.models.AnonymousUser`` is a class that implements
+the ``django.contrib.auth.models.User`` interface, with these differences:
+
+ * ``id`` is always ``None``.
+ * ``is_anonymous()`` returns ``True`` instead of ``False``.
+ * ``is_authenticated()`` returns ``False`` instead of ``True``.
+ * ``has_perm()`` always returns ``False``.
+ * ``set_password()``, ``check_password()``, ``save()``, ``delete()``,
+ ``set_groups()`` and ``set_permissions()`` raise ``NotImplementedError``.
+
+In practice, you probably won't need to use ``AnonymousUser`` objects on your
+own, but they're used by Web requests, as explained in the next section.
+
+Creating superusers
+-------------------
+
+``manage.py syncdb`` prompts you to create a superuser the first time you run
+it after adding ``'django.contrib.auth'`` to your ``INSTALLED_APPS``. But if
+you need to create a superuser after that via the command line, you can use the
+``create_superuser.py`` utility. Just run this command::
+
+ python /path/to/django/contrib/auth/create_superuser.py
+
+Make sure to substitute ``/path/to/`` with the path to the Django codebase on
+your filesystem.
+
+Authentication in Web requests
+==============================
+
+Until now, this document has dealt with the low-level APIs for manipulating
+authentication-related objects. On a higher level, Django can hook this
+authentication framework into its system of `request objects`_.
+
+First, install the ``SessionMiddleware`` and ``AuthenticationMiddleware``
+middlewares by adding them to your ``MIDDLEWARE_CLASSES`` setting. See the
+`session documentation`_ for more information.
+
+Once you have those middlewares installed, you'll be able to access
+``request.user`` in views. ``request.user`` will give you a ``User`` object
+representing the currently logged-in user. If a user isn't currently logged in,
+``request.user`` will be set to an instance of ``AnonymousUser`` (see the
+previous section). You can tell them apart with ``is_authenticated()``, like so::
+
+ if request.user.is_authenticated():
+ # Do something for authenticated users.
+ else:
+ # Do something for anonymous users.
+
+.. _request objects: ../request_response/#httprequest-objects
+.. _session documentation: ../sessions/
+
+How to log a user in
+--------------------
+
+Django provides two functions in ``django.contrib.auth``: ``authenticate()``
+and ``login()``.
+
+To authenticate a given username and password, use ``authenticate()``. It
+takes two keyword arguments, ``username`` and ``password``, and it returns
+a ``User`` object if the password is valid for the given username. If the
+password is invalid, ``authenticate()`` returns ``None``. Example::
+
+ from django.contrib.auth import authenticate
+ user = authenticate(username='john', password='secret')
+ if user is not None:
+ if user.is_active:
+ print "You provided a correct username and password!"
+ else:
+ print "Your account has been disabled!"
+ else:
+ print "Your username and password were incorrect."
+
+To log a user in, in a view, use ``login()``. It takes an ``HttpRequest``
+object and a ``User`` object. ``login()`` saves the user's ID in the session,
+using Django's session framework, so, as mentioned above, you'll need to make
+sure to have the session middleware installed.
+
+This example shows how you might use both ``authenticate()`` and ``login()``::
+
+ from django.contrib.auth import authenticate, login
+
+ def my_view(request):
+ username = request.POST['username']
+ password = request.POST['password']
+ user = authenticate(username=username, password=password)
+ if user is not None:
+ if user.is_active:
+ login(request, user)
+ # Redirect to a success page.
+ else:
+ # Return a 'disabled account' error message
+ else:
+ # Return an 'invalid login' error message.
+
+Manually checking a user's password
+-----------------------------------
+
+If you'd like to manually authenticate a user by comparing a
+plain-text password to the hashed password in the database, use the
+convenience function `django.contrib.auth.models.check_password`. It
+takes two arguments: the plain-text password to check, and the full
+value of a user's ``password`` field in the database to check against,
+and returns ``True`` if they match, ``False`` otherwise.
+
+How to log a user out
+---------------------
+
+To log out a user who has been logged in via ``django.contrib.auth.login()``,
+use ``django.contrib.auth.logout()`` within your view. It takes an
+``HttpRequest`` object and has no return value. Example::
+
+ from django.contrib.auth import logout
+
+ def logout_view(request):
+ logout(request)
+ # Redirect to a success page.
+
+Note that ``logout()`` doesn't throw any errors if the user wasn't logged in.
+
+Limiting access to logged-in users
+----------------------------------
+
+The raw way
+~~~~~~~~~~~
+
+The simple, raw way to limit access to pages is to check
+``request.user.is_authenticated()`` and either redirect to a login page::
+
+ from django.http import HttpResponseRedirect
+
+ def my_view(request):
+ if not request.user.is_authenticated():
+ return HttpResponseRedirect('/login/?next=%s' % request.path)
+ # ...
+
+...or display an error message::
+
+ def my_view(request):
+ if not request.user.is_authenticated():
+ return render_to_response('myapp/login_error.html')
+ # ...
+
+The login_required decorator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As a shortcut, you can use the convenient ``login_required`` decorator::
+
+ from django.contrib.auth.decorators import login_required
+
+ def my_view(request):
+ # ...
+ my_view = login_required(my_view)
+
+Here's an equivalent example, using the more compact decorator syntax
+introduced in Python 2.4::
+
+ from django.contrib.auth.decorators import login_required
+
+ @login_required
+ def my_view(request):
+ # ...
+
+``login_required`` does the following:
+
+ * If the user isn't logged in, redirect to ``/accounts/login/``, passing
+ the current absolute URL in the query string as ``next``. For example:
+ ``/accounts/login/?next=/polls/3/``.
+ * If the user is logged in, execute the view normally. The view code is
+ free to assume the user is logged in.
+
+Note that you'll need to map the appropriate Django view to ``/accounts/login/``.
+To do this, add the following line to your URLconf::
+
+ (r'^accounts/login/$', 'django.contrib.auth.views.login'),
+
+Here's what ``django.contrib.auth.views.login`` does:
+
+ * If called via ``GET``, it displays a login form that POSTs to the same
+ URL. More on this in a bit.
+
+ * If called via ``POST``, it tries to log the user in. If login is
+ successful, the view redirects to the URL specified in ``next``. If
+ ``next`` isn't provided, it redirects to ``/accounts/profile/`` (which is
+ currently hard-coded). If login isn't successful, it redisplays the login
+ form.
+
+It's your responsibility to provide the login form in a template called
+``registration/login.html`` by default. This template gets passed three
+template context variables:
+
+ * ``form``: A ``FormWrapper`` object representing the login form. See the
+ `forms documentation`_ for more on ``FormWrapper`` objects.
+ * ``next``: The URL to redirect to after successful login. This may contain
+ a query string, too.
+ * ``site_name``: The name of the current ``Site``, according to the
+ ``SITE_ID`` setting. See the `site framework docs`_.
+
+If you'd prefer not to call the template ``registration/login.html``, you can
+pass the ``template_name`` parameter via the extra arguments to the view in
+your URLconf. For example, this URLconf line would use ``myapp/login.html``
+instead::
+
+ (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'myapp/login.html'}),
+
+Here's a sample ``registration/login.html`` template you can use as a starting
+point. It assumes you have a ``base.html`` template that defines a ``content``
+block::
+
+ {% extends "base.html" %}
+
+ {% block content %}
+
+ {% if form.has_errors %}
+ <p>Your username and password didn't match. Please try again.</p>
+ {% endif %}
+
+ <form method="post" action=".">
+ <table>
+ <tr><td><label for="id_username">Username:</label></td><td>{{ form.username }}</td></tr>
+ <tr><td><label for="id_password">Password:</label></td><td>{{ form.password }}</td></tr>
+ </table>
+
+ <input type="submit" value="login" />
+ <input type="hidden" name="next" value="{{ next }}" />
+ </form>
+
+ {% endblock %}
+
+.. _forms documentation: ../forms/
+.. _site framework docs: ../sites/
+
+Other built-in views
+--------------------
+
+In addition to the `login` view, the authentication system includes a
+few other useful built-in views:
+
+``django.contrib.auth.views.logout``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Description:**
+
+Logs a user out.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to display after
+ logging the user out. This will default to
+ ``registration/logged_out.html`` if no argument is supplied.
+
+**Template context:**
+
+ * ``title``: The string "Logged out", localized.
+
+``django.contrib.auth.views.logout_then_login``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Description:**
+
+Logs a user out, then redirects to the login page.
+
+**Optional arguments:**
+
+ * ``login_url``: The URL of the login page to redirect to. This
+ will default to ``/accounts/login/`` if not supplied.
+
+``django.contrib.auth.views.password_change``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Description:**
+
+Allows a user to change their password.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to use for
+ displaying the password change form. This will default to
+ ``registration/password_change_form.html`` if not supplied.
+
+**Template context:**
+
+ * ``form``: The password change form.
+
+``django.contrib.auth.views.password_change_done``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Description:**
+
+The page shown after a user has changed their password.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to use. This will
+ default to ``registration/password_change_done.html`` if not
+ supplied.
+
+``django.contrib.auth.views.password_reset``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Description:**
+
+Allows a user to reset their password, and sends them the new password
+in an email.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to use for
+ displaying the password reset form. This will default to
+ ``registration/password_reset_form.html`` if not supplied.
+
+ * ``email_template_name``: The full name of a template to use for
+ generating the email with the new password. This will default to
+ ``registration/password_reset_email.html`` if not supplied.
+
+**Template context:**
+
+ * ``form``: The form for resetting the user's password.
+
+``django.contrib.auth.views.password_reset_done``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Description:**
+
+The page shown after a user has reset their password.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to use. This will
+ default to ``registration/password_reset_done.html`` if not
+ supplied.
+
+``django.contrib.auth.views.redirect_to_login``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Description:**
+
+Redirects to the login page, and then back to another URL after a
+successful login.
+
+**Required arguments:**
+
+ * ``next``: The URL to redirect to after a successful login.
+
+**Optional arguments:**
+
+ * ``login_url``: The URL of the login page to redirect to. This
+ will default to ``/accounts/login/`` if not supplied.
+
+Built-in manipulators
+---------------------
+
+If you don't want to use the built-in views, but want the convenience
+of not having to write manipulators for this functionality, the
+authentication system provides several built-in manipulators:
+
+ * ``django.contrib.auth.forms.AdminPasswordChangeForm``: A
+ manipulator used in the admin interface to change a user's
+ password.
+
+ * ``django.contrib.auth.forms.AuthenticationForm``: A manipulator
+ for logging a user in.
+
+ * ``django.contrib.auth.forms.PasswordChangeForm``: A manipulator
+ for allowing a user to change their password.
+
+ * ``django.contrib.auth.forms.PasswordResetForm``: A manipulator
+ for resetting a user's password and emailing the new password to
+ them.
+
+ * ``django.contrib.auth.forms.UserCreationForm``: A manipulator
+ for creating a new user.
+
+Limiting access to logged-in users that pass a test
+---------------------------------------------------
+
+To limit access based on certain permissions or some other test, you'd do
+essentially the same thing as described in the previous section.
+
+The simple way is to run your test on ``request.user`` in the view directly.
+For example, this view checks to make sure the user is logged in and has the
+permission ``polls.can_vote``::
+
+ def my_view(request):
+ if not (request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
+ return HttpResponse("You can't vote in this poll.")
+ # ...
+
+As a shortcut, you can use the convenient ``user_passes_test`` decorator::
+
+ from django.contrib.auth.decorators import user_passes_test
+
+ def my_view(request):
+ # ...
+ my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'))(my_view)
+
+We're using this particular test as a relatively simple example. However, if
+you just want to test whether a permission is available to a user, you can use
+the ``permission_required()`` decorator, described later in this document.
+
+Here's the same thing, using Python 2.4's decorator syntax::
+
+ from django.contrib.auth.decorators import user_passes_test
+
+ @user_passes_test(lambda u: u.has_perm('polls.can_vote'))
+ def my_view(request):
+ # ...
+
+``user_passes_test`` takes a required argument: a callable that takes a
+``User`` object and returns ``True`` if the user is allowed to view the page.
+Note that ``user_passes_test`` does not automatically check that the ``User``
+is not anonymous.
+
+``user_passes_test()`` takes an optional ``login_url`` argument, which lets you
+specify the URL for your login page (``/accounts/login/`` by default).
+
+Example in Python 2.3 syntax::
+
+ from django.contrib.auth.decorators import user_passes_test
+
+ def my_view(request):
+ # ...
+ my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')(my_view)
+
+Example in Python 2.4 syntax::
+
+ from django.contrib.auth.decorators import user_passes_test
+
+ @user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')
+ def my_view(request):
+ # ...
+
+The permission_required decorator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**New in Django development version**
+
+It's a relatively common task to check whether a user has a particular
+permission. For that reason, Django provides a shortcut for that case: the
+``permission_required()`` decorator. Using this decorator, the earlier example
+can be written as::
+
+ from django.contrib.auth.decorators import permission_required
+
+ def my_view(request):
+ # ...
+ my_view = permission_required('polls.can_vote')(my_view)
+
+Note that ``permission_required()`` also takes an optional ``login_url``
+parameter. Example::
+
+ from django.contrib.auth.decorators import permission_required
+
+ def my_view(request):
+ # ...
+ my_view = permission_required('polls.can_vote', login_url='/loginpage/')(my_view)
+
+As in the ``login_required`` decorator, ``login_url`` defaults to
+``'/accounts/login/'``.
+
+Limiting access to generic views
+--------------------------------
+
+To limit access to a `generic view`_, write a thin wrapper around the view,
+and point your URLconf to your wrapper instead of the generic view itself.
+For example::
+
+ from django.views.generic.date_based import object_detail
+
+ @login_required
+ def limited_object_detail(*args, **kwargs):
+ return object_detail(*args, **kwargs)
+
+.. _generic view: ../generic_views/
+
+Permissions
+===========
+
+Django comes with a simple permissions system. It provides a way to assign
+permissions to specific users and groups of users.
+
+It's used by the Django admin site, but you're welcome to use it in your own
+code.
+
+The Django admin site uses permissions as follows:
+
+ * Access to view the "add" form and add an object is limited to users with
+ the "add" permission for that type of object.
+ * Access to view the change list, view the "change" form and change an
+ object is limited to users with the "change" permission for that type of
+ object.
+ * Access to delete an object is limited to users with the "delete"
+ permission for that type of object.
+
+Permissions are set globally per type of object, not per specific object
+instance. For example, it's possible to say "Mary may change news stories," but
+it's not currently possible to say "Mary may change news stories, but only the
+ones she created herself" or "Mary may only change news stories that have a
+certain status, publication date or ID." The latter functionality is something
+Django developers are currently discussing.
+
+Default permissions
+-------------------
+
+Three basic permissions -- add, create and delete -- are automatically created
+for each Django model that has a ``class Admin`` set. Behind the scenes, these
+permissions are added to the ``auth_permission`` database table when you run
+``manage.py syncdb``.
+
+Note that if your model doesn't have ``class Admin`` set when you run
+``syncdb``, the permissions won't be created. If you initialize your database
+and add ``class Admin`` to models after the fact, you'll need to run
+``manage.py syncdb`` again. It will create any missing permissions for
+all of your installed apps.
+
+Custom permissions
+------------------
+
+To create custom permissions for a given model object, use the ``permissions``
+`model Meta attribute`_.
+
+This example model creates three custom permissions::
+
+ class USCitizen(models.Model):
+ # ...
+ class Meta:
+ permissions = (
+ ("can_drive", "Can drive"),
+ ("can_vote", "Can vote in elections"),
+ ("can_drink", "Can drink alcohol"),
+ )
+
+The only thing this does is create those extra permissions when you run
+``syncdb``.
+
+.. _model Meta attribute: ../model_api/#meta-options
+
+API reference
+-------------
+
+Just like users, permissions are implemented in a Django model that lives in
+`django/contrib/auth/models.py`_.
+
+.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py
+
+Fields
+~~~~~~
+
+``Permission`` objects have the following fields:
+
+ * ``name`` -- Required. 50 characters or fewer. Example: ``'Can vote'``.
+ * ``content_type`` -- Required. A reference to the ``django_content_type``
+ database table, which contains a record for each installed Django model.
+ * ``codename`` -- Required. 100 characters or fewer. Example: ``'can_vote'``.
+
+Methods
+~~~~~~~
+
+``Permission`` objects have the standard data-access methods like any other
+`Django model`_.
+
+Authentication data in templates
+================================
+
+The currently logged-in user and his/her permissions are made available in the
+`template context`_ when you use ``RequestContext``.
+
+.. admonition:: Technicality
+
+ Technically, these variables are only made available in the template context
+ if you use ``RequestContext`` *and* your ``TEMPLATE_CONTEXT_PROCESSORS``
+ setting contains ``"django.core.context_processors.auth"``, which is default.
+ For more, see the `RequestContext docs`_.
+
+ .. _RequestContext docs: ../templates_python/#subclassing-context-requestcontext
+
+Users
+-----
+
+The currently logged-in user, either a ``User`` instance or an``AnonymousUser``
+instance, is stored in the template variable ``{{ user }}``::
+
+ {% if user.is_authenticated %}
+ <p>Welcome, {{ user.username }}. Thanks for logging in.</p>
+ {% else %}
+ <p>Welcome, new user. Please log in.</p>
+ {% endif %}
+
+Permissions
+-----------
+
+The currently logged-in user's permissions are stored in the template variable
+``{{ perms }}``. This is an instance of ``django.core.context_processors.PermWrapper``,
+which is a template-friendly proxy of permissions.
+
+In the ``{{ perms }}`` object, single-attribute lookup is a proxy to
+``User.has_module_perms``. This example would display ``True`` if the logged-in
+user had any permissions in the ``foo`` app::
+
+ {{ perms.foo }}
+
+Two-level-attribute lookup is a proxy to ``User.has_perm``. This example would
+display ``True`` if the logged-in user had the permission ``foo.can_vote``::
+
+ {{ perms.foo.can_vote }}
+
+Thus, you can check permissions in template ``{% if %}`` statements::
+
+ {% if perms.foo %}
+ <p>You have permission to do something in the foo app.</p>
+ {% if perms.foo.can_vote %}
+ <p>You can vote!</p>
+ {% endif %}
+ {% if perms.foo.can_drive %}
+ <p>You can drive!</p>
+ {% endif %}
+ {% else %}
+ <p>You don't have permission to do anything in the foo app.</p>
+ {% endif %}
+
+.. _template context: ../templates_python/
+
+Groups
+======
+
+Groups are a generic way of categorizing users so you can apply permissions, or
+some other label, to those users. A user can belong to any number of groups.
+
+A user in a group automatically has the permissions granted to that group. For
+example, if the group ``Site editors`` has the permission
+``can_edit_home_page``, any user in that group will have that permission.
+
+Beyond permissions, groups are a convenient way to categorize users to give
+them some label, or extended functionality. For example, you could create a
+group ``'Special users'``, and you could write code that could, say, give them
+access to a members-only portion of your site, or send them members-only e-mail
+messages.
+
+Messages
+========
+
+The message system is a lightweight way to queue messages for given users.
+
+A message is associated with a ``User``. There's no concept of expiration or
+timestamps.
+
+Messages are used by the Django admin after successful actions. For example,
+``"The poll Foo was created successfully."`` is a message.
+
+The API is simple:
+
+ * To create a new message, use
+ ``user_obj.message_set.create(message='message_text')``.
+ * To retrieve/delete messages, use ``user_obj.get_and_delete_messages()``,
+ which returns a list of ``Message`` objects in the user's queue (if any)
+ and deletes the messages from the queue.
+
+In this example view, the system saves a message for the user after creating
+a playlist::
+
+ def create_playlist(request, songs):
+ # Create the playlist with the given songs.
+ # ...
+ request.user.message_set.create(message="Your playlist was added successfully.")
+ return render_to_response("playlists/create.html",
+ context_instance=RequestContext(request))
+
+When you use ``RequestContext``, the currently logged-in user and his/her
+messages are made available in the `template context`_ as the template variable
+``{{ messages }}``. Here's an example of template code that displays messages::
+
+ {% if messages %}
+ <ul>
+ {% for message in messages %}
+ <li>{{ message }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
+Note that ``RequestContext`` calls ``get_and_delete_messages`` behind the
+scenes, so any messages will be deleted even if you don't display them.
+
+Finally, note that this messages framework only works with users in the user
+database. To send messages to anonymous users, use the `session framework`_.
+
+.. _session framework: ../sessions/
+
+Other authentication sources
+============================
+
+The authentication that comes with Django is good enough for most common cases,
+but you may have the need to hook into another authentication source -- that
+is, another source of usernames and passwords or authentication methods.
+
+For example, your company may already have an LDAP setup that stores a username
+and password for every employee. It'd be a hassle for both the network
+administrator and the users themselves if users had separate accounts in LDAP
+and the Django-based applications.
+
+So, to handle situations like this, the Django authentication system lets you
+plug in another authentication sources. You can override Django's default
+database-based scheme, or you can use the default system in tandem with other
+systems.
+
+Specifying authentication backends
+----------------------------------
+
+Behind the scenes, Django maintains a list of "authentication backends" that it
+checks for authentication. When somebody calls
+``django.contrib.auth.authenticate()`` -- as described in "How to log a user in"
+above -- Django tries authenticating across all of its authentication backends.
+If the first authentication method fails, Django tries the second one, and so
+on, until all backends have been attempted.
+
+The list of authentication backends to use is specified in the
+``AUTHENTICATION_BACKENDS`` setting. This should be a tuple of Python path
+names that point to Python classes that know how to authenticate. These classes
+can be anywhere on your Python path.
+
+By default, ``AUTHENTICATION_BACKENDS`` is set to::
+
+ ('django.contrib.auth.backends.ModelBackend',)
+
+That's the basic authentication scheme that checks the Django users database.
+
+The order of ``AUTHENTICATION_BACKENDS`` matters, so if the same username and
+password is valid in multiple backends, Django will stop processing at the
+first positive match.
+
+Writing an authentication backend
+---------------------------------
+
+An authentication backend is a class that implements two methods:
+``get_user(id)`` and ``authenticate(**credentials)``.
+
+The ``get_user`` method takes an ``id`` -- which could be a username, database
+ID or whatever -- and returns a ``User`` object.
+
+The ``authenticate`` method takes credentials as keyword arguments. Most of
+the time, it'll just look like this::
+
+ class MyBackend:
+ def authenticate(self, username=None, password=None):
+ # Check the username/password and return a User.
+
+But it could also authenticate a token, like so::
+
+ class MyBackend:
+ def authenticate(self, token=None):
+ # Check the token and return a User.
+
+Either way, ``authenticate`` should check the credentials it gets, and it
+should return a ``User`` object that matches those credentials, if the
+credentials are valid. If they're not valid, it should return ``None``.
+
+The Django admin system is tightly coupled to the Django ``User`` object
+described at the beginning of this document. For now, the best way to deal with
+this is to create a Django ``User`` object for each user that exists for your
+backend (e.g., in your LDAP directory, your external SQL database, etc.) You
+can either write a script to do this in advance, or your ``authenticate``
+method can do it the first time a user logs in.
+
+Here's an example backend that authenticates against a username and password
+variable defined in your ``settings.py`` file and creates a Django ``User``
+object the first time a user authenticates::
+
+ from django.conf import settings
+ from django.contrib.auth.models import User, check_password
+
+ class SettingsBackend:
+ """
+ Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
+
+ Use the login name, and a hash of the password. For example:
+
+ ADMIN_LOGIN = 'admin'
+ ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
+ """
+ def authenticate(self, username=None, password=None):
+ login_valid = (settings.ADMIN_LOGIN == username)
+ pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
+ if login_valid and pwd_valid:
+ try:
+ user = User.objects.get(username=username)
+ except User.DoesNotExist:
+ # Create a new user. Note that we can set password
+ # to anything, because it won't be checked; the password
+ # from settings.py will.
+ user = User(username=username, password='get from settings.py')
+ user.is_staff = True
+ user.is_superuser = True
+ user.save()
+ return user
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(pk=user_id)
+ except User.DoesNotExist:
+ return None
diff --git a/google_appengine/lib/django/docs/cache.txt b/google_appengine/lib/django/docs/cache.txt
new file mode 100644
index 0000000..054d998
--- /dev/null
+++ b/google_appengine/lib/django/docs/cache.txt
@@ -0,0 +1,543 @@
+========================
+Django's cache framework
+========================
+
+A fundamental tradeoff in dynamic Web sites is, well, they're dynamic. Each
+time a user requests a page, the Web server makes all sorts of calculations --
+from database queries to template rendering to business logic -- to create the
+page that your site's visitor sees. This is a lot more expensive, from a
+processing-overhead perspective, than your standard read-a-file-off-the-filesystem
+server arrangement.
+
+For most Web applications, this overhead isn't a big deal. Most Web
+applications aren't washingtonpost.com or slashdot.org; they're simply small-
+to medium-sized sites with so-so traffic. But for medium- to high-traffic
+sites, it's essential to cut as much overhead as possible.
+
+That's where caching comes in.
+
+To cache something is to save the result of an expensive calculation so that
+you don't have to perform the calculation next time. Here's some pseudocode
+explaining how this would work for a dynamically generated Web page::
+
+ given a URL, try finding that page in the cache
+ if the page is in the cache:
+ return the cached page
+ else:
+ generate the page
+ save the generated page in the cache (for next time)
+ return the generated page
+
+Django comes with a robust cache system that lets you save dynamic pages so
+they don't have to be calculated for each request. For convenience, Django
+offers different levels of cache granularity: You can cache the output of
+specific views, you can cache only the pieces that are difficult to produce, or
+you can cache your entire site.
+
+Django also works well with "upstream" caches, such as Squid
+(http://www.squid-cache.org/) and browser-based caches. These are the types of
+caches that you don't directly control but to which you can provide hints (via
+HTTP headers) about which parts of your site should be cached, and how.
+
+Setting up the cache
+====================
+
+The cache system requires a small amount of setup. Namely, you have to tell it
+where your cached data should live -- whether in a database, on the filesystem
+or directly in memory. This is an important decision that affects your cache's
+performance; yes, some cache types are faster than others.
+
+Your cache preference goes in the ``CACHE_BACKEND`` setting in your settings
+file. Here's an explanation of all available values for CACHE_BACKEND.
+
+Memcached
+---------
+
+By far the fastest, most efficient type of cache available to Django, Memcached
+is an entirely memory-based cache framework originally developed to handle high
+loads at LiveJournal.com and subsequently open-sourced by Danga Interactive.
+It's used by sites such as Slashdot and Wikipedia to reduce database access and
+dramatically increase site performance.
+
+Memcached is available for free at http://danga.com/memcached/ . It runs as a
+daemon and is allotted a specified amount of RAM. All it does is provide an
+interface -- a *super-lightning-fast* interface -- for adding, retrieving and
+deleting arbitrary data in the cache. All data is stored directly in memory,
+so there's no overhead of database or filesystem usage.
+
+After installing Memcached itself, you'll need to install the Memcached Python
+bindings. They're in a single Python module, memcache.py, available at
+ftp://ftp.tummy.com/pub/python-memcached/ . If that URL is no longer valid,
+just go to the Memcached Web site (http://www.danga.com/memcached/) and get the
+Python bindings from the "Client APIs" section.
+
+To use Memcached with Django, set ``CACHE_BACKEND`` to
+``memcached://ip:port/``, where ``ip`` is the IP address of the Memcached
+daemon and ``port`` is the port on which Memcached is running.
+
+In this example, Memcached is running on localhost (127.0.0.1) port 11211::
+
+ CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
+
+One excellent feature of Memcached is its ability to share cache over multiple
+servers. To take advantage of this feature, include all server addresses in
+``CACHE_BACKEND``, separated by semicolons. In this example, the cache is
+shared over Memcached instances running on IP address 172.19.26.240 and
+172.19.26.242, both on port 11211::
+
+ CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'
+
+Memory-based caching has one disadvantage: Because the cached data is stored in
+memory, the data will be lost if your server crashes. Clearly, memory isn't
+intended for permanent data storage, so don't rely on memory-based caching as
+your only data storage. Actually, none of the Django caching backends should be
+used for permanent storage -- they're all intended to be solutions for caching,
+not storage -- but we point this out here because memory-based caching is
+particularly temporary.
+
+Database caching
+----------------
+
+To use a database table as your cache backend, first create a cache table in
+your database by running this command::
+
+ python manage.py createcachetable [cache_table_name]
+
+...where ``[cache_table_name]`` is the name of the database table to create.
+(This name can be whatever you want, as long as it's a valid table name that's
+not already being used in your database.) This command creates a single table
+in your database that is in the proper format that Django's database-cache
+system expects.
+
+Once you've created that database table, set your ``CACHE_BACKEND`` setting to
+``"db://tablename/"``, where ``tablename`` is the name of the database table.
+In this example, the cache table's name is ``my_cache_table``:
+
+ CACHE_BACKEND = 'db://my_cache_table'
+
+Database caching works best if you've got a fast, well-indexed database server.
+
+Filesystem caching
+------------------
+
+To store cached items on a filesystem, use the ``"file://"`` cache type for
+``CACHE_BACKEND``. For example, to store cached data in ``/var/tmp/django_cache``,
+use this setting::
+
+ CACHE_BACKEND = 'file:///var/tmp/django_cache'
+
+Note that there are three forward slashes toward the beginning of that example.
+The first two are for ``file://``, and the third is the first character of the
+directory path, ``/var/tmp/django_cache``.
+
+The directory path should be absolute -- that is, it should start at the root
+of your filesystem. It doesn't matter whether you put a slash at the end of the
+setting.
+
+Make sure the directory pointed-to by this setting exists and is readable and
+writable by the system user under which your Web server runs. Continuing the
+above example, if your server runs as the user ``apache``, make sure the
+directory ``/var/tmp/django_cache`` exists and is readable and writable by the
+user ``apache``.
+
+Local-memory caching
+--------------------
+
+If you want the speed advantages of in-memory caching but don't have the
+capability of running Memcached, consider the local-memory cache backend. This
+cache is multi-process and thread-safe. To use it, set ``CACHE_BACKEND`` to
+``"locmem:///"``. For example::
+
+ CACHE_BACKEND = 'locmem:///'
+
+Simple caching (for development)
+--------------------------------
+
+A simple, single-process memory cache is available as ``"simple:///"``. This
+merely saves cached data in-process, which means it should only be used in
+development or testing environments. For example::
+
+ CACHE_BACKEND = 'simple:///'
+
+Dummy caching (for development)
+-------------------------------
+
+Finally, Django comes with a "dummy" cache that doesn't actually cache -- it
+just implements the cache interface without doing anything.
+
+This is useful if you have a production site that uses heavy-duty caching in
+various places but a development/test environment on which you don't want to
+cache. In that case, set ``CACHE_BACKEND`` to ``"dummy:///"`` in the settings
+file for your development environment. As a result, your development
+environment won't use caching and your production environment still will.
+
+CACHE_BACKEND arguments
+-----------------------
+
+All caches may take arguments. They're given in query-string style on the
+``CACHE_BACKEND`` setting. Valid arguments are:
+
+ timeout
+ Default timeout, in seconds, to use for the cache. Defaults to 5
+ minutes (300 seconds).
+
+ max_entries
+ For the simple and database backends, the maximum number of entries
+ allowed in the cache before it is cleaned. Defaults to 300.
+
+ cull_percentage
+ The percentage of entries that are culled when max_entries is reached.
+ The actual percentage is 1/cull_percentage, so set cull_percentage=3 to
+ cull 1/3 of the entries when max_entries is reached.
+
+ A value of 0 for cull_percentage means that the entire cache will be
+ dumped when max_entries is reached. This makes culling *much* faster
+ at the expense of more cache misses.
+
+In this example, ``timeout`` is set to ``60``::
+
+ CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=60"
+
+In this example, ``timeout`` is ``30`` and ``max_entries`` is ``400``::
+
+ CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=30&max_entries=400"
+
+Invalid arguments are silently ignored, as are invalid values of known
+arguments.
+
+The per-site cache
+==================
+
+Once the cache is set up, the simplest way to use caching is to cache your
+entire site. Just add ``'django.middleware.cache.CacheMiddleware'`` to your
+``MIDDLEWARE_CLASSES`` setting, as in this example::
+
+ MIDDLEWARE_CLASSES = (
+ 'django.middleware.cache.CacheMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ )
+
+(The order of ``MIDDLEWARE_CLASSES`` matters. See "Order of MIDDLEWARE_CLASSES"
+below.)
+
+Then, add the following required settings to your Django settings file:
+
+* ``CACHE_MIDDLEWARE_SECONDS`` -- The number of seconds each page should be
+ cached.
+* ``CACHE_MIDDLEWARE_KEY_PREFIX`` -- If the cache is shared across multiple
+ sites using the same Django installation, set this to the name of the site,
+ or some other string that is unique to this Django instance, to prevent key
+ collisions. Use an empty string if you don't care.
+
+The cache middleware caches every page that doesn't have GET or POST
+parameters. Optionally, if the ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting is
+``True``, only anonymous requests (i.e., not those made by a logged-in user)
+will be cached. This is a simple and effective way of disabling caching for any
+user-specific pages (include Django's admin interface). Note that if you use
+``CACHE_MIDDLEWARE_ANONYMOUS_ONLY``, you should make sure you've activated
+``AuthenticationMiddleware`` and that ``AuthenticationMiddleware`` appears
+before ``CacheMiddleware`` in your ``MIDDLEWARE_CLASSES``.
+
+Additionally, ``CacheMiddleware`` automatically sets a few headers in each
+``HttpResponse``:
+
+* Sets the ``Last-Modified`` header to the current date/time when a fresh
+ (uncached) version of the page is requested.
+* Sets the ``Expires`` header to the current date/time plus the defined
+ ``CACHE_MIDDLEWARE_SECONDS``.
+* Sets the ``Cache-Control`` header to give a max age for the page -- again,
+ from the ``CACHE_MIDDLEWARE_SECONDS`` setting.
+
+See the `middleware documentation`_ for more on middleware.
+
+.. _`middleware documentation`: ../middleware/
+
+The per-view cache
+==================
+
+A more granular way to use the caching framework is by caching the output of
+individual views. ``django.views.decorators.cache`` defines a ``cache_page``
+decorator that will automatically cache the view's response for you. It's easy
+to use::
+
+ from django.views.decorators.cache import cache_page
+
+ def slashdot_this(request):
+ ...
+
+ slashdot_this = cache_page(slashdot_this, 60 * 15)
+
+Or, using Python 2.4's decorator syntax::
+
+ @cache_page(60 * 15)
+ def slashdot_this(request):
+ ...
+
+``cache_page`` takes a single argument: the cache timeout, in seconds. In the
+above example, the result of the ``slashdot_this()`` view will be cached for 15
+minutes.
+
+The low-level cache API
+=======================
+
+Sometimes, however, caching an entire rendered page doesn't gain you very much.
+For example, you may find it's only necessary to cache the result of an
+intensive database query. In cases like this, you can use the low-level cache
+API to store objects in the cache with any level of granularity you like.
+
+The cache API is simple. The cache module, ``django.core.cache``, exports a
+``cache`` object that's automatically created from the ``CACHE_BACKEND``
+setting::
+
+ >>> from django.core.cache import cache
+
+The basic interface is ``set(key, value, timeout_seconds)`` and ``get(key)``::
+
+ >>> cache.set('my_key', 'hello, world!', 30)
+ >>> cache.get('my_key')
+ 'hello, world!'
+
+The ``timeout_seconds`` argument is optional and defaults to the ``timeout``
+argument in the ``CACHE_BACKEND`` setting (explained above).
+
+If the object doesn't exist in the cache, ``cache.get()`` returns ``None``::
+
+ >>> cache.get('some_other_key')
+ None
+
+ # Wait 30 seconds for 'my_key' to expire...
+
+ >>> cache.get('my_key')
+ None
+
+get() can take a ``default`` argument::
+
+ >>> cache.get('my_key', 'has expired')
+ 'has expired'
+
+There's also a get_many() interface that only hits the cache once. get_many()
+returns a dictionary with all the keys you asked for that actually exist in the
+cache (and haven't expired)::
+
+ >>> cache.set('a', 1)
+ >>> cache.set('b', 2)
+ >>> cache.set('c', 3)
+ >>> cache.get_many(['a', 'b', 'c'])
+ {'a': 1, 'b': 2, 'c': 3}
+
+Finally, you can delete keys explicitly with ``delete()``. This is an easy way
+of clearing the cache for a particular object::
+
+ >>> cache.delete('a')
+
+That's it. The cache has very few restrictions: You can cache any object that
+can be pickled safely, although keys must be strings.
+
+Upstream caches
+===============
+
+So far, this document has focused on caching your *own* data. But another type
+of caching is relevant to Web development, too: caching performed by "upstream"
+caches. These are systems that cache pages for users even before the request
+reaches your Web site.
+
+Here are a few examples of upstream caches:
+
+ * Your ISP may cache certain pages, so if you requested a page from
+ somedomain.com, your ISP would send you the page without having to access
+ somedomain.com directly.
+
+ * Your Django Web site may sit behind a Squid Web proxy
+ (http://www.squid-cache.org/) that caches pages for performance. In this
+ case, each request first would be handled by Squid, and it'd only be
+ passed to your application if needed.
+
+ * Your Web browser caches pages, too. If a Web page sends out the right
+ headers, your browser will use the local (cached) copy for subsequent
+ requests to that page.
+
+Upstream caching is a nice efficiency boost, but there's a danger to it:
+Many Web pages' contents differ based on authentication and a host of other
+variables, and cache systems that blindly save pages based purely on URLs could
+expose incorrect or sensitive data to subsequent visitors to those pages.
+
+For example, say you operate a Web e-mail system, and the contents of the
+"inbox" page obviously depend on which user is logged in. If an ISP blindly
+cached your site, then the first user who logged in through that ISP would have
+his user-specific inbox page cached for subsequent visitors to the site. That's
+not cool.
+
+Fortunately, HTTP provides a solution to this problem: A set of HTTP headers
+exist to instruct caching mechanisms to differ their cache contents depending
+on designated variables, and to tell caching mechanisms not to cache particular
+pages.
+
+Using Vary headers
+==================
+
+One of these headers is ``Vary``. It defines which request headers a cache
+mechanism should take into account when building its cache key. For example, if
+the contents of a Web page depend on a user's language preference, the page is
+said to "vary on language."
+
+By default, Django's cache system creates its cache keys using the requested
+path -- e.g., ``"/stories/2005/jun/23/bank_robbed/"``. This means every request
+to that URL will use the same cached version, regardless of user-agent
+differences such as cookies or language preferences.
+
+That's where ``Vary`` comes in.
+
+If your Django-powered page outputs different content based on some difference
+in request headers -- such as a cookie, or language, or user-agent -- you'll
+need to use the ``Vary`` header to tell caching mechanisms that the page output
+depends on those things.
+
+To do this in Django, use the convenient ``vary_on_headers`` view decorator,
+like so::
+
+ from django.views.decorators.vary import vary_on_headers
+
+ # Python 2.3 syntax.
+ def my_view(request):
+ ...
+ my_view = vary_on_headers(my_view, 'User-Agent')
+
+ # Python 2.4 decorator syntax.
+ @vary_on_headers('User-Agent')
+ def my_view(request):
+ ...
+
+In this case, a caching mechanism (such as Django's own cache middleware) will
+cache a separate version of the page for each unique user-agent.
+
+The advantage to using the ``vary_on_headers`` decorator rather than manually
+setting the ``Vary`` header (using something like
+``response['Vary'] = 'user-agent'``) is that the decorator adds to the ``Vary``
+header (which may already exist) rather than setting it from scratch.
+
+You can pass multiple headers to ``vary_on_headers()``::
+
+ @vary_on_headers('User-Agent', 'Cookie')
+ def my_view(request):
+ ...
+
+Because varying on cookie is such a common case, there's a ``vary_on_cookie``
+decorator. These two views are equivalent::
+
+ @vary_on_cookie
+ def my_view(request):
+ ...
+
+ @vary_on_headers('Cookie')
+ def my_view(request):
+ ...
+
+Also note that the headers you pass to ``vary_on_headers`` are not case
+sensitive. ``"User-Agent"`` is the same thing as ``"user-agent"``.
+
+You can also use a helper function, ``django.utils.cache.patch_vary_headers``,
+directly::
+
+ from django.utils.cache import patch_vary_headers
+ def my_view(request):
+ ...
+ response = render_to_response('template_name', context)
+ patch_vary_headers(response, ['Cookie'])
+ return response
+
+``patch_vary_headers`` takes an ``HttpResponse`` instance as its first argument
+and a list/tuple of header names as its second argument.
+
+For more on Vary headers, see the `official Vary spec`_.
+
+.. _`official Vary spec`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
+
+Controlling cache: Using other headers
+======================================
+
+Another problem with caching is the privacy of data and the question of where
+data should be stored in a cascade of caches.
+
+A user usually faces two kinds of caches: his own browser cache (a private
+cache) and his provider's cache (a public cache). A public cache is used by
+multiple users and controlled by someone else. This poses problems with
+sensitive data: You don't want, say, your banking-account number stored in a
+public cache. So Web applications need a way to tell caches which data is
+private and which is public.
+
+The solution is to indicate a page's cache should be "private." To do this in
+Django, use the ``cache_control`` view decorator. Example::
+
+ from django.views.decorators.cache import cache_control
+ @cache_control(private=True)
+ def my_view(request):
+ ...
+
+This decorator takes care of sending out the appropriate HTTP header behind the
+scenes.
+
+There are a few other ways to control cache parameters. For example, HTTP
+allows applications to do the following:
+
+ * Define the maximum time a page should be cached.
+ * Specify whether a cache should always check for newer versions, only
+ delivering the cached content when there are no changes. (Some caches
+ might deliver cached content even if the server page changed -- simply
+ because the cache copy isn't yet expired.)
+
+In Django, use the ``cache_control`` view decorator to specify these cache
+parameters. In this example, ``cache_control`` tells caches to revalidate the
+cache on every access and to store cached versions for, at most, 3600 seconds::
+
+ from django.views.decorators.cache import cache_control
+ @cache_control(must_revalidate=True, max_age=3600)
+ def my_view(request):
+ ...
+
+Any valid ``Cache-Control`` HTTP directive is valid in ``cache_control()``.
+Here's a full list:
+
+ * ``public=True``
+ * ``private=True``
+ * ``no_cache=True``
+ * ``no_transform=True``
+ * ``must_revalidate=True``
+ * ``proxy_revalidate=True``
+ * ``max_age=num_seconds``
+ * ``s_maxage=num_seconds``
+
+For explanation of Cache-Control HTTP directives, see the `Cache-Control spec`_.
+
+(Note that the caching middleware already sets the cache header's max-age with
+the value of the ``CACHE_MIDDLEWARE_SETTINGS`` setting. If you use a custom
+``max_age`` in a ``cache_control`` decorator, the decorator will take
+precedence, and the header values will be merged correctly.)
+
+.. _`Cache-Control spec`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+
+Other optimizations
+===================
+
+Django comes with a few other pieces of middleware that can help optimize your
+apps' performance:
+
+ * ``django.middleware.http.ConditionalGetMiddleware`` adds support for
+ conditional GET. This makes use of ``ETag`` and ``Last-Modified``
+ headers.
+
+ * ``django.middleware.gzip.GZipMiddleware`` compresses content for browsers
+ that understand gzip compression (all modern browsers).
+
+Order of MIDDLEWARE_CLASSES
+===========================
+
+If you use ``CacheMiddleware``, it's important to put it in the right place
+within the ``MIDDLEWARE_CLASSES`` setting, because the cache middleware needs
+to know which headers by which to vary the cache storage. Middleware always
+adds something the ``Vary`` response header when it can.
+
+Put the ``CacheMiddleware`` after any middlewares that might add something to
+the ``Vary`` header. The following middlewares do so:
+
+ * ``SessionMiddleware`` adds ``Cookie``
+ * ``GZipMiddleware`` adds ``Accept-Encoding``
diff --git a/google_appengine/lib/django/docs/contributing.txt b/google_appengine/lib/django/docs/contributing.txt
new file mode 100644
index 0000000..1d2b635
--- /dev/null
+++ b/google_appengine/lib/django/docs/contributing.txt
@@ -0,0 +1,654 @@
+======================
+Contributing to Django
+======================
+
+If you think working *with* Django is fun, wait until you start working *on* it.
+We're passionate about helping Django users make the jump to contributing members
+of the community, so there are many ways you can help Django's development:
+
+ * Blog about Django. We syndicate all the Django blogs we know about on
+ the `community page`_; contact jacob@jacobian.org if you've got a blog
+ you'd like to see on that page.
+
+ * Report bugs and request features in our `ticket tracker`_. Please read
+ `Reporting bugs`_, below, for the details on how we like our bug reports
+ served up.
+
+ * Submit patches for new and/or fixed behavior. Please read `Submitting
+ patches`_, below, for details on how to submit a patch.
+
+ * Join the `django-developers`_ mailing list and share your ideas for how
+ to improve Django. We're always open to suggestions, although we're
+ likely to be skeptical of large-scale suggestions without some code to
+ back it up.
+
+ * Triage patches that have been submitted by other users. Please read
+ `Ticket triage`_ below, for details on the triage process.
+
+That's all you need to know if you'd like to join the Django development
+community. The rest of this document describes the details of how our community
+works and how it handles bugs, mailing lists, and all the other minutiae of
+Django development.
+
+Reporting bugs
+==============
+
+Well-written bug reports are *incredibly* helpful. However, there's a certain
+amount of overhead involved in working with any bug tracking system, so your
+help in keeping our ticket tracker as useful as possible is appreciated. In
+particular:
+
+ * **Do** read the FAQ_ to see if your issue might be a well-known question.
+
+ * **Do** `search the tracker`_ to see if your issue has already been filed.
+
+ * **Do** ask on `django-users`_ *first* if you're not sure if what you're
+ seeing is a bug.
+
+ * **Do** write complete, reproducible, specific bug reports. Include as
+ much information as you possibly can, complete with code snippets, test
+ cases, etc. This means including a clear, concise description of the
+ problem, and a clear set of instructions for replicating the problem.
+ A minimal example that illustrates the bug in a nice small test case
+ is the best possible bug report.
+
+ * **Don't** use the ticket system to ask support questions. Use the
+ `django-users`_ list, or the `#django`_ IRC channel for that.
+
+ * **Don't** use the ticket system to make large-scale feature requests.
+ We like to discuss any big changes to Django's core on the `django-developers`_
+ list before actually working on them.
+
+ * **Don't** reopen issues that have been marked "wontfix". This mark means
+ that the decision has been made that we can't or won't fix this particular
+ issue. If you're not sure why, please ask on `django-developers`_.
+
+ * **Don't** use the ticket tracker for lengthy discussions, because they're
+ likely to get lost. If a particular ticket is controversial, please move
+ discussion to `django-developers`_.
+
+Reporting security issues
+=========================
+
+Report security issues to security@djangoproject.com. This is a private list
+only open to long-time, highly trusted Django developers, and its archives are
+not publicly readable.
+
+In the event of a confirmed vulnerability in Django itself, we will take the
+following actions:
+
+ * Acknowledge to the reporter that we've received the report and that a fix
+ is forthcoming. We'll give a rough timeline and ask the reporter to keep
+ the issue confidential until we announce it.
+
+ * Halt all other development as long as is needed to develop a fix, including
+ patches against the current and two previous releases.
+
+ * Determine a go-public date for announcing the vulnerability and the fix.
+ To try to mitigate a possible "arms race" between those applying the patch
+ and those trying to exploit the hole, we will not announce security
+ problems immediately.
+
+ * Pre-notify everyone we know to be running the affected version(s) of
+ Django. We will send these notifications through private e-mail which will
+ include documentation of the vulnerability, links to the relevant patch(es),
+ and a request to keep the vulnerability confidential until the official
+ go-public date.
+
+ * Publicly announce the vulnerability and the fix on the pre-determined
+ go-public date. This will probably mean a new release of Django, but
+ in some cases it may simply be patches against current releases.
+
+Submitting patches
+==================
+
+We're always grateful for patches to Django's code. Indeed, bug reports with
+associated patches will get fixed *far* more quickly than those without patches.
+
+Patch style
+-----------
+
+ * Make sure your code matches our `coding style`_.
+
+ * Submit patches in the format returned by the ``svn diff`` command.
+ An exception is for code changes that are described more clearly in plain
+ English than in code. Indentation is the most common example; it's hard to
+ read patches when the only difference in code is that it's indented.
+
+ * Attach patches to a ticket in the `ticket tracker`_, using the "attach file"
+ button. Please *don't* put the patch in the ticket description or comment
+ unless it's a single line patch.
+
+ * Name the patch file with a ``.diff`` extension; this will let the ticket
+ tracker apply correct syntax highlighting, which is quite helpful.
+
+ * Check the "Has patch" box on the ticket details. This will make it
+ obvious that the ticket includes a patch, and it will add the ticket to
+ the `list of tickets with patches`_.
+
+ * The code required to fix a problem or add a feature is an essential part
+ of a patch, but it is not the only part. A good patch should also include
+ a regression test to validate the behavior that has been fixed (and prevent
+ the problem from arising again).
+
+ * If the code associated with a patch adds a new feature, or modifies behavior
+ of an existing feature, the patch should also contain documentation.
+
+Non-trivial patches
+-------------------
+
+A "non-trivial" patch is one that is more than a simple bug fix. It's a patch
+that introduces Django functionality and makes some sort of design decision.
+
+If you provide a non-trivial patch, include evidence that alternatives have
+been discussed on `django-developers`_. If you're not sure whether your patch
+should be considered non-trivial, just ask.
+
+Ticket triage
+=============
+
+Unfortunately, not all bug reports in the `ticket tracker`_ provide all
+the `required details`_. A number of tickets have patches, but those patches
+don't meet all the requirements of a `good patch`_.
+
+One way to help out is to *triage* bugs that have been reported by other
+users. A couple of dedicated volunteers work on this regularly, but more help
+is always appreciated.
+
+Most of the workflow is based around the concept of a ticket's "triage stage".
+This stage describes where in its lifetime a given ticket is at any time.
+Along with a handful of flags, this field easily tells us what and who each
+ticket is waiting on.
+
+Since a picture is worth a thousand words, let's start there:
+
+.. image:: http://media.djangoproject.com/img/doc/djangotickets.png
+ :height: 451
+ :width: 590
+ :alt: Django's ticket workflow
+
+We've got two roles here:
+
+ * Core developers: people with commit access who make the decisions and
+ write the bulk of the code.
+
+ * Ticket triagers: community members who keep track of tickets, making
+ sure the tickets are always categorized correctly.
+
+Second, note the four triage stages:
+
+ 1. A ticket starts as "Unreviewed", meaning that a triager has yet to
+ examine the ticket and move it along.
+
+ 2. "Design decision needed" means "this concept requires a design
+ decision," which should be discussed either in the ticket comments or on
+ django-developers.
+
+ 3. Once a ticket is ruled to be approved for fixing, it's moved into the
+ "Accepted" stage. This stage is where all the real work gets done.
+
+ 4. If a ticket has an associated patch (see below), a triager will review the
+ patch. If the patch is complete, it'll be marked as "ready for checkin" so
+ that a core developer knows to review and check in the patches.
+
+The second part of this workflow involves a set of flags the describe what the
+ticket has or needs in order to be "ready for checkin":
+
+ "Has patch"
+ This means the ticket has an associated patch_. These will be
+ reviewed to see if the patch is "good".
+
+ "Needs documentation"
+ This flag is used for tickets with patches that need associated
+ documentation. Complete documentation of features is a prerequisite
+ before we can check a fix into the codebase.
+
+ "Needs tests"
+ This flags the patch as needing associated unit tests. Again, this is a
+ required part of a valid patch.
+
+ "Patch needs improvement"
+ This flag means that although the ticket *has* a patch, it's not quite
+ ready for checkin. This could mean the patch no longer applies
+ cleanly, or that the code doesn't live up to our standards.
+
+A ticket can be resolved in a number of ways:
+
+ "fixed"
+ Used by one of the core developers once a patch has been rolled into
+ Django and the issue is fixed.
+
+ "invalid"
+ Used if the ticket is found to be incorrect or a user error.
+
+ "wontfix"
+ Used when a core developer decides that this request is not
+ appropriate for consideration in Django. This is usually chosen after
+ discussion in the ``django-developers`` mailing list, and you should
+ feel free to join in when it's something you care about.
+
+ "duplicate"
+ Used when another ticket covers the same issue. By closing duplicate
+ tickets, we keep all the discussion in one place, which helps everyone.
+
+ "worksforme"
+ Used when the triage team is unable to replicate the original bug.
+
+If you believe that the ticket was closed in error -- because you're
+still having the issue, or it's popped up somewhere else, or the triagers have
+-- made a mistake, please reopen the ticket and tell us why. Please do not
+reopen tickets that have been marked as "wontfix" by core developers.
+
+.. _required details: `Reporting bugs`_
+.. _good patch: `Patch style`_
+.. _patch: `Submitting patches`_
+
+Submitting and maintaining translations
+=======================================
+
+Various parts of Django, such as the admin site and validator error messages,
+are internationalized. This means they display different text depending on a
+user's language setting.
+
+These translations are contributed by Django users worldwide. If you find an
+incorrect translation, or if you'd like to add a language that isn't yet
+translated, here's what to do:
+
+ * Join the `Django i18n mailing list`_ and introduce yourself.
+ * Create and submit translations using the methods described in the
+ `i18n documentation`_.
+
+.. _Django i18n mailing list: http://groups.google.com/group/django-i18n/
+.. _i18n documentation: ../i18n/
+
+Coding style
+============
+
+Please follow these coding standards when writing code for inclusion in Django:
+
+ * Unless otherwise specified, follow `PEP 8`_.
+
+ * Use four spaces for indentation.
+
+ * Use underscores, not camelCase, for variable, function and method names
+ (i.e. ``poll.get_unique_voters()``, not ``poll.getUniqueVoters``).
+
+ * Use ``InitialCaps`` for class names (or for factory functions that
+ return classes).
+
+ * Mark all strings for internationalization; see the `i18n documentation`_
+ for details.
+
+ * In Django template code, put one (and only one) space between the curly
+ brackets and the tag contents.
+
+ Do this::
+
+ {{ foo }}
+
+ Don't do this::
+
+ {{foo}}
+
+ * In Django views, the first parameter in a view function should be called
+ ``request``.
+
+ Do this::
+
+ def my_view(request, foo):
+ # ...
+
+ Don't do this::
+
+ def my_view(req, foo):
+ # ...
+
+ * Please don't put your name in the code you contribute. Our policy is to
+ keep contributors' names in the ``AUTHORS`` file distributed with Django
+ -- not scattered throughout the codebase itself. Feel free to include a
+ change to the ``AUTHORS`` file in your patch if you make more than a
+ single trivial change.
+
+Committing code
+===============
+
+Please follow these guidelines when committing code to Django's Subversion
+repository:
+
+ * For any medium-to-big changes, where "medium-to-big" is according to your
+ judgment, please bring things up on the `django-developers`_ mailing list
+ before making the change.
+
+ If you bring something up on `django-developers`_ and nobody responds,
+ please don't take that to mean your idea is great and should be
+ implemented immediately because nobody contested it. Django's lead
+ developers don't have a lot of time to read mailing-list discussions
+ immediately, so you may have to wait a couple of days before getting a
+ response.
+
+ * Write detailed commit messages in the past tense, not present tense.
+
+ * Good: "Fixed Unicode bug in RSS API."
+ * Bad: "Fixes Unicode bug in RSS API."
+ * Bad: "Fixing Unicode bug in RSS API."
+
+ * For commits to a branch, prefix the commit message with the branch name.
+ For example: "magic-removal: Added support for mind reading."
+
+ * Limit commits to the most granular change that makes sense. This means,
+ use frequent small commits rather than infrequent large commits. For
+ example, if implementing feature X requires a small change to library Y,
+ first commit the change to library Y, then commit feature X in a separate
+ commit. This goes a *long way* in helping all core Django developers
+ follow your changes.
+
+ * If your commit closes a ticket in the Django `ticket tracker`_, begin
+ your commit message with the text "Fixed #abc", where "abc" is the number
+ of the ticket your commit fixes. Example: "Fixed #123 -- Added support
+ for foo". We've rigged Subversion and Trac so that any commit message
+ in that format will automatically close the referenced ticket and post a
+ comment to it with the full commit message.
+
+ If your commit closes a ticket and is in a branch, use the branch name
+ first, then the "Fixed #abc." For example:
+ "magic-removal: Fixed #123 -- Added whizbang feature."
+
+ For the curious: We're using a `Trac post-commit hook`_ for this.
+
+ .. _Trac post-commit hook: http://trac.edgewall.org/browser/trunk/contrib/trac-post-commit-hook
+
+ * If your commit references a ticket in the Django `ticket tracker`_ but
+ does *not* close the ticket, include the phrase "Refs #abc", where "abc"
+ is the number of the ticket your commit references. We've rigged
+ Subversion and Trac so that any commit message in that format will
+ automatically post a comment to the appropriate ticket.
+
+Unit tests
+==========
+
+Django comes with a test suite of its own, in the ``tests`` directory of the
+Django tarball. It's our policy to make sure all tests pass at all times.
+
+The tests cover:
+
+ * Models and the database API (``tests/modeltests/``).
+ * The cache system (``tests/regressiontests/cache.py``).
+ * The ``django.utils.dateformat`` module (``tests/regressiontests/dateformat/``).
+ * Database typecasts (``tests/regressiontests/db_typecasts/``).
+ * The template system (``tests/regressiontests/templates/`` and
+ ``tests/regressiontests/defaultfilters/``).
+ * ``QueryDict`` objects (``tests/regressiontests/httpwrappers/``).
+ * Markup template tags (``tests/regressiontests/markup/``).
+
+We appreciate any and all contributions to the test suite!
+
+The Django tests all use the testing infrastructure that ships with Django for
+testing applications. See `Testing Django applications`_ for an explanation of
+how to write new tests.
+
+.. _Testing Django applications: ../testing/
+
+Running the unit tests
+----------------------
+
+To run the tests, ``cd`` to the ``tests/`` directory and type::
+
+ ./runtests.py --settings=path.to.django.settings
+
+Yes, the unit tests need a settings module, but only for database connection
+info -- the ``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD``.
+You will also need a ``ROOT_URLCONF`` setting (its value is ignored; it just
+needs to be present) and a ``SITE_ID`` setting (any integer value will do) in
+order for all the tests to pass.
+
+The unit tests will not touch your existing databases; they create a new
+database, called ``django_test_db``, which is deleted when the tests are
+finished. This means your user account needs permission to execute ``CREATE
+DATABASE``.
+
+Requesting features
+===================
+
+We're always trying to make Django better, and your feature requests are a key
+part of that. Here are some tips on how to most effectively make a request:
+
+ * Request the feature on `django-developers`_, not in the ticket tracker;
+ it'll get read more closely if it's on the mailing list.
+
+ * Describe clearly and concisely what the missing feature is and how you'd
+ like to see it implemented. Include example code (non-functional is OK)
+ if possible.
+
+ * Explain *why* you'd like the feature. In some cases this is obvious, but
+ since Django is designed to help real developers get real work done,
+ you'll need to explain it, if it isn't obvious why the feature would be
+ useful.
+
+As with most open-source projects, code talks. If you are willing to write the
+code for the feature yourself or if (even better) you've already written it,
+it's much more likely to be accepted. If it's a large feature that might need
+multiple developers we're always happy to give you an experimental branch in
+our repository; see below.
+
+Branch policy
+=============
+
+In general, most development is confined to the trunk, and the trunk
+is kept stable. People should be able to run production sites against the
+trunk at any time.
+
+Thus, large architectural changes -- that is, changes too large to be
+encapsulated in a single patch, or changes that need multiple eyes on them --
+will have dedicated branches. See, for example, the `i18n branch`_. If you
+have a change of this nature that you'd like to work on, ask on
+`django-developers`_ for a branch to be created for you. We'll create a branch
+for pretty much any kind of experimenting you'd like to do.
+
+We will only branch entire copies of the Django tree, even if work is only
+happening on part of that tree. This makes it painless to switch to a branch.
+
+Developers working on a branch should periodically merge changes from the trunk
+into the branch. Please merge at least once a week. Every time you merge from
+the trunk, note the merge and revision numbers in the commit message.
+
+Once the branch is stable and ready to be merged into the trunk, alert
+`django-developers`_.
+
+After a branch has been merged, it should be considered "dead"; write access to
+it will be disabled, and old branches will be periodically "trimmed." To keep
+our SVN wrangling to a minimum, we won't be merging from a given branch into the
+trunk more than once.
+
+Using branches
+--------------
+
+To use a branch, you'll need to do two things:
+
+ * Get the branch's code through Subversion.
+
+ * Point your Python ``site-packages`` directory at the branch's version of
+ the ``django`` package rather than the version you already have
+ installed.
+
+Getting the code from Subversion
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To get the latest version of a branch's code, check it out using Subversion::
+
+ svn co http://code.djangoproject.com/svn/django/branches/<branch>/
+
+...where ``<branch>`` is the branch's name. See the `list of branch names`_.
+
+Alternatively, you can automatically convert an existing directory of the
+Django source code as long as you've checked it out via Subversion. To do the
+conversion, execute this command from within your ``django`` directory::
+
+ svn switch http://code.djangoproject.com/svn/django/branches/<branch>/
+
+The advantage of using ``svn switch`` instead of ``svn co`` is that the
+``switch`` command retains any changes you might have made to your local copy
+of the code. It attempts to merge those changes into the "switched" code. The
+disadvantage is that it may cause conflicts with your local changes if the
+"switched" code has altered the same lines of code.
+
+(Note that if you use ``svn switch``, you don't need to point Python at the new
+version, as explained in the next section.)
+
+.. _list of branch names: http://code.djangoproject.com/browser/django/branches
+
+Pointing Python at the new Django version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you've retrieved the branch's code, you'll need to change your Python
+``site-packages`` directory so that it points to the branch version of the
+``django`` directory. (The ``site-packages`` directory is somewhere such as
+``/usr/lib/python2.4/site-packages`` or
+``/usr/local/lib/python2.4/site-packages`` or ``C:\Python\site-packages``.)
+
+The simplest way to do this is by renaming the old ``django`` directory to
+``django.OLD`` and moving the trunk version of the code into the directory
+and calling it ``django``.
+
+Alternatively, you can use a symlink called ``django`` that points to the
+location of the branch's ``django`` package. If you want to switch back, just
+change the symlink to point to the old code.
+
+A third option is to use a `path file`_ (``<something>.pth``) which should
+work on all systems (including Windows, which doesn't have symlinks
+available). First, make sure there are no files, directories or symlinks named
+``django`` in your ``site-packages`` directory. Then create a text file named
+``django.pth`` and save it to your ``site-packages`` directory. That file
+should contain a path to your copy of Django on a single line and optional
+comments. Here is an example that points to multiple branches. Just uncomment
+the line for the branch you want to use ('Trunk' in this example) and make
+sure all other lines are commented::
+
+ # Trunk is a svn checkout of:
+ # http://code.djangoproject.com/svn/django/trunk/
+ #
+ /path/to/trunk
+
+ # <branch> is a svn checkout of:
+ # http://code.djangoproject.com/svn/django/branches/<branch>/
+ #
+ #/path/to/<branch>
+
+ # On windows a path may look like this:
+ # C:/path/to/<branch>
+
+If you're using Django 0.95 or earlier and installed it using
+``python setup.py install``, you'll have a directory called something like
+``Django-0.95-py2.4.egg`` instead of ``django``. In this case, edit the file
+``setuptools.pth`` and remove the line that references the Django ``.egg``
+file. Then copy the branch's version of the ``django`` directory into
+``site-packages``.
+
+.. _path file: http://docs.python.org/lib/module-site.html
+
+Official releases
+=================
+
+Django's release numbering works as follows:
+
+ * Versions are numbered in the form ``A.B`` or ``A.B.C``.
+
+ * ``A`` is the major version number, which is only incremented for major
+ changes to Django, and these changes are not necessarily
+ backwards-compatible. That is, code you wrote for Django 6.0 may break
+ when we release Django 7.0.
+
+ * ``B`` is the minor version number, which is incremented for large yet
+ backwards compatible changes. Code written for Django 6.4 will continue
+ to work under Django 6.5.
+
+ A minor release may deprecate certain features in previous releases. If a
+ feature in version ``A.B`` is deprecated, it will continue to work in
+ version ``A.B+1``. In version ``A.B+2``, use of the feature will raise a
+ ``PendingDeprecationWarning`` but will continue to work. Version
+ ``A.B+3`` will remove the feature entirely. Major point releases will
+ always remove deprecated features immediately.
+
+ * ``C`` is the micro version number which, is incremented for bug and
+ security fixes. A new micro-release will always be 100%
+ backwards-compatible with the previous micro-release.
+
+ * In some cases, we'll make release candidate releases. These are of the
+ form ``A.BrcN``, which means the ``Nth`` candidate release of version
+ ``A.B``.
+
+An exception to this version numbering scheme is the pre-1.0 Django code.
+There's no guarantee of backwards-compatibility until the 1.0 release.
+
+In Subversion, each Django release will be tagged under `tags/releases`_. If
+it's necessary to release a bug fix release or a security release that doesn't
+come from the trunk, we'll copy that tag to ``branches/releases`` to make the
+bug fix release.
+
+Deciding on features
+====================
+
+Once a feature's been requested and discussed, eventually we'll have a decision
+about whether to include the feature or drop it.
+
+Whenever possible, we strive for a rough consensus. To that end, we'll often
+have informal votes on `django-developers`_ about a feature. In these votes we
+follow the voting style invented by Apache and used on Python itself, where
+votes are given as +1, +0, -0, or -1. Roughly translated, these votes mean:
+
+ * +1: "I love the idea and I'm strongly committed to it."
+
+ * +0: "Sounds OK to me."
+
+ * -0: "I'm not thrilled, but I won't stand in the way."
+
+ * -1: "I strongly disagree and would be very unhappy to see the idea turn
+ into reality."
+
+Although these votes on django-developers are informal, they'll be taken very
+seriously. After a suitable voting period, if an obvious consensus arises
+we'll follow the votes.
+
+However, consensus is not always possible. Tough decisions will be discussed by
+all full committers and finally decided by the Benevolent Dictators for Life,
+Adrian and Jacob.
+
+Commit access
+=============
+
+Django has two types of committers:
+
+Full committers
+ These are people who have a long history of contributions to Django's
+ codebase, a solid track record of being polite and helpful on the mailing
+ lists, and a proven desire to dedicate serious time to Django's development.
+
+ The bar is very high for full commit access. It will only be granted by
+ unanimous approval of all existing full committers, and the decision will err
+ on the side of rejection.
+
+Partial committers
+ These are people who are "domain experts." They have direct check-in access
+ to the subsystems that fall under their jurisdiction, and they're given a
+ formal vote in questions that involve their subsystems. This type of access
+ is likely to be given to someone who contributes a large subframework to
+ Django and wants to continue to maintain it.
+
+ Like full committers, partial commit access is by unanimous approval of all
+ full committers (and any other partial committers in the same area).
+ However, the bar is set lower; proven expertise in the area in question is
+ likely to be sufficient.
+
+To request commit access, please contact an existing committer privately. Public
+requests for commit access are potential flame-war starters, and will be ignored.
+
+.. _community page: http://www.djangoproject.com/community/
+.. _ticket tracker: http://code.djangoproject.com/newticket
+.. _django-developers: http://groups.google.com/group/django-developers
+.. _FAQ: http://www.djangoproject.com/documentation/faq/
+.. _search the tracker: http://code.djangoproject.com/search
+.. _django-users: http://groups.google.com/group/django-users
+.. _`#django`: irc://irc.freenode.net/django
+.. _list of tickets with patches: http://code.djangoproject.com/query?status=new&status=assigned&status=reopened&has_patch=1&order=priority
+.. _PEP 8: http://www.python.org/peps/pep-0008.html
+.. _i18n branch: http://code.djangoproject.com/browser/django/branches/i18n
+.. _`tags/releases`: http://code.djangoproject.com/browser/django/tags/releases
diff --git a/google_appengine/lib/django/docs/csrf.txt b/google_appengine/lib/django/docs/csrf.txt
new file mode 100644
index 0000000..c12dd1d
--- /dev/null
+++ b/google_appengine/lib/django/docs/csrf.txt
@@ -0,0 +1,69 @@
+=====================================
+Cross Site Request Forgery protection
+=====================================
+
+The CsrfMiddleware class provides easy-to-use protection against
+`Cross Site Request Forgeries`_. This type of attack occurs when a malicious
+web site creates a link or form button that is intended to perform some action
+on your web site, using the credentials of a logged-in user who is tricked
+into clicking on the link in their browser.
+
+The first defense against CSRF attacks is to ensure that GET requests
+are side-effect free. POST requests can then be protected by adding this
+middleware into your list of installed middleware.
+
+.. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF
+
+How to use it
+=============
+
+Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
+your list of middleware classes, ``MIDDLEWARE_CLASSES``. It needs to process
+the response after the SessionMiddleware, so must come before it in the
+list. It also must process the response before things like compression
+happen to the response, so it must come after GZipMiddleware in the list.
+
+How it works
+============
+
+CsrfMiddleware does two things:
+
+1. It modifies outgoing requests by adding a hidden form field to all
+ 'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
+ a hash of the session ID plus a secret. If there is no session ID set,
+ this modification of the response isn't done, so there is very little
+ performance penalty for those requests that don't have a session.
+
+2. On all incoming POST requests that have the session cookie set, it
+ checks that the 'csrfmiddlewaretoken' is present and correct. If it
+ isn't, the user will get a 403 error.
+
+This ensures that only forms that have originated from your web site
+can be used to POST data back.
+
+It deliberately only targets HTTP POST requests (and the corresponding
+POST forms). GET requests ought never to have side effects (if you are
+using HTTP GET and POST correctly), and so a CSRF attack with a GET
+request will always be harmless.
+
+POST requests that are not accompanied by a session cookie are not protected,
+but they do not need to be protected, since the 'attacking' web site
+could make these kind of requests anyway.
+
+The Content-Type is checked before modifying the response, and only
+pages that are served as 'text/html' or 'application/xml+xhtml'
+are modified.
+
+Limitations
+===========
+
+CsrfMiddleware requires Django's session framework to work. If you have
+a custom authentication system that manually sets cookies and the like,
+it won't help you.
+
+If your app creates HTML pages and forms in some unusual way, (e.g.
+it sends fragments of HTML in javascript document.write statements)
+you might bypass the filter that adds the hidden field to the form,
+in which case form submission will always fail. It may still be possible
+to use the middleware, provided you can find some way to get the
+CSRF token and ensure that is included when your form is submitted. \ No newline at end of file
diff --git a/google_appengine/lib/django/docs/databases.txt b/google_appengine/lib/django/docs/databases.txt
new file mode 100644
index 0000000..3545b58
--- /dev/null
+++ b/google_appengine/lib/django/docs/databases.txt
@@ -0,0 +1,162 @@
+===============================
+Notes about supported databases
+===============================
+
+Django attempts to support as many features as possible on all database
+backends. However, not all database backends are alike, and we've had to make
+design decisions on which features to support and which assumptions we can make
+safely.
+
+This file describes some of the features that might be relevant to Django
+usage. Of course, it is not intended as a replacement for server-specific
+documentation or reference manuals.
+
+MySQL notes
+===========
+
+Django expects the database to support transactions, referential integrity,
+and Unicode support (UTF-8 encoding). Fortunately, MySQL_ has all these
+features as available as far back as 3.23. While it may be possible to use
+3.23 or 4.0, you'll probably have less trouble if you use 4.1 or 5.0.
+
+MySQL 4.1
+---------
+
+`MySQL 4.1`_ has greatly improved support for character sets. It is possible to
+set different default character sets on the database, table, and column.
+Previous versions have only a server-wide character set setting. It's also the
+first version where the character set can be changed on the fly. 4.1 also has
+support for views, but Django currently doesn't use views.
+
+MySQL 5.0
+---------
+
+`MySQL 5.0`_ adds the ``information_schema`` database, which contains detailed
+data on all database schema. Django's ``inspectdb`` feature uses this
+``information_schema`` if it's available. 5.0 also has support for stored
+procedures, but Django currently doesn't use stored procedures.
+
+.. _MySQL: http://www.mysql.com/
+.. _MySQL 4.1: http://dev.mysql.com/doc/refman/4.1/en/index.html
+.. _MySQL 5.0: http://dev.mysql.com/doc/refman/5.0/en/index.html
+
+Storage engines
+---------------
+
+MySQL has several `storage engines`_ (previously called table types). You can
+change the default storage engine in the server configuration.
+
+The default engine is MyISAM_. The main drawback of MyISAM is that it doesn't
+currently support transactions or foreign keys. On the plus side, it's
+currently the only engine that supports full-text indexing and searching.
+
+The InnoDB_ engine is fully transactional and supports foreign key references.
+
+The BDB_ engine, like InnoDB, is also fully transactional and supports foreign
+key references. However, its use seems to be deprecated.
+
+`Other storage engines`_, including SolidDB_ and Falcon_, are on the horizon.
+For now, InnoDB is probably your best choice.
+
+.. _storage engines: http://dev.mysql.com/doc/refman/5.0/en/storage-engines.html
+.. _MyISAM: http://dev.mysql.com/doc/refman/5.0/en/myisam-storage-engine.html
+.. _BDB: http://dev.mysql.com/doc/refman/5.0/en/bdb-storage-engine.html
+.. _InnoDB: http://dev.mysql.com/doc/refman/5.0/en/innodb.html
+.. _Other storage engines: http://dev.mysql.com/doc/refman/5.1/en/storage-engines-other.html
+.. _SolidDB: http://forge.mysql.com/projects/view.php?id=139
+.. _Falcon: http://dev.mysql.com/doc/falcon/en/index.html
+
+MySQLdb
+-------
+
+`MySQLdb`_ is the Python interface to MySQL. 1.2.1 is the first version that
+has support for MySQL 4.1 and newer. If you are trying to use an older version
+of MySQL, then 1.2.0 *might* work for you.
+
+.. _MySQLdb: http://sourceforge.net/projects/mysql-python
+
+Creating your database
+----------------------
+
+You can `create your database`_ using the command-line tools and this SQL::
+
+ CREATE DATABASE <dbname> CHARACTER SET utf8;
+
+This ensures all tables and columns will use UTF-8 by default.
+
+.. _create your database: http://dev.mysql.com/doc/refman/5.0/en/create-database.html
+
+Connecting to the database
+--------------------------
+
+Refer to the `settings documentation`_.
+
+Connection settings are used in this order:
+
+ 1. ``DATABASE_OPTIONS``
+ 2. ``DATABASE_NAME``, ``DATABASE_USER``, ``DATABASE_PASSWORD``, ``DATABASE_HOST``,
+ ``DATABASE_PORT``
+ 3. MySQL option files.
+
+In other words, if you set the name of the database in ``DATABASE_OPTIONS``,
+this will take precedence over ``DATABASE_NAME``, which would override
+anything in a `MySQL option file`_.
+
+Here's a sample configuration which uses a MySQL option file::
+
+ # settings.py
+ DATABASE_ENGINE = "mysql"
+ DATABASE_OPTIONS = {
+ 'read_default_file': '/path/to/my.cnf',
+ }
+
+ # my.cnf
+ [client]
+ database = DATABASE_NAME
+ user = DATABASE_USER
+ passwd = DATABASE_PASSWORD
+ default-character-set = utf8
+
+Several other MySQLdb connection options may be useful, such as ``ssl``,
+``use_unicode``, ``init_command``, and ``sql_mode``. Consult the
+`MySQLdb documentation`_ for more details.
+
+.. _settings documentation: http://www.djangoproject.com/documentation/settings/#database-engine
+.. _MySQL option file: http://dev.mysql.com/doc/refman/5.0/en/option-files.html
+.. _MySQLdb documentation: http://mysql-python.sourceforge.net/
+
+Creating your tables
+--------------------
+
+When Django generates the schema, it doesn't specify a storage engine, so
+tables will be created with whatever default storage engine your database
+server is configured for. The easiest solution is to set your database server's
+default storage engine to the desired engine.
+
+If you're using a hosting service and can't change your server's default
+storage engine, you have a couple of options.
+
+ * After the tables are created, execute an ``ALTER TABLE`` statement to
+ convert a table to a new storage engine (such as InnoDB)::
+
+ ALTER TABLE <tablename> ENGINE=INNODB;
+
+ This can be tedious if you have a lot of tables.
+
+ * Another option is to use the ``init_command`` option for MySQLdb prior to
+ creating your tables::
+
+ DATABASE_OPTIONS = {
+ # ...
+ "init_command": "SET storage_engine=INNODB",
+ # ...
+ }
+
+ This sets the default storage engine upon connecting to the database.
+ After your tables have been created, you should remove this option.
+
+ * Another method for changing the storage engine is described in
+ AlterModelOnSyncDB_.
+
+.. _AlterModelOnSyncDB: http://code.djangoproject.com/wiki/AlterModelOnSyncDB
+
diff --git a/google_appengine/lib/django/docs/db-api.txt b/google_appengine/lib/django/docs/db-api.txt
new file mode 100644
index 0000000..64db3de
--- /dev/null
+++ b/google_appengine/lib/django/docs/db-api.txt
@@ -0,0 +1,1804 @@
+======================
+Database API reference
+======================
+
+Once you've created your `data models`_, Django automatically gives you a
+database-abstraction API that lets you create, retrieve, update and delete
+objects. This document explains that API.
+
+.. _`data models`: ../model_api/
+
+Throughout this reference, we'll refer to the following models, which comprise
+a weblog application::
+
+ class Blog(models.Model):
+ name = models.CharField(maxlength=100)
+ tagline = models.TextField()
+
+ def __str__(self):
+ return self.name
+
+ class Author(models.Model):
+ name = models.CharField(maxlength=50)
+ email = models.URLField()
+
+ def __str__(self):
+ return self.name
+
+ class Entry(models.Model):
+ blog = models.ForeignKey(Blog)
+ headline = models.CharField(maxlength=255)
+ body_text = models.TextField()
+ pub_date = models.DateTimeField()
+ authors = models.ManyToManyField(Author)
+
+ def __str__(self):
+ return self.headline
+
+Creating objects
+================
+
+To represent database-table data in Python objects, Django uses an intuitive
+system: A model class represents a database table, and an instance of that
+class represents a particular record in the database table.
+
+To create an object, instantiate it using keyword arguments to the model class,
+then call ``save()`` to save it to the database.
+
+You import the model class from wherever it lives on the Python path, as you
+may expect. (We point this out here because previous Django versions required
+funky model importing.)
+
+Assuming models live in a file ``mysite/blog/models.py``, here's an example::
+
+ from mysite.blog.models import Blog
+ b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
+ b.save()
+
+This performs an ``INSERT`` SQL statement behind the scenes. Django doesn't hit
+the database until you explicitly call ``save()``.
+
+The ``save()`` method has no return value.
+
+To create an object and save it all in one step see the `create`__ method.
+
+__ `create(**kwargs)`_
+
+Auto-incrementing primary keys
+------------------------------
+
+If a model has an ``AutoField`` -- an auto-incrementing primary key -- then
+that auto-incremented value will be calculated and saved as an attribute on
+your object the first time you call ``save()``.
+
+Example::
+
+ b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
+ b2.id # Returns None, because b doesn't have an ID yet.
+ b2.save()
+ b2.id # Returns the ID of your new object.
+
+There's no way to tell what the value of an ID will be before you call
+``save()``, because that value is calculated by your database, not by Django.
+
+(For convenience, each model has an ``AutoField`` named ``id`` by default
+unless you explicitly specify ``primary_key=True`` on a field. See the
+`AutoField documentation`_.)
+
+.. _AutoField documentation: ../model_api/#autofield
+
+Explicitly specifying auto-primary-key values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a model has an ``AutoField`` but you want to define a new object's ID
+explicitly when saving, just define it explicitly before saving, rather than
+relying on the auto-assignment of the ID.
+
+Example::
+
+ b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
+ b3.id # Returns 3.
+ b3.save()
+ b3.id # Returns 3.
+
+If you assign auto-primary-key values manually, make sure not to use an
+already-existing primary-key value! If you create a new object with an explicit
+primary-key value that already exists in the database, Django will assume
+you're changing the existing record rather than creating a new one.
+
+Given the above ``'Cheddar Talk'`` blog example, this example would override
+the previous record in the database::
+
+ b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
+ b4.save() # Overrides the previous blog with ID=3!
+
+See _`How Django knows to UPDATE vs. INSERT`, below, for the reason this
+happens.
+
+Explicitly specifying auto-primary-key values is mostly useful for bulk-saving
+objects, when you're confident you won't have primary-key collision.
+
+Saving changes to objects
+=========================
+
+To save changes to an object that's already in the database, use ``save()``.
+
+Given a ``Blog`` instance ``b5`` that has already been saved to the database,
+this example changes its name and updates its record in the database::
+
+ b5.name = 'New name'
+ b5.save()
+
+This performs an ``UPDATE`` SQL statement behind the scenes. Django doesn't hit
+the database until you explicitly call ``save()``.
+
+The ``save()`` method has no return value.
+
+How Django knows to UPDATE vs. INSERT
+-------------------------------------
+
+You may have noticed Django database objects use the same ``save()`` method
+for creating and changing objects. Django abstracts the need to use ``INSERT``
+or ``UPDATE`` SQL statements. Specifically, when you call ``save()``, Django
+follows this algorithm:
+
+ * If the object's primary key attribute is set to a value that evaluates to
+ ``True`` (i.e., a value other than ``None`` or the empty string), Django
+ executes a ``SELECT`` query to determine whether a record with the given
+ primary key already exists.
+ * If the record with the given primary key does already exist, Django
+ executes an ``UPDATE`` query.
+ * If the object's primary key attribute is *not* set, or if it's set but a
+ record doesn't exist, Django executes an ``INSERT``.
+
+The one gotcha here is that you should be careful not to specify a primary-key
+value explicitly when saving new objects, if you cannot guarantee the
+primary-key value is unused. For more on this nuance, see
+"Explicitly specifying auto-primary-key values" above.
+
+Retrieving objects
+==================
+
+To retrieve objects from your database, you construct a ``QuerySet`` via a
+``Manager`` on your model class.
+
+A ``QuerySet`` represents a collection of objects from your database. It can
+have zero, one or many *filters* -- criteria that narrow down the collection
+based on given parameters. In SQL terms, a ``QuerySet`` equates to a ``SELECT``
+statement, and a filter is a limiting clause such as ``WHERE`` or ``LIMIT``.
+
+You get a ``QuerySet`` by using your model's ``Manager``. Each model has at
+least one ``Manager``, and it's called ``objects`` by default. Access it
+directly via the model class, like so::
+
+ Blog.objects # <django.db.models.manager.Manager object at ...>
+ b = Blog(name='Foo', tagline='Bar')
+ b.objects # AttributeError: "Manager isn't accessible via Blog instances."
+
+(``Managers`` are accessible only via model classes, rather than from model
+instances, to enforce a separation between "table-level" operations and
+"record-level" operations.)
+
+The ``Manager`` is the main source of ``QuerySets`` for a model. It acts as a
+"root" ``QuerySet`` that describes all objects in the model's database table.
+For example, ``Blog.objects`` is the initial ``QuerySet`` that contains all
+``Blog`` objects in the database.
+
+Retrieving all objects
+----------------------
+
+The simplest way to retrieve objects from a table is to get all of them.
+To do this, use the ``all()`` method on a ``Manager``.
+
+Example::
+
+ all_entries = Entry.objects.all()
+
+The ``all()`` method returns a ``QuerySet`` of all the objects in the database.
+
+(If ``Entry.objects`` is a ``QuerySet``, why can't we just do ``Entry.objects``?
+That's because ``Entry.objects``, the root ``QuerySet``, is a special case
+that cannot be evaluated. The ``all()`` method returns a ``QuerySet`` that
+*can* be evaluated.)
+
+Filtering objects
+-----------------
+
+The root ``QuerySet`` provided by the ``Manager`` describes all objects in the
+database table. Usually, though, you'll need to select only a subset of the
+complete set of objects.
+
+To create such a subset, you refine the initial ``QuerySet``, adding filter
+conditions. The two most common ways to refine a ``QuerySet`` are:
+
+``filter(**kwargs)``
+ Returns a new ``QuerySet`` containing objects that match the given lookup
+ parameters.
+
+``exclude(**kwargs)``
+ Returns a new ``QuerySet`` containing objects that do *not* match the given
+ lookup parameters.
+
+The lookup parameters (``**kwargs`` in the above function definitions) should
+be in the format described in `Field lookups`_ below.
+
+For example, to get a ``QuerySet`` of blog entries from the year 2006, use
+``filter()`` like so::
+
+ Entry.objects.filter(pub_date__year=2006)
+
+(Note we don't have to add an ``all()`` -- ``Entry.objects.all().filter(...)``.
+That would still work, but you only need ``all()`` when you want all objects
+from the root ``QuerySet``.)
+
+Chaining filters
+~~~~~~~~~~~~~~~~
+
+The result of refining a ``QuerySet`` is itself a ``QuerySet``, so it's
+possible to chain refinements together. For example::
+
+ Entry.objects.filter(
+ headline__startswith='What').exclude(
+ pub_date__gte=datetime.now()).filter(
+ pub_date__gte=datetime(2005, 1, 1))
+
+...takes the initial ``QuerySet`` of all entries in the database, adds a
+filter, then an exclusion, then another filter. The final result is a
+``QuerySet`` containing all entries with a headline that starts with "What",
+that were published between January 1, 2005, and the current day.
+
+Filtered QuerySets are unique
+-----------------------------
+
+Each time you refine a ``QuerySet``, you get a brand-new ``QuerySet`` that is
+in no way bound to the previous ``QuerySet``. Each refinement creates a
+separate and distinct ``QuerySet`` that can be stored, used and reused.
+
+Example::
+
+ q1 = Entry.objects.filter(headline__startswith="What")
+ q2 = q1.exclude(pub_date__gte=datetime.now())
+ q3 = q1.filter(pub_date__gte=datetime.now())
+
+These three ``QuerySets`` are separate. The first is a base ``QuerySet``
+containing all entries that contain a headline starting with "What". The second
+is a subset of the first, with an additional criteria that excludes records
+whose ``pub_date`` is greater than now. The third is a subset of the first,
+with an additional criteria that selects only the records whose ``pub_date`` is
+greater than now. The initial ``QuerySet`` (``q1``) is unaffected by the
+refinement process.
+
+QuerySets are lazy
+------------------
+
+``QuerySets`` are lazy -- the act of creating a ``QuerySet`` doesn't involve
+any database activity. You can stack filters together all day long, and Django
+won't actually run the query until the ``QuerySet`` is *evaluated*.
+
+When QuerySets are evaluated
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can evaluate a ``QuerySet`` in the following ways:
+
+ * **Iteration.** A ``QuerySet`` is iterable, and it executes its database
+ query the first time you iterate over it. For example, this will print
+ the headline of all entries in the database::
+
+ for e in Entry.objects.all():
+ print e.headline
+
+ * **Slicing.** As explained in `Limiting QuerySets`_ below, a ``QuerySet``
+ can be sliced, using Python's array-slicing syntax. Usually slicing a
+ ``QuerySet`` returns another (unevaluated )``QuerySet``, but Django will
+ execute the database query if you use the "step" parameter of slice
+ syntax.
+
+ * **repr().** A ``QuerySet`` is evaluated when you call ``repr()`` on it.
+ This is for convenience in the Python interactive interpreter, so you can
+ immediately see your results when using the API interactively.
+
+ * **len().** A ``QuerySet`` is evaluated when you call ``len()`` on it.
+ This, as you might expect, returns the length of the result list.
+
+ Note: *Don't* use ``len()`` on ``QuerySet``\s if all you want to do is
+ determine the number of records in the set. It's much more efficient to
+ handle a count at the database level, using SQL's ``SELECT COUNT(*)``,
+ and Django provides a ``count()`` method for precisely this reason. See
+ ``count()`` below.
+
+ * **list().** Force evaluation of a ``QuerySet`` by calling ``list()`` on
+ it. For example::
+
+ entry_list = list(Entry.objects.all())
+
+ Be warned, though, that this could have a large memory overhead, because
+ Django will load each element of the list into memory. In contrast,
+ iterating over a ``QuerySet`` will take advantage of your database to
+ load data and instantiate objects only as you need them.
+
+Limiting QuerySets
+------------------
+
+Use Python's array-slicing syntax to limit your ``QuerySet`` to a certain
+number of results. This is the equivalent of SQL's ``LIMIT`` and ``OFFSET``
+clauses.
+
+For example, this returns the first 5 objects (``LIMIT 5``)::
+
+ Entry.objects.all()[:5]
+
+This returns the fifth through tenth objects (``OFFSET 5 LIMIT 5``)::
+
+ Entry.objects.all()[5:10]
+
+Generally, slicing a ``QuerySet`` returns a new ``QuerySet`` -- it doesn't
+evaluate the query. An exception is if you use the "step" parameter of Python
+slice syntax. For example, this would actually execute the query in order to
+return a list of every *second* object of the first 10::
+
+ Entry.objects.all()[:10:2]
+
+To retrieve a *single* object rather than a list
+(e.g. ``SELECT foo FROM bar LIMIT 1``), use a simple index instead of a
+slice. For example, this returns the first ``Entry`` in the database, after
+ordering entries alphabetically by headline::
+
+ Entry.objects.order_by('headline')[0]
+
+This is roughly equivalent to::
+
+ Entry.objects.order_by('headline')[0:1].get()
+
+Note, however, that the first of these will raise ``IndexError`` while the
+second will raise ``DoesNotExist`` if no objects match the given criteria.
+
+QuerySet methods that return new QuerySets
+------------------------------------------
+
+Django provides a range of ``QuerySet`` refinement methods that modify either
+the types of results returned by the ``QuerySet`` or the way its SQL query is
+executed.
+
+``filter(**kwargs)``
+~~~~~~~~~~~~~~~~~~~~
+
+Returns a new ``QuerySet`` containing objects that match the given lookup
+parameters.
+
+The lookup parameters (``**kwargs``) should be in the format described in
+`Field lookups`_ below. Multiple parameters are joined via ``AND`` in the
+underlying SQL statement.
+
+``exclude(**kwargs)``
+~~~~~~~~~~~~~~~~~~~~~
+
+Returns a new ``QuerySet`` containing objects that do *not* match the given
+lookup parameters.
+
+The lookup parameters (``**kwargs``) should be in the format described in
+`Field lookups`_ below. Multiple parameters are joined via ``AND`` in the
+underlying SQL statement, and the whole thing is enclosed in a ``NOT()``.
+
+This example excludes all entries whose ``pub_date`` is the current date/time
+AND whose ``headline`` is "Hello"::
+
+ Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
+
+In SQL terms, that evaluates to::
+
+ SELECT ...
+ WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
+
+This example excludes all entries whose ``pub_date`` is the current date/time
+OR whose ``headline`` is "Hello"::
+
+ Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')
+
+In SQL terms, that evaluates to::
+
+ SELECT ...
+ WHERE NOT pub_date > '2005-1-3'
+ AND NOT headline = 'Hello'
+
+Note the second example is more restrictive.
+
+``order_by(*fields)``
+~~~~~~~~~~~~~~~~~~~~~
+
+By default, results returned by a ``QuerySet`` are ordered by the ordering
+tuple given by the ``ordering`` option in the model's ``Meta``. You can
+override this on a per-``QuerySet`` basis by using the ``order_by`` method.
+
+Example::
+
+ Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')
+
+The result above will be ordered by ``pub_date`` descending, then by
+``headline`` ascending. The negative sign in front of ``"-pub_date"`` indicates
+*descending* order. Ascending order is implied. To order randomly, use ``"?"``,
+like so::
+
+ Entry.objects.order_by('?')
+
+To order by a field in a different table, add the other table's name and a dot,
+like so::
+
+ Entry.objects.order_by('blogs_blog.name', 'headline')
+
+There's no way to specify whether ordering should be case sensitive. With
+respect to case-sensitivity, Django will order results however your database
+backend normally orders them.
+
+``distinct()``
+~~~~~~~~~~~~~~
+
+Returns a new ``QuerySet`` that uses ``SELECT DISTINCT`` in its SQL query. This
+eliminates duplicate rows from the query results.
+
+By default, a ``QuerySet`` will not eliminate duplicate rows. In practice, this
+is rarely a problem, because simple queries such as ``Blog.objects.all()``
+don't introduce the possibility of duplicate result rows.
+
+However, if your query spans multiple tables, it's possible to get duplicate
+results when a ``QuerySet`` is evaluated. That's when you'd use ``distinct()``.
+
+``values(*fields)``
+~~~~~~~~~~~~~~~~~~~
+
+Returns a ``ValuesQuerySet`` -- a ``QuerySet`` that evaluates to a list of
+dictionaries instead of model-instance objects.
+
+Each of those dictionaries represents an object, with the keys corresponding to
+the attribute names of model objects.
+
+This example compares the dictionaries of ``values()`` with the normal model
+objects::
+
+ # This list contains a Blog object.
+ >>> Blog.objects.filter(name__startswith='Beatles')
+ [Beatles Blog]
+
+ # This list contains a dictionary.
+ >>> Blog.objects.filter(name__startswith='Beatles').values()
+ [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]
+
+``values()`` takes optional positional arguments, ``*fields``, which specify
+field names to which the ``SELECT`` should be limited. If you specify the
+fields, each dictionary will contain only the field keys/values for the fields
+you specify. If you don't specify the fields, each dictionary will contain a
+key and value for every field in the database table.
+
+Example::
+
+ >>> Blog.objects.values()
+ [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}],
+ >>> Blog.objects.values('id', 'name')
+ [{'id': 1, 'name': 'Beatles Blog'}]
+
+A ``ValuesQuerySet`` is useful when you know you're only going to need values
+from a small number of the available fields and you won't need the
+functionality of a model instance object. It's more efficient to select only
+the fields you need to use.
+
+Finally, note a ``ValuesQuerySet`` is a subclass of ``QuerySet``, so it has all
+methods of ``QuerySet``. You can call ``filter()`` on it, or ``order_by()``, or
+whatever. Yes, that means these two calls are identical::
+
+ Blog.objects.values().order_by('id')
+ Blog.objects.order_by('id').values()
+
+The people who made Django prefer to put all the SQL-affecting methods first,
+followed (optionally) by any output-affecting methods (such as ``values()``),
+but it doesn't really matter. This is your chance to really flaunt your
+individualism.
+
+``dates(field, kind, order='ASC')``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Returns a ``DateQuerySet`` -- a ``QuerySet`` that evaluates to a list of
+``datetime.datetime`` objects representing all available dates of a particular
+kind within the contents of the ``QuerySet``.
+
+``field`` should be the name of a ``DateField`` or ``DateTimeField`` of your
+model.
+
+``kind`` should be either ``"year"``, ``"month"`` or ``"day"``. Each
+``datetime.datetime`` object in the result list is "truncated" to the given
+``type``.
+
+ * ``"year"`` returns a list of all distinct year values for the field.
+ * ``"month"`` returns a list of all distinct year/month values for the field.
+ * ``"day"`` returns a list of all distinct year/month/day values for the field.
+
+``order``, which defaults to ``'ASC'``, should be either ``'ASC'`` or
+``'DESC'``. This specifies how to order the results.
+
+Examples::
+
+ >>> Entry.objects.dates('pub_date', 'year')
+ [datetime.datetime(2005, 1, 1)]
+ >>> Entry.objects.dates('pub_date', 'month')
+ [datetime.datetime(2005, 2, 1), datetime.datetime(2005, 3, 1)]
+ >>> Entry.objects.dates('pub_date', 'day')
+ [datetime.datetime(2005, 2, 20), datetime.datetime(2005, 3, 20)]
+ >>> Entry.objects.dates('pub_date', 'day', order='DESC')
+ [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)]
+ >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
+ [datetime.datetime(2005, 3, 20)]
+
+``none()``
+~~~~~~~~~~
+
+**New in Django development version**
+
+Returns an ``EmptyQuerySet`` -- a ``QuerySet`` that always evaluates to
+an empty list. This can be used in cases where you know that you should
+return an empty result set and your caller is expecting a ``QuerySet``
+object (instead of returning an empty list, for example.)
+
+Examples::
+
+ >>> Entry.objects.none()
+ []
+
+``select_related()``
+~~~~~~~~~~~~~~~~~~~~
+
+Returns a ``QuerySet`` that will automatically "follow" foreign-key
+relationships, selecting that additional related-object data when it executes
+its query. This is a performance booster which results in (sometimes much)
+larger queries but means later use of foreign-key relationships won't require
+database queries.
+
+The following examples illustrate the difference between plain lookups and
+``select_related()`` lookups. Here's standard lookup::
+
+ # Hits the database.
+ e = Entry.objects.get(id=5)
+
+ # Hits the database again to get the related Blog object.
+ b = e.blog
+
+And here's ``select_related`` lookup::
+
+ # Hits the database.
+ e = Entry.objects.select_related().get(id=5)
+
+ # Doesn't hit the database, because e.blog has been prepopulated
+ # in the previous query.
+ b = e.blog
+
+``select_related()`` follows foreign keys as far as possible. If you have the
+following models::
+
+ class City(models.Model):
+ # ...
+
+ class Person(models.Model):
+ # ...
+ hometown = models.ForeignKey(City)
+
+ class Book(models.Model):
+ # ...
+ author = models.ForeignKey(Person)
+
+...then a call to ``Book.objects.select_related().get(id=4)`` will cache the
+related ``Person`` *and* the related ``City``::
+
+ b = Book.objects.select_related().get(id=4)
+ p = b.author # Doesn't hit the database.
+ c = p.hometown # Doesn't hit the database.
+
+ sv = Book.objects.get(id=4) # No select_related() in this example.
+ p = b.author # Hits the database.
+ c = p.hometown # Hits the database.
+
+Note that ``select_related()`` does not follow foreign keys that have
+``null=True``.
+
+Usually, using ``select_related()`` can vastly improve performance because your
+app can avoid many database calls. However, in situations with deeply nested
+sets of relationships ``select_related()`` can sometimes end up following "too
+many" relations, and can generate queries so large that they end up being slow.
+
+In these situations, you can use the ``depth`` argument to ``select_related()``
+to control how many "levels" of relations ``select_related()`` will actually
+follow::
+
+ b = Book.objects.select_related(depth=1).get(id=4)
+ p = b.author # Doesn't hit the database.
+ c = p.hometown # Requires a database call.
+
+The ``depth`` argument is new in the Django development version.
+
+``extra(select=None, where=None, params=None, tables=None)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, the Django query syntax by itself can't easily express a complex
+``WHERE`` clause. For these edge cases, Django provides the ``extra()``
+``QuerySet`` modifier -- a hook for injecting specific clauses into the SQL
+generated by a ``QuerySet``.
+
+By definition, these extra lookups may not be portable to different database
+engines (because you're explicitly writing SQL code) and violate the DRY
+principle, so you should avoid them if possible.
+
+Specify one or more of ``params``, ``select``, ``where`` or ``tables``. None
+of the arguments is required, but you should use at least one of them.
+
+``select``
+ The ``select`` argument lets you put extra fields in the ``SELECT`` clause.
+ It should be a dictionary mapping attribute names to SQL clauses to use to
+ calculate that attribute.
+
+ Example::
+
+ Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
+
+ As a result, each ``Entry`` object will have an extra attribute,
+ ``is_recent``, a boolean representing whether the entry's ``pub_date`` is
+ greater than Jan. 1, 2006.
+
+ Django inserts the given SQL snippet directly into the ``SELECT``
+ statement, so the resulting SQL of the above example would be::
+
+ SELECT blog_entry.*, (pub_date > '2006-01-01')
+ FROM blog_entry;
+
+
+ The next example is more advanced; it does a subquery to give each
+ resulting ``Blog`` object an ``entry_count`` attribute, an integer count
+ of associated ``Entry`` objects::
+
+ Blog.objects.extra(
+ select={
+ 'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'
+ },
+ )
+
+ (In this particular case, we're exploiting the fact that the query will
+ already contain the ``blog_blog`` table in its ``FROM`` clause.)
+
+ The resulting SQL of the above example would be::
+
+ SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id)
+ FROM blog_blog;
+
+ Note that the parenthesis required by most database engines around
+ subqueries are not required in Django's ``select`` clauses. Also note that
+ some database backends, such as some MySQL versions, don't support
+ subqueries.
+
+``where`` / ``tables``
+ You can define explicit SQL ``WHERE`` clauses -- perhaps to perform
+ non-explicit joins -- by using ``where``. You can manually add tables to
+ the SQL ``FROM`` clause by using ``tables``.
+
+ ``where`` and ``tables`` both take a list of strings. All ``where``
+ parameters are "AND"ed to any other search criteria.
+
+ Example::
+
+ Entry.objects.extra(where=['id IN (3, 4, 5, 20)'])
+
+ ...translates (roughly) into the following SQL::
+
+ SELECT * FROM blog_entry WHERE id IN (3, 4, 5, 20);
+
+``params``
+ The ``select`` and ``where`` parameters described above may use standard
+ Python database string placeholders -- ``'%s'`` to indicate parameters the
+ database engine should automatically quote. The ``params`` argument is a
+ list of any extra parameters to be substituted.
+
+ Example::
+
+ Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
+
+ Always use ``params`` instead of embedding values directly into ``select``
+ or ``where`` because ``params`` will ensure values are quoted correctly
+ according to your particular backend. (For example, quotes will be escaped
+ correctly.)
+
+ Bad::
+
+ Entry.objects.extra(where=["headline='Lennon'"])
+
+ Good::
+
+ Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
+
+QuerySet methods that do not return QuerySets
+---------------------------------------------
+
+The following ``QuerySet`` methods evaluate the ``QuerySet`` and return
+something *other than* a ``QuerySet``.
+
+These methods do not use a cache (see _`Caching and QuerySets` below). Rather,
+they query the database each time they're called.
+
+``get(**kwargs)``
+~~~~~~~~~~~~~~~~~
+
+Returns the object matching the given lookup parameters, which should be in
+the format described in `Field lookups`_.
+
+``get()`` raises ``AssertionError`` if more than one object was found.
+
+``get()`` raises a ``DoesNotExist`` exception if an object wasn't found for the
+given parameters. The ``DoesNotExist`` exception is an attribute of the model
+class. Example::
+
+ Entry.objects.get(id='foo') # raises Entry.DoesNotExist
+
+The ``DoesNotExist`` exception inherits from
+``django.core.exceptions.ObjectDoesNotExist``, so you can target multiple
+``DoesNotExist`` exceptions. Example::
+
+ from django.core.exceptions import ObjectDoesNotExist
+ try:
+ e = Entry.objects.get(id=3)
+ b = Blog.objects.get(id=1)
+ except ObjectDoesNotExist:
+ print "Either the entry or blog doesn't exist."
+
+``create(**kwargs)``
+~~~~~~~~~~~~~~~~~~~~
+
+A convenience method for creating an object and saving it all in one step. Thus::
+
+ p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
+
+and::
+
+ p = Person(first_name="Bruce", last_name="Springsteen")
+ p.save()
+
+are equivalent.
+
+``get_or_create(**kwargs)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A convenience method for looking up an object with the given kwargs, creating
+one if necessary.
+
+Returns a tuple of ``(object, created)``, where ``object`` is the retrieved or
+created object and ``created`` is a boolean specifying whether a new object was
+created.
+
+This is meant as a shortcut to boilerplatish code and is mostly useful for
+data-import scripts. For example::
+
+ try:
+ obj = Person.objects.get(first_name='John', last_name='Lennon')
+ except Person.DoesNotExist:
+ obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
+ obj.save()
+
+This pattern gets quite unwieldy as the number of fields in a model goes up.
+The above example can be rewritten using ``get_or_create()`` like so::
+
+ obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon',
+ defaults={'birthday': date(1940, 10, 9)})
+
+Any keyword arguments passed to ``get_or_create()`` -- *except* an optional one
+called ``defaults`` -- will be used in a ``get()`` call. If an object is found,
+``get_or_create()`` returns a tuple of that object and ``False``. If an object
+is *not* found, ``get_or_create()`` will instantiate and save a new object,
+returning a tuple of the new object and ``True``. The new object will be
+created according to this algorithm::
+
+ defaults = kwargs.pop('defaults', {})
+ params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
+ params.update(defaults)
+ obj = self.model(**params)
+ obj.save()
+
+In English, that means start with any non-``'defaults'`` keyword argument that
+doesn't contain a double underscore (which would indicate a non-exact lookup).
+Then add the contents of ``defaults``, overriding any keys if necessary, and
+use the result as the keyword arguments to the model class.
+
+If you have a field named ``defaults`` and want to use it as an exact lookup in
+``get_or_create()``, just use ``'defaults__exact'``, like so::
+
+ Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})
+
+Finally, a word on using ``get_or_create()`` in Django views. As mentioned
+earlier, ``get_or_create()`` is mostly useful in scripts that need to parse
+data and create new records if existing ones aren't available. But if you need
+to use ``get_or_create()`` in a view, please make sure to use it only in
+``POST`` requests unless you have a good reason not to. ``GET`` requests
+shouldn't have any effect on data; use ``POST`` whenever a request to a page
+has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.
+
+.. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
+
+``count()``
+~~~~~~~~~~~
+
+Returns an integer representing the number of objects in the database matching
+the ``QuerySet``. ``count()`` never raises exceptions.
+
+Example::
+
+ # Returns the total number of entries in the database.
+ Entry.objects.count()
+
+ # Returns the number of entries whose headline contains 'Lennon'
+ Entry.objects.filter(headline__contains='Lennon').count()
+
+``count()`` performs a ``SELECT COUNT(*)`` behind the scenes, so you should
+always use ``count()`` rather than loading all of the record into Python
+objects and calling ``len()`` on the result.
+
+Depending on which database you're using (e.g. PostgreSQL vs. MySQL),
+``count()`` may return a long integer instead of a normal Python integer. This
+is an underlying implementation quirk that shouldn't pose any real-world
+problems.
+
+``in_bulk(id_list)``
+~~~~~~~~~~~~~~~~~~~~
+
+Takes a list of primary-key values and returns a dictionary mapping each
+primary-key value to an instance of the object with the given ID.
+
+Example::
+
+ >>> Blog.objects.in_bulk([1])
+ {1: Beatles Blog}
+ >>> Blog.objects.in_bulk([1, 2])
+ {1: Beatles Blog, 2: Cheddar Talk}
+ >>> Blog.objects.in_bulk([])
+ {}
+
+If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary.
+
+``latest(field_name=None)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Returns the latest object in the table, by date, using the ``field_name``
+provided as the date field.
+
+This example returns the latest ``Entry`` in the table, according to the
+``pub_date`` field::
+
+ Entry.objects.latest('pub_date')
+
+If your model's ``Meta`` specifies ``get_latest_by``, you can leave off the
+``field_name`` argument to ``latest()``. Django will use the field specified in
+``get_latest_by`` by default.
+
+Like ``get()``, ``latest()`` raises ``DoesNotExist`` if an object doesn't
+exist with the given parameters.
+
+Note ``latest()`` exists purely for convenience and readability.
+
+Field lookups
+-------------
+
+Field lookups are how you specify the meat of an SQL ``WHERE`` clause. They're
+specified as keyword arguments to the ``QuerySet`` methods ``filter()``,
+``exclude()`` and ``get()``.
+
+Basic lookups keyword arguments take the form ``field__lookuptype=value``.
+(That's a double-underscore). For example::
+
+ Entry.objects.filter(pub_date__lte='2006-01-01')
+
+translates (roughly) into the following SQL::
+
+ SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
+
+.. admonition:: How this is possible
+
+ Python has the ability to define functions that accept arbitrary name-value
+ arguments whose names and values are evaluated at runtime. For more
+ information, see `Keyword Arguments`_ in the official Python tutorial.
+
+ .. _`Keyword Arguments`: http://docs.python.org/tut/node6.html#SECTION006720000000000000000
+
+If you pass an invalid keyword argument, a lookup function will raise
+``TypeError``.
+
+The database API supports the following lookup types:
+
+exact
+~~~~~
+
+Exact match. If the value provided for comparison is ``None``, it will
+be interpreted as an SQL ``NULL`` (See isnull_ for more details).
+
+Examples::
+
+ Entry.objects.get(id__exact=14)
+ Entry.objects.get(id__exact=None)
+
+SQL equivalents::
+
+ SELECT ... WHERE id = 14;
+ SELECT ... WHERE id = NULL;
+
+iexact
+~~~~~~
+
+Case-insensitive exact match.
+
+Example::
+
+ Blog.objects.get(name__iexact='beatles blog')
+
+SQL equivalent::
+
+ SELECT ... WHERE name ILIKE 'beatles blog';
+
+Note this will match ``'Beatles Blog'``, ``'beatles blog'``,
+``'BeAtLes BLoG'``, etc.
+
+contains
+~~~~~~~~
+
+Case-sensitive containment test.
+
+Example::
+
+ Entry.objects.get(headline__contains='Lennon')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline LIKE '%Lennon%';
+
+Note this will match the headline ``'Today Lennon honored'`` but not
+``'today lennon honored'``.
+
+SQLite doesn't support case-sensitive ``LIKE`` statements; ``contains`` acts
+like ``icontains`` for SQLite.
+
+icontains
+~~~~~~~~~
+
+Case-insensitive containment test.
+
+Example::
+
+ Entry.objects.get(headline__icontains='Lennon')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline ILIKE '%Lennon%';
+
+gt
+~~
+
+Greater than.
+
+Example::
+
+ Entry.objects.filter(id__gt=4)
+
+SQL equivalent::
+
+ SELECT ... WHERE id > 4;
+
+gte
+~~~
+
+Greater than or equal to.
+
+lt
+~~
+
+Less than.
+
+lte
+~~~
+
+Less than or equal to.
+
+in
+~~
+
+In a given list.
+
+Example::
+
+ Entry.objects.filter(id__in=[1, 3, 4])
+
+SQL equivalent::
+
+ SELECT ... WHERE id IN (1, 3, 4);
+
+startswith
+~~~~~~~~~~
+
+Case-sensitive starts-with.
+
+Example::
+
+ Entry.objects.filter(headline__startswith='Will')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline LIKE 'Will%';
+
+SQLite doesn't support case-sensitive ``LIKE`` statements; ``startswith`` acts
+like ``istartswith`` for SQLite.
+
+istartswith
+~~~~~~~~~~~
+
+Case-insensitive starts-with.
+
+Example::
+
+ Entry.objects.filter(headline__istartswith='will')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline ILIKE 'Will%';
+
+endswith
+~~~~~~~~
+
+Case-sensitive ends-with.
+
+Example::
+
+ Entry.objects.filter(headline__endswith='cats')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline LIKE '%cats';
+
+SQLite doesn't support case-sensitive ``LIKE`` statements; ``endswith`` acts
+like ``iendswith`` for SQLite.
+
+iendswith
+~~~~~~~~~
+
+Case-insensitive ends-with.
+
+Example::
+
+ Entry.objects.filter(headline__iendswith='will')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline ILIKE '%will'
+
+range
+~~~~~
+
+Range test (inclusive).
+
+Example::
+
+ start_date = datetime.date(2005, 1, 1)
+ end_date = datetime.date(2005, 3, 31)
+ Entry.objects.filter(pub_date__range=(start_date, end_date))
+
+SQL equivalent::
+
+ SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';
+
+You can use ``range`` anywhere you can use ``BETWEEN`` in SQL -- for dates,
+numbers and even characters.
+
+year
+~~~~
+
+For date/datetime fields, exact year match. Takes a four-digit year.
+
+Example::
+
+ Entry.objects.filter(pub_date__year=2005)
+
+SQL equivalent::
+
+ SELECT ... WHERE EXTRACT('year' FROM pub_date) = '2005';
+
+(The exact SQL syntax varies for each database engine.)
+
+month
+~~~~~
+
+For date/datetime fields, exact month match. Takes an integer 1 (January)
+through 12 (December).
+
+Example::
+
+ Entry.objects.filter(pub_date__month=12)
+
+SQL equivalent::
+
+ SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';
+
+(The exact SQL syntax varies for each database engine.)
+
+day
+~~~
+
+For date/datetime fields, exact day match.
+
+Example::
+
+ Entry.objects.filter(pub_date__day=3)
+
+SQL equivalent::
+
+ SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';
+
+(The exact SQL syntax varies for each database engine.)
+
+Note this will match any record with a pub_date on the third day of the month,
+such as January 3, July 3, etc.
+
+isnull
+~~~~~~
+
+Takes either ``True`` or ``False``, which correspond to SQL queries of
+``IS NULL`` and ``IS NOT NULL``, respectively.
+
+Example::
+
+ Entry.objects.filter(pub_date__isnull=True)
+
+SQL equivalent::
+
+ SELECT ... WHERE pub_date IS NULL;
+
+.. admonition:: ``__isnull=True`` vs ``__exact=None``
+
+ There is an important difference between ``__isnull=True`` and
+ ``__exact=None``. ``__exact=None`` will *always* return an empty result
+ set, because SQL requires that no value is equal to ``NULL``.
+ ``__isnull`` determines if the field is currently holding the value
+ of ``NULL`` without performing a comparison.
+
+search
+~~~~~~
+
+A boolean full-text search, taking advantage of full-text indexing. This is
+like ``contains`` but is significantly faster due to full-text indexing.
+
+Note this is only available in MySQL and requires direct manipulation of the
+database to add the full-text index.
+
+Default lookups are exact
+-------------------------
+
+If you don't provide a lookup type -- that is, if your keyword argument doesn't
+contain a double underscore -- the lookup type is assumed to be ``exact``.
+
+For example, the following two statements are equivalent::
+
+ Blog.objects.get(id__exact=14) # Explicit form
+ Blog.objects.get(id=14) # __exact is implied
+
+This is for convenience, because ``exact`` lookups are the common case.
+
+The pk lookup shortcut
+----------------------
+
+For convenience, Django provides a ``pk`` lookup type, which stands for
+"primary_key".
+
+In the example ``Blog`` model, the primary key is the ``id`` field, so these
+three statements are equivalent::
+
+ Blog.objects.get(id__exact=14) # Explicit form
+ Blog.objects.get(id=14) # __exact is implied
+ Blog.objects.get(pk=14) # pk implies id__exact
+
+The use of ``pk`` isn't limited to ``__exact`` queries -- any query term
+can be combined with ``pk`` to perform a query on the primary key of a model::
+
+ # Get blogs entries with id 1, 4 and 7
+ Blog.objects.filter(pk__in=[1,4,7])
+ # Get all blog entries with id > 14
+ Blog.objects.filter(pk__gt=14)
+
+``pk`` lookups also work across joins. For example, these three statements are
+equivalent::
+
+ Entry.objects.filter(blog__id__exact=3) # Explicit form
+ Entry.objects.filter(blog__id=3) # __exact is implied
+ Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
+
+Lookups that span relationships
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Django offers a powerful and intuitive way to "follow" relationships in
+lookups, taking care of the SQL ``JOIN``\s for you automatically, behind the
+scenes. To span a relationship, just use the field name of related fields
+across models, separated by double underscores, until you get to the field you
+want.
+
+This example retrieves all ``Entry`` objects with a ``Blog`` whose ``name``
+is ``'Beatles Blog'``::
+
+ Entry.objects.filter(blog__name__exact='Beatles Blog')
+
+This spanning can be as deep as you'd like.
+
+It works backwards, too. To refer to a "reverse" relationship, just use the
+lowercase name of the model.
+
+This example retrieves all ``Blog`` objects which have at least one ``Entry``
+whose ``headline`` contains ``'Lennon'``::
+
+ Blog.objects.filter(entry__headline__contains='Lennon')
+
+Escaping parenthesis and underscores in LIKE statements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
+``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``
+and ``iendswith``) will automatically escape the two special characters used in
+``LIKE`` statements -- the percent sign and the underscore. (In a ``LIKE``
+statement, the percent sign signifies a multiple-character wildcard and the
+underscore signifies a single-character wildcard.)
+
+This means things should work intuitively, so the abstraction doesn't leak.
+For example, to retrieve all the entries that contain a percent sign, just use
+the percent sign as any other character::
+
+ Entry.objects.filter(headline__contains='%')
+
+Django takes care of the quoting for you; the resulting SQL will look something
+like this::
+
+ SELECT ... WHERE headline LIKE '%\%%';
+
+Same goes for underscores. Both percentage signs and underscores are handled
+for you transparently.
+
+Caching and QuerySets
+---------------------
+
+Each ``QuerySet`` contains a cache, to minimize database access. It's important
+to understand how it works, in order to write the most efficient code.
+
+In a newly created ``QuerySet``, the cache is empty. The first time a
+``QuerySet`` is evaluated -- and, hence, a database query happens -- Django
+saves the query results in the ``QuerySet``'s cache and returns the results
+that have been explicitly requested (e.g., the next element, if the
+``QuerySet`` is being iterated over). Subsequent evaluations of the
+``QuerySet`` reuse the cached results.
+
+Keep this caching behavior in mind, because it may bite you if you don't use
+your ``QuerySet``\s correctly. For example, the following will create two
+``QuerySet``\s, evaluate them, and throw them away::
+
+ print [e.headline for e in Entry.objects.all()]
+ print [e.pub_date for e in Entry.objects.all()]
+
+That means the same database query will be executed twice, effectively doubling
+your database load. Also, there's a possibility the two lists may not include
+the same database records, because an ``Entry`` may have been added or deleted
+in the split second between the two requests.
+
+To avoid this problem, simply save the ``QuerySet`` and reuse it::
+
+ queryset = Poll.objects.all()
+ print [p.headline for p in queryset] # Evaluate the query set.
+ print [p.pub_date for p in queryset] # Re-use the cache from the evaluation.
+
+Comparing objects
+=================
+
+To compare two model instances, just use the standard Python comparison operator,
+the double equals sign: ``==``. Behind the scenes, that compares the primary
+key values of two models.
+
+Using the ``Entry`` example above, the following two statements are equivalent::
+
+ some_entry == other_entry
+ some_entry.id == other_entry.id
+
+If a model's primary key isn't called ``id``, no problem. Comparisons will
+always use the primary key, whatever it's called. For example, if a model's
+primary key field is called ``name``, these two statements are equivalent::
+
+ some_obj == other_obj
+ some_obj.name == other_obj.name
+
+Complex lookups with Q objects
+==============================
+
+Keyword argument queries -- in ``filter()``, etc. -- are "AND"ed together. If
+you need to execute more complex queries (for example, queries with ``OR``
+statements), you can use ``Q`` objects.
+
+A ``Q`` object (``django.db.models.Q``) is an object used to encapsulate a
+collection of keyword arguments. These keyword arguments are specified as in
+"Field lookups" above.
+
+For example, this ``Q`` object encapsulates a single ``LIKE`` query::
+
+ Q(question__startswith='What')
+
+``Q`` objects can be combined using the ``&`` and ``|`` operators. When an
+operator is used on two ``Q`` objects, it yields a new ``Q`` object.
+
+For example, this statement yields a single ``Q`` object that represents the
+"OR" of two ``"question__startswith"`` queries::
+
+ Q(question__startswith='Who') | Q(question__startswith='What')
+
+This is equivalent to the following SQL ``WHERE`` clause::
+
+ WHERE question LIKE 'Who%' OR question LIKE 'What%'
+
+You can compose statements of arbitrary complexity by combining ``Q`` objects
+with the ``&`` and ``|`` operators. You can also use parenthetical grouping.
+
+Each lookup function that takes keyword-arguments (e.g. ``filter()``,
+``exclude()``, ``get()``) can also be passed one or more ``Q`` objects as
+positional (not-named) arguments. If you provide multiple ``Q`` object
+arguments to a lookup function, the arguments will be "AND"ed together. For
+example::
+
+ Poll.objects.get(
+ Q(question__startswith='Who'),
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
+ )
+
+... roughly translates into the SQL::
+
+ SELECT * from polls WHERE question LIKE 'Who%'
+ AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
+
+Lookup functions can mix the use of ``Q`` objects and keyword arguments. All
+arguments provided to a lookup function (be they keyword arguments or ``Q``
+objects) are "AND"ed together. However, if a ``Q`` object is provided, it must
+precede the definition of any keyword arguments. For example::
+
+ Poll.objects.get(
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
+ question__startswith='Who')
+
+... would be a valid query, equivalent to the previous example; but::
+
+ # INVALID QUERY
+ Poll.objects.get(
+ question__startswith='Who',
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
+
+... would not be valid.
+
+See the `OR lookups examples page`_ for more examples.
+
+.. _OR lookups examples page: http://www.djangoproject.com/documentation/models/or_lookups/
+
+Related objects
+===============
+
+When you define a relationship in a model (i.e., a ``ForeignKey``,
+``OneToOneField``, or ``ManyToManyField``), instances of that model will have
+a convenient API to access the related object(s).
+
+Using the models at the top of this page, for example, an ``Entry`` object ``e``
+can get its associated ``Blog`` object by accessing the ``blog`` attribute:
+``e.blog``.
+
+(Behind the scenes, this functionality is implemented by Python descriptors_.
+This shouldn't really matter to you, but we point it out here for the curious.)
+
+Django also creates API accessors for the "other" side of the relationship --
+the link from the related model to the model that defines the relationship.
+For example, a ``Blog`` object ``b`` has access to a list of all related
+``Entry`` objects via the ``entry_set`` attribute: ``b.entry_set.all()``.
+
+All examples in this section use the sample ``Blog``, ``Author`` and ``Entry``
+models defined at the top of this page.
+
+.. _descriptors: http://users.rcn.com/python/download/Descriptor.htm
+
+One-to-many relationships
+-------------------------
+
+Forward
+~~~~~~~
+
+If a model has a ``ForeignKey``, instances of that model will have access to
+the related (foreign) object via a simple attribute of the model.
+
+Example::
+
+ e = Entry.objects.get(id=2)
+ e.blog # Returns the related Blog object.
+
+You can get and set via a foreign-key attribute. As you may expect, changes to
+the foreign key aren't saved to the database until you call ``save()``.
+Example::
+
+ e = Entry.objects.get(id=2)
+ e.blog = some_blog
+ e.save()
+
+If a ``ForeignKey`` field has ``null=True`` set (i.e., it allows ``NULL``
+values), you can assign ``None`` to it. Example::
+
+ e = Entry.objects.get(id=2)
+ e.blog = None
+ e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
+
+Forward access to one-to-many relationships is cached the first time the
+related object is accessed. Subsequent accesses to the foreign key on the same
+object instance are cached. Example::
+
+ e = Entry.objects.get(id=2)
+ print e.blog # Hits the database to retrieve the associated Blog.
+ print e.blog # Doesn't hit the database; uses cached version.
+
+Note that the ``select_related()`` ``QuerySet`` method recursively prepopulates
+the cache of all one-to-many relationships ahead of time. Example::
+
+ e = Entry.objects.select_related().get(id=2)
+ print e.blog # Doesn't hit the database; uses cached version.
+ print e.blog # Doesn't hit the database; uses cached version.
+
+``select_related()`` is documented in the "QuerySet methods that return new
+QuerySets" section above.
+
+Backward
+~~~~~~~~
+
+If a model has a ``ForeignKey``, instances of the foreign-key model will have
+access to a ``Manager`` that returns all instances of the first model. By
+default, this ``Manager`` is named ``FOO_set``, where ``FOO`` is the source
+model name, lowercased. This ``Manager`` returns ``QuerySets``, which can be
+filtered and manipulated as described in the "Retrieving objects" section
+above.
+
+Example::
+
+ b = Blog.objects.get(id=1)
+ b.entry_set.all() # Returns all Entry objects related to Blog.
+
+ # b.entry_set is a Manager that returns QuerySets.
+ b.entry_set.filter(headline__contains='Lennon')
+ b.entry_set.count()
+
+You can override the ``FOO_set`` name by setting the ``related_name``
+parameter in the ``ForeignKey()`` definition. For example, if the ``Entry``
+model was altered to ``blog = ForeignKey(Blog, related_name='entries')``, the
+above example code would look like this::
+
+ b = Blog.objects.get(id=1)
+ b.entries.all() # Returns all Entry objects related to Blog.
+
+ # b.entries is a Manager that returns QuerySets.
+ b.entries.filter(headline__contains='Lennon')
+ b.entries.count()
+
+You cannot access a reverse ``ForeignKey`` ``Manager`` from the class; it must
+be accessed from an instance. Example::
+
+ Blog.entry_set # Raises AttributeError: "Manager must be accessed via instance".
+
+In addition to the ``QuerySet`` methods defined in "Retrieving objects" above,
+the ``ForeignKey`` ``Manager`` has these additional methods:
+
+ * ``add(obj1, obj2, ...)``: Adds the specified model objects to the related
+ object set.
+
+ Example::
+
+ b = Blog.objects.get(id=1)
+ e = Entry.objects.get(id=234)
+ b.entry_set.add(e) # Associates Entry e with Blog b.
+
+ * ``create(**kwargs)``: Creates a new object, saves it and puts it in the
+ related object set. Returns the newly created object.
+
+ Example::
+
+ b = Blog.objects.get(id=1)
+ e = b.entry_set.create(headline='Hello', body_text='Hi', pub_date=datetime.date(2005, 1, 1))
+ # No need to call e.save() at this point -- it's already been saved.
+
+ This is equivalent to (but much simpler than)::
+
+ b = Blog.objects.get(id=1)
+ e = Entry(blog=b, headline='Hello', body_text='Hi', pub_date=datetime.date(2005, 1, 1))
+ e.save()
+
+ Note that there's no need to specify the keyword argument of the model
+ that defines the relationship. In the above example, we don't pass the
+ parameter ``blog`` to ``create()``. Django figures out that the new
+ ``Entry`` object's ``blog`` field should be set to ``b``.
+
+ * ``remove(obj1, obj2, ...)``: Removes the specified model objects from the
+ related object set.
+
+ Example::
+
+ b = Blog.objects.get(id=1)
+ e = Entry.objects.get(id=234)
+ b.entry_set.remove(e) # Disassociates Entry e from Blog b.
+
+ In order to prevent database inconsistency, this method only exists on
+ ``ForeignKey`` objects where ``null=True``. If the related field can't be
+ set to ``None`` (``NULL``), then an object can't be removed from a
+ relation without being added to another. In the above example, removing
+ ``e`` from ``b.entry_set()`` is equivalent to doing ``e.blog = None``,
+ and because the ``blog`` ``ForeignKey`` doesn't have ``null=True``, this
+ is invalid.
+
+ * ``clear()``: Removes all objects from the related object set.
+
+ Example::
+
+ b = Blog.objects.get(id=1)
+ b.entry_set.clear()
+
+ Note this doesn't delete the related objects -- it just disassociates
+ them.
+
+ Just like ``remove()``, ``clear()`` is only available on ``ForeignKey``s
+ where ``null=True``.
+
+To assign the members of a related set in one fell swoop, just assign to it
+from any iterable object. Example::
+
+ b = Blog.objects.get(id=1)
+ b.entry_set = [e1, e2]
+
+If the ``clear()`` method is available, any pre-existing objects will be
+removed from the ``entry_set`` before all objects in the iterable (in this
+case, a list) are added to the set. If the ``clear()`` method is *not*
+available, all objects in the iterable will be added without removing any
+existing elements.
+
+Each "reverse" operation described in this section has an immediate effect on
+the database. Every addition, creation and deletion is immediately and
+automatically saved to the database.
+
+Many-to-many relationships
+--------------------------
+
+Both ends of a many-to-many relationship get automatic API access to the other
+end. The API works just as a "backward" one-to-many relationship. See Backward_
+above.
+
+The only difference is in the attribute naming: The model that defines the
+``ManyToManyField`` uses the attribute name of that field itself, whereas the
+"reverse" model uses the lowercased model name of the original model, plus
+``'_set'`` (just like reverse one-to-many relationships).
+
+An example makes this easier to understand::
+
+ e = Entry.objects.get(id=3)
+ e.authors.all() # Returns all Author objects for this Entry.
+ e.authors.count()
+ e.authors.filter(name__contains='John')
+
+ a = Author.objects.get(id=5)
+ a.entry_set.all() # Returns all Entry objects for this Author.
+
+Like ``ForeignKey``, ``ManyToManyField`` can specify ``related_name``. In the
+above example, if the ``ManyToManyField`` in ``Entry`` had specified
+``related_name='entries'``, then each ``Author`` instance would have an
+``entries`` attribute instead of ``entry_set``.
+
+One-to-one relationships
+------------------------
+
+The semantics of one-to-one relationships will be changing soon, so we don't
+recommend you use them.
+
+How are the backward relationships possible?
+--------------------------------------------
+
+Other object-relational mappers require you to define relationships on both
+sides. The Django developers believe this is a violation of the DRY (Don't
+Repeat Yourself) principle, so Django only requires you to define the
+relationship on one end.
+
+But how is this possible, given that a model class doesn't know which other
+model classes are related to it until those other model classes are loaded?
+
+The answer lies in the ``INSTALLED_APPS`` setting. The first time any model is
+loaded, Django iterates over every model in ``INSTALLED_APPS`` and creates the
+backward relationships in memory as needed. Essentially, one of the functions
+of ``INSTALLED_APPS`` is to tell Django the entire model domain.
+
+Queries over related objects
+----------------------------
+
+Queries involving related objects follow the same rules as queries involving
+normal value fields. When specifying the the value for a query to match, you
+may use either an object instance itself, or the primary key value for the
+object.
+
+For example, if you have a Blog object ``b`` with ``id=5``, the following
+three queries would be identical::
+
+ Entry.objects.filter(blog=b) # Query using object instance
+ Entry.objects.filter(blog=b.id) # Query using id from instance
+ Entry.objects.filter(blog=5) # Query using id directly
+
+Deleting objects
+================
+
+The delete method, conveniently, is named ``delete()``. This method immediately
+deletes the object and has no return value. Example::
+
+ e.delete()
+
+You can also delete objects in bulk. Every ``QuerySet`` has a ``delete()``
+method, which deletes all members of that ``QuerySet``.
+
+For example, this deletes all ``Entry`` objects with a ``pub_date`` year of
+2005::
+
+ Entry.objects.filter(pub_date__year=2005).delete()
+
+When Django deletes an object, it emulates the behavior of the SQL
+constraint ``ON DELETE CASCADE`` -- in other words, any objects which
+had foreign keys pointing at the object to be deleted will be deleted
+along with it. For example::
+
+ b = Blog.objects.get(pk=1)
+ # This will delete the Blog and all of its Entry objects.
+ b.delete()
+
+Note that ``delete()`` is the only ``QuerySet`` method that is not exposed on a
+``Manager`` itself. This is a safety mechanism to prevent you from accidentally
+requesting ``Entry.objects.delete()``, and deleting *all* the entries. If you
+*do* want to delete all the objects, then you have to explicitly request a
+complete query set::
+
+ Entry.objects.all().delete()
+
+Extra instance methods
+======================
+
+In addition to ``save()``, ``delete()``, a model object might get any or all
+of the following methods:
+
+get_FOO_display()
+-----------------
+
+For every field that has ``choices`` set, the object will have a
+``get_FOO_display()`` method, where ``FOO`` is the name of the field. This
+method returns the "human-readable" value of the field. For example, in the
+following model::
+
+ GENDER_CHOICES = (
+ ('M', 'Male'),
+ ('F', 'Female'),
+ )
+ class Person(models.Model):
+ name = models.CharField(maxlength=20)
+ gender = models.CharField(maxlength=1, choices=GENDER_CHOICES)
+
+...each ``Person`` instance will have a ``get_gender_display()`` method. Example::
+
+ >>> p = Person(name='John', gender='M')
+ >>> p.save()
+ >>> p.gender
+ 'M'
+ >>> p.get_gender_display()
+ 'Male'
+
+get_next_by_FOO(\**kwargs) and get_previous_by_FOO(\**kwargs)
+-------------------------------------------------------------
+
+For every ``DateField`` and ``DateTimeField`` that does not have ``null=True``,
+the object will have ``get_next_by_FOO()`` and ``get_previous_by_FOO()``
+methods, where ``FOO`` is the name of the field. This returns the next and
+previous object with respect to the date field, raising the appropriate
+``DoesNotExist`` exception when appropriate.
+
+Both methods accept optional keyword arguments, which should be in the format
+described in `Field lookups`_ above.
+
+Note that in the case of identical date values, these methods will use the ID
+as a fallback check. This guarantees that no records are skipped or duplicated.
+For a full example, see the `lookup API sample model`_.
+
+.. _lookup API sample model: http://www.djangoproject.com/documentation/models/lookup/
+
+get_FOO_filename()
+------------------
+
+For every ``FileField``, the object will have a ``get_FOO_filename()`` method,
+where ``FOO`` is the name of the field. This returns the full filesystem path
+to the file, according to your ``MEDIA_ROOT`` setting.
+
+Note that ``ImageField`` is technically a subclass of ``FileField``, so every
+model with an ``ImageField`` will also get this method.
+
+get_FOO_url()
+-------------
+
+For every ``FileField``, the object will have a ``get_FOO_url()`` method,
+where ``FOO`` is the name of the field. This returns the full URL to the file,
+according to your ``MEDIA_URL`` setting. If the value is blank, this method
+returns an empty string.
+
+get_FOO_size()
+--------------
+
+For every ``FileField``, the object will have a ``get_FOO_size()`` method,
+where ``FOO`` is the name of the field. This returns the size of the file, in
+bytes. (Behind the scenes, it uses ``os.path.getsize``.)
+
+save_FOO_file(filename, raw_contents)
+-------------------------------------
+
+For every ``FileField``, the object will have a ``save_FOO_file()`` method,
+where ``FOO`` is the name of the field. This saves the given file to the
+filesystem, using the given filename. If a file with the given filename already
+exists, Django adds an underscore to the end of the filename (but before the
+extension) until the filename is available.
+
+get_FOO_height() and get_FOO_width()
+------------------------------------
+
+For every ``ImageField``, the object will have ``get_FOO_height()`` and
+``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This
+returns the height (or width) of the image, as an integer, in pixels.
+
+Shortcuts
+=========
+
+As you develop views, you will discover a number of common idioms in the
+way you use the database API. Django encodes some of these idioms as
+shortcuts that can be used to simplify the process of writing views.
+
+get_object_or_404()
+-------------------
+
+One common idiom to use ``get()`` and raise ``Http404`` if the
+object doesn't exist. This idiom is captured by ``get_object_or_404()``.
+This function takes a Django model as its first argument and an
+arbitrary number of keyword arguments, which it passes to the manager's
+``get()`` function. It raises ``Http404`` if the object doesn't
+exist. For example::
+
+ # Get the Entry with a primary key of 3
+ e = get_object_or_404(Entry, pk=3)
+
+When you provide a model to this shortcut function, the default manager
+is used to execute the underlying ``get()`` query. If you don't want to
+use the default manager, or you want to search a list of related objects,
+you can provide ``get_object_or_404()`` with a manager object, instead.
+For example::
+
+ # Get the author of blog instance `e` with a name of 'Fred'
+ a = get_object_or_404(e.authors, name='Fred')
+
+ # Use a custom manager 'recent_entries' in the search for an
+ # entry with a primary key of 3
+ e = get_object_or_404(Entry.recent_entries, pk=3)
+
+get_list_or_404()
+-----------------
+
+``get_list_or_404`` behaves the same was as ``get_object_or_404()``
+-- except the it uses using ``filter()`` instead of ``get()``. It raises
+``Http404`` if the list is empty.
+
+Falling back to raw SQL
+=======================
+
+If you find yourself needing to write an SQL query that is too complex for
+Django's database-mapper to handle, you can fall back into raw-SQL statement
+mode.
+
+The preferred way to do this is by giving your model custom methods or custom
+manager methods that execute queries. Although there's nothing in Django that
+*requires* database queries to live in the model layer, this approach keeps all
+your data-access logic in one place, which is smart from an code-organization
+standpoint. For instructions, see `Executing custom SQL`_.
+
+Finally, it's important to note that the Django database layer is merely an
+interface to your database. You can access your database via other tools,
+programming languages or database frameworks; there's nothing Django-specific
+about your database.
+
+.. _Executing custom SQL: ../model_api/#executing-custom-sql
diff --git a/google_appengine/lib/django/docs/design_philosophies.txt b/google_appengine/lib/django/docs/design_philosophies.txt
new file mode 100644
index 0000000..72aa8ad
--- /dev/null
+++ b/google_appengine/lib/django/docs/design_philosophies.txt
@@ -0,0 +1,281 @@
+===================
+Design philosophies
+===================
+
+This document explains some of the fundamental philosophies Django's developers
+have used in creating the framework. Its goal is to explain the past and guide
+the future.
+
+Overall
+=======
+
+Loose coupling
+--------------
+
+A fundamental goal of Django's stack is `loose coupling and tight cohesion`_.
+The various layers of the framework shouldn't "know" about each other unless
+absolutely necessary.
+
+For example, the template system knows nothing about Web requests, the database
+layer knows nothing about data display and the view system doesn't care which
+template system a programmer uses.
+
+Although Django comes with a full stack for convenience, the pieces of the
+stack are independent of another wherever possible.
+
+.. _`loose coupling and tight cohesion`: http://c2.com/cgi/wiki?CouplingAndCohesion
+
+Less code
+---------
+
+Django apps should use as little code as possible; they should lack boilerplate.
+Django should take full advantage of Python's dynamic capabilities, such as
+introspection.
+
+Quick development
+-----------------
+
+The point of a Web framework in the 21st century is to make the tedious aspects
+of Web development fast. Django should allow for incredibly quick Web
+development.
+
+Don't repeat yourself (DRY)
+---------------------------
+
+Every distinct concept and/or piece of data should live in one, and only one,
+place. Redundancy is bad. Normalization is good.
+
+The framework, within reason, should deduce as much as possible from as little
+as possible.
+
+Explicit is better than implicit
+--------------------------------
+
+This, a `core Python principle`_, means Django shouldn't do too much "magic."
+Magic shouldn't happen unless there's a really good reason for it. Magic is
+worth using only if it creates a huge convenience unattainable in other ways,
+and it isn't implemented in a way that confuses developers who are trying to
+learn how to use the feature.
+
+.. _`core Python principle`: http://www.python.org/doc/Humor.html#zen
+
+Consistency
+-----------
+
+The framework should be consistent at all levels. Consistency applies to
+everything from low-level (the Python coding style used) to high-level (the
+"experience" of using Django).
+
+Models
+======
+
+Explicit is better than implicit
+--------------------------------
+
+Fields shouldn't assume certain behaviors based solely on the name of the
+field. This requires too much knowledge of the system and is prone to errors.
+Instead, behaviors should be based on keyword arguments and, in some cases, on
+the type of the field.
+
+Include all relevant domain logic
+---------------------------------
+
+Models should encapsulate every aspect of an "object," following Martin
+Fowler's `Active Record`_ design pattern.
+
+This is why model-specific admin options are included in the model itself; data
+related to a model should be stored *in* the model.
+
+.. _`Active Record`: http://www.martinfowler.com/eaaCatalog/activeRecord.html
+
+Database API
+============
+
+The core goals of the database API are:
+
+SQL efficiency
+--------------
+
+It should execute SQL statements as few times as possible, and it should
+optimize statements internally.
+
+This is why developers need to call ``save()`` explicitly, rather than the
+framework saving things behind the scenes silently.
+
+This is also why the ``select_related()`` ``QuerySet`` method exists. It's an
+optional performance booster for the common case of selecting "every related
+object."
+
+Terse, powerful syntax
+----------------------
+
+The database API should allow rich, expressive statements in as little syntax
+as possible. It should not rely on importing other modules or helper objects.
+
+Joins should be performed automatically, behind the scenes, when necessary.
+
+Every object should be able to access every related object, systemwide. This
+access should work both ways.
+
+Option to drop into raw SQL easily, when needed
+-----------------------------------------------
+
+The database API should realize it's a shortcut but not necessarily an
+end-all-be-all. The framework should make it easy to write custom SQL -- entire
+statements, or just custom ``WHERE`` clauses as custom parameters to API calls.
+
+URL design
+==========
+
+Loose coupling
+--------------
+
+URLs in a Django app should not be coupled to the underlying Python code. Tying
+URLs to Python function names is a Bad And Ugly Thing.
+
+Along these lines, the Django URL system should allow URLs for the same app to
+be different in different contexts. For example, one site may put stories at
+``/stories/``, while another may use ``/news/``.
+
+Infinite flexibility
+--------------------
+
+URLs should be as flexible as possible. Any conceivable URL design should be
+allowed.
+
+Encourage best practices
+------------------------
+
+The framework should make it just as easy (or even easier) for a developer to
+design pretty URLs than ugly ones.
+
+File extensions in Web-page URLs should be avoided.
+
+Vignette-style commas in URLs deserve severe punishment.
+
+Definitive URLs
+---------------
+
+Technically, ``foo.com/bar`` and ``foo.com/bar/`` are two different URLs, and
+search-engine robots (and some Web traffic-analyzing tools) would treat them as
+separate pages. Django should make an effort to "normalize" URLs so that
+search-engine robots don't get confused.
+
+This is the reasoning behind the ``APPEND_SLASH`` setting.
+
+Template system
+===============
+
+Separate logic from presentation
+--------------------------------
+
+We see a template system as a tool that controls presentation and
+presentation-related logic -- and that's it. The template system shouldn't
+support functionality that goes beyond this basic goal.
+
+If we wanted to put everything in templates, we'd be using PHP. Been there,
+done that, wised up.
+
+Discourage redundancy
+---------------------
+
+The majority of dynamic Web sites use some sort of common sitewide design --
+a common header, footer, navigation bar, etc. The Django template system should
+make it easy to store those elements in a single place, eliminating duplicate
+code.
+
+This is the philosophy behind `template inheritance`_.
+
+.. _template inheritance: ../templates/#template-inheritance
+
+Be decoupled from HTML
+----------------------
+
+The template system shouldn't be designed so that it only outputs HTML. It
+should be equally good at generating other text-based formats, or just plain
+text.
+
+XML should not be used for template languages
+---------------------------------------------
+
+Using an XML engine to parse templates introduces a whole new world of human
+error in editing templates -- and incurs an unacceptable level of overhead in
+template processing.
+
+Assume designer competence
+--------------------------
+
+The template system shouldn't be designed so that templates necessarily are
+displayed nicely in WYSIWYG editors such as Dreamweaver. That is too severe of
+a limitation and wouldn't allow the syntax to be as nice as it is. Django
+expects template authors are comfortable editing HTML directly.
+
+Treat whitespace obviously
+--------------------------
+
+The template system shouldn't do magic things with whitespace. If a template
+includes whitespace, the system should treat the whitespace as it treats text
+-- just display it. Any whitespace that's not in a template tag should be
+displayed.
+
+Don't invent a programming language
+-----------------------------------
+
+The template system intentionally doesn't allow the following:
+
+ * Assignment to variables
+ * Advanced logic
+
+The goal is not to invent a programming language. The goal is to offer just
+enough programming-esque functionality, such as branching and looping, that is
+essential for making presentation-related decisions.
+
+The Django template system recognizes that templates are most often written by
+*designers*, not *programmers*, and therefore should not assume Python
+knowledge.
+
+Safety and security
+-------------------
+
+The template system, out of the box, should forbid the inclusion of malicious
+code -- such as commands that delete database records.
+
+This is another reason the template system doesn't allow arbitrary Python code.
+
+Extensibility
+-------------
+
+The template system should recognize that advanced template authors may want
+to extend its technology.
+
+This is the philosophy behind custom template tags and filters.
+
+Views
+=====
+
+Simplicity
+----------
+
+Writing a view should be as simple as writing a Python function. Developers
+shouldn't have to instantiate a class when a function will do.
+
+Use request objects
+-------------------
+
+Views should have access to a request object -- an object that stores metadata
+about the current request. The object should be passed directly to a view
+function, rather than the view function having to access the request data from
+a global variable. This makes it light, clean and easy to test views by passing
+in "fake" request objects.
+
+Loose coupling
+--------------
+
+A view shouldn't care about which template system the developer uses -- or even
+whether a template system is used at all.
+
+Differentiate between GET and POST
+----------------------------------
+
+GET and POST are distinct; developers should explicitly use one or the other.
+The framework should make it easy to distinguish between GET and POST data.
diff --git a/google_appengine/lib/django/docs/distributions.txt b/google_appengine/lib/django/docs/distributions.txt
new file mode 100644
index 0000000..63206c5
--- /dev/null
+++ b/google_appengine/lib/django/docs/distributions.txt
@@ -0,0 +1,76 @@
+===================================
+Third-party distributions of Django
+===================================
+
+Several third-party distributors are now providing versions of Django integrated
+with their package-management systems. These can make installation and upgrading
+much easier for users of Django since the integration includes the ability to
+automatically install dependancies (like database adapters) that Django
+requires.
+
+Typically, these packages are based on the latest stable release of Django, so
+if you want to use the development version of Django you'll need to follow the
+instructions for `installing the development version`_ from our Subversion
+repository.
+
+.. _installing the development version: ../install/#installing-the-development-version
+
+Linux distributions
+===================
+
+Debian
+------
+
+A `packaged version of Django`_ is available for `Debian GNU/Linux`_, and can be
+installed from either the "testing" or the "unstable" repositories by typing
+``apt-get install python-django``.
+
+When you install this package, ``apt`` will recommend installing a database
+adapter; you should select and install the adapter for whichever database you
+plan to use with Django.
+
+.. _Debian GNU/Linux: http://www.debian.org/
+.. _packaged version of Django: http://packages.debian.org/testing/python/python-django
+
+Ubuntu
+------
+
+The Debian ``python-django`` package is also available for `Ubuntu Linux`_, in
+the "universe" repository for Ubuntu 7.04 ("Feisty Fawn"). The `current Ubuntu
+package`_ is also based on Django 0.95.1 and can be installed in the same
+fashion as for Debian.
+
+.. _Ubuntu Linux: http://www.ubuntu.com/
+.. _current Ubuntu package: http://packages.ubuntu.com/feisty/python/python-django
+
+Fedora
+------
+
+A Django package is available for `Fedora Linux`_, in the "Fedora Extras"
+repository. The `current Fedora package`_ is based on Django 0.95.1, and can be
+installed by typing ``yum install Django``.
+
+.. _Fedora Linux: http://fedora.redhat.com/
+.. _current Fedora package: http://fedoraproject.org/extras/6/i386/repodata/repoview/Django-0-0.95.1-1.fc6.html
+
+Gentoo
+------
+
+A Django build is available for `Gentoo Linux`_, and is based on Django 0.95.1.
+The `current Gentoo build`_ can be installed by typing ``emerge django``.
+
+.. _Gentoo Linux: http://www.gentoo.org/
+.. _current Gentoo build: http://packages.gentoo.org/packages/?category=dev-python;name=django
+
+For distributors
+================
+
+If you'd like to package Django for distribution, we'd be happy to help out!
+Please join the `django-developers mailing list`_ and introduce yourself.
+
+We also encourage all distributors to subscribe to the `django-announce mailing
+list`_, which is a (very) low-traffic list for announcing new releases of Django
+and important bugfixes.
+
+.. _django-developers mailing list: http://groups.google.com/group/django-developers/
+.. _django-announce mailing list: http://groups.google.com/group/django-announce/
diff --git a/google_appengine/lib/django/docs/django-admin.txt b/google_appengine/lib/django/docs/django-admin.txt
new file mode 100644
index 0000000..28e2808
--- /dev/null
+++ b/google_appengine/lib/django/docs/django-admin.txt
@@ -0,0 +1,544 @@
+=============================
+django-admin.py and manage.py
+=============================
+
+``django-admin.py`` is Django's command-line utility for administrative tasks.
+This document outlines all it can do.
+
+In addition, ``manage.py`` is automatically created in each Django project.
+``manage.py`` is a thin wrapper around ``django-admin.py`` that takes care of
+two things for you before delegating to ``django-admin.py``:
+
+ * It puts your project's package on ``sys.path``.
+
+ * It sets the ``DJANGO_SETTINGS_MODULE`` environment variable so that it
+ points to your project's ``settings.py`` file.
+
+The ``django-admin.py`` script should be on your system path if you installed
+Django via its ``setup.py`` utility. If it's not on your path, you can find it in
+``site-packages/django/bin`` within your Python installation. Consider
+symlinking it from some place on your path, such as ``/usr/local/bin``.
+
+For Windows users, who do not have symlinking functionality available, you
+can copy ``django-admin.py`` to a location on your existing path or edit the
+``PATH`` settings (under ``Settings - Control Panel - System - Advanced - Environment...``)
+to point to its installed location.
+
+Generally, when working on a single Django project, it's easier to use
+``manage.py``. Use ``django-admin.py`` with ``DJANGO_SETTINGS_MODULE``, or the
+``--settings`` command line option, if you need to switch between multiple
+Django settings files.
+
+Usage
+=====
+
+``django-admin.py action [options]``
+
+``manage.py action [options]``
+
+``action`` should be one of the actions listed in this document. ``options``,
+which is optional, should be zero or more of the options listed in this
+document.
+
+Run ``django-admin.py --help`` to display a help message that includes a terse
+list of all available actions and options.
+
+Most actions take a list of ``appname``s. An ``appname`` is the basename of the
+package containing your models. For example, if your ``INSTALLED_APPS``
+contains the string ``'mysite.blog'``, the ``appname`` is ``blog``.
+
+Available actions
+=================
+
+adminindex [appname appname ...]
+--------------------------------
+
+Prints the admin-index template snippet for the given appnames.
+
+Use admin-index template snippets if you want to customize the look and feel of
+your admin's index page. See `Tutorial 2`_ for more information.
+
+.. _Tutorial 2: ../tutorial2/
+
+createcachetable [tablename]
+----------------------------
+
+Creates a cache table named ``tablename`` for use with the database cache
+backend. See the `cache documentation`_ for more information.
+
+.. _cache documentation: ../cache/
+
+dbshell
+-------
+
+Runs the command-line client for the database engine specified in your
+``DATABASE_ENGINE`` setting, with the connection parameters specified in your
+``DATABASE_USER``, ``DATABASE_PASSWORD``, etc., settings.
+
+ * For PostgreSQL, this runs the ``psql`` command-line client.
+ * For MySQL, this runs the ``mysql`` command-line client.
+ * For SQLite, this runs the ``sqlite3`` command-line client.
+
+This command assumes the programs are on your ``PATH`` so that a simple call to
+the program name (``psql``, ``mysql``, ``sqlite3``) will find the program in
+the right place. There's no way to specify the location of the program
+manually.
+
+diffsettings
+------------
+
+Displays differences between the current settings file and Django's default
+settings.
+
+Settings that don't appear in the defaults are followed by ``"###"``. For
+example, the default settings don't define ``ROOT_URLCONF``, so
+``ROOT_URLCONF`` is followed by ``"###"`` in the output of ``diffsettings``.
+
+Note that Django's default settings live in ``django/conf/global_settings.py``,
+if you're ever curious to see the full list of defaults.
+
+dumpdata [appname appname ...]
+------------------------------
+
+Output to standard output all data in the database associated with the named
+application(s).
+
+By default, the database will be dumped in JSON format. If you want the output
+to be in another format, use the ``--format`` option (e.g., ``format=xml``).
+You may specify any Django serialization backend (including any user specified
+serialization backends named in the ``SERIALIZATION_MODULES`` setting).
+
+If no application name is provided, all installed applications will be dumped.
+
+The output of ``dumpdata`` can be used as input for ``loaddata``.
+
+flush
+-----
+
+Return the database to the state it was in immediately after syncdb was
+executed. This means that all data will be removed from the database, any
+post-synchronization handlers will be re-executed, and the ``initial_data``
+fixture will be re-installed.
+
+inspectdb
+---------
+
+Introspects the database tables in the database pointed-to by the
+``DATABASE_NAME`` setting and outputs a Django model module (a ``models.py``
+file) to standard output.
+
+Use this if you have a legacy database with which you'd like to use Django.
+The script will inspect the database and create a model for each table within
+it.
+
+As you might expect, the created models will have an attribute for every field
+in the table. Note that ``inspectdb`` has a few special cases in its field-name
+output:
+
+ * If ``inspectdb`` cannot map a column's type to a model field type, it'll
+ use ``TextField`` and will insert the Python comment
+ ``'This field type is a guess.'`` next to the field in the generated
+ model.
+
+ * If the database column name is a Python reserved word (such as
+ ``'pass'``, ``'class'`` or ``'for'``), ``inspectdb`` will append
+ ``'_field'`` to the attribute name. For example, if a table has a column
+ ``'for'``, the generated model will have a field ``'for_field'``, with
+ the ``db_column`` attribute set to ``'for'``. ``inspectdb`` will insert
+ the Python comment
+ ``'Field renamed because it was a Python reserved word.'`` next to the
+ field.
+
+This feature is meant as a shortcut, not as definitive model generation. After
+you run it, you'll want to look over the generated models yourself to make
+customizations. In particular, you'll need to rearrange models' order, so that
+models that refer to other models are ordered properly.
+
+Primary keys are automatically introspected for PostgreSQL, MySQL and
+SQLite, in which case Django puts in the ``primary_key=True`` where
+needed.
+
+``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection
+only works in PostgreSQL and with certain types of MySQL tables.
+
+loaddata [fixture fixture ...]
+------------------------------
+
+Searches for and loads the contents of the named fixture into the database.
+
+A *Fixture* is a collection of files that contain the serialized contents of
+the database. Each fixture has a unique name; however, the files that
+comprise the fixture can be distributed over multiple directories, in
+multiple applications.
+
+Django will search in three locations for fixtures:
+
+ 1. In the ``fixtures`` directory of every installed application
+ 2. In any directory named in the ``FIXTURE_DIRS`` setting
+ 3. In the literal path named by the fixture
+
+Django will load any and all fixtures it finds in these locations that match
+the provided fixture names.
+
+If the named fixture has a file extension, only fixtures of that type
+will be loaded. For example::
+
+ django-admin.py loaddata mydata.json
+
+would only load JSON fixtures called ``mydata``. The fixture extension
+must correspond to the registered name of a serializer (e.g., ``json`` or
+``xml``).
+
+If you omit the extension, Django will search all available fixture types
+for a matching fixture. For example::
+
+ django-admin.py loaddata mydata
+
+would look for any fixture of any fixture type called ``mydata``. If a fixture
+directory contained ``mydata.json``, that fixture would be loaded
+as a JSON fixture. However, if two fixtures with the same name but different
+fixture type are discovered (for example, if ``mydata.json`` and
+``mydata.xml`` were found in the same fixture directory), fixture
+installation will be aborted, and any data installed in the call to
+``loaddata`` will be removed from the database.
+
+The fixtures that are named can include directory components. These
+directories will be included in the search path. For example::
+
+ django-admin.py loaddata foo/bar/mydata.json
+
+would search ``<appname>/fixtures/foo/bar/mydata.json`` for each installed
+application, ``<dirname>/foo/bar/mydata.json`` for each directory in
+``FIXTURE_DIRS``, and the literal path ``foo/bar/mydata.json``.
+
+Note that the order in which fixture files are processed is undefined. However,
+all fixture data is installed as a single transaction, so data in
+one fixture can reference data in another fixture. If the database backend
+supports row-level constraints, these constraints will be checked at the
+end of the transaction.
+
+.. admonition:: MySQL and Fixtures
+
+ Unfortunately, MySQL isn't capable of completely supporting all the
+ features of Django fixtures. If you use MyISAM tables, MySQL doesn't
+ support transactions or constraints, so you won't get a rollback if
+ multiple transaction files are found, or validation of fixture data.
+ If you use InnoDB tables, you won't be able to have any forward
+ references in your data files - MySQL doesn't provide a mechanism to
+ defer checking of row constraints until a transaction is committed.
+
+reset [appname appname ...]
+---------------------------
+Executes the equivalent of ``sqlreset`` for the given appnames.
+
+runfcgi [options]
+-----------------
+Starts a set of FastCGI processes suitable for use with any web server
+which supports the FastCGI protocol. See the `FastCGI deployment
+documentation`_ for details. Requires the Python FastCGI module from
+`flup`_.
+
+.. _FastCGI deployment documentation: ../fastcgi/
+.. _flup: http://www.saddi.com/software/flup/
+
+runserver [optional port number, or ipaddr:port]
+------------------------------------------------
+
+Starts a lightweight development Web server on the local machine. By default,
+the server runs on port 8000 on the IP address 127.0.0.1. You can pass in an
+IP address and port number explicitly.
+
+If you run this script as a user with normal privileges (recommended), you
+might not have access to start a port on a low port number. Low port numbers
+are reserved for the superuser (root).
+
+DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through
+security audits or performance tests. (And that's how it's gonna stay. We're in
+the business of making Web frameworks, not Web servers, so improving this
+server to be able to handle a production environment is outside the scope of
+Django.)
+
+The development server automatically reloads Python code for each request, as
+needed. You don't need to restart the server for code changes to take effect.
+
+When you start the server, and each time you change Python code while the
+server is running, the server will validate all of your installed models. (See
+the ``validate`` command below.) If the validator finds errors, it will print
+them to standard output, but it won't stop the server.
+
+You can run as many servers as you want, as long as they're on separate ports.
+Just execute ``django-admin.py runserver`` more than once.
+
+Note that the default IP address, 127.0.0.1, is not accessible from other
+machines on your network. To make your development server viewable to other
+machines on the network, use its own IP address (e.g. ``192.168.2.1``) or
+``0.0.0.0``.
+
+Examples:
+~~~~~~~~~
+
+Port 7000 on IP address 127.0.0.1::
+
+ django-admin.py runserver 7000
+
+Port 7000 on IP address 1.2.3.4::
+
+ django-admin.py runserver 1.2.3.4:7000
+
+Serving static files with the development server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the development server doesn't serve any static files for your site
+(such as CSS files, images, things under ``MEDIA_ROOT_URL`` and so forth). If
+you want to configure Django to serve static media, read the `serving static files`_
+documentation.
+
+.. _serving static files: ../static_files/
+
+Turning off auto-reload
+~~~~~~~~~~~~~~~~~~~~~~~
+
+To disable auto-reloading of code while the development server is running, use the
+``--noreload`` option, like so::
+
+ django-admin.py runserver --noreload
+
+shell
+-----
+
+Starts the Python interactive interpreter.
+
+Django will use IPython_, if it's installed. If you have IPython installed and
+want to force use of the "plain" Python interpreter, use the ``--plain``
+option, like so::
+
+ django-admin.py shell --plain
+
+.. _IPython: http://ipython.scipy.org/
+
+sql [appname appname ...]
+-------------------------
+
+Prints the CREATE TABLE SQL statements for the given appnames.
+
+sqlall [appname appname ...]
+----------------------------
+
+Prints the CREATE TABLE and initial-data SQL statements for the given appnames.
+
+Refer to the description of ``sqlinitialdata`` for an explanation of how to
+specify initial data.
+
+sqlclear [appname appname ...]
+--------------------------------------
+
+Prints the DROP TABLE SQL statements for the given appnames.
+
+sqlcustom [appname appname ...]
+-------------------------------
+
+Prints the custom SQL statements for the given appnames.
+
+For each model in each specified app, this command looks for the file
+``<appname>/sql/<modelname>.sql``, where ``<appname>`` is the given appname and
+``<modelname>`` is the model's name in lowercase. For example, if you have an
+app ``news`` that includes a ``Story`` model, ``sqlinitialdata`` will attempt
+to read a file ``news/sql/story.sql`` and append it to the output of this
+command.
+
+Each of the SQL files, if given, is expected to contain valid SQL. The SQL
+files are piped directly into the database after all of the models'
+table-creation statements have been executed. Use this SQL hook to make any
+table modifications, or insert any SQL functions into the database.
+
+Note that the order in which the SQL files are processed is undefined.
+
+sqlindexes [appname appname ...]
+----------------------------------------
+
+Prints the CREATE INDEX SQL statements for the given appnames.
+
+sqlreset [appname appname ...]
+--------------------------------------
+
+Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given appnames.
+
+sqlsequencereset [appname appname ...]
+----------------------------------------------
+
+Prints the SQL statements for resetting PostgreSQL sequences for the given
+appnames.
+
+See http://simon.incutio.com/archive/2004/04/21/postgres for more information.
+
+startapp [appname]
+------------------
+
+Creates a Django app directory structure for the given app name in the current
+directory.
+
+startproject [projectname]
+--------------------------
+
+Creates a Django project directory structure for the given project name in the
+current directory.
+
+syncdb
+------
+
+Creates the database tables for all apps in ``INSTALLED_APPS`` whose tables
+have not already been created.
+
+Use this command when you've added new applications to your project and want to
+install them in the database. This includes any apps shipped with Django that
+might be in ``INSTALLED_APPS`` by default. When you start a new project, run
+this command to install the default apps.
+
+If you're installing the ``django.contrib.auth`` application, ``syncdb`` will
+give you the option of creating a superuser immediately.
+
+``syncdb`` will also search for and install any fixture named ``initial_data``.
+See the documentation for ``loaddata`` for details on the specification of
+fixture data files.
+
+test
+----
+
+Discover and run tests for all installed models. See `Testing Django applications`_ for more information.
+
+.. _testing django applications: ../testing/
+
+validate
+--------
+
+Validates all installed models (according to the ``INSTALLED_APPS`` setting)
+and prints validation errors to standard output.
+
+Available options
+=================
+
+--settings
+----------
+
+Example usage::
+
+ django-admin.py syncdb --settings=mysite.settings
+
+Explicitly specifies the settings module to use. The settings module should be
+in Python package syntax, e.g. ``mysite.settings``. If this isn't provided,
+``django-admin.py`` will use the ``DJANGO_SETTINGS_MODULE`` environment
+variable.
+
+Note that this option is unnecessary in ``manage.py``, because it takes care of
+setting ``DJANGO_SETTINGS_MODULE`` for you.
+
+--pythonpath
+------------
+
+Example usage::
+
+ django-admin.py syncdb --pythonpath='/home/djangoprojects/myproject'
+
+Adds the given filesystem path to the Python `import search path`_. If this
+isn't provided, ``django-admin.py`` will use the ``PYTHONPATH`` environment
+variable.
+
+Note that this option is unnecessary in ``manage.py``, because it takes care of
+setting the Python path for you.
+
+.. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html
+
+--format
+--------
+
+Example usage::
+
+ django-admin.py dumpdata --format=xml
+
+Specifies the output format that will be used. The name provided must be the name
+of a registered serializer.
+
+--help
+------
+
+Displays a help message that includes a terse list of all available actions and
+options.
+
+--indent
+--------
+
+Example usage::
+
+ django-admin.py dumpdata --indent=4
+
+Specifies the number of spaces that will be used for indentation when
+pretty-printing output. By default, output will *not* be pretty-printed.
+Pretty-printing will only be enabled if the indent option is provided.
+
+--noinput
+---------
+
+Inform django-admin that the user should NOT be prompted for any input. Useful
+if the django-admin script will be executed as an unattended, automated
+script.
+
+--noreload
+----------
+
+Disable the use of the auto-reloader when running the development server.
+
+--version
+---------
+
+Displays the current Django version.
+
+Example output::
+
+ 0.9.1
+ 0.9.1 (SVN)
+
+--verbosity
+-----------
+
+Example usage::
+
+ django-admin.py syncdb --verbosity=2
+
+Verbosity determines the amount of notification and debug information that
+will be printed to the console. '0' is no output, '1' is normal output,
+and `2` is verbose output.
+
+--adminmedia
+------------
+
+Example usage::
+ django-admin.py manage.py --adminmedia=/tmp/new-admin-style/
+
+Tells Django where to find the various CSS and JavaScript files for the admin
+interface when running the development server. Normally these files are served
+out of the Django source tree, but because some designers customize these files
+for their site, this option allows you to test against custom versions.
+
+Extra niceties
+==============
+
+Syntax coloring
+---------------
+
+The ``django-admin.py`` / ``manage.py`` commands that output SQL to standard
+output will use pretty color-coded output if your terminal supports
+ANSI-colored output. It won't use the color codes if you're piping the
+command's output to another program.
+
+Bash completion
+---------------
+
+If you use the Bash shell, consider installing the Django bash completion
+script, which lives in ``extras/django_bash_completion`` in the Django
+distribution. It enables tab-completion of ``django-admin.py`` and
+``manage.py`` commands, so you can, for instance...
+
+ * Type ``django-admin.py``.
+ * Press [TAB] to see all available options.
+ * Type ``sql``, then [TAB], to see all available options whose names start
+ with ``sql``.
diff --git a/google_appengine/lib/django/docs/documentation.txt b/google_appengine/lib/django/docs/documentation.txt
new file mode 100644
index 0000000..bacfb17
--- /dev/null
+++ b/google_appengine/lib/django/docs/documentation.txt
@@ -0,0 +1,148 @@
+====================================
+How to read the Django documentation
+====================================
+
+We've put a lot of effort into making Django's documentation useful, easy to
+read and as complete as possible. Here are a few tips on how to make the best
+of it, along with some style guidelines.
+
+(Yes, this is documentation about documentation. Rest assured we have no plans
+to write a document about how to read the document about documentation.)
+
+How documentation is updated
+============================
+
+Just as the Django code base is developed and improved on a daily basis, our
+documentation is consistently improving. We improve documentation for several
+reasons:
+
+ * To make content fixes, such as grammar/typo corrections.
+ * To add information and/or examples to existing sections that need to be
+ expanded.
+ * To document Django features that aren't yet documented. (The list of
+ such features is shrinking but exists nonetheless.)
+ * To add documentation for new features as new features get added, or as
+ Django APIs or behaviors change.
+
+Django's documentation is kept in the same source control system as its code.
+It lives in the `django/trunk/docs`_ directory of our Subversion repository.
+Each document is a separate text file that covers a narrowly focused topic,
+such as the "generic views" framework or how to construct a database model.
+
+.. _django/trunk/docs: http://code.djangoproject.com/browser/django/trunk/docs
+
+Where to get it
+===============
+
+You can read Django documentation in several ways. They are, in order of
+preference:
+
+On the Web
+----------
+
+The most recent version of the Django documentation lives at
+http://www.djangoproject.com/documentation/ . These HTML pages are generated
+automatically from the text files in source control every 15 minutes. That
+means they reflect the "latest and greatest" in Django -- they include the very
+latest corrections and additions, and they discuss the latest Django features,
+which may only be available to users of the Django development version. (See
+"Differences between versions" below.)
+
+A key advantage of the Web-based documentation is the comment section at the
+bottom of each document. This is an area for anybody to submit changes,
+corrections and suggestions about the given document. The Django developers
+frequently monitor the comments there and use them to improve the documentation
+for everybody.
+
+We encourage you to help improve the docs: it's easy! Note, however, that
+comments should explicitly relate to the documentation, rather than asking
+broad tech-support questions. If you need help with your particular Django
+setup, try the `django-users mailing list`_ instead of posting a comment to the
+documentation.
+
+.. _django-users mailing list: http://groups.google.com/group/django-users
+
+In plain text
+-------------
+
+For offline reading, or just for convenience, you can read the Django
+documentation in plain text.
+
+If you're using an official release of Django, note that the zipped package
+(tarball) of the code includes a ``docs/`` directory, which contains all the
+documentation for that release.
+
+If you're using the development version of Django (aka the Subversion "trunk"),
+note that the ``docs/`` directory contains all of the documentation. You can
+``svn update`` it, just as you ``svn update`` the Python code, in order to get
+the latest changes.
+
+You can check out the latest Django documentation from Subversion using this
+shell command::
+
+ svn co http://code.djangoproject.com/svn/django/trunk/docs/ django_docs
+
+One low-tech way of taking advantage of the text documentation is by using the
+Unix ``grep`` utility to search for a phrase in all of the documentation. For
+example, this will show you each mention of the phrase "edit_inline" in any
+Django document::
+
+ grep edit_inline /path/to/django/docs/*.txt
+
+Formatting
+~~~~~~~~~~
+
+The text documentation is written in ReST (ReStructured Text) format. That
+means it's easy to read but is also formatted in a way that makes it easy to
+convert into other formats, such as HTML. If you're interested, the script that
+converts the ReST text docs into djangoproject.com's HTML lives at
+`djangoproject.com/django_website/apps/docs/parts/build_documentation.py`_ in
+the Django Subversion repository.
+
+.. _djangoproject.com/django_website/apps/docs/parts/build_documentation.py: http://code.djangoproject.com/browser/djangoproject.com/django_website/apps/docs/parts/build_documentation.py
+
+Differences between versions
+============================
+
+As previously mentioned, the text documentation in our Subversion repository
+contains the "latest and greatest" changes and additions. These changes often
+include documentation of new features added in the Django development version
+-- the Subversion ("trunk") version of Django. For that reason, it's worth
+pointing out our policy on keeping straight the documentation for various
+versions of the framework.
+
+We follow this policy:
+
+ * The primary documentation on djangoproject.com is an HTML version of the
+ latest docs in Subversion. These docs always correspond to the latest
+ official Django release, plus whatever features we've added/changed in
+ the framework *since* the latest release.
+
+ * As we add features to Django's development version, we try to update the
+ documentation in the same Subversion commit transaction.
+
+ * To distinguish feature changes/additions in the docs, we use the phrase
+ **New in Django development version**. In practice, this means that the
+ current documentation on djangoproject.com can be used by users of either
+ the latest release *or* the development version.
+
+ * Documentation for a particular Django release is frozen once the version
+ has been released officially. It remains a snapshot of the docs as of the
+ moment of the release. We will make exceptions to this rule in
+ the case of retroactive security updates or other such retroactive
+ changes. Once documentation is frozen, we add a note to the top of each
+ frozen document that says "These docs are frozen for Django version XXX"
+ and links to the current version of that document.
+
+ * Once a document is frozen for a Django release, we remove comments from
+ that page, in favor of having comments on the latest version of that
+ document. This is for the sake of maintainability and usability, so that
+ users have one, and only one, place to leave comments on a particular
+ document. We realize that some people may be stuck on a previous version
+ of Django, but we believe the usability problems with multiple versions
+ of a document the outweigh the benefits.
+
+ * The `main documentation Web page`_ includes links to documentation for
+ all previous versions.
+
+.. _main documentation Web page: http://www.djangoproject.com/documentation/
diff --git a/google_appengine/lib/django/docs/email.txt b/google_appengine/lib/django/docs/email.txt
new file mode 100644
index 0000000..1f4ce4e
--- /dev/null
+++ b/google_appengine/lib/django/docs/email.txt
@@ -0,0 +1,176 @@
+==============
+Sending e-mail
+==============
+
+Although Python makes sending e-mail relatively easy via the `smtplib library`_,
+Django provides a couple of light wrappers over it, to make sending e-mail
+extra quick.
+
+The code lives in a single module: ``django.core.mail``.
+
+.. _smtplib library: http://www.python.org/doc/current/lib/module-smtplib.html
+
+Quick example
+=============
+
+In two lines::
+
+ from django.core.mail import send_mail
+
+ send_mail('Subject here', 'Here is the message.', 'from@example.com',
+ ['to@example.com'], fail_silently=False)
+
+.. note::
+
+ The character set of email sent with ``django.core.mail`` will be set to
+ the value of your `DEFAULT_CHARSET setting`_.
+
+.. _DEFAULT_CHARSET setting: ../settings/#DEFAULT_CHARSET
+
+send_mail()
+===========
+
+The simplest way to send e-mail is using the function
+``django.core.mail.send_mail()``. Here's its definition::
+
+ send_mail(subject, message, from_email, recipient_list,
+ fail_silently=False, auth_user=None,
+ auth_password=None)
+
+The ``subject``, ``message``, ``from_email`` and ``recipient_list`` parameters
+are required.
+
+ * ``subject``: A string.
+ * ``message``: A string.
+ * ``from_email``: A string.
+ * ``recipient_list``: A list of strings, each an e-mail address. Each
+ member of ``recipient_list`` will see the other recipients in the "To:"
+ field of the e-mail message.
+ * ``fail_silently``: A boolean. If it's ``False``, ``send_mail`` will raise
+ an ``smtplib.SMTPException``. See the `smtplib docs`_ for a list of
+ possible exceptions, all of which are subclasses of ``SMTPException``.
+ * ``auth_user``: The optional username to use to authenticate to the SMTP
+ server. If this isn't provided, Django will use the value of the
+ ``EMAIL_HOST_USER`` setting.
+ * ``auth_password``: The optional password to use to authenticate to the
+ SMTP server. If this isn't provided, Django will use the value of the
+ ``EMAIL_HOST_PASSWORD`` setting.
+
+.. _smtplib docs: http://www.python.org/doc/current/lib/module-smtplib.html
+
+send_mass_mail()
+================
+
+``django.core.mail.send_mass_mail()`` is intended to handle mass e-mailing.
+Here's the definition::
+
+ send_mass_mail(datatuple, fail_silently=False,
+ auth_user=None, auth_password=None):
+
+``datatuple`` is a tuple in which each element is in this format::
+
+ (subject, message, from_email, recipient_list)
+
+``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
+as in ``send_mail()``.
+
+Each separate element of ``datatuple`` results in a separate e-mail message.
+As in ``send_mail()``, recipients in the same ``recipient_list`` will all see
+the other addresses in the e-mail messages's "To:" field.
+
+send_mass_mail() vs. send_mail()
+--------------------------------
+
+The main difference between ``send_mass_mail()`` and ``send_mail()`` is that
+``send_mail()`` opens a connection to the mail server each time it's executed,
+while ``send_mass_mail()`` uses a single connection for all of its messages.
+This makes ``send_mass_mail()`` slightly more efficient.
+
+mail_admins()
+=============
+
+``django.core.mail.mail_admins()`` is a shortcut for sending an e-mail to the
+site admins, as defined in the `ADMINS setting`_. Here's the definition::
+
+ mail_admins(subject, message, fail_silently=False)
+
+``mail_admins()`` prefixes the subject with the value of the
+`EMAIL_SUBJECT_PREFIX setting`_, which is ``"[Django] "`` by default.
+
+The "From:" header of the e-mail will be the value of the `SERVER_EMAIL setting`_.
+
+This method exists for convenience and readability.
+
+.. _ADMINS setting: ../settings/#admins
+.. _EMAIL_SUBJECT_PREFIX setting: ../settings/#email-subject-prefix
+.. _SERVER_EMAIL setting: ../settings/#server-email
+
+mail_managers() function
+========================
+
+``django.core.mail.mail_managers()`` is just like ``mail_admins()``, except it
+sends an e-mail to the site managers, as defined in the `MANAGERS setting`_.
+Here's the definition::
+
+ mail_managers(subject, message, fail_silently=False)
+
+.. _MANAGERS setting: ../settings/#managers
+
+Examples
+========
+
+This sends a single e-mail to john@example.com and jane@example.com, with them
+both appearing in the "To:"::
+
+ send_mail('Subject', 'Message.', 'from@example.com',
+ ['john@example.com', 'jane@example.com'])
+
+This sends a message to john@example.com and jane@example.com, with them both
+receiving a separate e-mail::
+
+ datatuple = (
+ ('Subject', 'Message.', 'from@example.com', ['john@example.com']),
+ ('Subject', 'Message.', 'from@example.com', ['jane@example.com']),
+ )
+ send_mass_mail(datatuple)
+
+Preventing header injection
+===========================
+
+`Header injection`_ is a security exploit in which an attacker inserts extra
+e-mail headers to control the "To:" and "From:" in e-mail messages that your
+scripts generate.
+
+The Django e-mail functions outlined above all protect against header injection
+by forbidding newlines in header values. If any ``subject``, ``from_email`` or
+``recipient_list`` contains a newline (in either Unix, Windows or Mac style),
+the e-mail function (e.g. ``send_mail()``) will raise
+``django.core.mail.BadHeaderError`` (a subclass of ``ValueError``) and, hence,
+will not send the e-mail. It's your responsibility to validate all data before
+passing it to the e-mail functions.
+
+If a ``message`` contains headers at the start of the string, the headers will
+simply be printed as the first bit of the e-mail message.
+
+Here's an example view that takes a ``subject``, ``message`` and ``from_email``
+from the request's POST data, sends that to admin@example.com and redirects to
+"/contact/thanks/" when it's done::
+
+ from django.core.mail import send_mail, BadHeaderError
+
+ def send_email(request):
+ subject = request.POST.get('subject', '')
+ message = request.POST.get('message', '')
+ from_email = request.POST.get('from_email', '')
+ if subject and message and from_email:
+ try:
+ send_mail(subject, message, from_email, ['admin@example.com'])
+ except BadHeaderError:
+ return HttpResponse('Invalid header found.')
+ return HttpResponseRedirect('/contact/thanks/')
+ else:
+ # In reality we'd use a manipulator
+ # to get proper validation errors.
+ return HttpResponse('Make sure all fields are entered and valid.')
+
+.. _Header injection: http://securephp.damonkohler.com/index.php/Email_Injection
diff --git a/google_appengine/lib/django/docs/faq.txt b/google_appengine/lib/django/docs/faq.txt
new file mode 100644
index 0000000..33e8ef0
--- /dev/null
+++ b/google_appengine/lib/django/docs/faq.txt
@@ -0,0 +1,669 @@
+==========
+Django FAQ
+==========
+
+General questions
+=================
+
+Why does this project exist?
+----------------------------
+
+Django grew from a very practical need: World Online, a newspaper Web
+operation, is responsible for building intensive Web applications on journalism
+deadlines. In the fast-paced newsroom, World Online often has only a matter of
+hours to take a complicated Web application from concept to public launch.
+
+At the same time, the World Online Web developers have consistently been
+perfectionists when it comes to following best practices of Web development.
+
+In fall 2003, the World Online developers (Adrian Holovaty and Simon Willison)
+ditched PHP and began using Python to develop its Web sites. As they built
+intensive, richly interactive sites such as Lawrence.com, they began to extract
+a generic Web development framework that let them build Web applications more
+and more quickly. They tweaked this framework constantly, adding improvements
+over two years.
+
+In summer 2005, World Online decided to open-source the resulting software,
+Django. Django would not be possible without a whole host of open-source
+projects -- `Apache`_, `Python`_, and `PostgreSQL`_ to name a few -- and we're
+thrilled to be able to give something back to the open-source community.
+
+.. _Apache: http://httpd.apache.org/
+.. _Python: http://www.python.org/
+.. _PostgreSQL: http://www.postgresql.org/
+
+What does "Django" mean, and how do you pronounce it?
+-----------------------------------------------------
+
+Django is named after `Django Reinhardt`_, a gypsy jazz guitarist from the 1930s
+to early 1950s. To this day, he's considered one of the best guitarists of all time.
+
+Listen to his music. You'll like it.
+
+Django is pronounced **JANG**-oh. Rhymes with FANG-oh. The "D" is silent.
+
+.. _Django Reinhardt: http://en.wikipedia.org/wiki/Django_Reinhardt
+
+Is Django stable?
+-----------------
+
+Yes. World Online has been using Django for more than three years. Sites built
+on Django have weathered traffic spikes of over one million hits an hour and a
+number of Slashdottings. Yes, it's quite stable.
+
+Does Django scale?
+------------------
+
+Yes. Compared to development time, hardware is cheap, and so Django is
+designed to take advantage of as much hardware as you can throw at it.
+
+Django uses a "shared-nothing" architecture, which means you can add hardware
+at any level -- database servers, caching servers or Web/application servers.
+
+The framework cleanly separates components such as its database layer and
+application layer. And it ships with a simple-yet-powerful `cache framework`_.
+
+.. _`cache framework`: ../cache/
+
+Who's behind this?
+------------------
+
+Django was developed at `World Online`_, the Web department of a newspaper in
+Lawrence, Kansas, USA.
+
+`Adrian Holovaty`_
+ Adrian is a Web developer with a background in journalism. He was lead
+ developer at World Online for 2.5 years, during which time Django was
+ developed and implemented on World Online's sites. Now he works for
+ washingtonpost.com building rich, database-backed information sites, and
+ continues to oversee Django development. He likes playing guitar (Django
+ Reinhardt style) and hacking on side projects such as `chicagocrime.org`_.
+ He lives in Chicago.
+
+ On IRC, Adrian goes by ``adrian_h``.
+
+`Jacob Kaplan-Moss`_
+ Jacob is a whipper-snapper from California who spends equal time coding and
+ cooking. He's lead developer at World Online and actively hacks on various
+ cool side projects. He's contributed to the Python-ObjC bindings and was
+ the first guy to figure out how to write Tivo apps in Python. Lately he's
+ been messing with Python on the PSP. He lives in Lawrence, Kansas.
+
+ On IRC, Jacob goes by ``jacobkm``.
+
+`Simon Willison`_
+ Simon is a well-respected Web developer from England. He had a one-year
+ internship at World Online, during which time he and Adrian developed
+ Django from scratch. The most enthusiastic Brit you'll ever meet, he's
+ passionate about best practices in Web development and has maintained a
+ well-read Web-development blog for years at http://simon.incutio.com.
+ He works for Yahoo UK, where he managed to score the title "Hacker Liason."
+ He lives in London.
+
+ On IRC, Simon goes by ``SimonW``.
+
+`Wilson Miner`_
+ Wilson's design-fu makes us all look like rock stars. By day, he's an
+ interactive designer for `Apple`. Don't ask him what he's working on, or
+ he'll have to kill you. He lives in San Francisco.
+
+ On IRC, Wilson goes by ``wilsonian``.
+
+.. _`World Online`: http://code.djangoproject.com/wiki/WorldOnline
+.. _`Adrian Holovaty`: http://www.holovaty.com/
+.. _`washingtonpost.com`: http://www.washingtonpost.com/
+.. _`chicagocrime.org`: http://www.chicagocrime.org/
+.. _`Simon Willison`: http://simon.incutio.com/
+.. _`simon.incutio.com`: http://simon.incutio.com/
+.. _`Jacob Kaplan-Moss`: http://www.jacobian.org/
+.. _`Wilson Miner`: http://www.wilsonminer.com/
+.. _`Apple`: http://www.apple.com/
+
+Which sites use Django?
+-----------------------
+
+The Django wiki features a consistently growing `list of Django-powered sites`_.
+Feel free to add your Django-powered site to the list.
+
+.. _list of Django-powered sites: http://code.djangoproject.com/wiki/DjangoPoweredSites
+
+Django appears to be a MVC framework, but you call the Controller the "view", and the View the "template". How come you don't use the standard names?
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+
+Well, the standard names are debatable.
+
+In our interpretation of MVC, the "view" describes the data that gets presented
+to the user. It's not necessarily *how* the data *looks*, but *which* data is
+presented. The view describes *which data you see*, not *how you see it.* It's
+a subtle distinction.
+
+So, in our case, a "view" is the Python callback function for a particular URL,
+because that callback function describes which data is presented.
+
+Furthermore, it's sensible to separate content from presentation -- which is
+where templates come in. In Django, a "view" describes which data is presented,
+but a view normally delegates to a template, which describes *how* the data is
+presented.
+
+Where does the "controller" fit in, then? In Django's case, it's probably the
+framework itself: the machinery that sends a request to the appropriate view,
+according to the Django URL configuration.
+
+If you're hungry for acronyms, you might say that Django is a "MTV" framework
+-- that is, "model", "template", and "view." That breakdown makes much more
+sense.
+
+At the end of the day, of course, it comes down to getting stuff done. And,
+regardless of how things are named, Django gets stuff done in a way that's most
+logical to us.
+
+<Framework X> does <feature Y> -- why doesn't Django?
+-----------------------------------------------------
+
+We're well aware that there are other awesome Web frameworks out there, and
+we're not averse to borrowing ideas where appropriate. However, Django was
+developed precisely because we were unhappy with the status quo, so please be
+aware that "because <Framework X>" does it is not going to be sufficient reason
+to add a given feature to Django.
+
+Why did you write all of Django from scratch, instead of using other Python libraries?
+--------------------------------------------------------------------------------------
+
+When Django was originally written a couple of years ago, Adrian and Simon
+spent quite a bit of time exploring the various Python Web frameworks
+available.
+
+In our opinion, none of them were completely up to snuff.
+
+We're picky. You might even call us perfectionists. (With deadlines.)
+
+Over time, we stumbled across open-source libraries that did things we'd
+already implemented. It was reassuring to see other people solving similar
+problems in similar ways, but it was too late to integrate outside code: We'd
+already written, tested and implemented our own framework bits in several
+production settings -- and our own code met our needs delightfully.
+
+In most cases, however, we found that existing frameworks/tools inevitably had
+some sort of fundamental, fatal flaw that made us squeamish. No tool fit our
+philosophies 100%.
+
+Like we said: We're picky.
+
+We've documented our philosophies on the `design philosophies page`_.
+
+.. _design philosophies page: ../design_philosophies/
+
+Do you have any of those nifty "screencast" things?
+---------------------------------------------------
+
+You can bet your bottom they're on the way. But, since we're still hammering
+out a few points, we want to make sure they reflect the final state of things
+at Django 1.0, not some intermediary step. In other words, we don't want to
+spend a lot of energy creating screencasts yet, because Django APIs will shift.
+
+In the meantime, though, check out this `unofficial Django screencast`_.
+
+.. _unofficial Django screencast: http://www.throwingbeans.org/django_screencasts.html
+
+Is Django a content-management-system (CMS)?
+--------------------------------------------
+
+No, Django is not a CMS, or any sort of "turnkey product" in and of itself.
+It's a Web framework; it's a programming tool that lets you build Web sites.
+
+For example, it doesn't make much sense to compare Django to something like
+Drupal_, because Django is something you use to *create* things like Drupal.
+
+Of course, Django's automatic admin site is fantastic and timesaving -- but
+the admin site is one module of Django the framework. Furthermore, although
+Django has special conveniences for building "CMS-y" apps, that doesn't mean
+it's not just as appropriate for building "non-CMS-y" apps (whatever that
+means!).
+
+.. _Drupal: http://drupal.org/
+
+When will you release Django 1.0?
+---------------------------------
+
+Short answer: When we're comfortable with Django's APIs, have added all
+features that we feel are necessary to earn a "1.0" status, and are ready to
+begin maintaining backwards compatibility.
+
+The merging of Django's `magic-removal branch`_ went a long way toward Django
+1.0.
+
+Of course, you should note that `quite a few production sites`_ use Django in
+its current status. Don't let the lack of a 1.0 turn you off.
+
+.. _magic-removal branch: http://code.djangoproject.com/wiki/RemovingTheMagic
+.. _quite a few production sites: http://code.djangoproject.com/wiki/DjangoPoweredSites
+
+How can I download the Django documentation to read it offline?
+---------------------------------------------------------------
+
+The Django docs are available in the ``docs`` directory of each Django tarball
+release. These docs are in ReST (ReStructured Text) format, and each text file
+corresponds to a Web page on the official Django site.
+
+Because the documentation is `stored in revision control`_, you can browse
+documentation changes just like you can browse code changes.
+
+Technically, the docs on Django's site are generated from the latest development
+versions of those ReST documents, so the docs on the Django site may offer more
+information than the docs that come with the latest Django release.
+
+.. _stored in revision control: http://code.djangoproject.com/browser/django/trunk/docs
+
+Where can I find Django developers for hire?
+--------------------------------------------
+
+Consult our `developers for hire page`_ for a list of Django developers who
+would be happy to help you.
+
+You might also be interested in posting a job to http://www.gypsyjobs.com/ .
+
+.. _developers for hire page: http://code.djangoproject.com/wiki/DevelopersForHire
+
+Installation questions
+======================
+
+How do I get started?
+---------------------
+
+ #. `Download the code`_.
+ #. Install Django (read the `installation guide`_).
+ #. Walk through the tutorial_.
+ #. Check out the rest of the documentation_, and `ask questions`_ if you
+ run into trouble.
+
+.. _`Download the code`: http://www.djangoproject.com/download/
+.. _`installation guide`: ../install/
+.. _tutorial: ../tutorial1/
+.. _documentation: ../
+.. _ask questions: http://www.djangoproject.com/community/
+
+How do I fix the "install a later version of setuptools" error?
+---------------------------------------------------------------
+
+Just run the ``ez_setup.py`` script in the Django distribution.
+
+What are Django's prerequisites?
+--------------------------------
+
+Django requires Python_ 2.3 or later. No other Python libraries are required
+for basic Django usage.
+
+For a development environment -- if you just want to experiment with Django --
+you don't need to have a separate Web server installed; Django comes with its
+own lightweight development server. For a production environment, we recommend
+`Apache 2`_ and mod_python_, although Django follows the WSGI_ spec, which
+means it can run on a variety of server platforms.
+
+If you want to use Django with a database, which is probably the case, you'll
+also need a database engine. PostgreSQL_ is recommended, because we're
+PostgreSQL fans, and MySQL_ and `SQLite 3`_ are also supported.
+
+.. _Python: http://www.python.org/
+.. _Apache 2: http://httpd.apache.org/
+.. _mod_python: http://www.modpython.org/
+.. _WSGI: http://www.python.org/peps/pep-0333.html
+.. _PostgreSQL: http://www.postgresql.org/
+.. _MySQL: http://www.mysql.com/
+.. _`SQLite 3`: http://www.sqlite.org/
+
+Do I lose anything by using Python 2.3 versus newer Python versions, such as Python 2.5?
+----------------------------------------------------------------------------------------
+
+No. Django itself is guaranteed to work with any version of Python from 2.3
+and higher.
+
+If you use a Python version newer than 2.3, you will, of course, be able to
+take advantage of newer Python features in your own code, along with the speed
+improvements and other optimizations that have been made to the Python language
+itself. But the Django framework itself should work equally well on 2.3 as it
+does on 2.4 or 2.5.
+
+Do I have to use mod_python?
+----------------------------
+
+Although we recommend mod_python for production use, you don't have to use it,
+thanks to the fact that Django uses an arrangement called WSGI_. Django can
+talk to any WSGI-enabled server. The most common non-mod_python deployment
+setup is FastCGI. See `How to use Django with FastCGI`_ for full information.
+
+Also, see the `server arrangements wiki page`_ for other deployment strategies.
+
+If you just want to play around and develop things on your local computer, use
+the development Web server that comes with Django. Things should Just Work.
+
+.. _WSGI: http://www.python.org/peps/pep-0333.html
+.. _How to use Django with FastCGI: ../fastcgi/
+.. _server arrangements wiki page: http://code.djangoproject.com/wiki/ServerArrangements
+
+How do I install mod_python on Windows?
+---------------------------------------
+
+ * For Python 2.4, grab mod_python from `win32 build of mod_python for
+ Python 2.4`_.
+ * For Python 2.4, check out this `Django on Windows howto`_.
+ * For Python 2.3, grab mod_python from http://www.modpython.org/ and read
+ `Running mod_python on Apache on Windows2000`_.
+ * Also, try this (not Windows-specific) `guide to getting mod_python
+ working`_.
+
+.. _`win32 build of mod_python for Python 2.4`: http://www.lehuen.com/nicolas/index.php/2005/02/21/39-win32-build-of-mod_python-314-for-python-24
+.. _`Django on Windows howto`: http://thinkhole.org/wp/2006/04/03/django-on-windows-howto/
+.. _`Running mod_python on Apache on Windows2000`: http://groups-beta.google.com/group/comp.lang.python/msg/139af8c83a5a9d4f
+.. _`guide to getting mod_python working`: http://www.dscpl.com.au/articles/modpython-001.html
+
+Will Django run under shared hosting (like TextDrive or Dreamhost)?
+-------------------------------------------------------------------
+
+See our `Django-friendly Web hosts`_ page.
+
+.. _`Django-friendly Web hosts`: http://code.djangoproject.com/wiki/DjangoFriendlyWebHosts
+
+Should I use the official version or development version?
+---------------------------------------------------------
+
+The Django developers improve Django every day and are pretty good about not
+checking in broken code. We use the development code (from the Subversion
+repository) directly on our servers, so we consider it stable. With that in
+mind, we recommend that you use the latest development code, because it
+generally contains more features and fewer bugs than the "official" releases.
+
+Using Django
+============
+
+Why do I get an error about importing DJANGO_SETTINGS_MODULE?
+-------------------------------------------------------------
+
+Make sure that:
+
+ * The environment variable DJANGO_SETTINGS_MODULE is set to a fully-qualified
+ Python module (i.e. "mysite.settings.main").
+
+ * Said module is on ``sys.path`` (``import mysite.settings.main`` should work).
+
+ * The module doesn't contain syntax errors (of course).
+
+ * If you're using mod_python but *not* using Django's request handler,
+ you'll need to work around a mod_python bug related to the use of
+ ``SetEnv``; before you import anything from Django you'll need to do
+ the following::
+
+ os.environ.update(req.subprocess_env)
+
+ (where ``req`` is the mod_python request object).
+
+I can't stand your template language. Do I have to use it?
+----------------------------------------------------------
+
+We happen to think our template engine is the best thing since chunky bacon,
+but we recognize that choosing a template language runs close to religion.
+There's nothing about Django that requires using the template language, so
+if you're attached to ZPT, Cheetah, or whatever, feel free to use those.
+
+Do I have to use your model/database layer?
+-------------------------------------------
+
+Nope. Just like the template system, the model/database layer is decoupled from
+the rest of the framework.
+
+The one exception is: If you use a different database library, you won't get to
+use Django's automatically-generated admin site. That app is coupled to the
+Django database layer.
+
+How do I use image and file fields?
+-----------------------------------
+
+Using a ``FileField`` or an ``ImageField`` in a model takes a few steps:
+
+ #. In your settings file, define ``MEDIA_ROOT`` as the full path to
+ a directory where you'd like Django to store uploaded files. (For
+ performance, these files are not stored in the database.) Define
+ ``MEDIA_URL`` as the base public URL of that directory. Make sure that
+ this directory is writable by the Web server's user account.
+
+ #. Add the ``FileField`` or ``ImageField`` to your model, making sure
+ to define the ``upload_to`` option to tell Django to which subdirectory
+ of ``MEDIA_ROOT`` it should upload files.
+
+ #. All that will be stored in your database is a path to the file
+ (relative to ``MEDIA_ROOT``). You'll must likely want to use the
+ convenience ``get_<fieldname>_url`` function provided by Django. For
+ example, if your ``ImageField`` is called ``mug_shot``, you can get the
+ absolute URL to your image in a template with
+ ``{{ object.get_mug_shot_url }}``.
+
+Databases and models
+====================
+
+How can I see the raw SQL queries Django is running?
+----------------------------------------------------
+
+Make sure your Django ``DEBUG`` setting is set to ``True``. Then, just do
+this::
+
+ >>> from django.db import connection
+ >>> connection.queries
+ [{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',
+ 'time': '0.002'}]
+
+``connection.queries`` is only available if ``DEBUG`` is ``True``. It's a list
+of dictionaries in order of query execution. Each dictionary has the following::
+
+ ``sql`` -- The raw SQL statement
+ ``time`` -- How long the statement took to execute, in seconds.
+
+``connection.queries`` includes all SQL statements -- INSERTs, UPDATES,
+SELECTs, etc. Each time your app hits the database, the query will be recorded.
+
+Can I use Django with a pre-existing database?
+----------------------------------------------
+
+Yes. See `Integrating with a legacy database`_.
+
+.. _`Integrating with a legacy database`: ../legacy_databases/
+
+If I make changes to a model, how do I update the database?
+-----------------------------------------------------------
+
+If you don't mind clearing data, your project's ``manage.py`` utility has an
+option to reset the SQL for a particular application::
+
+ manage.py reset appname
+
+This drops any tables associated with ``appname`` and recreates them.
+
+If you do care about deleting data, you'll have to execute the ``ALTER TABLE``
+statements manually in your database. That's the way we've always done it,
+because dealing with data is a very sensitive operation that we've wanted to
+avoid automating. That said, there's some work being done to add partially
+automated database-upgrade functionality.
+
+Do Django models support multiple-column primary keys?
+------------------------------------------------------
+
+No. Only single-column primary keys are supported.
+
+But this isn't an issue in practice, because there's nothing stopping you from
+adding other constraints (using the ``unique_together`` model option or
+creating the constraint directly in your database), and enforcing the
+uniqueness at that level. Single-column primary keys are needed for things such
+as the admin interface to work; e.g., you need a simple way of being able to
+specify an object to edit or delete.
+
+How do I add database-specific options to my CREATE TABLE statements, such as specifying MyISAM as the table type?
+------------------------------------------------------------------------------------------------------------------
+
+We try to avoid adding special cases in the Django code to accommodate all the
+database-specific options such as table type, etc. If you'd like to use any of
+these options, create an `SQL initial data file`_ that contains ``ALTER TABLE``
+statements that do what you want to do. The initial data files are executed in
+your database after the ``CREATE TABLE`` statements.
+
+For example, if you're using MySQL and want your tables to use the MyISAM table
+type, create an initial data file and put something like this in it::
+
+ ALTER TABLE myapp_mytable ENGINE=MyISAM;
+
+As explained in the `SQL initial data file`_ documentation, this SQL file can
+contain arbitrary SQL, so you can make any sorts of changes you need to make.
+
+.. _SQL initial data file: ../model_api/#providing-initial-sql-data
+
+Why is Django leaking memory?
+-----------------------------
+
+Django isn't known to leak memory. If you find your Django processes are
+allocating more and more memory, with no sign of releasing it, check to make
+sure your ``DEBUG`` setting is set to ``True``. If ``DEBUG`` is ``True``, then
+Django saves a copy of every SQL statement it has executed.
+
+(The queries are saved in ``django.db.connection.queries``. See
+`How can I see the raw SQL queries Django is running?`_.)
+
+To fix the problem, set ``DEBUG`` to ``False``.
+
+If you need to clear the query list manually at any point in your functions,
+just call ``reset_queries()``, like this::
+
+ from django import db
+ db.reset_queries()
+
+The admin site
+==============
+
+I can't log in. When I enter a valid username and password, it just brings up the login page again, with no error messages.
+---------------------------------------------------------------------------------------------------------------------------
+
+The login cookie isn't being set correctly, because the domain of the cookie
+sent out by Django doesn't match the domain in your browser. Try these two
+things:
+
+ * Set the ``SESSION_COOKIE_DOMAIN`` setting in your admin config file
+ to match your domain. For example, if you're going to
+ "http://www.mysite.com/admin/" in your browser, in
+ "myproject.settings" you should set ``SESSION_COOKIE_DOMAIN = 'www.mysite.com'``.
+
+ * Some browsers (Firefox?) don't like to accept cookies from domains that
+ don't have dots in them. If you're running the admin site on "localhost"
+ or another domain that doesn't have a dot in it, try going to
+ "localhost.localdomain" or "127.0.0.1". And set
+ ``SESSION_COOKIE_DOMAIN`` accordingly.
+
+I can't log in. When I enter a valid username and password, it brings up the login page again, with a "Please enter a correct username and password" error.
+-----------------------------------------------------------------------------------------------------------------------------------------------------------
+
+If you're sure your username and password are correct, make sure your user
+account has ``is_active`` and ``is_staff`` set to True. The admin site only
+allows access to users with those two fields both set to True.
+
+How can I prevent the cache middleware from caching the admin site?
+-------------------------------------------------------------------
+
+Set the ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting to ``True``. See the
+`cache documentation`_ for more information.
+
+.. _cache documentation: ../cache/#the-per-site-cache
+
+How do I automatically set a field's value to the user who last edited the object in the admin?
+-----------------------------------------------------------------------------------------------
+
+At this point, Django doesn't have an official way to do this. But it's an oft-requested
+feature, so we're discussing how it can be implemented. The problem is we don't want to couple
+the model layer with the admin layer with the request layer (to get the current user). It's a
+tricky problem.
+
+One person hacked up a `solution that doesn't require patching Django`_, but note that it's an
+unofficial solution, and there's no guarantee it won't break at some point.
+
+.. _solution that doesn't require patching Django: http://lukeplant.me.uk/blog.php?id=1107301634
+
+How do I limit admin access so that objects can only be edited by the users who created them?
+---------------------------------------------------------------------------------------------
+
+See the answer to the previous question.
+
+My admin-site CSS and images showed up fine using the development server, but they're not displaying when using mod_python.
+---------------------------------------------------------------------------------------------------------------------------
+
+See `serving the admin files`_ in the "How to use Django with mod_python"
+documentation.
+
+.. _serving the admin files: ../modpython/#serving-the-admin-files
+
+My "list_filter" contains a ManyToManyField, but the filter doesn't display.
+----------------------------------------------------------------------------
+
+Django won't bother displaying the filter for a ``ManyToManyField`` if there
+are fewer than two related objects.
+
+For example, if your ``list_filter`` includes ``sites``, and there's only one
+site in your database, it won't display a "Site" filter. In that case,
+filtering by site would be meaningless.
+
+How can I customize the functionality of the admin interface?
+-------------------------------------------------------------
+
+You've got several options. If you want to piggyback on top of an add/change
+form that Django automatically generates, you can attach arbitrary JavaScript
+modules to the page via the model's ``class Admin`` ``js`` parameter. That
+parameter is a list of URLs, as strings, pointing to JavaScript modules that
+will be included within the admin form via a ``<script>`` tag.
+
+If you want more flexibility than simply tweaking the auto-generated forms,
+feel free to write custom views for the admin. The admin is powered by Django
+itself, and you can write custom views that hook into the authentication
+system, check permissions and do whatever else they need to do.
+
+If you want to customize the look-and-feel of the admin interface, read the
+next question.
+
+The dynamically-generated admin site is ugly! How can I change it?
+------------------------------------------------------------------
+
+We like it, but if you don't agree, you can modify the admin site's
+presentation by editing the CSS stylesheet and/or associated image files. The
+site is built using semantic HTML and plenty of CSS hooks, so any changes you'd
+like to make should be possible by editing the stylesheet. We've got a
+`guide to the CSS used in the admin`_ to get you started.
+
+.. _`guide to the CSS used in the admin`: ../admin_css/
+
+How do I create users without having to edit password hashes?
+-------------------------------------------------------------
+
+If you'd like to use the admin site to create users, upgrade to the Django
+development version, where this problem was fixed on Aug. 4, 2006.
+
+You can also use the Python API. See `creating users`_ for full info.
+
+.. _creating users: ../authentication/#creating-users
+
+Contributing code
+=================
+
+How can I get started contributing code to Django?
+--------------------------------------------------
+
+Thanks for asking! We've written an entire document devoted to this question.
+It's titled `Contributing to Django`_.
+
+.. _Contributing to Django: ../contributing/
+
+I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch?
+--------------------------------------------------------------------------------------------
+
+Don't worry: We're not ignoring you!
+
+It's important to understand there is a difference between "a ticket is being
+ignored" and "a ticket has not been attended to yet." Django's ticket system
+contains hundreds of open tickets, of various degrees of impact on end-user
+functionality, and Django's developers have to review and prioritize.
+
+Besides, if your feature request stands no chance of inclusion in Django, we
+won't ignore it -- we'll just close the ticket. So if your ticket is still
+open, it doesn't mean we're ignoring you; it just means we haven't had time to
+look at it yet.
diff --git a/google_appengine/lib/django/docs/fastcgi.txt b/google_appengine/lib/django/docs/fastcgi.txt
new file mode 100644
index 0000000..5ecaac8
--- /dev/null
+++ b/google_appengine/lib/django/docs/fastcgi.txt
@@ -0,0 +1,317 @@
+==============================
+How to use Django with FastCGI
+==============================
+
+Although the `current preferred setup`_ for running Django is Apache_ with
+`mod_python`_, many people use shared hosting, on which FastCGI is the only
+viable option. In some setups, FastCGI also allows better security -- and,
+possibly, better performance -- than mod_python.
+
+Essentially, FastCGI is an efficient way of letting an external application
+serve pages to a Web server. The Web server delegates the incoming Web requests
+(via a socket) to FastCGI, which executes the code and passes the response back
+to the Web server, which, in turn, passes it back to the client's Web browser.
+
+Like mod_python, FastCGI allows code to stay in memory, allowing requests to be
+served with no startup time. Unlike mod_python (or `mod_perl`_), a FastCGI
+process doesn't run inside the Web server process, but in a separate,
+persistent process.
+
+.. _current preferred setup: ../modpython/
+.. _Apache: http://httpd.apache.org/
+.. _mod_python: http://www.modpython.org/
+.. _mod_perl: http://perl.apache.org/
+
+.. admonition:: Why run code in a separate process?
+
+ The traditional ``mod_*`` arrangements in Apache embed various scripting
+ languages (most notably PHP, Python and Perl) inside the process space of
+ your Web server. Although this lowers startup time -- because code doesn't
+ have to be read off disk for every request -- it comes at the cost of
+ memory use. For mod_python, for example, every Apache process gets its own
+ Python interpreter, which uses up a considerable amount of RAM.
+
+ Due to the nature of FastCGI, it's even possible to have processes that run
+ under a different user account than the Web server process. That's a nice
+ security benefit on shared systems, because it means you can secure your
+ code from other users.
+
+Prerequisite: flup
+==================
+
+Before you can start using FastCGI with Django, you'll need to install flup_,
+which is a Python library for dealing with FastCGI. Make sure to use the latest
+Subversion snapshot of flup, as some users have reported stalled pages with
+older flup versions.
+
+.. _flup: http://www.saddi.com/software/flup/
+
+Starting your FastCGI server
+============================
+
+FastCGI operates on a client-server model, and in most cases you'll be starting
+the FastCGI process on your own. Your Web server (be it Apache, lighttpd, or
+otherwise) only contacts your Django-FastCGI process when the server needs a
+dynamic page to be loaded. Because the daemon is already running with the code
+in memory, it's able to serve the response very quickly.
+
+.. admonition:: Note
+
+ If you're on a shared hosting system, you'll probably be forced to use
+ Web server-managed FastCGI processes. See the section below on running
+ Django with Web server-managed processes for more information.
+
+A Web server can connect to a FastCGI server in one of two ways: It can use
+either a Unix domain socket (a "named pipe" on Win32 systems), or it can use a
+TCP socket. What you choose is a manner of preference; a TCP socket is usually
+easier due to permissions issues.
+
+To start your server, first change into the directory of your project (wherever
+your ``manage.py`` is), and then run ``manage.py`` with the ``runfcgi`` option::
+
+ ./manage.py runfcgi [options]
+
+If you specify ``help`` as the only option after ``runfcgi``, it'll display a
+list of all the available options.
+
+You'll need to specify either a ``socket`` or both ``host`` and ``port``. Then,
+when you set up your Web server, you'll just need to point it at the host/port
+or socket you specified when starting the FastCGI server.
+
+Examples
+--------
+
+Running a threaded server on a TCP port::
+
+ ./manage.py runfcgi method=threaded host=127.0.0.1 port=3033
+
+Running a preforked server on a Unix domain socket::
+
+ ./manage.py runfcgi method=prefork socket=/home/user/mysite.sock pidfile=django.pid
+
+Run without daemonizing (backgrounding) the process (good for debugging)::
+
+ ./manage.py runfcgi daemonize=false socket=/tmp/mysite.sock
+
+Stopping the FastCGI daemon
+---------------------------
+
+If you have the process running in the foreground, it's easy enough to stop it:
+Simply hitting ``Ctrl-C`` will stop and quit the FastCGI server. However, when
+you're dealing with background processes, you'll need to resort to the Unix
+``kill`` command.
+
+If you specify the ``pidfile`` option to your ``manage.py runfcgi``, you can
+kill the running FastCGI daemon like this::
+
+ kill `cat $PIDFILE`
+
+...where ``$PIDFILE`` is the ``pidfile`` you specified.
+
+To easily restart your FastCGI daemon on Unix, try this small shell script::
+
+ #!/bin/bash
+
+ # Replace these three settings.
+ PROJDIR="/home/user/myproject"
+ PIDFILE="$PROJDIR/mysite.pid"
+ SOCKET="$PROJDIR/mysite.sock"
+
+ cd $PROJDIR
+ if [ -f $PIDFILE ]; then
+ kill `cat -- $PIDFILE`
+ rm -f -- $PIDFILE
+ fi
+
+ exec /usr/bin/env - \
+ PYTHONPATH="../python:.." \
+ ./manage.py runfcgi socket=$SOCKET pidfile=$PIDFILE
+
+Apache setup
+============
+
+To use Django with Apache and FastCGI, you'll need Apache installed and
+configured, with `mod_fastcgi`_ installed and enabled. Consult the Apache
+documentation for instructions.
+
+Once you've got that set up, point Apache at your Django FastCGI instance by
+editing the ``httpd.conf`` (Apache configuration) file. You'll need to do two
+things:
+
+ * Use the ``FastCGIExternalServer`` directive to specify the location of
+ your FastCGI server.
+ * Use ``mod_rewrite`` to point URLs at FastCGI as appropriate.
+
+.. _mod_fastcgi: http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html
+
+Specifying the location of the FastCGI server
+---------------------------------------------
+
+The ``FastCGIExternalServer`` directive tells Apache how to find your FastCGI
+server. As the `FastCGIExternalServer docs`_ explain, you can specify either a
+``socket`` or a ``host``. Here are examples of both::
+
+ # Connect to FastCGI via a socket / named pipe.
+ FastCGIExternalServer /home/user/public_html/mysite.fcgi -socket /home/user/mysite.sock
+
+ # Connect to FastCGI via a TCP host/port.
+ FastCGIExternalServer /home/user/public_html/mysite.fcgi -host 127.0.0.1:3033
+
+In either case, the file ``/home/user/public_html/mysite.fcgi`` doesn't
+actually have to exist. It's just a URL used by the Web server internally -- a
+hook for signifying which requests at a URL should be handled by FastCGI. (More
+on this in the next section.)
+
+.. _FastCGIExternalServer docs: http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html#FastCgiExternalServer
+
+Using mod_rewrite to point URLs at FastCGI
+------------------------------------------
+
+The second step is telling Apache to use FastCGI for URLs that match a certain
+pattern. To do this, use the `mod_rewrite`_ module and rewrite URLs to
+``mysite.fcgi`` (or whatever you specified in the ``FastCGIExternalServer``
+directive, as explained in the previous section).
+
+In this example, we tell Apache to use FastCGI to handle any request that
+doesn't represent a file on the filesystem and doesn't start with ``/media/``.
+This is probably the most common case, if you're using Django's admin site::
+
+ <VirtualHost 12.34.56.78>
+ ServerName example.com
+ DocumentRoot /home/user/public_html
+ Alias /media /home/user/python/django/contrib/admin/media
+ RewriteEngine On
+ RewriteRule ^/(media.*)$ /$1 [QSA,L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L]
+ </VirtualHost>
+
+.. _mod_rewrite: http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html
+
+lighttpd setup
+==============
+
+lighttpd is a lightweight Web server commonly used for serving static files. It
+supports FastCGI natively and, thus, is a good choice for serving both static
+and dynamic pages, if your site doesn't have any Apache-specific needs.
+
+Make sure ``mod_fastcgi`` is in your modules list, somewhere after
+``mod_rewrite`` and ``mod_access``, but not after ``mod_accesslog``. You'll
+probably want ``mod_alias`` as well, for serving admin media.
+
+Add the following to your lighttpd config file::
+
+ server.document-root = "/home/user/public_html"
+ fastcgi.server = (
+ "/mysite.fcgi" => (
+ "main" => (
+ # Use host / port instead of socket for TCP fastcgi
+ # "host" => "127.0.0.1",
+ # "port" => 3033,
+ "socket" => "/home/user/mysite.sock",
+ "check-local" => "disable",
+ )
+ ),
+ )
+ alias.url = (
+ "/media/" => "/home/user/django/contrib/admin/media/",
+ )
+
+ url.rewrite-once = (
+ "^(/media.*)$" => "$1",
+ "^/favicon\.ico$" => "/media/favicon.ico",
+ "^(/.*)$" => "/mysite.fcgi$1",
+ )
+
+Running multiple Django sites on one lighttpd
+---------------------------------------------
+
+lighttpd lets you use "conditional configuration" to allow configuration to be
+customized per host. To specify multiple FastCGI sites, just add a conditional
+block around your FastCGI config for each site::
+
+ # If the hostname is 'www.example1.com'...
+ $HTTP["host"] == "www.example1.com" {
+ server.document-root = "/foo/site1"
+ fastcgi.server = (
+ ...
+ )
+ ...
+ }
+
+ # If the hostname is 'www.example2.com'...
+ $HTTP["host"] == "www.example2.com" {
+ server.document-root = "/foo/site2"
+ fastcgi.server = (
+ ...
+ )
+ ...
+ }
+
+You can also run multiple Django installations on the same site simply by
+specifying multiple entries in the ``fastcgi.server`` directive. Add one
+FastCGI host for each.
+
+Running Django on a shared-hosting provider with Apache
+=======================================================
+
+Many shared-hosting providers don't allow you to run your own server daemons or
+edit the ``httpd.conf`` file. In these cases, it's still possible to run Django
+using Web server-spawned processes.
+
+.. admonition:: Note
+
+ If you're using Web server-spawned processes, as explained in this section,
+ there's no need for you to start the FastCGI server on your own. Apache
+ will spawn a number of processes, scaling as it needs to.
+
+In your Web root directory, add this to a file named ``.htaccess`` ::
+
+ AddHandler fastcgi-script .fcgi
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^(.*)$ mysite.fcgi/$1 [QSA,L]
+
+Then, create a small script that tells Apache how to spawn your FastCGI
+program. Create a file ``mysite.fcgi`` and place it in your Web directory, and
+be sure to make it executable::
+
+ #!/usr/bin/python
+ import sys, os
+
+ # Add a custom Python path.
+ sys.path.insert(0, "/home/user/python")
+
+ # Switch to the directory of your project. (Optional.)
+ # os.chdir("/home/user/myproject")
+
+ # Set the DJANGO_SETTINGS_MODULE environment variable.
+ os.environ['DJANGO_SETTINGS_MODULE'] = "myproject.settings"
+
+ from django.core.servers.fastcgi import runfastcgi
+ runfastcgi(method="threaded", daemonize="false")
+
+Restarting the spawned server
+-----------------------------
+
+If you change any Python code on your site, you'll need to tell FastCGI the
+code has changed. But there's no need to restart Apache in this case. Rather,
+just reupload ``mysite.fcgi``, or edit the file, so that the timestamp on the
+file will change. When Apache sees the file has been updated, it will restart
+your Django application for you.
+
+If you have access to a command shell on a Unix system, you can accomplish this
+easily by using the ``touch`` command::
+
+ touch mysite.fcgi
+
+Serving admin media files
+=========================
+
+Regardless of the server and configuration you eventually decide to use, you will
+also need to give some thought to how to serve the admin media files. The
+advice given in the modpython_ documentation is also applicable in the setups
+detailed above.
+
+.. _modpython: ../modpython/#serving-the-admin-files
+
diff --git a/google_appengine/lib/django/docs/flatpages.txt b/google_appengine/lib/django/docs/flatpages.txt
new file mode 100644
index 0000000..cb910e8
--- /dev/null
+++ b/google_appengine/lib/django/docs/flatpages.txt
@@ -0,0 +1,115 @@
+=================
+The flatpages app
+=================
+
+Django comes with an optional "flatpages" application. It lets you store simple
+"flat" HTML content in a database and handles the management for you via
+Django's admin interface and a Python API.
+
+A flatpage is a simple object with a URL, title and content. Use it for
+one-off, special-case pages, such as "About" or "Privacy Policy" pages, that
+you want to store in a database but for which you don't want to develop a
+custom Django application.
+
+A flatpage can use a custom template or a default, systemwide flatpage
+template. It can be associated with one, or multiple, sites.
+
+Here are some examples of flatpages on Django-powered sites:
+
+ * http://www.chicagocrime.org/about/
+ * http://www.lawrence.com/about/contact/
+
+Installation
+============
+
+To install the flatpages app, follow these steps:
+
+ 1. Add ``'django.contrib.flatpages'`` to your INSTALLED_APPS_ setting.
+ 2. Add ``'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'``
+ to your MIDDLEWARE_CLASSES_ setting.
+ 3. Run the command ``manage.py syncdb``.
+
+.. _INSTALLED_APPS: ../settings/#installed-apps
+.. _MIDDLEWARE_CLASSES: ../settings/#middleware-classes
+
+How it works
+============
+
+``manage.py syncdb`` creates two tables in your database: ``django_flatpage``
+and ``django_flatpage_sites``. ``django_flatpage`` is a simple lookup table
+that simply maps a URL to a title and bunch of text content.
+``django_flatpage_sites`` associates a flatpage with a site.
+
+The ``FlatpageFallbackMiddleware`` does all of the work. Each time any Django
+application raises a 404 error, this middleware checks the flatpages database
+for the requested URL as a last resort. Specifically, it checks for a flatpage
+with the given URL with a site ID that corresponds to the SITE_ID_ setting.
+
+If it finds a match, it follows this algorithm:
+
+ * If the flatpage has a custom template, it loads that template. Otherwise,
+ it loads the template ``flatpages/default``.
+ * It passes that template a single context variable, ``flatpage``, which is
+ the flatpage object. It uses RequestContext_ in rendering the template.
+
+If it doesn't find a match, the request continues to be processed as usual.
+
+The middleware only gets activated for 404s -- not for 500s or responses of any
+other status code.
+
+Note that the order of ``MIDDLEWARE_CLASSES`` matters. Generally, you can put
+``FlatpageFallbackMiddleware`` at the end of the list, because it's a last
+resort.
+
+For more on middleware, read the `middleware docs`_.
+
+.. _SITE_ID: ../settings/#site-id
+.. _RequestContext: ../templates_python/#subclassing-context-djangocontext
+.. _middleware docs: ../middleware/
+
+How to add, change and delete flatpages
+=======================================
+
+Via the admin interface
+-----------------------
+
+If you've activated the automatic Django admin interface, you should see a
+"Flatpages" section on the admin index page. Edit flatpages as you edit any
+other object in the system.
+
+Via the Python API
+------------------
+
+Flatpages are represented by a standard `Django model`_, which lives in
+`django/contrib/flatpages/models.py`_. You can access flatpage objects via the
+`Django database API`_.
+
+.. _Django model: ../model_api/
+.. _django/contrib/flatpages/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/flatpages/models.py
+.. _Django database API: ../db_api/
+
+Flatpage templates
+==================
+
+By default, flatpages are rendered via the template ``flatpages/default.html``,
+but you can override that for a particular flatpage.
+
+Creating the ``flatpages/default.html`` template is your responsibility; in
+your template directory, just create a ``flatpages`` directory containing a
+file ``default.html``.
+
+Flatpage templates are passed a single context variable, ``flatpage``, which is
+the flatpage object.
+
+Here's a sample ``flatpages/default.html`` template::
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+ <html>
+ <head>
+ <title>{{ flatpage.title }}</title>
+ </head>
+ <body>
+ {{ flatpage.content }}
+ </body>
+ </html>
diff --git a/google_appengine/lib/django/docs/forms.txt b/google_appengine/lib/django/docs/forms.txt
new file mode 100644
index 0000000..f76f6d2
--- /dev/null
+++ b/google_appengine/lib/django/docs/forms.txt
@@ -0,0 +1,695 @@
+===============================
+Forms, fields, and manipulators
+===============================
+
+Forwards-compatibility note
+===========================
+
+The legacy forms/manipulators system described in this document is going to be
+replaced in the next Django release. If you're starting from scratch, we
+strongly encourage you not to waste your time learning this. Instead, learn and
+use the django.newforms system, which we have begun to document in the
+`newforms documentation`_.
+
+If you have legacy form/manipulator code, read the "Migration plan" section in
+that document to understand how we're making the switch.
+
+.. _newforms documentation: ../newforms/
+
+Introduction
+============
+
+Once you've got a chance to play with Django's admin interface, you'll probably
+wonder if the fantastic form validation framework it uses is available to user
+code. It is, and this document explains how the framework works.
+
+We'll take a top-down approach to examining Django's form validation framework,
+because much of the time you won't need to use the lower-level APIs. Throughout
+this document, we'll be working with the following model, a "place" object::
+
+ from django.db import models
+
+ PLACE_TYPES = (
+ (1, 'Bar'),
+ (2, 'Restaurant'),
+ (3, 'Movie Theater'),
+ (4, 'Secret Hideout'),
+ )
+
+ class Place(models.Model):
+ name = models.CharField(maxlength=100)
+ address = models.CharField(maxlength=100, blank=True)
+ city = models.CharField(maxlength=50, blank=True)
+ state = models.USStateField()
+ zip_code = models.CharField(maxlength=5, blank=True)
+ place_type = models.IntegerField(choices=PLACE_TYPES)
+
+ class Admin:
+ pass
+
+ def __str__(self):
+ return self.name
+
+Defining the above class is enough to create an admin interface to a ``Place``,
+but what if you want to allow public users to submit places?
+
+Automatic Manipulators
+======================
+
+The highest-level interface for object creation and modification is the
+**automatic Manipulator** framework. An automatic manipulator is a utility
+class tied to a given model that "knows" how to create or modify instances of
+that model and how to validate data for the object. Automatic Manipulators come
+in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally
+they are quite similar, but the former knows how to create new instances of the
+model, while the latter modifies existing instances. Both types of classes are
+automatically created when you define a new class::
+
+ >>> from mysite.myapp.models import Place
+ >>> Place.AddManipulator
+ <class 'django.models.manipulators.AddManipulator'>
+ >>> Place.ChangeManipulator
+ <class 'django.models.manipulators.ChangeManipulator'>
+
+Using the ``AddManipulator``
+----------------------------
+
+We'll start with the ``AddManipulator``. Here's a very simple view that takes
+POSTed data from the browser and creates a new ``Place`` object::
+
+ from django.shortcuts import render_to_response
+ from django.http import Http404, HttpResponse, HttpResponseRedirect
+ from django import forms
+ from mysite.myapp.models import Place
+
+ def naive_create_place(request):
+ """A naive approach to creating places; don't actually use this!"""
+ # Create the AddManipulator.
+ manipulator = Place.AddManipulator()
+
+ # Make a copy of the POSTed data so that do_html2python can
+ # modify it in place (request.POST is immutable).
+ new_data = request.POST.copy()
+
+ # Convert the request data (which will all be strings) into the
+ # appropriate Python types for those fields.
+ manipulator.do_html2python(new_data)
+
+ # Save the new object.
+ new_place = manipulator.save(new_data)
+
+ # It worked!
+ return HttpResponse("Place created: %s" % new_place)
+
+The ``naive_create_place`` example works, but as you probably can tell, this
+view has a number of problems:
+
+ * No validation of any sort is performed. If, for example, the ``name`` field
+ isn't given in ``request.POST``, the save step will cause a database error
+ because that field is required. Ugly.
+
+ * Even if you *do* perform validation, there's still no way to give that
+ information to the user in any sort of useful way.
+
+ * You'll have to separately create a form (and view) that submits to this
+ page, which is a pain and is redundant.
+
+Let's dodge these problems momentarily to take a look at how you could create a
+view with a form that submits to this flawed creation view::
+
+ def naive_create_place_form(request):
+ """Simplistic place form view; don't actually use anything like this!"""
+ # Create a FormWrapper object that the template can use. Ignore
+ # the last two arguments to FormWrapper for now.
+ form = forms.FormWrapper(Place.AddManipulator(), {}, {})
+ return render_to_response('places/naive_create_form.html', {'form': form})
+
+(This view, as well as all the following ones, has the same imports as in the
+first example above.)
+
+The ``forms.FormWrapper`` object is a wrapper that templates can
+easily deal with to create forms. Here's the ``naive_create_form.html``
+template::
+
+ {% extends "base.html" %}
+
+ {% block content %}
+ <h1>Create a place:</h1>
+
+ <form method="post" action="../do_new/">
+ <p><label for="id_name">Name:</label> {{ form.name }}</p>
+ <p><label for="id_address">Address:</label> {{ form.address }}</p>
+ <p><label for="id_city">City:</label> {{ form.city }}</p>
+ <p><label for="id_state">State:</label> {{ form.state }}</p>
+ <p><label for="id_zip_code">Zip:</label> {{ form.zip_code }}</p>
+ <p><label for="id_place_type">Place type:</label> {{ form.place_type }}</p>
+ <input type="submit" />
+ </form>
+ {% endblock %}
+
+Before we get back to the problems with these naive set of views, let's go over
+some salient points of the above template:
+
+ * Field "widgets" are handled for you: ``{{ form.field }}`` automatically
+ creates the "right" type of widget for the form, as you can see with the
+ ``place_type`` field above.
+
+ * There isn't a way just to spit out the form. You'll still need to define
+ how the form gets laid out. This is a feature: Every form should be
+ designed differently. Django doesn't force you into any type of mold.
+ If you must use tables, use tables. If you're a semantic purist, you can
+ probably find better HTML than in the above template.
+
+ * To avoid name conflicts, the ``id`` values of form elements take the
+ form "id_*fieldname*".
+
+By creating a creation form we've solved problem number 3 above, but we still
+don't have any validation. Let's revise the validation issue by writing a new
+creation view that takes validation into account::
+
+ def create_place_with_validation(request):
+ manipulator = Place.AddManipulator()
+ new_data = request.POST.copy()
+
+ # Check for validation errors
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+ if errors:
+ return render_to_response('places/errors.html', {'errors': errors})
+ else:
+ new_place = manipulator.save(new_data)
+ return HttpResponse("Place created: %s" % new_place)
+
+In this new version, errors will be found -- ``manipulator.get_validation_errors``
+handles all the validation for you -- and those errors can be nicely presented
+on an error page (templated, of course)::
+
+ {% extends "base.html" %}
+
+ {% block content %}
+
+ <h1>Please go back and correct the following error{{ errors|pluralize }}:</h1>
+ <ul>
+ {% for e in errors.items %}
+ <li>Field "{{ e.0 }}": {{ e.1|join:", " }}</li>
+ {% endfor %}
+ </ul>
+
+ {% endblock %}
+
+Still, this has its own problems:
+
+ * There's still the issue of creating a separate (redundant) view for the
+ submission form.
+
+ * Errors, though nicely presented, are on a separate page, so the user will
+ have to use the "back" button to fix errors. That's ridiculous and unusable.
+
+The best way to deal with these issues is to collapse the two views -- the form
+and the submission -- into a single view. This view will be responsible for
+creating the form, validating POSTed data, and creating the new object (if the
+data is valid). An added bonus of this approach is that errors and the form will
+both be available on the same page, so errors with fields can be presented in
+context.
+
+.. admonition:: Philosophy:
+
+ Finally, for the HTTP purists in the audience (and the authorship), this
+ nicely matches the "true" meanings of HTTP GET and HTTP POST: GET fetches
+ the form, and POST creates the new object.
+
+Below is the finished view::
+
+ def create_place(request):
+ manipulator = Place.AddManipulator()
+
+ if request.method == 'POST':
+ # If data was POSTed, we're trying to create a new Place.
+ new_data = request.POST.copy()
+
+ # Check for errors.
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+
+ if not errors:
+ # No errors. This means we can save the data!
+ new_place = manipulator.save(new_data)
+
+ # Redirect to the object's "edit" page. Always use a redirect
+ # after POST data, so that reloads don't accidently create
+ # duplicate entires, and so users don't see the confusing
+ # "Repost POST data?" alert box in their browsers.
+ return HttpResponseRedirect("/places/edit/%i/" % new_place.id)
+ else:
+ # No POST, so we want a brand new form without any data or errors.
+ errors = new_data = {}
+
+ # Create the FormWrapper, template, context, response.
+ form = forms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('places/create_form.html', {'form': form})
+
+and here's the ``create_form`` template::
+
+ {% extends "base.html" %}
+
+ {% block content %}
+ <h1>Create a place:</h1>
+
+ {% if form.has_errors %}
+ <h2>Please correct the following error{{ form.error_dict|pluralize }}:</h2>
+ {% endif %}
+
+ <form method="post" action=".">
+ <p>
+ <label for="id_name">Name:</label> {{ form.name }}
+ {% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %}
+ </p>
+ <p>
+ <label for="id_address">Address:</label> {{ form.address }}
+ {% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %}
+ </p>
+ <p>
+ <label for="id_city">City:</label> {{ form.city }}
+ {% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %}
+ </p>
+ <p>
+ <label for="id_state">State:</label> {{ form.state }}
+ {% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %}
+ </p>
+ <p>
+ <label for="id_zip_code">Zip:</label> {{ form.zip_code }}
+ {% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %}
+ </p>
+ <p>
+ <label for="id_place_type">Place type:</label> {{ form.place_type }}
+ {% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %}
+ </p>
+ <input type="submit" />
+ </form>
+ {% endblock %}
+
+The second two arguments to ``FormWrapper`` (``new_data`` and ``errors``)
+deserve some mention.
+
+The first is any "default" data to be used as values for the fields. Pulling
+the data from ``request.POST``, as is done above, makes sure that if there are
+errors, the values the user put in aren't lost. If you try the above example,
+you'll see this in action.
+
+The second argument is the error list retrieved from
+``manipulator.get_validation_errors``. When passed into the ``FormWrapper``,
+this gives each field an ``errors`` item (which is a list of error messages
+associated with the field) as well as a ``html_error_list`` item, which is a
+``<ul>`` of error messages. The above template uses these error items to
+display a simple error message next to each field. The error list is saved as
+an ``error_dict`` attribute of the ``FormWrapper`` object.
+
+Using the ``ChangeManipulator``
+-------------------------------
+
+The above has covered using the ``AddManipulator`` to create a new object. What
+about editing an existing one? It's shockingly similar to creating a new one::
+
+ def edit_place(request, place_id):
+ # Get the place in question from the database and create a
+ # ChangeManipulator at the same time.
+ try:
+ manipulator = Place.ChangeManipulator(place_id)
+ except Place.DoesNotExist:
+ raise Http404
+
+ # Grab the Place object in question for future use.
+ place = manipulator.original_object
+
+ if request.method == 'POST':
+ new_data = request.POST.copy()
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+ if not errors:
+ manipulator.save(new_data)
+
+ # Do a post-after-redirect so that reload works, etc.
+ return HttpResponseRedirect("/places/edit/%i/" % place.id)
+ else:
+ errors = {}
+ # This makes sure the form accurate represents the fields of the place.
+ new_data = manipulator.flatten_data()
+
+ form = forms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('places/edit_form.html', {'form': form, 'place': place})
+
+The only real differences are:
+
+ * We create a ``ChangeManipulator`` instead of an ``AddManipulator``.
+ The argument to a ``ChangeManipulator`` is the ID of the object
+ to be changed. As you can see, the initializer will raise an
+ ``ObjectDoesNotExist`` exception if the ID is invalid.
+
+ * ``ChangeManipulator.original_object`` stores the instance of the
+ object being edited.
+
+ * We set ``new_data`` based upon ``flatten_data()`` from the manipulator.
+ ``flatten_data()`` takes the data from the original object under
+ manipulation, and converts it into a data dictionary that can be used
+ to populate form elements with the existing values for the object.
+
+ * The above example uses a different template, so create and edit can be
+ "skinned" differently if needed, but the form chunk itself is completely
+ identical to the one in the create form above.
+
+The astute programmer will notice the add and create functions are nearly
+identical and could in fact be collapsed into a single view. This is left as an
+exercise for said programmer.
+
+(However, the even-more-astute programmer will take heed of the note at the top
+of this document and check out the `generic views`_ documentation if all she
+wishes to do is this type of simple create/update.)
+
+Custom forms and manipulators
+=============================
+
+All the above is fine and dandy if you just want to use the automatically
+created manipulators. But the coolness doesn't end there: You can easily create
+your own custom manipulators for handling custom forms.
+
+Custom manipulators are pretty simple. Here's a manipulator that you might use
+for a "contact" form on a website::
+
+ from django import forms
+
+ urgency_choices = (
+ (1, "Extremely urgent"),
+ (2, "Urgent"),
+ (3, "Normal"),
+ (4, "Unimportant"),
+ )
+
+ class ContactManipulator(forms.Manipulator):
+ def __init__(self):
+ self.fields = (
+ forms.EmailField(field_name="from", is_required=True),
+ forms.TextField(field_name="subject", length=30, maxlength=200, is_required=True),
+ forms.SelectField(field_name="urgency", choices=urgency_choices),
+ forms.LargeTextField(field_name="contents", is_required=True),
+ )
+
+A certain similarity to Django's models should be apparent. The only required
+method of a custom manipulator is ``__init__`` which must define the fields
+present in the manipulator. See the ``django.forms`` module for
+all the form fields provided by Django.
+
+You use this custom manipulator exactly as you would use an auto-generated one.
+Here's a simple function that might drive the above form::
+
+ def contact_form(request):
+ manipulator = ContactManipulator()
+ if request.method == 'POST':
+ new_data = request.POST.copy()
+ errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+ if not errors:
+
+ # Send e-mail using new_data here...
+
+ return HttpResponseRedirect("/contact/thankyou/")
+ else:
+ errors = new_data = {}
+ form = forms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('contact_form.html', {'form': form})
+
+Implementing ``flatten_data`` for custom manipulators
+------------------------------------------------------
+
+It is possible (although rarely needed) to replace the default automatically
+created manipulators on a model with your own custom manipulators. If you do
+this and you are intending to use those models in generic views, you should
+also define a ``flatten_data`` method in any ``ChangeManipulator`` replacement.
+This should act like the default ``flatten_data`` and return a dictionary
+mapping field names to their values, like so::
+
+ def flatten_data(self):
+ obj = self.original_object
+ return dict(
+ from = obj.from,
+ subject = obj.subject,
+ ...
+ )
+
+In this way, your new change manipulator will act exactly like the default
+version.
+
+``FileField`` and ``ImageField`` special cases
+==============================================
+
+Dealing with ``FileField`` and ``ImageField`` objects is a little more
+complicated.
+
+First, you'll need to make sure that your ``<form>`` element correctly defines
+the ``enctype`` as ``"multipart/form-data"``, in order to upload files::
+
+ <form enctype="multipart/form-data" method="post" action="/foo/">
+
+Next, you'll need to treat the field in the template slightly differently. A
+``FileField`` or ``ImageField`` is represented by *two* HTML form elements.
+
+For example, given this field in a model::
+
+ photo = model.ImageField('/path/to/upload/location')
+
+You'd need to display two formfields in the template::
+
+ <p><label for="id_photo">Photo:</label> {{ form.photo }}{{ form.photo_file }}</p>
+
+The first bit (``{{ form.photo }}``) displays the currently-selected file,
+while the second (``{{ form.photo_file }}``) actually contains the file upload
+form field. Thus, at the validation layer you need to check the ``photo_file``
+key.
+
+Finally, in your view, make sure to access ``request.FILES``, rather than
+``request.POST``, for the uploaded files. This is necessary because
+``request.POST`` does not contain file-upload data.
+
+For example, following the ``new_data`` convention, you might do something like
+this::
+
+ new_data = request.POST.copy()
+ new_data.update(request.FILES)
+
+Validators
+==========
+
+One useful feature of manipulators is the automatic validation. Validation is
+done using a simple validation API: A validator is a callable that raises a
+``ValidationError`` if there's something wrong with the data.
+``django.core.validators`` defines a host of validator functions (see below),
+but defining your own couldn't be easier::
+
+ from django.core import validators
+ from django import forms
+
+ class ContactManipulator(forms.Manipulator):
+ def __init__(self):
+ self.fields = (
+ # ... snip fields as above ...
+ forms.EmailField(field_name="to", validator_list=[self.isValidToAddress])
+ )
+
+ def isValidToAddress(self, field_data, all_data):
+ if not field_data.endswith("@example.com"):
+ raise validators.ValidationError("You can only send messages to example.com e-mail addresses.")
+
+Above, we've added a "to" field to the contact form, but required that the "to"
+address end with "@example.com" by adding the ``isValidToAddress`` validator to
+the field's ``validator_list``.
+
+The arguments to a validator function take a little explanation. ``field_data``
+is the value of the field in question, and ``all_data`` is a dictionary of all
+the data being validated.
+
+.. admonition:: Note::
+
+ At the point validators are called all data will still be
+ strings (as ``do_html2python`` hasn't been called yet).
+
+Also, because consistency in user interfaces is important, we strongly urge you
+to put punctuation at the end of your validation messages.
+
+When are validators called?
+---------------------------
+
+After a form has been submitted, Django first checks to see that all the
+required fields are present and non-empty. For each field that passes that
+test *and if the form submission contained data* for that field, all the
+validators for that field are called in turn. The emphasized portion in the
+last sentence is important: if a form field is not submitted (because it
+contains no data -- which is normal HTML behavior), the validators are not
+run against the field.
+
+This feature is particularly important for models using
+``models.BooleanField`` or custom manipulators using things like
+``forms.CheckBoxField``. If the checkbox is not selected, it will not
+contribute to the form submission.
+
+If you would like your validator to run *always*, regardless of whether its
+attached field contains any data, set the ``always_test`` attribute on the
+validator function. For example::
+
+ def my_custom_validator(field_data, all_data):
+ # ...
+ my_custom_validator.always_test = True
+
+This validator will always be executed for any field it is attached to.
+
+Ready-made validators
+---------------------
+
+Writing your own validator is not difficult, but there are some situations
+that come up over and over again. Django comes with a number of validators
+that can be used directly in your code. All of these functions and classes
+reside in ``django/core/validators.py``.
+
+The following validators should all be self-explanatory. Each one provides a
+check for the given property:
+
+ * isAlphaNumeric
+ * isAlphaNumericURL
+ * isSlug
+ * isLowerCase
+ * isUpperCase
+ * isCommaSeparatedIntegerList
+ * isCommaSeparatedEmailList
+ * isValidIPAddress4
+ * isNotEmpty
+ * isOnlyDigits
+ * isNotOnlyDigits
+ * isInteger
+ * isOnlyLetters
+ * isValidANSIDate
+ * isValidANSITime
+ * isValidEmail
+ * isValidImage
+ * isValidImageURL
+ * isValidPhone
+ * isValidQuicktimeVideoURL
+ * isValidURL
+ * isValidHTML
+ * isWellFormedXml
+ * isWellFormedXmlFragment
+ * isExistingURL
+ * isValidUSState
+ * hasNoProfanities
+
+There are also a group of validators that are slightly more flexible. For
+these validators, you create a validator instance, passing in the parameters
+described below. The returned object is a callable that can be used as a
+validator.
+
+For example::
+
+ from django.core import validators
+ from django import forms
+
+ power_validator = validators.IsAPowerOf(2)
+
+ class InstallationManipulator(forms.Manipulator)
+ def __init__(self):
+ self.fields = (
+ ...
+ forms.IntegerField(field_name = "size", validator_list=[power_validator])
+ )
+
+Here, ``validators.IsAPowerOf(...)`` returned something that could be used as
+a validator (in this case, a check that a number was a power of 2).
+
+Each of the standard validators that take parameters have an optional final
+argument (``error_message``) that is the message returned when validation
+fails. If no message is passed in, a default message is used.
+
+``AlwaysMatchesOtherField``
+ Takes a field name and the current field is valid if and only if its value
+ matches the contents of the other field.
+
+``ValidateIfOtherFieldEquals``
+ Takes three parameters: ``other_field``, ``other_value`` and
+ ``validator_list``, in that order. If ``other_field`` has a value of
+ ``other_value``, then the validators in ``validator_list`` are all run
+ against the current field.
+
+``RequiredIfOtherFieldNotGiven``
+ Takes the name of the other field and this field is only required if the
+ other field has no value.
+
+``RequiredIfOtherFieldsNotGiven``
+ Similar to ``RequiredIfOtherFieldNotGiven``, except that it takes a list
+ of field names and if any one of the supplied fields does not have a value
+ provided, the field being validated is required.
+
+``RequiredIfOtherFieldEquals`` and ``RequiredIfOtherFieldDoesNotEqual``
+ Each of these validator classes takes a field name and a value (in that
+ order). If the given field does (or does not have, in the latter case) the
+ given value, then the current field being validated is required.
+
+ An optional ``other_label`` argument can be passed which, if given, is used
+ in error messages instead of the value. This allows more user friendly error
+ messages if the value itself is not descriptive enough.
+
+ Note that because validators are called before any ``do_html2python()``
+ functions, the value being compared against is a string. So
+ ``RequiredIfOtherFieldEquals('choice', '1')`` is correct, whilst
+ ``RequiredIfOtherFieldEquals('choice', 1)`` will never result in the
+ equality test succeeding.
+
+``IsLessThanOtherField``
+ Takes a field name and validates that the current field being validated
+ has a value that is less than (or equal to) the other field's value.
+ Again, comparisons are done using strings, so be cautious about using
+ this function to compare data that should be treated as another type. The
+ string "123" is less than the string "2", for example. If you don't want
+ string comparison here, you will need to write your own validator.
+
+``NumberIsInRange``
+ Takes two boundary numbers, ``lower`` and ``upper``, and checks that the
+ field is greater than ``lower`` (if given) and less than ``upper`` (if
+ given).
+
+ Both checks are inclusive. That is, ``NumberIsInRange(10, 20)`` will allow
+ values of both 10 and 20. This validator only checks numeric values
+ (e.g., float and integer values).
+
+``IsAPowerOf``
+ Takes an integer argument and when called as a validator, checks that the
+ field being validated is a power of the integer.
+
+``IsValidFloat``
+ Takes a maximum number of digits and number of decimal places (in that
+ order) and validates whether the field is a float with less than the
+ maximum number of digits and decimal place.
+
+``MatchesRegularExpression``
+ Takes a regular expression (a string) as a parameter and validates the
+ field value against it.
+
+``AnyValidator``
+ Takes a list of validators as a parameter. At validation time, if the
+ field successfully validates against any one of the validators, it passes
+ validation. The validators are tested in the order specified in the
+ original list.
+
+``URLMimeTypeCheck``
+ Used to validate URL fields. Takes a list of MIME types (such as
+ ``text/plain``) at creation time. At validation time, it verifies that the
+ field is indeed a URL and then tries to retrieve the content at the URL.
+ Validation succeeds if the content could be retrieved and it has a content
+ type from the list used to create the validator.
+
+``RelaxNGCompact``
+ Used to validate an XML document against a Relax NG compact schema. Takes
+ a file path to the location of the schema and an optional root element
+ (which is wrapped around the XML fragment before validation, if supplied).
+ At validation time, the XML fragment is validated against the schema using
+ the executable specified in the ``JING_PATH`` setting (see the settings_
+ document for more details).
+
+.. _`generic views`: ../generic_views/
+.. _`models API`: ../model_api/
+.. _settings: ../settings/
diff --git a/google_appengine/lib/django/docs/generic_views.txt b/google_appengine/lib/django/docs/generic_views.txt
new file mode 100644
index 0000000..7659a42
--- /dev/null
+++ b/google_appengine/lib/django/docs/generic_views.txt
@@ -0,0 +1,1076 @@
+=============
+Generic views
+=============
+
+Writing Web applications can be monotonous, because we repeat certain patterns
+again and again. In Django, the most common of these patterns have been
+abstracted into "generic views" that let you quickly provide common views of
+an object without actually needing to write any Python code.
+
+Django's generic views contain the following:
+
+ * A set of views for doing list/detail interfaces (for example,
+ Django's `documentation index`_ and `detail pages`_).
+
+ * A set of views for year/month/day archive pages and associated
+ detail and "latest" pages (for example, the Django weblog's year_,
+ month_, day_, detail_, and latest_ pages).
+
+ * A set of views for creating, editing, and deleting objects.
+
+.. _`documentation index`: http://www.djangoproject.com/documentation/
+.. _`detail pages`: http://www.djangoproject.com/documentation/faq/
+.. _year: http://www.djangoproject.com/weblog/2005/
+.. _month: http://www.djangoproject.com/weblog/2005/jul/
+.. _day: http://www.djangoproject.com/weblog/2005/jul/20/
+.. _detail: http://www.djangoproject.com/weblog/2005/jul/20/autoreload/
+.. _latest: http://www.djangoproject.com/weblog/
+
+All of these views are used by creating configuration dictionaries in
+your URLconf files and passing those dictionaries as the third member of the
+URLconf tuple for a given pattern. For example, here's the URLconf for the
+simple weblog app that drives the blog on djangoproject.com::
+
+ from django.conf.urls.defaults import *
+ from django_website.apps.blog.models import Entry
+
+ info_dict = {
+ 'queryset': Entry.objects.all(),
+ 'date_field': 'pub_date',
+ }
+
+ urlpatterns = patterns('django.views.generic.date_based',
+ (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'object_detail', dict(info_dict, slug_field='slug')),
+ (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', info_dict),
+ (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', info_dict),
+ (r'^(?P<year>\d{4})/$', 'archive_year', info_dict),
+ (r'^/?$', 'archive_index', info_dict),
+ )
+
+As you can see, this URLconf defines a few options in ``info_dict``.
+``'queryset'`` gives the generic view a ``QuerySet`` of objects to use (in this
+case, all of the ``Entry`` objects) and tells the generic view which model is
+being used.
+
+Documentation of each generic view follows, along with a list of all keyword
+arguments that a generic view expects. Remember that as in the example above,
+arguments may either come from the URL pattern (as ``month``, ``day``,
+``year``, etc. do above) or from the additional-information dictionary (as for
+``queryset``, ``date_field``, etc.).
+
+Most generic views require the ``queryset`` key, which is a ``QuerySet``
+instance; see the `database API docs`_ for more information about ``Queryset``
+objects.
+
+Most views also take an optional ``extra_context`` dictionary that you can use
+to pass any auxiliary information you wish to the view. The values in the
+``extra_context`` dictionary can be either functions (or other callables) or
+other objects. Functions are evaluated just before they are passed to the
+template. However, note that QuerySets retrieve and cache their data when they
+are first evaluated, so if you want to pass in a QuerySet via
+``extra_context`` that is always fresh you need to wrap it in a function or
+lambda that returns the QuerySet.
+
+.. _database API docs: ../db_api/
+
+"Simple" generic views
+======================
+
+The ``django.views.generic.simple`` module contains simple views to handle a
+couple of common cases: rendering a template when no view logic is needed,
+and issuing a redirect.
+
+``django.views.generic.simple.direct_to_template``
+--------------------------------------------------
+
+**Description:**
+
+Renders a given template, passing it a ``{{ params }}`` template variable,
+which is a dictionary of the parameters captured in the URL.
+
+**Required arguments:**
+
+ * ``template``: The full name of a template to use.
+
+**Optional arguments:**
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+**Example:**
+
+Given the following URL patterns::
+
+ urlpatterns = patterns('django.views.generic.simple',
+ (r'^foo/$', 'direct_to_template', {'template': 'foo_index.html'}),
+ (r'^foo/(?P<id>\d+)/$', 'direct_to_template', {'template': 'foo_detail.html'}),
+ )
+
+... a request to ``/foo/`` would render the template ``foo_index.html``, and a
+request to ``/foo/15/`` would render the ``foo_detail.html`` with a context
+variable ``{{ params.id }}`` that is set to ``15``.
+
+``django.views.generic.simple.redirect_to``
+-------------------------------------------
+
+**Description:**
+
+Redirects to a given URL.
+
+The given URL may contain dictionary-style string formatting, which will be
+interpolated against the parameters captured in the URL.
+
+If the given URL is ``None``, Django will return an ``HttpResponseGone`` (410).
+
+**Required arguments:**
+
+ * ``url``: The URL to redirect to, as a string. Or ``None`` to raise a 410
+ (Gone) HTTP error.
+
+**Example:**
+
+This example redirects from ``/foo/<id>/`` to ``/bar/<id>/``::
+
+ urlpatterns = patterns('django.views.generic.simple',
+ ('^foo/(?P<id>\d+)/$', 'redirect_to', {'url': '/bar/%(id)s/'}),
+ )
+
+This example returns a 410 HTTP error for requests to ``/bar/``::
+
+ urlpatterns = patterns('django.views.generic.simple',
+ ('^bar/$', 'redirect_to', {'url': None}),
+ )
+
+Date-based generic views
+========================
+
+Date-based generic views (in the module ``django.views.generic.date_based``)
+are views for displaying drilldown pages for date-based data.
+
+``django.views.generic.date_based.archive_index``
+-------------------------------------------------
+
+**Description:**
+
+A top-level index page showing the "latest" objects, by date. Objects with
+a date in the *future* are not included unless you set ``allow_future`` to
+``True``.
+
+**Required arguments:**
+
+ * ``queryset``: A ``QuerySet`` of objects for which the archive serves.
+
+ * ``date_field``: The name of the ``DateField`` or ``DateTimeField`` in
+ the ``QuerySet``'s model that the date-based archive should use to
+ determine the objects on the page.
+
+**Optional arguments:**
+
+ * ``num_latest``: The number of latest objects to send to the template
+ context. By default, it's 15.
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``allow_empty``: A boolean specifying whether to display the page if no
+ objects are available. If this is ``False`` and no objects are available,
+ the view will raise a 404 instead of displaying an empty page. By
+ default, this is ``False``.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+ * ``allow_future``: A boolean specifying whether to include "future"
+ objects on this page, where "future" means objects in which the field
+ specified in ``date_field`` is greater than the current date/time. By
+ default, this is ``False``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_archive.html`` by default, where:
+
+ * ``<model_name>`` is your model's name in all lowercase. For a model
+ ``StaffMember``, that'd be ``staffmember``.
+
+ * ``<app_label>`` is the right-most part of the full Python path to
+ your model's app. For example, if your model lives in
+ ``apps/blog/models.py``, that'd be ``blog``.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``date_list``: A list of ``datetime.date`` objects representing all
+ years that have objects available according to ``queryset``. These are
+ ordered in reverse. This is equivalent to
+ ``queryset.dates(date_field, 'year')[::-1]``.
+ * ``latest``: The ``num_latest`` objects in the system, ordered descending
+ by ``date_field``. For example, if ``num_latest`` is ``10``, then
+ ``latest`` will be a list of the latest 10 objects in ``queryset``.
+
+.. _RequestContext docs: ../templates_python/#subclassing-context-djangocontext
+
+``django.views.generic.date_based.archive_year``
+------------------------------------------------
+
+**Description:**
+
+A yearly archive page showing all available months in a given year. Objects
+with a date in the *future* are not displayed unless you set ``allow_future``
+to ``True``.
+
+**Required arguments:**
+
+ * ``year``: The four-digit year for which the archive serves.
+
+ * ``queryset``: A ``QuerySet`` of objects for which the archive serves.
+
+ * ``date_field``: The name of the ``DateField`` or ``DateTimeField`` in
+ the ``QuerySet``'s model that the date-based archive should use to
+ determine the objects on the page.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``allow_empty``: A boolean specifying whether to display the page if no
+ objects are available. If this is ``False`` and no objects are available,
+ the view will raise a 404 instead of displaying an empty page. By
+ default, this is ``False``.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``. The
+ view will append ``'_list'`` to the value of this parameter in
+ determining the variable's name.
+
+ * ``make_object_list``: A boolean specifying whether to retrieve the full
+ list of objects for this year and pass those to the template. If ``True``,
+ this list of objects will be made available to the template as
+ ``object_list``. (The name ``object_list`` may be different; see the docs
+ for ``object_list`` in the "Template context" section below.) By default,
+ this is ``False``.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+ * ``allow_future``: A boolean specifying whether to include "future"
+ objects on this page, where "future" means objects in which the field
+ specified in ``date_field`` is greater than the current date/time. By
+ default, this is ``False``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_archive_year.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``date_list``: A list of ``datetime.date`` objects representing all
+ months that have objects available in the given year, according to
+ ``queryset``, in ascending order.
+
+ * ``year``: The given year, as a four-character string.
+
+ * ``object_list``: If the ``make_object_list`` parameter is ``True``, this
+ will be set to a list of objects available for the given year, ordered by
+ the date field. This variable's name depends on the
+ ``template_object_name`` parameter, which is ``'object'`` by default. If
+ ``template_object_name`` is ``'foo'``, this variable's name will be
+ ``foo_list``.
+
+ If ``make_object_list`` is ``False``, ``object_list`` will be passed to
+ the template as an empty list.
+
+``django.views.generic.date_based.archive_month``
+-------------------------------------------------
+
+**Description:**
+
+A monthly archive page showing all objects in a given month. Objects with a
+date in the *future* are not displayed unless you set ``allow_future`` to
+``True``.
+
+**Required arguments:**
+
+ * ``year``: The four-digit year for which the archive serves (a string).
+
+ * ``month``: The month for which the archive serves, formatted according to
+ the ``month_format`` argument.
+
+ * ``queryset``: A ``QuerySet`` of objects for which the archive serves.
+
+ * ``date_field``: The name of the ``DateField`` or ``DateTimeField`` in
+ the ``QuerySet``'s model that the date-based archive should use to
+ determine the objects on the page.
+
+**Optional arguments:**
+
+ * ``month_format``: A format string that regulates what format the
+ ``month`` parameter uses. This should be in the syntax accepted by
+ Python's ``time.strftime``. (See the `strftime docs`_.) It's set to
+ ``"%b"`` by default, which is a three-letter month abbreviation. To
+ change it to use numbers, use ``"%m"``.
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``allow_empty``: A boolean specifying whether to display the page if no
+ objects are available. If this is ``False`` and no objects are available,
+ the view will raise a 404 instead of displaying an empty page. By
+ default, this is ``False``.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``. The
+ view will append ``'_list'`` to the value of this parameter in
+ determining the variable's name.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+ * ``allow_future``: A boolean specifying whether to include "future"
+ objects on this page, where "future" means objects in which the field
+ specified in ``date_field`` is greater than the current date/time. By
+ default, this is ``False``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_archive_month.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``month``: A ``datetime.date`` object representing the given month.
+
+ * ``next_month``: A ``datetime.date`` object representing the first day of
+ the next month. If the next month is in the future, this will be
+ ``None``.
+
+ * ``previous_month``: A ``datetime.date`` object representing the first day
+ of the previous month. Unlike ``next_month``, this will never be
+ ``None``.
+
+ * ``object_list``: A list of objects available for the given month. This
+ variable's name depends on the ``template_object_name`` parameter, which
+ is ``'object'`` by default. If ``template_object_name`` is ``'foo'``,
+ this variable's name will be ``foo_list``.
+
+.. _strftime docs: http://www.python.org/doc/current/lib/module-time.html#l2h-1941
+
+``django.views.generic.date_based.archive_week``
+------------------------------------------------
+
+**Description:**
+
+A weekly archive page showing all objects in a given week. Objects with a date
+in the *future* are not displayed unless you set ``allow_future`` to ``True``.
+
+**Required arguments:**
+
+ * ``year``: The four-digit year for which the archive serves (a string).
+
+ * ``week``: The week of the year for which the archive serves (a string).
+ Weeks start with Sunday.
+
+ * ``queryset``: A ``QuerySet`` of objects for which the archive serves.
+
+ * ``date_field``: The name of the ``DateField`` or ``DateTimeField`` in
+ the ``QuerySet``'s model that the date-based archive should use to
+ determine the objects on the page.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``allow_empty``: A boolean specifying whether to display the page if no
+ objects are available. If this is ``False`` and no objects are available,
+ the view will raise a 404 instead of displaying an empty page. By
+ default, this is ``True``.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``. The
+ view will append ``'_list'`` to the value of this parameter in
+ determining the variable's name.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+ * ``allow_future``: A boolean specifying whether to include "future"
+ objects on this page, where "future" means objects in which the field
+ specified in ``date_field`` is greater than the current date/time. By
+ default, this is ``False``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_archive_week.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``week``: A ``datetime.date`` object representing the first day of the
+ given week.
+
+ * ``object_list``: A list of objects available for the given week. This
+ variable's name depends on the ``template_object_name`` parameter, which
+ is ``'object'`` by default. If ``template_object_name`` is ``'foo'``,
+ this variable's name will be ``foo_list``.
+
+``django.views.generic.date_based.archive_day``
+-----------------------------------------------
+
+**Description:**
+
+A day archive page showing all objects in a given day. Days in the future throw
+a 404 error, regardless of whether any objects exist for future days, unless
+you set ``allow_future`` to ``True``.
+
+**Required arguments:**
+
+ * ``year``: The four-digit year for which the archive serves (a string).
+
+ * ``month``: The month for which the archive serves, formatted according to
+ the ``month_format`` argument.
+
+ * ``day``: The day for which the archive serves, formatted according to the
+ ``day_format`` argument.
+
+ * ``queryset``: A ``QuerySet`` of objects for which the archive serves.
+
+ * ``date_field``: The name of the ``DateField`` or ``DateTimeField`` in
+ the ``QuerySet``'s model that the date-based archive should use to
+ determine the objects on the page.
+
+**Optional arguments:**
+
+ * ``month_format``: A format string that regulates what format the
+ ``month`` parameter uses. This should be in the syntax accepted by
+ Python's ``time.strftime``. (See the `strftime docs`_.) It's set to
+ ``"%b"`` by default, which is a three-letter month abbreviation. To
+ change it to use numbers, use ``"%m"``.
+
+ * ``day_format``: Like ``month_format``, but for the ``day`` parameter.
+ It defaults to ``"%d"`` (day of the month as a decimal number, 01-31).
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``allow_empty``: A boolean specifying whether to display the page if no
+ objects are available. If this is ``False`` and no objects are available,
+ the view will raise a 404 instead of displaying an empty page. By
+ default, this is ``False``.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``. The
+ view will append ``'_list'`` to the value of this parameter in
+ determining the variable's name.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+ * ``allow_future``: A boolean specifying whether to include "future"
+ objects on this page, where "future" means objects in which the field
+ specified in ``date_field`` is greater than the current date/time. By
+ default, this is ``False``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_archive_day.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``day``: A ``datetime.date`` object representing the given day.
+
+ * ``next_day``: A ``datetime.date`` object representing the next day. If
+ the next day is in the future, this will be ``None``.
+
+ * ``previous_day``: A ``datetime.date`` object representing the given day.
+ Unlike ``next_day``, this will never be ``None``.
+
+ * ``object_list``: A list of objects available for the given day. This
+ variable's name depends on the ``template_object_name`` parameter, which
+ is ``'object'`` by default. If ``template_object_name`` is ``'foo'``,
+ this variable's name will be ``foo_list``.
+
+``django.views.generic.date_based.archive_today``
+-------------------------------------------------
+
+**Description:**
+
+A day archive page showing all objects for *today*. This is exactly the same as
+``archive_day``, except the ``year``/``month``/``day`` arguments are not used,
+and today's date is used instead.
+
+``django.views.generic.date_based.object_detail``
+-------------------------------------------------
+
+**Description:**
+
+A page representing an individual object. If the object has a date value in the
+future, the view will throw a 404 error by default, unless you set
+``allow_future`` to ``True``.
+
+**Required arguments:**
+
+ * ``year``: The object's four-digit year (a string).
+
+ * ``month``: The object's month , formatted according to the
+ ``month_format`` argument.
+
+ * ``day``: The object's day , formatted according to the ``day_format``
+ argument.
+
+ * ``queryset``: A ``QuerySet`` that contains the object.
+
+ * ``date_field``: The name of the ``DateField`` or ``DateTimeField`` in
+ the ``QuerySet``'s model that the generic view should use to look up the
+ object according to ``year``, ``month`` and ``day``.
+
+ * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
+
+ If you provide ``object_id``, it should be the value of the primary-key
+ field for the object being displayed on this page.
+
+ Otherwise, ``slug`` should be the slug of the given object, and
+ ``slug_field`` should be the name of the slug field in the ``QuerySet``'s
+ model.
+
+**Optional arguments:**
+
+ * ``month_format``: A format string that regulates what format the
+ ``month`` parameter uses. This should be in the syntax accepted by
+ Python's ``time.strftime``. (See the `strftime docs`_.) It's set to
+ ``"%b"`` by default, which is a three-letter month abbreviation. To
+ change it to use numbers, use ``"%m"``.
+
+ * ``day_format``: Like ``month_format``, but for the ``day`` parameter.
+ It defaults to ``"%d"`` (day of the month as a decimal number, 01-31).
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_name_field``: The name of a field on the object whose value is
+ the template name to use. This lets you store template names in the data.
+ In other words, if your object has a field ``'the_template'`` that
+ contains a string ``'foo.html'``, and you set ``template_name_field`` to
+ ``'the_template'``, then the generic view for this object will use the
+ template ``'foo.html'``.
+
+ It's a bit of a brain-bender, but it's useful in some cases.
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+ * ``allow_future``: A boolean specifying whether to include "future"
+ objects on this page, where "future" means objects in which the field
+ specified in ``date_field`` is greater than the current date/time. By
+ default, this is ``False``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_detail.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``object``: The object. This variable's name depends on the
+ ``template_object_name`` parameter, which is ``'object'`` by default. If
+ ``template_object_name`` is ``'foo'``, this variable's name will be
+ ``foo``.
+
+List/detail generic views
+=========================
+
+The list-detail generic-view framework (in the
+``django.views.generic.list_detail`` module) is similar to the date-based one,
+except the former simply has two views: a list of objects and an individual
+object page.
+
+``django.views.generic.list_detail.object_list``
+------------------------------------------------
+
+**Description:**
+
+A page representing a list of objects.
+
+**Required arguments:**
+
+ * ``queryset``: A ``QuerySet`` that represents the objects.
+
+**Optional arguments:**
+
+ * ``paginate_by``: An integer specifying how many objects should be
+ displayed per page. If this is given, the view will paginate objects with
+ ``paginate_by`` objects per page. The view will expect either a ``page``
+ query string parameter (via ``GET``) containing a 1-based page
+ number, or a ``page`` variable specified in the URLconf. See
+ "Notes on pagination" below.
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``allow_empty``: A boolean specifying whether to display the page if no
+ objects are available. If this is ``False`` and no objects are available,
+ the view will raise a 404 instead of displaying an empty page. By
+ default, this is ``False``.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``. The
+ view will append ``'_list'`` to the value of this parameter in
+ determining the variable's name.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_list.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``object_list``: The list of objects. This variable's name depends on the
+ ``template_object_name`` parameter, which is ``'object'`` by default. If
+ ``template_object_name`` is ``'foo'``, this variable's name will be
+ ``foo_list``.
+
+ * ``is_paginated``: A boolean representing whether the results are
+ paginated. Specifically, this is set to ``False`` if the number of
+ available objects is less than or equal to ``paginate_by``.
+
+If the results are paginated, the context will contain these extra variables:
+
+ * ``results_per_page``: The number of objects per page. (Same as the
+ ``paginate_by`` parameter.)
+
+ * ``has_next``: A boolean representing whether there's a next page.
+
+ * ``has_previous``: A boolean representing whether there's a previous page.
+
+ * ``page``: The current page number, as an integer. This is 1-based.
+
+ * ``next``: The next page number, as an integer. If there's no next page,
+ this will still be an integer representing the theoretical next-page
+ number. This is 1-based.
+
+ * ``previous``: The previous page number, as an integer. This is 1-based.
+
+ * `last_on_page`: The number of the
+ last result on the current page. This is 1-based.
+
+ * `first_on_page`: The number of the
+ first result on the current page. This is 1-based.
+
+ * ``pages``: The total number of pages, as an integer.
+
+ * ``hits``: The total number of objects across *all* pages, not just this
+ page.
+
+Notes on pagination
+~~~~~~~~~~~~~~~~~~~
+
+If ``paginate_by`` is specified, Django will paginate the results. You can
+specify the page number in the URL in one of two ways:
+
+ * Use the ``page`` parameter in the URLconf. For example, this is what
+ your URLconf might look like::
+
+ (r'^objects/page(?P<page>[0-9]+)/$', 'object_list', dict(info_dict))
+
+ * Pass the page number via the ``page`` query-string parameter. For
+ example, a URL would look like this:
+
+ /objects/?page=3
+
+In both cases, ``page`` is 1-based, not 0-based, so the first page would be
+represented as page ``1``.
+
+``django.views.generic.list_detail.object_detail``
+--------------------------------------------------
+
+A page representing an individual object.
+
+**Description:**
+
+A page representing an individual object.
+
+**Required arguments:**
+
+ * ``queryset``: A ``QuerySet`` that contains the object.
+
+ * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
+
+ If you provide ``object_id``, it should be the value of the primary-key
+ field for the object being displayed on this page.
+
+ Otherwise, ``slug`` should be the slug of the given object, and
+ ``slug_field`` should be the name of the slug field in the ``QuerySet``'s
+ model.
+
+**Optional arguments:**
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_name_field``: The name of a field on the object whose value is
+ the template name to use. This lets you store template names in the data.
+ In other words, if your object has a field ``'the_template'`` that
+ contains a string ``'foo.html'``, and you set ``template_name_field`` to
+ ``'the_template'``, then the generic view for this object will use the
+ template ``'foo.html'``.
+
+ It's a bit of a brain-bender, but it's useful in some cases.
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_CONTENT_TYPE`` setting.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_detail.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``object``: The object. This variable's name depends on the
+ ``template_object_name`` parameter, which is ``'object'`` by default. If
+ ``template_object_name`` is ``'foo'``, this variable's name will be
+ ``foo``.
+
+Create/update/delete generic views
+==================================
+
+The ``django.views.generic.create_update`` module contains a set of functions
+for creating, editing and deleting objects.
+
+``django.views.generic.create_update.create_object``
+----------------------------------------------------
+
+**Description:**
+
+A page that displays a form for creating an object, redisplaying the form with
+validation errors (if there are any) and saving the object. This uses the
+automatic manipulators that come with Django models.
+
+**Required arguments:**
+
+ * ``model``: The Django model class of the object that the form will
+ create.
+
+**Optional arguments:**
+
+ * ``post_save_redirect``: A URL to which the view will redirect after
+ saving the object. By default, it's ``object.get_absolute_url()``.
+
+ ``post_save_redirect`` may contain dictionary string formatting, which
+ will be interpolated against the object's field attributes. For example,
+ you could use ``post_save_redirect="/polls/%(slug)s/"``.
+
+ * ``login_required``: A boolean that designates whether a user must be
+ logged in, in order to see the page and save changes. This hooks into the
+ Django `authentication system`_. By default, this is ``False``.
+
+ If this is ``True``, and a non-logged-in user attempts to visit this page
+ or save the form, Django will redirect the request to ``/accounts/login/``.
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_form.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
+ for editing the object. This lets you refer to form fields easily in the
+ template system.
+
+ For example, if ``model`` has two fields, ``name`` and ``address``::
+
+ <form action="" method="post">
+ <p><label for="id_name">Name:</label> {{ form.name }}</p>
+ <p><label for="id_address">Address:</label> {{ form.address }}</p>
+ </form>
+
+ See the `manipulator and formfield documentation`_ for more information
+ about using ``FormWrapper`` objects in templates.
+
+.. _authentication system: ../authentication/
+.. _manipulator and formfield documentation: ../forms/
+
+``django.views.generic.create_update.update_object``
+----------------------------------------------------
+
+**Description:**
+
+A page that displays a form for editing an existing object, redisplaying the
+form with validation errors (if there are any) and saving changes to the
+object. This uses the automatic manipulators that come with Django models.
+
+**Required arguments:**
+
+ * ``model``: The Django model class of the object that the form will
+ create.
+
+ * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
+
+ If you provide ``object_id``, it should be the value of the primary-key
+ field for the object being displayed on this page.
+
+ Otherwise, ``slug`` should be the slug of the given object, and
+ ``slug_field`` should be the name of the slug field in the ``QuerySet``'s
+ model.
+
+**Optional arguments:**
+
+ * ``post_save_redirect``: A URL to which the view will redirect after
+ saving the object. By default, it's ``object.get_absolute_url()``.
+
+ ``post_save_redirect`` may contain dictionary string formatting, which
+ will be interpolated against the object's field attributes. For example,
+ you could use ``post_save_redirect="/polls/%(slug)s/"``.
+
+ * ``login_required``: A boolean that designates whether a user must be
+ logged in, in order to see the page and save changes. This hooks into the
+ Django `authentication system`_. By default, this is ``False``.
+
+ If this is ``True``, and a non-logged-in user attempts to visit this page
+ or save the form, Django will redirect the request to ``/accounts/login/``.
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_form.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
+ for editing the object. This lets you refer to form fields easily in the
+ template system.
+
+ For example, if ``model`` has two fields, ``name`` and ``address``::
+
+ <form action="" method="post">
+ <p><label for="id_name">Name:</label> {{ form.name }}</p>
+ <p><label for="id_address">Address:</label> {{ form.address }}</p>
+ </form>
+
+ See the `manipulator and formfield documentation`_ for more information
+ about using ``FormWrapper`` objects in templates.
+
+ * ``object``: The original object being edited. This variable's name
+ depends on the ``template_object_name`` parameter, which is ``'object'``
+ by default. If ``template_object_name`` is ``'foo'``, this variable's
+ name will be ``foo``.
+
+``django.views.generic.create_update.delete_object``
+----------------------------------------------------
+
+**Description:**
+
+A view that displays a confirmation page and deletes an existing object. The
+given object will only be deleted if the request method is ``POST``. If this
+view is fetched via ``GET``, it will display a confirmation page that should
+contain a form that POSTs to the same URL.
+
+**Required arguments:**
+
+ * ``model``: The Django model class of the object that the form will
+ create.
+
+ * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
+
+ If you provide ``object_id``, it should be the value of the primary-key
+ field for the object being displayed on this page.
+
+ Otherwise, ``slug`` should be the slug of the given object, and
+ ``slug_field`` should be the name of the slug field in the ``QuerySet``'s
+ model.
+
+ * ``post_delete_redirect``: A URL to which the view will redirect after
+ deleting the object.
+
+**Optional arguments:**
+
+ * ``login_required``: A boolean that designates whether a user must be
+ logged in, in order to see the page and save changes. This hooks into the
+ Django `authentication system`_. By default, this is ``False``.
+
+ If this is ``True``, and a non-logged-in user attempts to visit this page
+ or save the form, Django will redirect the request to ``/accounts/login/``.
+
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
+
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
+
+ * ``extra_context``: A dictionary of values to add to the template
+ context. By default, this is an empty dictionary. If a value in the
+ dictionary is callable, the generic view will call it
+ just before rendering the template.
+
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
+
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``.
+
+**Template name:**
+
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_confirm_delete.html`` by default.
+
+**Template context:**
+
+In addition to ``extra_context``, the template's context will be:
+
+ * ``object``: The original object that's about to be deleted. This
+ variable's name depends on the ``template_object_name`` parameter, which
+ is ``'object'`` by default. If ``template_object_name`` is ``'foo'``,
+ this variable's name will be ``foo``.
diff --git a/google_appengine/lib/django/docs/i18n.txt b/google_appengine/lib/django/docs/i18n.txt
new file mode 100644
index 0000000..4a05e53
--- /dev/null
+++ b/google_appengine/lib/django/docs/i18n.txt
@@ -0,0 +1,765 @@
+====================
+Internationalization
+====================
+
+Django has full support for internationalization of text in code and templates.
+Here's how it works.
+
+Overview
+========
+
+The goal of internationalization is to allow a single Web application to offer
+its content and functionality in multiple languages.
+
+You, the Django developer, can accomplish this goal by adding a minimal amount
+of hooks to your Python code and templates. These hooks are called
+**translation strings**. They tell Django: "This text should be translated into
+the end user's language, if a translation for this text is available in that
+language."
+
+Django takes care of using these hooks to translate Web apps, on the fly,
+according to users' language preferences.
+
+Essentially, Django does two things:
+
+ * It lets developers and template authors specify which parts of their apps
+ should be translatable.
+ * It uses these hooks to translate Web apps for particular users according
+ to their language preferences.
+
+How to internationalize your app: in three steps
+------------------------------------------------
+
+ 1. Embed translation strings in your Python code and templates.
+ 2. Get translations for those strings, in whichever languages you want to
+ support.
+ 3. Activate the locale middleware in your Django settings.
+
+.. admonition:: Behind the scenes
+
+ Django's translation machinery uses the standard ``gettext`` module that
+ comes with Python.
+
+If you don't need internationalization
+======================================
+
+Django's internationalization hooks are on by default, and that means there's a
+bit of i18n-related overhead in certain places of the framework. If you don't
+use internationalization, you should take the two seconds to set
+``USE_I18N = False`` in your settings file. If ``USE_I18N`` is set to
+``False``, then Django will make some optimizations so as not to load the
+internationalization machinery. See the `documentation for USE_I18N`_.
+
+You'll probably also want to remove ``'django.core.context_processors.i18n'``
+from your ``TEMPLATE_CONTEXT_PROCESSORS`` setting.
+
+.. _documentation for USE_I18N: ../settings/#use-i18n
+
+How to specify translation strings
+==================================
+
+Translation strings specify "This text should be translated." These strings can
+appear in your Python code and templates. It's your responsibility to mark
+translatable strings; the system can only translate strings it knows about.
+
+In Python code
+--------------
+
+Standard translation
+~~~~~~~~~~~~~~~~~~~~
+
+Specify a translation string by using the function ``_()``. (Yes, the name of
+the function is the "underscore" character.) This function is available
+globally in any Python module; you don't have to import it.
+
+In this example, the text ``"Welcome to my site."`` is marked as a translation
+string::
+
+ def my_view(request):
+ output = _("Welcome to my site.")
+ return HttpResponse(output)
+
+The function ``django.utils.translation.gettext()`` is identical to ``_()``.
+This example is identical to the previous one::
+
+ from django.utils.translation import gettext
+ def my_view(request):
+ output = gettext("Welcome to my site.")
+ return HttpResponse(output)
+
+Translation works on computed values. This example is identical to the previous
+two::
+
+ def my_view(request):
+ words = ['Welcome', 'to', 'my', 'site.']
+ output = _(' '.join(words))
+ return HttpResponse(output)
+
+Translation works on variables. Again, here's an identical example::
+
+ def my_view(request):
+ sentence = 'Welcome to my site.'
+ output = _(sentence)
+ return HttpResponse(output)
+
+(The caveat with using variables or computed values, as in the previous two
+examples, is that Django's translation-string-detecting utility,
+``make-messages.py``, won't be able to find these strings. More on
+``make-messages`` later.)
+
+The strings you pass to ``_()`` or ``gettext()`` can take placeholders,
+specified with Python's standard named-string interpolation syntax. Example::
+
+ def my_view(request, n):
+ output = _('%(name)s is my name.') % {'name': n}
+ return HttpResponse(output)
+
+This technique lets language-specific translations reorder the placeholder
+text. For example, an English translation may be ``"Adrian is my name."``,
+while a Spanish translation may be ``"Me llamo Adrian."`` -- with the
+placeholder (the name) placed after the translated text instead of before it.
+
+For this reason, you should use named-string interpolation (e.g., ``%(name)s``)
+instead of positional interpolation (e.g., ``%s`` or ``%d``). If you used
+positional interpolation, translations wouldn't be able to reorder placeholder
+text.
+
+Marking strings as no-op
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use the function ``django.utils.translation.gettext_noop()`` to mark a string
+as a translation string without translating it. The string is later translated
+from a variable.
+
+Use this if you have constant strings that should be stored in the source
+language because they are exchanged over systems or users -- such as strings in
+a database -- but should be translated at the last possible point in time, such
+as when the string is presented to the user.
+
+Lazy translation
+~~~~~~~~~~~~~~~~
+
+Use the function ``django.utils.translation.gettext_lazy()`` to translate
+strings lazily -- when the value is accessed rather than when the
+``gettext_lazy()`` function is called.
+
+For example, to translate a model's ``help_text``, do the following::
+
+ from django.utils.translation import gettext_lazy
+
+ class MyThing(models.Model):
+ name = models.CharField(help_text=gettext_lazy('This is the help text'))
+
+In this example, ``gettext_lazy()`` stores a lazy reference to the string --
+not the actual translation. The translation itself will be done when the string
+is used in a string context, such as template rendering on the Django admin site.
+
+If you don't like the verbose name ``gettext_lazy``, you can just alias it as
+``_`` (underscore), like so::
+
+ from django.utils.translation import gettext_lazy as _
+
+ class MyThing(models.Model):
+ name = models.CharField(help_text=_('This is the help text'))
+
+Always use lazy translations in `Django models`_. And it's a good idea to add
+translations for the field names and table names, too. This means writing
+explicit ``verbose_name`` and ``verbose_name_plural`` options in the ``Meta``
+class, though::
+
+ from django.utils.translation import gettext_lazy as _
+
+ class MyThing(models.Model):
+ name = models.CharField(_('name'), help_text=_('This is the help text'))
+ class Meta:
+ verbose_name = _('my thing')
+ verbose_name_plural = _('mythings')
+
+.. _Django models: ../model_api/
+
+Pluralization
+~~~~~~~~~~~~~
+
+Use the function ``django.utils.translation.ngettext()`` to specify pluralized
+messages. Example::
+
+ from django.utils.translation import ngettext
+ def hello_world(request, count):
+ page = ngettext('there is %(count)d object', 'there are %(count)d objects', count) % {
+ 'count': count,
+ }
+ return HttpResponse(page)
+
+``ngettext`` takes three arguments: the singular translation string, the plural
+translation string and the number of objects (which is passed to the
+translation languages as the ``count`` variable).
+
+In template code
+----------------
+
+Using translations in `Django templates`_ uses two template tags and a slightly
+different syntax than in Python code. To give your template access to these
+tags, put ``{% load i18n %}`` toward the top of your template.
+
+The ``{% trans %}`` template tag translates a constant string or a variable
+content::
+
+ <title>{% trans "This is the title." %}</title>
+
+If you only want to mark a value for translation, but translate it later from a
+variable, use the ``noop`` option::
+
+ <title>{% trans "value" noop %}</title>
+
+It's not possible to use template variables in ``{% trans %}`` -- only constant
+strings, in single or double quotes, are allowed. If your translations require
+variables (placeholders), use ``{% blocktrans %}``. Example::
+
+ {% blocktrans %}This will have {{ value }} inside.{% endblocktrans %}
+
+To translate a template expression -- say, using template filters -- you need
+to bind the expression to a local variable for use within the translation
+block::
+
+ {% blocktrans with value|filter as myvar %}
+ This will have {{ myvar }} inside.
+ {% endblocktrans %}
+
+If you need to bind more than one expression inside a ``blocktrans`` tag,
+separate the pieces with ``and``::
+
+ {% blocktrans with book|title as book_t and author|title as author_t %}
+ This is {{ book_t }} by {{ author_t }}
+ {% endblocktrans %}
+
+To pluralize, specify both the singular and plural forms with the
+``{% plural %}`` tag, which appears within ``{% blocktrans %}`` and
+``{% endblocktrans %}``. Example::
+
+ {% blocktrans count list|count as counter %}
+ There is only one {{ name }} object.
+ {% plural %}
+ There are {{ counter }} {{ name }} objects.
+ {% endblocktrans %}
+
+Internally, all block and inline translations use the appropriate
+``gettext`` / ``ngettext`` call.
+
+Each ``RequestContext`` has access to two translation-specific variables:
+
+ * ``LANGUAGES`` is a list of tuples in which the first element is the
+ language code and the second is the language name (in that language).
+ * ``LANGUAGE_CODE`` is the current user's preferred language, as a string.
+ Example: ``en-us``. (See "How language preference is discovered", below.)
+ * ``LANGUAGE_BIDI`` is the current language's direction. If True, it's a
+ right-to-left language, e.g: Hebrew, Arabic. If False it's a
+ left-to-right language, e.g: English, French, German etc.
+
+
+If you don't use the ``RequestContext`` extension, you can get those values with
+three tags::
+
+ {% get_current_language as LANGUAGE_CODE %}
+ {% get_available_languages as LANGUAGES %}
+ {% get_current_language_bidi as LANGUAGE_BIDI %}
+
+These tags also require a ``{% load i18n %}``.
+
+Translation hooks are also available within any template block tag that accepts
+constant strings. In those cases, just use ``_()`` syntax to specify a
+translation string. Example::
+
+ {% some_special_tag _("Page not found") value|yesno:_("yes,no") %}
+
+In this case, both the tag and the filter will see the already-translated
+string, so they don't need to be aware of translations.
+
+.. _Django templates: ../templates_python/
+
+How to create language files
+============================
+
+Once you've tagged your strings for later translation, you need to write (or
+obtain) the language translations themselves. Here's how that works.
+
+.. admonition:: Locale restrictions
+
+ Django does not support localizing your application into a locale for
+ which Django itself has not been translated. In this case, it will ignore
+ your translation files. If you were to try this and Django supported it,
+ you would inevitably see a mixture of translated strings (from your
+ application) and English strings (from Django itself). If you want to
+ support a locale for your application that is not already part of
+ Django, you'll need to make at least a minimal translation of the Django
+ core.
+
+Message files
+-------------
+
+The first step is to create a **message file** for a new language. A message
+file is a plain-text file, representing a single language, that contains all
+available translation strings and how they should be represented in the given
+language. Message files have a ``.po`` file extension.
+
+Django comes with a tool, ``bin/make-messages.py``, that automates the creation
+and upkeep of these files.
+
+To create or update a message file, run this command::
+
+ bin/make-messages.py -l de
+
+...where ``de`` is the language code for the message file you want to create.
+The language code, in this case, is in locale format. For example, it's
+``pt_BR`` for Brazilian and ``de_AT`` for Austrian German.
+
+The script should be run from one of three places:
+
+ * The root ``django`` directory (not a Subversion checkout, but the one
+ that is linked-to via ``$PYTHONPATH`` or is located somewhere on that
+ path).
+ * The root directory of your Django project.
+ * The root directory of your Django app.
+
+The script runs over the entire Django source tree and pulls out all strings
+marked for translation. It creates (or updates) a message file in the directory
+``conf/locale``. In the ``de`` example, the file will be
+``conf/locale/de/LC_MESSAGES/django.po``.
+
+If run over your project source tree or your application source tree, it will
+do the same, but the location of the locale directory is ``locale/LANG/LC_MESSAGES``
+(note the missing ``conf`` prefix).
+
+.. admonition:: No gettext?
+
+ If you don't have the ``gettext`` utilities installed, ``make-messages.py``
+ will create empty files. If that's the case, either install the ``gettext``
+ utilities or just copy the English message file
+ (``conf/locale/en/LC_MESSAGES/django.po``) and use it as a starting point;
+ it's just an empty translation file.
+
+The format of ``.po`` files is straightforward. Each ``.po`` file contains a
+small bit of metadata, such as the translation maintainer's contact
+information, but the bulk of the file is a list of **messages** -- simple
+mappings between translation strings and the actual translated text for the
+particular language.
+
+For example, if your Django app contained a translation string for the text
+``"Welcome to my site."``, like so::
+
+ _("Welcome to my site.")
+
+...then ``make-messages.py`` will have created a ``.po`` file containing the
+following snippet -- a message::
+
+ #: path/to/python/module.py:23
+ msgid "Welcome to my site."
+ msgstr ""
+
+A quick explanation:
+
+ * ``msgid`` is the translation string, which appears in the source. Don't
+ change it.
+ * ``msgstr`` is where you put the language-specific translation. It starts
+ out empty, so it's your responsibility to change it. Make sure you keep
+ the quotes around your translation.
+ * As a convenience, each message includes the filename and line number
+ from which the translation string was gleaned.
+
+Long messages are a special case. There, the first string directly after the
+``msgstr`` (or ``msgid``) is an empty string. Then the content itself will be
+written over the next few lines as one string per line. Those strings are
+directly concatenated. Don't forget trailing spaces within the strings;
+otherwise, they'll be tacked together without whitespace!
+
+.. admonition:: Mind your charset
+
+ When creating a ``.po`` file with your favorite text editor, first edit
+ the charset line (search for ``"CHARSET"``) and set it to the charset
+ you'll be using to edit the content. Generally, utf-8 should work for most
+ languages, but ``gettext`` should handle any charset you throw at it.
+
+To reexamine all source code and templates for new translation strings and
+update all message files for **all** languages, run this::
+
+ make-messages.py -a
+
+Compiling message files
+-----------------------
+
+After you create your message file -- and each time you make changes to it --
+you'll need to compile it into a more efficient form, for use by ``gettext``.
+Do this with the ``bin/compile-messages.py`` utility.
+
+This tool runs over all available ``.po`` files and creates ``.mo`` files,
+which are binary files optimized for use by ``gettext``. In the same directory
+from which you ran ``make-messages.py``, run ``compile-messages.py`` like
+this::
+
+ bin/compile-messages.py
+
+That's it. Your translations are ready for use.
+
+.. admonition:: A note to translators
+
+ If you've created a translation in a language Django doesn't yet support,
+ please let us know! See `Submitting and maintaining translations`_ for
+ the steps to take.
+
+ .. _Submitting and maintaining translations: ../contributing/
+
+How Django discovers language preference
+========================================
+
+Once you've prepared your translations -- or, if you just want to use the
+translations that come with Django -- you'll just need to activate translation
+for your app.
+
+Behind the scenes, Django has a very flexible model of deciding which language
+should be used -- installation-wide, for a particular user, or both.
+
+To set an installation-wide language preference, set ``LANGUAGE_CODE`` in your
+`settings file`_. Django uses this language as the default translation -- the
+final attempt if no other translator finds a translation.
+
+If all you want to do is run Django with your native language, and a language
+file is available for your language, all you need to do is set
+``LANGUAGE_CODE``.
+
+If you want to let each individual user specify which language he or she
+prefers, use ``LocaleMiddleware``. ``LocaleMiddleware`` enables language
+selection based on data from the request. It customizes content for each user.
+
+To use ``LocaleMiddleware``, add ``'django.middleware.locale.LocaleMiddleware'``
+to your ``MIDDLEWARE_CLASSES`` setting. Because middleware order matters, you
+should follow these guidelines:
+
+ * Make sure it's one of the first middlewares installed.
+ * It should come after ``SessionMiddleware``, because ``LocaleMiddleware``
+ makes use of session data.
+ * If you use ``CacheMiddleware``, put ``LocaleMiddleware`` after it.
+
+For example, your ``MIDDLEWARE_CLASSES`` might look like this::
+
+ MIDDLEWARE_CLASSES = (
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ )
+
+(For more on middleware, see the `middleware documentation`_.)
+
+``LocaleMiddleware`` tries to determine the user's language preference by
+following this algorithm:
+
+ * First, it looks for a ``django_language`` key in the the current user's
+ `session`_.
+ * Failing that, it looks for a cookie called ``django_language``.
+ * Failing that, it looks at the ``Accept-Language`` HTTP header. This
+ header is sent by your browser and tells the server which language(s) you
+ prefer, in order by priority. Django tries each language in the header
+ until it finds one with available translations.
+ * Failing that, it uses the global ``LANGUAGE_CODE`` setting.
+
+Notes:
+
+ * In each of these places, the language preference is expected to be in the
+ standard language format, as a string. For example, Brazilian is
+ ``pt-br``.
+ * If a base language is available but the sublanguage specified is not,
+ Django uses the base language. For example, if a user specifies ``de-at``
+ (Austrian German) but Django only has ``de`` available, Django uses
+ ``de``.
+ * Only languages listed in the `LANGUAGES setting`_ can be selected. If
+ you want to restrict the language selection to a subset of provided
+ languages (because your application doesn't provide all those languages),
+ set ``LANGUAGES`` to a list of languages. For example::
+
+ LANGUAGES = (
+ ('de', _('German')),
+ ('en', _('English')),
+ )
+
+ This example restricts languages that are available for automatic
+ selection to German and English (and any sublanguage, like de-ch or
+ en-us).
+
+ .. _LANGUAGES setting: ../settings/#languages
+
+ * If you define a custom ``LANGUAGES`` setting, as explained in the
+ previous bullet, it's OK to mark the languages as translation strings
+ -- but use a "dummy" ``gettext()`` function, not the one in
+ ``django.utils.translation``. You should *never* import
+ ``django.utils.translation`` from within your settings file, because that
+ module in itself depends on the settings, and that would cause a circular
+ import.
+
+ The solution is to use a "dummy" ``gettext()`` function. Here's a sample
+ settings file::
+
+ gettext = lambda s: s
+
+ LANGUAGES = (
+ ('de', gettext('German')),
+ ('en', gettext('English')),
+ )
+
+ With this arrangement, ``make-messages.py`` will still find and mark
+ these strings for translation, but the translation won't happen at
+ runtime -- so you'll have to remember to wrap the languages in the *real*
+ ``gettext()`` in any code that uses ``LANGUAGES`` at runtime.
+
+ * The ``LocaleMiddleware`` can only select languages for which there is a
+ Django-provided base translation. If you want to provide translations
+ for your application that aren't already in the set of translations
+ in Django's source tree, you'll want to provide at least basic
+ translations for that language. For example, Django uses technical
+ message IDs to translate date formats and time formats -- so you will
+ need at least those translations for the system to work correctly.
+
+ A good starting point is to copy the English ``.po`` file and to
+ translate at least the technical messages -- maybe the validator
+ messages, too.
+
+ Technical message IDs are easily recognized; they're all upper case. You
+ don't translate the message ID as with other messages, you provide the
+ correct local variant on the provided English value. For example, with
+ ``DATETIME_FORMAT`` (or ``DATE_FORMAT`` or ``TIME_FORMAT``), this would
+ be the format string that you want to use in your language. The format
+ is identical to the format strings used by the ``now`` template tag.
+
+Once ``LocaleMiddleware`` determines the user's preference, it makes this
+preference available as ``request.LANGUAGE_CODE`` for each `request object`_.
+Feel free to read this value in your view code. Here's a simple example::
+
+ def hello_world(request, count):
+ if request.LANGUAGE_CODE == 'de-at':
+ return HttpResponse("You prefer to read Austrian German.")
+ else:
+ return HttpResponse("You prefer to read another language.")
+
+Note that, with static (middleware-less) translation, the language is in
+``settings.LANGUAGE_CODE``, while with dynamic (middleware) translation, it's
+in ``request.LANGUAGE_CODE``.
+
+.. _settings file: ../settings/
+.. _middleware documentation: ../middleware/
+.. _session: ../sessions/
+.. _request object: ../request_response/#httprequest-objects
+
+The ``set_language`` redirect view
+==================================
+
+As a convenience, Django comes with a view, ``django.views.i18n.set_language``,
+that sets a user's language preference and redirects back to the previous page.
+
+Activate this view by adding the following line to your URLconf::
+
+ (r'^i18n/', include('django.conf.urls.i18n')),
+
+(Note that this example makes the view available at ``/i18n/setlang/``.)
+
+The view expects to be called via the ``GET`` method, with a ``language``
+parameter set in the query string. If session support is enabled, the view
+saves the language choice in the user's session. Otherwise, it saves the
+language choice in a ``django_language`` cookie.
+
+After setting the language choice, Django redirects the user, following this
+algorithm:
+
+ * Django looks for a ``next`` parameter in the query string.
+ * If that doesn't exist, or is empty, Django tries the URL in the
+ ``Referer`` header.
+ * If that's empty -- say, if a user's browser suppresses that header --
+ then the user will be redirected to ``/`` (the site root) as a fallback.
+
+Here's example HTML template code::
+
+ <form action="/i18n/setlang/" method="get">
+ <input name="next" type="hidden" value="/next/page/" />
+ <select name="language">
+ {% for lang in LANGUAGES %}
+ <option value="{{ lang.0 }}">{{ lang.1 }}</option>
+ {% endfor %}
+ </select>
+ <input type="submit" value="Go" />
+ </form>
+
+Using translations in your own projects
+=======================================
+
+Django looks for translations by following this algorithm:
+
+ * First, it looks for a ``locale`` directory in the application directory
+ of the view that's being called. If it finds a translation for the
+ selected language, the translation will be installed.
+ * Next, it looks for a ``locale`` directory in the project directory. If it
+ finds a translation, the translation will be installed.
+ * Finally, it checks the base translation in ``django/conf/locale``.
+
+This way, you can write applications that include their own translations, and
+you can override base translations in your project path. Or, you can just build
+a big project out of several apps and put all translations into one big project
+message file. The choice is yours.
+
+.. note::
+
+ If you're using manually configured settings, as described in the
+ `settings documentation`_, the ``locale`` directory in the project
+ directory will not be examined, since Django loses the ability to work out
+ the location of the project directory. (Django normally uses the location
+ of the settings file to determine this, and a settings file doesn't exist
+ if you're manually configuring your settings.)
+
+.. _settings documentation: ../settings/#using-settings-without-the-django-settings-module-environment-variable
+
+All message file repositories are structured the same way. They are:
+
+ * ``$APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)``
+ * ``$PROJECTPATH/locale/<language>/LC_MESSAGES/django.(po|mo)``
+ * All paths listed in ``LOCALE_PATHS`` in your settings file are
+ searched in that order for ``<language>/LC_MESSAGES/django.(po|mo)``
+ * ``$PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)``
+
+To create message files, you use the same ``make-messages.py`` tool as with the
+Django message files. You only need to be in the right place -- in the directory
+where either the ``conf/locale`` (in case of the source tree) or the ``locale/``
+(in case of app messages or project messages) directory are located. And you
+use the same ``compile-messages.py`` to produce the binary ``django.mo`` files that
+are used by ``gettext``.
+
+Application message files are a bit complicated to discover -- they need the
+``LocaleMiddleware``. If you don't use the middleware, only the Django message
+files and project message files will be processed.
+
+Finally, you should give some thought to the structure of your translation
+files. If your applications need to be delivered to other users and will
+be used in other projects, you might want to use app-specific translations.
+But using app-specific translations and project translations could produce
+weird problems with ``make-messages``: ``make-messages`` will traverse all
+directories below the current path and so might put message IDs into the
+project message file that are already in application message files.
+
+The easiest way out is to store applications that are not part of the project
+(and so carry their own translations) outside the project tree. That way,
+``make-messages`` on the project level will only translate strings that are
+connected to your explicit project and not strings that are distributed
+independently.
+
+Translations and JavaScript
+===========================
+
+Adding translations to JavaScript poses some problems:
+
+ * JavaScript code doesn't have access to a ``gettext`` implementation.
+
+ * JavaScript code doesn't have access to .po or .mo files; they need to be
+ delivered by the server.
+
+ * The translation catalogs for JavaScript should be kept as small as
+ possible.
+
+Django provides an integrated solution for these problems: It passes the
+translations into JavaScript, so you can call ``gettext``, etc., from within
+JavaScript.
+
+The ``javascript_catalog`` view
+-------------------------------
+
+The main solution to these problems is the ``javascript_catalog`` view, which
+sends out a JavaScript code library with functions that mimic the ``gettext``
+interface, plus an array of translation strings. Those translation strings are
+taken from the application, project or Django core, according to what you
+specify in either the {{{info_dict}}} or the URL.
+
+You hook it up like this::
+
+ js_info_dict = {
+ 'packages': ('your.app.package',),
+ }
+
+ urlpatterns = patterns('',
+ (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
+ )
+
+Each string in ``packages`` should be in Python dotted-package syntax (the
+same format as the strings in ``INSTALLED_APPS``) and should refer to a package
+that contains a ``locale`` directory. If you specify multiple packages, all
+those catalogs are merged into one catalog. This is useful if you have
+JavaScript that uses strings from different applications.
+
+You can make the view dynamic by putting the packages into the URL pattern::
+
+ urlpatterns = patterns('',
+ (r'^jsi18n/(?P<packages>\S+?)/$, 'django.views.i18n.javascript_catalog'),
+ )
+
+With this, you specify the packages as a list of package names delimited by '+'
+signs in the URL. This is especially useful if your pages use code from
+different apps and this changes often and you don't want to pull in one big
+catalog file. As a security measure, these values can only be either
+``django.conf`` or any package from the ``INSTALLED_APPS`` setting.
+
+Using the JavaScript translation catalog
+----------------------------------------
+
+To use the catalog, just pull in the dynamically generated script like this::
+
+ <script type="text/javascript" src="/path/to/jsi18n/"></script>
+
+This is how the admin fetches the translation catalog from the server. When the
+catalog is loaded, your JavaScript code can use the standard ``gettext``
+interface to access it::
+
+ document.write(gettext('this is to be translated'));
+
+There even is a ``ngettext`` interface and a string interpolation function::
+
+ d = {
+ count: 10
+ };
+ s = interpolate(ngettext('this is %(count)s object', 'this are %(count)s objects', d.count), d);
+
+The ``interpolate`` function supports both positional interpolation and named
+interpolation. So the above could have been written as::
+
+ s = interpolate(ngettext('this is %s object', 'this are %s objects', 11), [11]);
+
+The interpolation syntax is borrowed from Python. You shouldn't go over the top
+with string interpolation, though: this is still JavaScript, so the code will
+have to do repeated regular-expression substitutions. This isn't as fast as
+string interpolation in Python, so keep it to those cases where you really
+need it (for example, in conjunction with ``ngettext`` to produce proper
+pluralizations).
+
+Creating JavaScript translation catalogs
+----------------------------------------
+
+You create and update the translation catalogs the same way as the other Django
+translation catalogs -- with the {{{make-messages.py}}} tool. The only
+difference is you need to provide a ``-d djangojs`` parameter, like this::
+
+ make-messages.py -d djangojs -l de
+
+This would create or update the translation catalog for JavaScript for German.
+After updating translation catalogs, just run ``compile-messages.py`` the same
+way as you do with normal Django translation catalogs.
+
+Specialities of Django translation
+==================================
+
+If you know ``gettext``, you might note these specialities in the way Django
+does translation:
+
+ * The string domain is ``django`` or ``djangojs``. The string domain is used to
+ differentiate between different programs that store their data in a
+ common message-file library (usually ``/usr/share/locale/``). The ``django``
+ domain is used for python and template translation strings and is loaded into
+ the global translation catalogs. The ``djangojs`` domain is only used for
+ JavaScript translation catalogs to make sure that those are as small as
+ possible.
+ * Django only uses ``gettext`` and ``gettext_noop``. That's because Django
+ always uses ``DEFAULT_CHARSET`` strings internally. There isn't much use
+ in using ``ugettext``, because you'll always need to produce utf-8
+ anyway.
+ * Django doesn't use ``xgettext`` alone. It uses Python wrappers around
+ ``xgettext`` and ``msgfmt``. That's mostly for convenience.
diff --git a/google_appengine/lib/django/docs/install.txt b/google_appengine/lib/django/docs/install.txt
new file mode 100644
index 0000000..3eede02
--- /dev/null
+++ b/google_appengine/lib/django/docs/install.txt
@@ -0,0 +1,143 @@
+=====================
+How to install Django
+=====================
+
+This document will get you up and running with Django.
+
+Install Python
+==============
+
+Being a Python Web framework, Django requires Python.
+
+It works with any Python version 2.3 and higher.
+
+Get Python at www.python.org. If you're running Linux or Mac OS X, you probably
+already have it installed.
+
+Install Apache and mod_python
+=============================
+
+If you just want to experiment with Django, skip this step. Django comes with
+its own Web server for development purposes.
+
+If you want to use Django on a production site, use Apache with `mod_python`_.
+mod_python is similar to mod_perl -- it embeds Python within Apache and loads
+Python code into memory when the server starts. Code stays in memory throughout
+the life of an Apache process, which leads to significant performance gains
+over other server arrangements. Make sure you have Apache installed, with the
+mod_python module activated. Django requires Apache 2.x and mod_python 3.x.
+
+See `How to use Django with mod_python`_ for information on how to configure
+mod_python once you have it installed.
+
+If you can't use mod_python for some reason, fear not: Django follows the WSGI_
+spec, which allows it to run on a variety of server platforms. See the
+`server-arrangements wiki page`_ for specific installation instructions for
+each platform.
+
+.. _Apache: http://httpd.apache.org/
+.. _mod_python: http://www.modpython.org/
+.. _WSGI: http://www.python.org/peps/pep-0333.html
+.. _How to use Django with mod_python: ../modpython/
+.. _server-arrangements wiki page: http://code.djangoproject.com/wiki/ServerArrangements
+
+Get your database running
+=========================
+
+If you plan to use Django's database API functionality, you'll need to
+make sure a database server is running. Django works with PostgreSQL_
+(recommended), MySQL_ and SQLite_.
+
+Additionally, you'll need to make sure your Python database bindings are
+installed.
+
+* If you're using PostgreSQL, you'll need the psycopg_ package (version 2 is
+ recommended with ``postgresql_psycopg2`` backend, version 1.1 works also with the
+ ``postgresql``` backend).
+
+ If you're on Windows, check out the unofficial `compiled Windows version`_.
+
+* If you're using MySQL, you'll need MySQLdb_, version 1.2.1p2 or higher.
+
+* If you're using SQLite, you'll need pysqlite_. Use version 2.0.3 or higher.
+
+.. _PostgreSQL: http://www.postgresql.org/
+.. _MySQL: http://www.mysql.com/
+.. _Django's ticket system: http://code.djangoproject.com/report/1
+.. _psycopg: http://initd.org/tracker/psycopg
+.. _compiled Windows version: http://stickpeople.com/projects/python/win-psycopg/
+.. _MySQLdb: http://sourceforge.net/projects/mysql-python
+.. _SQLite: http://www.sqlite.org/
+.. _pysqlite: http://initd.org/tracker/pysqlite
+
+Install the Django code
+=======================
+
+Installation instructions are slightly different depending on whether you're
+using the latest official version or the latest development version.
+
+It's easy either way.
+
+Installing the official version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ 1. Check the `distribution specific notes`_ to see if your
+ platform/distribution provides official Django packages/installers.
+ Distribution-provided packages will typically allow for automatic
+ installation of dependancies and easy upgrade paths.
+
+ 2. Download Django-0.95.tar.gz from our `download page`_.
+
+ 3. ``tar xzvf Django-0.95.tar.gz``
+
+ 4. ``cd Django-0.95``
+
+ 5. ``sudo python setup.py install``
+
+Note that the last command will automatically download and install setuptools_
+if you don't already have it installed. This requires a working Internet
+connection and may cause problems on Python 2.5. If you run into problems,
+try using our development version by following the instructions below. The
+development version no longer uses setuptools nor requires an Internet
+connection.
+
+The command will install Django in your Python installation's ``site-packages``
+directory.
+
+.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
+.. _distribution specific notes: ../distributions/
+
+Installing the development version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you'd like to be able to update your Django code occasionally with the
+latest bug fixes and improvements, follow these instructions:
+
+1. Make sure you have Subversion_ installed.
+2. Check out the Django code into your Python ``site-packages`` directory.
+ On Linux / Mac OSX / Unix, do this::
+
+ svn co http://code.djangoproject.com/svn/django/trunk/ django_src
+ ln -s `pwd`/django_src/django /usr/lib/python2.3/site-packages/django
+
+ (In the above line, change ``python2.3`` to match your current Python version.)
+
+ On Windows, do this::
+
+ svn co http://code.djangoproject.com/svn/django/trunk/django c:\Python24\lib\site-packages\django
+
+3. Copy the file ``django_src/django/bin/django-admin.py`` to somewhere on your
+ system path, such as ``/usr/local/bin`` (Unix) or ``C:\Python24\Scripts``
+ (Windows). This step simply lets you type ``django-admin.py`` from within
+ any directory, rather than having to qualify the command with the full path
+ to the file.
+
+You *don't* have to run ``python setup.py install``, because that command
+takes care of steps 2 and 3 for you.
+
+When you want to update your copy of the Django source code, just run the
+command ``svn update`` from within the ``django`` directory. When you do this,
+Subversion will automatically download any changes.
+
+.. _`download page`: http://www.djangoproject.com/download/
+.. _Subversion: http://subversion.tigris.org/
diff --git a/google_appengine/lib/django/docs/legacy_databases.txt b/google_appengine/lib/django/docs/legacy_databases.txt
new file mode 100644
index 0000000..8230c11
--- /dev/null
+++ b/google_appengine/lib/django/docs/legacy_databases.txt
@@ -0,0 +1,69 @@
+==================================
+Integrating with a legacy database
+==================================
+
+While Django is best suited for developing new applications, it's quite
+possible to integrate it into legacy databases. Django includes a couple of
+utilities to automate as much of this process as possible.
+
+This document assumes you know the Django basics, as covered in the
+`official tutorial`_.
+
+.. _official tutorial: ../tutorial1/
+
+Give Django your database parameters
+====================================
+
+You'll need to tell Django what your database connection parameters are, and
+what the name of the database is. Do that by editing these settings in your
+`settings file`_:
+
+ * `DATABASE_NAME`
+ * `DATABASE_ENGINE`_
+ * `DATABASE_USER`_
+ * `DATABASE_PASSWORD`_
+ * `DATABASE_HOST`_
+ * `DATABASE_PORT`_
+
+.. _settings file: ../settings/
+.. _DATABASE_NAME: ../settings/#database-name
+.. _DATABASE_ENGINE: ../settings/#database-engine
+.. _DATABASE_USER: ../settings/#database-user
+.. _DATABASE_PASSWORD: ../settings/#database-password
+.. _DATABASE_HOST: ../settings/#database-host
+.. _DATABASE_PORT: ../settings/#database-port
+
+Auto-generate the models
+========================
+
+Django comes with a utility that can create models by introspecting an existing
+database. You can view the output by running this command::
+
+ django-admin.py inspectdb --settings=path.to.settings
+
+Save this as a file by using standard Unix output redirection::
+
+ django-admin.py inspectdb --settings=path.to.settings > models.py
+
+This feature is meant as a shortcut, not as definitive model generation. See
+the `django-admin.py documentation`_ for more information.
+
+Once you've cleaned up your models, name the file ``models.py`` and put it in
+the Python package that holds your app. Then add the app to your
+``INSTALLED_APPS`` setting.
+
+.. _django-admin.py documentation: ../django_admin/
+
+Install the core Django tables
+==============================
+
+Next, run the ``manage.py syncdb`` command to install any extra needed database
+records such as admin permissions and content types::
+
+ django-admin.py init --settings=path.to.settings
+
+See whether it worked
+=====================
+
+That's it. Try accessing your data via the Django database API, and try editing
+objects via Django's admin site.
diff --git a/google_appengine/lib/django/docs/middleware.txt b/google_appengine/lib/django/docs/middleware.txt
new file mode 100644
index 0000000..0d53344
--- /dev/null
+++ b/google_appengine/lib/django/docs/middleware.txt
@@ -0,0 +1,229 @@
+==========
+Middleware
+==========
+
+Middleware is a framework of hooks into Django's request/response processing.
+It's a light, low-level "plugin" system for globally altering Django's input
+and/or output.
+
+Each middleware component is responsible for doing some specific function. For
+example, Django includes a middleware component, ``XViewMiddleware``, that adds
+an ``"X-View"`` HTTP header to every response to a ``HEAD`` request.
+
+This document explains all middleware components that come with Django, how to
+use them, and how to write your own middleware.
+
+Activating middleware
+=====================
+
+To activate a middleware component, add it to the ``MIDDLEWARE_CLASSES`` list
+in your Django settings. In ``MIDDLEWARE_CLASSES``, each middleware component
+is represented by a string: the full Python path to the middleware's class
+name. For example, here's the default ``MIDDLEWARE_CLASSES`` created by
+``django-admin.py startproject``::
+
+ MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.middleware.doc.XViewMiddleware',
+ )
+
+Django applies middleware in the order it's defined in ``MIDDLEWARE_CLASSES``,
+except in the case of response and exception middleware, which is applied in
+reverse order.
+
+A Django installation doesn't require any middleware -- e.g.,
+``MIDDLEWARE_CLASSES`` can be empty, if you'd like -- but it's strongly
+suggested that you use ``CommonMiddleware``.
+
+Available middleware
+====================
+
+django.middleware.cache.CacheMiddleware
+---------------------------------------
+
+Enables site-wide cache. If this is enabled, each Django-powered page will be
+cached for as long as the ``CACHE_MIDDLEWARE_SECONDS`` setting defines. See
+the `cache documentation`_.
+
+.. _`cache documentation`: ../cache/#the-per-site-cache
+
+django.middleware.common.CommonMiddleware
+-----------------------------------------
+
+Adds a few conveniences for perfectionists:
+
+* Forbids access to user agents in the ``DISALLOWED_USER_AGENTS`` setting,
+ which should be a list of strings.
+
+* Performs URL rewriting based on the ``APPEND_SLASH`` and ``PREPEND_WWW``
+ settings. If ``APPEND_SLASH`` is ``True``, URLs that lack a trailing
+ slash will be redirected to the same URL with a trailing slash, unless the
+ last component in the path contains a period. So ``foo.com/bar`` is
+ redirected to ``foo.com/bar/``, but ``foo.com/bar/file.txt`` is passed
+ through unchanged.
+
+ If ``PREPEND_WWW`` is ``True``, URLs that lack a leading "www." will be
+ redirected to the same URL with a leading "www."
+
+ Both of these options are meant to normalize URLs. The philosophy is that
+ each URL should exist in one, and only one, place. Technically a URL
+ ``foo.com/bar`` is distinct from ``foo.com/bar/`` -- a search-engine
+ indexer would treat them as separate URLs -- so it's best practice to
+ normalize URLs.
+
+* Handles ETags based on the ``USE_ETAGS`` setting. If ``USE_ETAGS`` is set
+ to ``True``, Django will calculate an ETag for each request by
+ MD5-hashing the page content, and it'll take care of sending
+ ``Not Modified`` responses, if appropriate.
+
+django.middleware.doc.XViewMiddleware
+-------------------------------------
+
+Sends custom ``X-View`` HTTP headers to HEAD requests that come from IP
+addresses defined in the ``INTERNAL_IPS`` setting. This is used by Django's
+automatic documentation system.
+
+django.middleware.gzip.GZipMiddleware
+-------------------------------------
+
+Compresses content for browsers that understand gzip compression (all modern
+browsers).
+
+django.middleware.http.ConditionalGetMiddleware
+-----------------------------------------------
+
+Handles conditional GET operations. If the response has a ``ETag`` or
+``Last-Modified`` header, and the request has ``If-None-Match`` or
+``If-Modified-Since``, the response is replaced by an HttpNotModified.
+
+Also removes the content from any response to a HEAD request and sets the
+``Date`` and ``Content-Length`` response-headers.
+
+django.middleware.http.SetRemoteAddrFromForwardedFor
+----------------------------------------------------
+
+Sets ``request.META['REMOTE_ADDR']`` based on
+``request.META['HTTP_X_FORWARDED_FOR']``, if the latter is set. This is useful
+if you're sitting behind a reverse proxy that causes each request's
+``REMOTE_ADDR`` to be set to ``127.0.0.1``.
+
+**Important note:** This does NOT validate ``HTTP_X_FORWARDED_FOR``. If you're
+not behind a reverse proxy that sets ``HTTP_X_FORWARDED_FOR`` automatically, do
+not use this middleware. Anybody can spoof the value of
+``HTTP_X_FORWARDED_FOR``, and because this sets ``REMOTE_ADDR`` based on
+``HTTP_X_FORWARDED_FOR``, that means anybody can "fake" their IP address. Only
+use this when you can absolutely trust the value of ``HTTP_X_FORWARDED_FOR``.
+
+django.contrib.sessions.middleware.SessionMiddleware
+----------------------------------------------------
+
+Enables session support. See the `session documentation`_.
+
+.. _`session documentation`: ../sessions/
+
+django.contrib.auth.middleware.AuthenticationMiddleware
+-------------------------------------------------------
+
+Adds the ``user`` attribute, representing the currently-logged-in user, to
+every incoming ``HttpRequest`` object. See `Authentication in Web requests`_.
+
+.. _Authentication in Web requests: ../authentication/#authentication-in-web-requests
+
+django.middleware.transaction.TransactionMiddleware
+---------------------------------------------------
+
+Binds commit and rollback to the request/response phase. If a view function runs
+successfully, a commit is done. If it fails with an exception, a rollback is
+done.
+
+The order of this middleware in the stack is important: middleware modules
+running outside of it run with commit-on-save - the default Django behavior.
+Middleware modules running inside it (coming later in the stack) will be under
+the same transaction control as the view functions.
+
+See the `transaction management documentation`_.
+
+.. _`transaction management documentation`: ../transactions/
+
+Writing your own middleware
+===========================
+
+Writing your own middleware is easy. Each middleware component is a single
+Python class that defines one or more of the following methods:
+
+process_request
+---------------
+
+Interface: ``process_request(self, request)``
+
+``request`` is an ``HttpRequest`` object. This method is called on each
+request, before Django decides which view to execute.
+
+``process_request()`` should return either ``None`` or an ``HttpResponse``
+object. If it returns ``None``, Django will continue processing this request,
+executing any other middleware and, then, the appropriate view. If it returns
+an ``HttpResponse`` object, Django won't bother calling ANY other middleware or
+the appropriate view; it'll return that ``HttpResponse``.
+
+process_view
+------------
+
+Interface: ``process_view(self, request, view_func, view_args, view_kwargs)``
+
+``request`` is an ``HttpRequest`` object. ``view_func`` is the Python function
+that Django is about to use. (It's the actual function object, not the name of
+the function as a string.) ``view_args`` is a list of positional arguments that
+will be passed to the view, and ``view_kwargs`` is a dictionary of keyword
+arguments that will be passed to the view. Neither ``view_args`` nor
+``view_kwargs`` include the first view argument (``request``).
+
+``process_view()`` is called just before Django calls the view. It should
+return either ``None`` or an ``HttpResponse`` object. If it returns ``None``,
+Django will continue processing this request, executing any other
+``process_view()`` middleware and, then, the appropriate view. If it returns an
+``HttpResponse`` object, Django won't bother calling ANY other middleware or
+the appropriate view; it'll return that ``HttpResponse``.
+
+process_response
+----------------
+
+Interface: ``process_response(self, request, response)``
+
+``request`` is an ``HttpRequest`` object. ``response`` is the ``HttpResponse``
+object returned by a Django view.
+
+``process_response()`` should return an ``HttpResponse`` object. It could alter
+the given ``response``, or it could create and return a brand-new
+``HttpResponse``.
+
+process_exception
+-----------------
+
+Interface: ``process_exception(self, request, exception)``
+
+``request`` is an ``HttpRequest`` object. ``exception`` is an ``Exception``
+object raised by the view function.
+
+Django calls ``process_exception()`` when a view raises an exception.
+``process_exception()`` should return either ``None`` or an ``HttpResponse``
+object. If it returns an ``HttpResponse`` object, the response will be returned
+to the browser. Otherwise, default exception handling kicks in.
+
+Guidelines
+----------
+
+ * Middleware classes don't have to subclass anything.
+
+ * The middleware class can live anywhere on your Python path. All Django
+ cares about is that the ``MIDDLEWARE_CLASSES`` setting includes the path
+ to it.
+
+ * Feel free to look at Django's available middleware for examples. The
+ core Django middleware classes are in ``django/middleware/`` in the
+ Django distribution. The session middleware is in ``django/contrib/sessions``.
+
+ * If you write a middleware component that you think would be useful to
+ other people, contribute to the community! Let us know, and we'll
+ consider adding it to Django.
diff --git a/google_appengine/lib/django/docs/model-api.txt b/google_appengine/lib/django/docs/model-api.txt
new file mode 100644
index 0000000..155ef63
--- /dev/null
+++ b/google_appengine/lib/django/docs/model-api.txt
@@ -0,0 +1,1921 @@
+===============
+Model reference
+===============
+
+A model is the single, definitive source of data about your data. It contains
+the essential fields and behaviors of the data you're storing. Generally, each
+model maps to a single database table.
+
+The basics:
+
+ * Each model is a Python class that subclasses ``django.db.models.Model``.
+ * Each attribute of the model represents a database field.
+ * Model metadata (non-field information) goes in an inner class named
+ ``Meta``.
+ * Metadata used for Django's admin site goes into an inner class named
+ ``Admin``.
+ * With all of this, Django gives you an automatically-generated
+ database-access API, which is explained in the `Database API reference`_.
+
+A companion to this document is the `official repository of model examples`_.
+(In the Django source distribution, these examples are in the
+``tests/modeltests`` directory.)
+
+.. _Database API reference: http://www.djangoproject.com/documentation/db_api/
+.. _official repository of model examples: http://www.djangoproject.com/documentation/models/
+
+Quick example
+=============
+
+This example model defines a ``Person``, which has a ``first_name`` and
+``last_name``::
+
+ from django.db import models
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+``first_name`` and ``last_name`` are *fields* of the model. Each field is
+specified as a class attribute, and each attribute maps to a database column.
+
+The above ``Person`` model would create a database table like this::
+
+ CREATE TABLE myapp_person (
+ "id" serial NOT NULL PRIMARY KEY,
+ "first_name" varchar(30) NOT NULL,
+ "last_name" varchar(30) NOT NULL
+ );
+
+Some technical notes:
+
+ * The name of the table, ``myapp_person``, is automatically derived from
+ some model metadata but can be overridden. See _`Table names` below.
+ * An ``id`` field is added automatically, but this behavior can be
+ overriden. See `Automatic primary key fields`_ below.
+ * The ``CREATE TABLE`` SQL in this example is formatted using PostgreSQL
+ syntax, but it's worth noting Django uses SQL tailored to the database
+ backend specified in your `settings file`_.
+
+.. _settings file: http://www.djangoproject.com/documentation/settings/
+
+Fields
+======
+
+The most important part of a model -- and the only required part of a model --
+is the list of database fields it defines. Fields are specified by class
+attributes.
+
+Example::
+
+ class Musician(models.Model):
+ first_name = models.CharField(maxlength=50)
+ last_name = models.CharField(maxlength=50)
+ instrument = models.CharField(maxlength=100)
+
+ class Album(models.Model):
+ artist = models.ForeignKey(Musician)
+ name = models.CharField(maxlength=100)
+ release_date = models.DateField()
+ num_stars = models.IntegerField()
+
+Field name restrictions
+-----------------------
+
+Django places only two restrictions on model field names:
+
+ 1. A field name cannot be a Python reserved word, because that would result
+ in a Python syntax error. For example::
+
+ class Example(models.Model):
+ pass = models.IntegerField() # 'pass' is a reserved word!
+
+ 2. A field name cannot contain more than one underscore in a row, due to
+ the way Django's query lookup syntax works. For example::
+
+ class Example(models.Model):
+ foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
+
+These limitations can be worked around, though, because your field name doesn't
+necessarily have to match your database column name. See `db_column`_ below.
+
+SQL reserved words, such as ``join``, ``where`` or ``select``, *are* allowed as
+model field names, because Django escapes all database table names and column
+names in every underlying SQL query. It uses the quoting syntax of your
+particular database engine.
+
+Field types
+-----------
+
+Each field in your model should be an instance of the appropriate ``Field``
+class. Django uses the field class types to determine a few things:
+
+ * The database column type (e.g. ``INTEGER``, ``VARCHAR``).
+ * The widget to use in Django's admin interface, if you care to use it
+ (e.g. ``<input type="text">``, ``<select>``).
+ * The minimal validation requirements, used in Django's admin and in
+ manipulators.
+
+Here are all available field types:
+
+``AutoField``
+~~~~~~~~~~~~~
+
+An ``IntegerField`` that automatically increments according to available IDs.
+You usually won't need to use this directly; a primary key field will
+automatically be added to your model if you don't specify otherwise. See
+`Automatic primary key fields`_.
+
+``BooleanField``
+~~~~~~~~~~~~~~~~
+
+A true/false field.
+
+The admin represents this as a checkbox.
+
+``CharField``
+~~~~~~~~~~~~~
+
+A string field, for small- to large-sized strings.
+
+For large amounts of text, use ``TextField``.
+
+The admin represents this as an ``<input type="text">`` (a single-line input).
+
+``CharField`` has an extra required argument, ``maxlength``, the maximum length
+(in characters) of the field. The maxlength is enforced at the database level
+and in Django's validation.
+
+``CommaSeparatedIntegerField``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A field of integers separated by commas. As in ``CharField``, the ``maxlength``
+argument is required.
+
+``DateField``
+~~~~~~~~~~~~~
+
+A date field. Has a few extra optional arguments:
+
+ ====================== ===================================================
+ Argument Description
+ ====================== ===================================================
+ ``auto_now`` Automatically set the field to now every time the
+ object is saved. Useful for "last-modified"
+ timestamps. Note that the current date is *always*
+ used; it's not just a default value that you can
+ override.
+
+ ``auto_now_add`` Automatically set the field to now when the object
+ is first created. Useful for creation of
+ timestamps. Note that the current date is *always*
+ used; it's not just a default value that you can
+ override.
+ ====================== ===================================================
+
+The admin represents this as an ``<input type="text">`` with a JavaScript
+calendar and a shortcut for "Today."
+
+``DateTimeField``
+~~~~~~~~~~~~~~~~~
+
+A date and time field. Takes the same extra options as ``DateField``.
+
+The admin represents this as two ``<input type="text">`` fields, with
+JavaScript shortcuts.
+
+``EmailField``
+~~~~~~~~~~~~~~
+
+A ``CharField`` that checks that the value is a valid e-mail address.
+This doesn't accept ``maxlength``; its ``maxlength`` is automatically set to
+75.
+
+``FileField``
+~~~~~~~~~~~~~
+
+A file-upload field.
+
+Has an extra required argument, ``upload_to``, a local filesystem path to
+which files should be upload. This path may contain `strftime formatting`_,
+which will be replaced by the date/time of the file upload (so that
+uploaded files don't fill up the given directory).
+
+The admin represents this as an ``<input type="file">`` (a file-upload widget).
+
+Using a ``FileField`` or an ``ImageField`` (see below) in a model takes a few
+steps:
+
+ 1. In your settings file, you'll need to define ``MEDIA_ROOT`` as the
+ full path to a directory where you'd like Django to store uploaded
+ files. (For performance, these files are not stored in the database.)
+ Define ``MEDIA_URL`` as the base public URL of that directory. Make
+ sure that this directory is writable by the Web server's user
+ account.
+
+ 2. Add the ``FileField`` or ``ImageField`` to your model, making sure
+ to define the ``upload_to`` option to tell Django to which
+ subdirectory of ``MEDIA_ROOT`` it should upload files.
+
+ 3. All that will be stored in your database is a path to the file
+ (relative to ``MEDIA_ROOT``). You'll most likely want to use the
+ convenience ``get_<fieldname>_url`` function provided by Django. For
+ example, if your ``ImageField`` is called ``mug_shot``, you can get
+ the absolute URL to your image in a template with ``{{
+ object.get_mug_shot_url }}``.
+
+For example, say your ``MEDIA_ROOT`` is set to ``'/home/media'``, and
+``upload_to`` is set to ``'photos/%Y/%m/%d'``. The ``'%Y/%m/%d'`` part of
+``upload_to`` is strftime formatting; ``'%Y'`` is the four-digit year,
+``'%m'`` is the two-digit month and ``'%d'`` is the two-digit day. If you
+upload a file on Jan. 15, 2007, it will be saved in the directory
+``/home/media/photos/2007/01/15``.
+
+Note that whenever you deal with uploaded files, you should pay close attention
+to where you're uploading them and what type of files they are, to avoid
+security holes. *Validate all uploaded files* so that you're sure the files are
+what you think they are. For example, if you blindly let somebody upload files,
+without validation, to a directory that's within your Web server's document
+root, then somebody could upload a CGI or PHP script and execute that script by
+visiting its URL on your site. Don't allow that.
+
+.. _`strftime formatting`: http://docs.python.org/lib/module-time.html#l2h-1941
+
+``FilePathField``
+~~~~~~~~~~~~~~~~~
+
+A field whose choices are limited to the filenames in a certain directory
+on the filesystem. Has three special arguments, of which the first is
+required:
+
+ ====================== ===================================================
+ Argument Description
+ ====================== ===================================================
+ ``path`` Required. The absolute filesystem path to a
+ directory from which this ``FilePathField`` should
+ get its choices. Example: ``"/home/images"``.
+
+ ``match`` Optional. A regular expression, as a string, that
+ ``FilePathField`` will use to filter filenames.
+ Note that the regex will be applied to the
+ base filename, not the full path. Example:
+ ``"foo.*\.txt^"``, which will match a file called
+ ``foo23.txt`` but not ``bar.txt`` or ``foo23.gif``.
+
+ ``recursive`` Optional. Either ``True`` or ``False``. Default is
+ ``False``. Specifies whether all subdirectories of
+ ``path`` should be included.
+ ====================== ===================================================
+
+Of course, these arguments can be used together.
+
+The one potential gotcha is that ``match`` applies to the base filename,
+not the full path. So, this example::
+
+ FilePathField(path="/home/images", match="foo.*", recursive=True)
+
+...will match ``/home/images/foo.gif`` but not ``/home/images/foo/bar.gif``
+because the ``match`` applies to the base filename (``foo.gif`` and
+``bar.gif``).
+
+``FloatField``
+~~~~~~~~~~~~~~
+
+A floating-point number. Has two **required** arguments:
+
+ ====================== ===================================================
+ Argument Description
+ ====================== ===================================================
+ ``max_digits`` The maximum number of digits allowed in the number.
+
+ ``decimal_places`` The number of decimal places to store with the
+ number.
+ ====================== ===================================================
+
+For example, to store numbers up to 999 with a resolution of 2 decimal places,
+you'd use::
+
+ models.FloatField(..., max_digits=5, decimal_places=2)
+
+And to store numbers up to approximately one billion with a resolution of 10
+decimal places::
+
+ models.FloatField(..., max_digits=19, decimal_places=10)
+
+The admin represents this as an ``<input type="text">`` (a single-line input).
+
+``ImageField``
+~~~~~~~~~~~~~~
+
+Like ``FileField``, but validates that the uploaded object is a valid
+image. Has two extra optional arguments, ``height_field`` and
+``width_field``, which, if set, will be auto-populated with the height and
+width of the image each time a model instance is saved.
+
+Requires the `Python Imaging Library`_.
+
+.. _Python Imaging Library: http://www.pythonware.com/products/pil/
+
+``IntegerField``
+~~~~~~~~~~~~~~~~
+
+An integer.
+
+The admin represents this as an ``<input type="text">`` (a single-line input).
+
+``IPAddressField``
+~~~~~~~~~~~~~~~~~~
+
+An IP address, in string format (i.e. "24.124.1.30").
+
+The admin represents this as an ``<input type="text">`` (a single-line input).
+
+``NullBooleanField``
+~~~~~~~~~~~~~~~~~~~~
+
+Like a ``BooleanField``, but allows ``NULL`` as one of the options. Use this
+instead of a ``BooleanField`` with ``null=True``.
+
+The admin represents this as a ``<select>`` box with "Unknown", "Yes" and "No" choices.
+
+``PhoneNumberField``
+~~~~~~~~~~~~~~~~~~~~
+
+A ``CharField`` that checks that the value is a valid U.S.A.-style phone
+number (in the format ``XXX-XXX-XXXX``).
+
+``PositiveIntegerField``
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Like an ``IntegerField``, but must be positive.
+
+``PositiveSmallIntegerField``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Like a ``PositiveIntegerField``, but only allows values under a certain
+(database-dependent) point.
+
+``SlugField``
+~~~~~~~~~~~~~
+
+"Slug" is a newspaper term. A slug is a short label for something,
+containing only letters, numbers, underscores or hyphens. They're generally
+used in URLs.
+
+Like a CharField, you can specify ``maxlength``. If ``maxlength`` is
+not specified, Django will use a default length of 50.
+
+Implies ``db_index=True``.
+
+Accepts an extra option, ``prepopulate_from``, which is a list of fields
+from which to auto-populate the slug, via JavaScript, in the object's admin
+form::
+
+ models.SlugField(prepopulate_from=("pre_name", "name"))
+
+``prepopulate_from`` doesn't accept DateTimeFields.
+
+The admin represents ``SlugField`` as an ``<input type="text">`` (a
+single-line input).
+
+``SmallIntegerField``
+~~~~~~~~~~~~~~~~~~~~~
+
+Like an ``IntegerField``, but only allows values under a certain
+(database-dependent) point.
+
+``TextField``
+~~~~~~~~~~~~~
+
+A large text field.
+
+The admin represents this as a ``<textarea>`` (a multi-line input).
+
+``TimeField``
+~~~~~~~~~~~~~
+
+A time. Accepts the same auto-population options as ``DateField`` and
+``DateTimeField``.
+
+The admin represents this as an ``<input type="text">`` with some
+JavaScript shortcuts.
+
+``URLField``
+~~~~~~~~~~~~
+
+A field for a URL. If the ``verify_exists`` option is ``True`` (default),
+the URL given will be checked for existence (i.e., the URL actually loads
+and doesn't give a 404 response).
+
+The admin represents this as an ``<input type="text">`` (a single-line input).
+
+``USStateField``
+~~~~~~~~~~~~~~~~
+
+A two-letter U.S. state abbreviation.
+
+The admin represents this as an ``<input type="text">`` (a single-line input).
+
+``XMLField``
+~~~~~~~~~~~~
+
+A ``TextField`` that checks that the value is valid XML that matches a
+given schema. Takes one required argument, ``schema_path``, which is the
+filesystem path to a RelaxNG_ schema against which to validate the field.
+
+.. _RelaxNG: http://www.relaxng.org/
+
+Field options
+-------------
+
+The following arguments are available to all field types. All are optional.
+
+``null``
+~~~~~~~~
+
+If ``True``, Django will store empty values as ``NULL`` in the database.
+Default is ``False``.
+
+Note that empty string values will always get stored as empty strings, not
+as ``NULL`` -- so use ``null=True`` for non-string fields such as integers,
+booleans and dates.
+
+Avoid using ``null`` on string-based fields such as ``CharField`` and
+``TextField`` unless you have an excellent reason. If a string-based field
+has ``null=True``, that means it has two possible values for "no data":
+``NULL``, and the empty string. In most cases, it's redundant to have two
+possible values for "no data;" Django convention is to use the empty
+string, not ``NULL``.
+
+``blank``
+~~~~~~~~~
+
+If ``True``, the field is allowed to be blank.
+
+Note that this is different than ``null``. ``null`` is purely
+database-related, whereas ``blank`` is validation-related. If a field has
+``blank=True``, validation on Django's admin site will allow entry of an
+empty value. If a field has ``blank=False``, the field will be required.
+
+``choices``
+~~~~~~~~~~~
+
+An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
+field.
+
+If this is given, Django's admin will use a select box instead of the
+standard text field and will limit choices to the choices given.
+
+A choices list looks like this::
+
+ YEAR_IN_SCHOOL_CHOICES = (
+ ('FR', 'Freshman'),
+ ('SO', 'Sophomore'),
+ ('JR', 'Junior'),
+ ('SR', 'Senior'),
+ ('GR', 'Graduate'),
+ )
+
+The first element in each tuple is the actual value to be stored. The
+second element is the human-readable name for the option.
+
+The choices list can be defined either as part of your model class::
+
+ class Foo(models.Model):
+ GENDER_CHOICES = (
+ ('M', 'Male'),
+ ('F', 'Female'),
+ )
+ gender = models.CharField(maxlength=1, choices=GENDER_CHOICES)
+
+or outside your model class altogether::
+
+ GENDER_CHOICES = (
+ ('M', 'Male'),
+ ('F', 'Female'),
+ )
+ class Foo(models.Model):
+ gender = models.CharField(maxlength=1, choices=GENDER_CHOICES)
+
+For each model field that has ``choices`` set, Django will add a method to
+retrieve the human-readable name for the field's current value. See
+`get_FOO_display`_ in the database API documentation.
+
+.. _get_FOO_display: ../db_api/#get-foo-display
+
+Finally, note that choices can be any iterable object -- not necessarily a
+list or tuple. This lets you construct choices dynamically. But if you find
+yourself hacking ``choices`` to be dynamic, you're probably better off using
+a proper database table with a ``ForeignKey``. ``choices`` is meant for static
+data that doesn't change much, if ever.
+
+``core``
+~~~~~~~~
+
+For objects that are edited inline to a related object.
+
+In the Django admin, if all "core" fields in an inline-edited object are
+cleared, the object will be deleted.
+
+It is an error to have an inline-editable relation without at least one
+``core=True`` field.
+
+Please note that each field marked "core" is treated as a required field by the
+Django admin site. Essentially, this means you should put ``core=True`` on all
+required fields in your related object that is being edited inline.
+
+``db_column``
+~~~~~~~~~~~~~
+
+The name of the database column to use for this field. If this isn't given,
+Django will use the field's name.
+
+If your database column name is an SQL reserved word, or contains
+characters that aren't allowed in Python variable names -- notably, the
+hyphen -- that's OK. Django quotes column and table names behind the
+scenes.
+
+``db_index``
+~~~~~~~~~~~~
+
+If ``True``, ``django-admin.py sqlindexes`` will output a ``CREATE INDEX``
+statement for this field.
+
+``default``
+~~~~~~~~~~~
+
+The default value for the field.
+
+``editable``
+~~~~~~~~~~~~
+
+If ``False``, the field will not be editable in the admin or via form
+processing using the object's ``AddManipulator`` or ``ChangeManipulator``
+classes. Default is ``True``.
+
+``help_text``
+~~~~~~~~~~~~~
+
+Extra "help" text to be displayed under the field on the object's admin
+form. It's useful for documentation even if your object doesn't have an
+admin form.
+
+``primary_key``
+~~~~~~~~~~~~~~~
+
+If ``True``, this field is the primary key for the model.
+
+If you don't specify ``primary_key=True`` for any fields in your model,
+Django will automatically add this field::
+
+ id = models.AutoField('ID', primary_key=True)
+
+Thus, you don't need to set ``primary_key=True`` on any of your fields
+unless you want to override the default primary-key behavior.
+
+``primary_key=True`` implies ``blank=False``, ``null=False`` and
+``unique=True``. Only one primary key is allowed on an object.
+
+``radio_admin``
+~~~~~~~~~~~~~~~
+
+By default, Django's admin uses a select-box interface (<select>) for
+fields that are ``ForeignKey`` or have ``choices`` set. If ``radio_admin``
+is set to ``True``, Django will use a radio-button interface instead.
+
+Don't use this for a field unless it's a ``ForeignKey`` or has ``choices``
+set.
+
+``unique``
+~~~~~~~~~~
+
+If ``True``, this field must be unique throughout the table.
+
+This is enforced at the database level and at the Django admin-form level.
+
+``unique_for_date``
+~~~~~~~~~~~~~~~~~~~
+
+Set this to the name of a ``DateField`` or ``DateTimeField`` to require
+that this field be unique for the value of the date field.
+
+For example, if you have a field ``title`` that has
+``unique_for_date="pub_date"``, then Django wouldn't allow the entry of
+two records with the same ``title`` and ``pub_date``.
+
+This is enforced at the Django admin-form level but not at the database level.
+
+``unique_for_month``
+~~~~~~~~~~~~~~~~~~~~
+
+Like ``unique_for_date``, but requires the field to be unique with respect
+to the month.
+
+``unique_for_year``
+~~~~~~~~~~~~~~~~~~~
+
+Like ``unique_for_date`` and ``unique_for_month``.
+
+``validator_list``
+~~~~~~~~~~~~~~~~~~
+
+A list of extra validators to apply to the field. Each should be a callable
+that takes the parameters ``field_data, all_data`` and raises
+``django.core.validators.ValidationError`` for errors. (See the
+`validator docs`_.)
+
+Django comes with quite a few validators. They're in ``django.core.validators``.
+
+.. _validator docs: http://www.djangoproject.com/documentation/forms/#validators
+
+Verbose field names
+-------------------
+
+Each field type, except for ``ForeignKey``, ``ManyToManyField`` and
+``OneToOneField``, takes an optional first positional argument -- a
+verbose name. If the verbose name isn't given, Django will automatically create
+it using the field's attribute name, converting underscores to spaces.
+
+In this example, the verbose name is ``"Person's first name"``::
+
+ first_name = models.CharField("Person's first name", maxlength=30)
+
+In this example, the verbose name is ``"first name"``::
+
+ first_name = models.CharField(maxlength=30)
+
+``ForeignKey``, ``ManyToManyField`` and ``OneToOneField`` require the first
+argument to be a model class, so use the ``verbose_name`` keyword argument::
+
+ poll = models.ForeignKey(Poll, verbose_name="the related poll")
+ sites = models.ManyToManyField(Site, verbose_name="list of sites")
+ place = models.OneToOneField(Place, verbose_name="related place")
+
+Convention is not to capitalize the first letter of the ``verbose_name``.
+Django will automatically capitalize the first letter where it needs to.
+
+Relationships
+-------------
+
+Clearly, the power of relational databases lies in relating tables to each
+other. Django offers ways to define the three most common types of database
+relationships: Many-to-one, many-to-many and one-to-one.
+
+Many-to-one relationships
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To define a many-to-one relationship, use ``ForeignKey``. You use it just like
+any other ``Field`` type: by including it as a class attribute of your model.
+
+``ForeignKey`` requires a positional argument: The class to which the model is
+related.
+
+For example, if a ``Car`` model has a ``Manufacturer`` -- that is, a
+``Manufacturer`` makes multiple cars but each ``Car`` only has one
+``Manufacturer`` -- use the following definitions::
+
+ class Manufacturer(models.Model):
+ # ...
+
+ class Car(models.Model):
+ manufacturer = models.ForeignKey(Manufacturer)
+ # ...
+
+To create a recursive relationship -- an object that has a many-to-one
+relationship with itself -- use ``models.ForeignKey('self')``.
+
+If you need to create a relationship on a model that has not yet been defined,
+you can use the name of the model, rather than the model object itself::
+
+ class Car(models.Model):
+ manufacturer = models.ForeignKey('Manufacturer')
+ # ...
+
+ class Manufacturer(models.Model):
+ # ...
+
+Note, however, that you can only use strings to refer to models in the same
+models.py file -- you cannot use a string to reference a model in a different
+application, or to reference a model that has been imported from elsewhere.
+
+Behind the scenes, Django appends ``"_id"`` to the field name to create its
+database column name. In the above example, the database table for the ``Car``
+model will have a ``manufacturer_id`` column. (You can change this explicitly
+by specifying ``db_column``; see ``db_column`` below.) However, your code
+should never have to deal with the database column name, unless you write
+custom SQL. You'll always deal with the field names of your model object.
+
+It's suggested, but not required, that the name of a ``ForeignKey`` field
+(``manufacturer`` in the example above) be the name of the model, lowercase.
+You can, of course, call the field whatever you want. For example::
+
+ class Car(models.Model):
+ company_that_makes_it = models.ForeignKey(Manufacturer)
+ # ...
+
+See the `Many-to-one relationship model example`_ for a full example.
+
+.. _Many-to-one relationship model example: http://www.djangoproject.com/documentation/models/many_to_one/
+
+``ForeignKey`` fields take a number of extra arguments for defining how the
+relationship should work. All are optional:
+
+ ======================= ============================================================
+ Argument Description
+ ======================= ============================================================
+ ``edit_inline`` If not ``False``, this related object is edited
+ "inline" on the related object's page. This means
+ that the object will not have its own admin
+ interface. Use either ``models.TABULAR`` or ``models.STACKED``,
+ which, respectively, designate whether the inline-editable
+ objects are displayed as a table or as a "stack" of
+ fieldsets.
+
+ ``limit_choices_to`` A dictionary of lookup arguments and values (see
+ the `Database API reference`_) that limit the
+ available admin choices for this object. Use this
+ with ``models.LazyDate`` to limit choices of objects
+ by date. For example::
+
+ limit_choices_to = {'pub_date__lte': models.LazyDate()}
+
+ only allows the choice of related objects with a
+ ``pub_date`` before the current date/time to be
+ chosen.
+
+ Instead of a dictionary this can also be a ``Q`` object
+ (an object with a ``get_sql()`` method) for more complex
+ queries.
+
+ Not compatible with ``edit_inline``.
+
+ ``max_num_in_admin`` For inline-edited objects, this is the maximum
+ number of related objects to display in the admin.
+ Thus, if a pizza could only have up to 10
+ toppings, ``max_num_in_admin=10`` would ensure
+ that a user never enters more than 10 toppings.
+
+ Note that this doesn't ensure more than 10 related
+ toppings ever get created. It simply controls the
+ admin interface; it doesn't enforce things at the
+ Python API level or database level.
+
+ ``min_num_in_admin`` The minimum number of related objects displayed in
+ the admin. Normally, at the creation stage,
+ ``num_in_admin`` inline objects are shown, and at
+ the edit stage ``num_extra_on_change`` blank
+ objects are shown in addition to all pre-existing
+ related objects. However, no fewer than
+ ``min_num_in_admin`` related objects will ever be
+ displayed.
+
+ ``num_extra_on_change`` The number of extra blank related-object fields to
+ show at the change stage.
+
+ ``num_in_admin`` The default number of inline objects to display
+ on the object page at the add stage.
+
+ ``raw_id_admin`` Only display a field for the integer to be entered
+ instead of a drop-down menu. This is useful when
+ related to an object type that will have too many
+ rows to make a select box practical.
+
+ Not used with ``edit_inline``.
+
+ ``related_name`` The name to use for the relation from the related
+ object back to this one. See the
+ `related objects documentation`_ for a full
+ explanation and example.
+
+ ``to_field`` The field on the related object that the relation
+ is to. By default, Django uses the primary key of
+ the related object.
+ ======================= ============================================================
+
+.. _`Database API reference`: http://www.djangoproject.com/documentation/db_api/
+.. _related objects documentation: http://www.djangoproject.com/documentation/db_api/#related-objects
+
+Many-to-many relationships
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To define a many-to-many relationship, use ``ManyToManyField``. You use it just
+like any other ``Field`` type: by including it as a class attribute of your
+model.
+
+``ManyToManyField`` requires a positional argument: The class to which the
+model is related.
+
+For example, if a ``Pizza`` has multiple ``Topping`` objects -- that is, a
+``Topping`` can be on multiple pizzas and each ``Pizza`` has multiple toppings --
+here's how you'd represent that::
+
+ class Topping(models.Model):
+ # ...
+
+ class Pizza(models.Model):
+ # ...
+ toppings = models.ManyToManyField(Topping)
+
+As with ``ForeignKey``, a relationship to self can be defined by using the
+string ``'self'`` instead of the model name, and you can refer to as-yet
+undefined models by using a string containing the model name. However, you
+can only use strings to refer to models in the same models.py file -- you
+cannot use a string to reference a model in a different application, or to
+reference a model that has been imported from elsewhere.
+
+It's suggested, but not required, that the name of a ``ManyToManyField``
+(``toppings`` in the example above) be a plural describing the set of related
+model objects.
+
+Behind the scenes, Django creates an intermediary join table to represent the
+many-to-many relationship.
+
+It doesn't matter which model gets the ``ManyToManyField``, but you only need
+it in one of the models -- not in both.
+
+Generally, ``ManyToManyField`` instances should go in the object that's going
+to be edited in the admin interface, if you're using Django's admin. In the
+above example, ``toppings`` is in ``Pizza`` (rather than ``Topping`` having a
+``pizzas`` ``ManyToManyField`` ) because it's more natural to think about a
+``Pizza`` having toppings than a topping being on multiple pizzas. The way it's
+set up above, the ``Pizza`` admin form would let users select the toppings.
+
+See the `Many-to-many relationship model example`_ for a full example.
+
+.. _Many-to-many relationship model example: http://www.djangoproject.com/documentation/models/many_to_many/
+
+``ManyToManyField`` objects take a number of extra arguments for defining how
+the relationship should work. All are optional:
+
+ ======================= ============================================================
+ Argument Description
+ ======================= ============================================================
+ ``related_name`` See the description under ``ForeignKey`` above.
+
+ ``filter_interface`` Use a nifty unobtrusive Javascript "filter" interface
+ instead of the usability-challenged ``<select multiple>``
+ in the admin form for this object. The value should be
+ ``models.HORIZONTAL`` or ``models.VERTICAL`` (i.e.
+ should the interface be stacked horizontally or
+ vertically).
+
+ ``limit_choices_to`` See the description under ``ForeignKey`` above.
+
+ ``symmetrical`` Only used in the definition of ManyToManyFields on self.
+ Consider the following model:
+
+ class Person(models.Model):
+ friends = models.ManyToManyField("self")
+
+ When Django processes this model, it identifies that it has
+ a ``ManyToManyField`` on itself, and as a result, it
+ doesn't add a ``person_set`` attribute to the ``Person``
+ class. Instead, the ``ManyToManyField`` is assumed to be
+ symmetrical -- that is, if I am your friend, then you are
+ my friend.
+
+ If you do not want symmetry in ``ManyToMany`` relationships
+ with ``self``, set ``symmetrical`` to ``False``. This will
+ force Django to add the descriptor for the reverse
+ relationship, allowing ``ManyToMany`` relationships to be
+ non-symmetrical.
+
+ ``db_table`` The name of the table to create for storing the many-to-many
+ data. If this is not provided, Django will assume a default
+ name based upon the names of the two tables being joined.
+
+ ======================= ============================================================
+
+One-to-one relationships
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The semantics of one-to-one relationships will be changing soon, so we don't
+recommend you use them. If that doesn't scare you away, keep reading.
+
+To define a one-to-one relationship, use ``OneToOneField``. You use it just
+like any other ``Field`` type: by including it as a class attribute of your
+model.
+
+This is most useful on the primary key of an object when that object "extends"
+another object in some way.
+
+``OneToOneField`` requires a positional argument: The class to which the
+model is related.
+
+For example, if you're building a database of "places", you would build pretty
+standard stuff such as address, phone number, etc. in the database. Then, if you
+wanted to build a database of restaurants on top of the places, instead of
+repeating yourself and replicating those fields in the ``Restaurant`` model, you
+could make ``Restaurant`` have a ``OneToOneField`` to ``Place`` (because a
+restaurant "is-a" place).
+
+As with ``ForeignKey``, a relationship to self can be defined by using the
+string ``"self"`` instead of the model name; references to as-yet undefined
+models can be made by using a string containing the model name.
+
+This ``OneToOneField`` will actually replace the primary key ``id`` field
+(since one-to-one relations share the same primary key), and will be displayed
+as a read-only field when you edit an object in the admin interface:
+
+See the `One-to-one relationship model example`_ for a full example.
+
+.. _One-to-one relationship model example: http://www.djangoproject.com/documentation/models/one_to_one/
+
+Meta options
+============
+
+Give your model metadata by using an inner ``class Meta``, like so::
+
+ class Foo(models.Model):
+ bar = models.CharField(maxlength=30)
+
+ class Meta:
+ # ...
+
+Model metadata is "anything that's not a field", such as ordering options, etc.
+
+Here's a list of all possible ``Meta`` options. No options are required. Adding
+``class Meta`` to a model is completely optional.
+
+``db_table``
+------------
+
+The name of the database table to use for the model::
+
+ db_table = 'music_album'
+
+If this isn't given, Django will use ``app_label + '_' + model_class_name``.
+See "Table names" below for more.
+
+If your database table name is an SQL reserved word, or contains characters
+that aren't allowed in Python variable names -- notably, the hyphen --
+that's OK. Django quotes column and table names behind the scenes.
+
+``get_latest_by``
+-----------------
+
+The name of a ``DateField`` or ``DateTimeField`` in the model. This specifies
+the default field to use in your model ``Manager``'s ``latest()`` method.
+
+Example::
+
+ get_latest_by = "order_date"
+
+See the `docs for latest()`_ for more.
+
+.. _docs for latest(): http://www.djangoproject.com/documentation/db_api/#latest-field-name-none
+
+``order_with_respect_to``
+-------------------------
+
+Marks this object as "orderable" with respect to the given field. This is
+almost always used with related objects to allow them to be ordered with
+respect to a parent object. For example, if an ``Answer`` relates to a
+``Question`` object, and a question has more than one answer, and the order
+of answers matters, you'd do this::
+
+ class Answer(models.Model):
+ question = models.ForeignKey(Question)
+ # ...
+
+ class Meta:
+ order_with_respect_to = 'question'
+
+``ordering``
+------------
+
+The default ordering for the object, for use when obtaining lists of objects::
+
+ ordering = ['-order_date']
+
+This is a tuple or list of strings. Each string is a field name with an
+optional "-" prefix, which indicates descending order. Fields without a
+leading "-" will be ordered ascending. Use the string "?" to order randomly.
+
+For example, to order by a ``pub_date`` field ascending, use this::
+
+ ordering = ['pub_date']
+
+To order by ``pub_date`` descending, use this::
+
+ ordering = ['-pub_date']
+
+To order by ``pub_date`` descending, then by ``author`` ascending, use this::
+
+ ordering = ['-pub_date', 'author']
+
+See `Specifying ordering`_ for more examples.
+
+Note that, regardless of how many fields are in ``ordering``, the admin
+site uses only the first field.
+
+.. _Specifying ordering: http://www.djangoproject.com/documentation/models/ordering/
+
+``permissions``
+---------------
+
+Extra permissions to enter into the permissions table when creating this
+object. Add, delete and change permissions are automatically created for
+each object that has ``admin`` set. This example specifies an extra
+permission, ``can_deliver_pizzas``::
+
+ permissions = (("can_deliver_pizzas", "Can deliver pizzas"),)
+
+This is a list or tuple of 2-tuples in the format
+``(permission_code, human_readable_permission_name)``.
+
+``unique_together``
+-------------------
+
+Sets of field names that, taken together, must be unique::
+
+ unique_together = (("driver", "restaurant"),)
+
+This is a list of lists of fields that must be unique when considered
+together. It's used in the Django admin and is enforced at the database
+level (i.e., the appropriate ``UNIQUE`` statements are included in the
+``CREATE TABLE`` statement).
+
+``verbose_name``
+----------------
+
+A human-readable name for the object, singular::
+
+ verbose_name = "pizza"
+
+If this isn't given, Django will use a munged version of the class name:
+``CamelCase`` becomes ``camel case``.
+
+``verbose_name_plural``
+-----------------------
+
+The plural name for the object::
+
+ verbose_name_plural = "stories"
+
+If this isn't given, Django will use ``verbose_name + "s"``.
+
+Table names
+===========
+
+To save you time, Django automatically derives the name of the database table
+from the name of your model class and the app that contains it. A model's
+database table name is constructed by joining the model's "app label" -- the
+name you used in ``manage.py startapp`` -- to the model's class name, with an
+underscore between them.
+
+For example, if you have an app ``bookstore`` (as created by
+``manage.py startapp bookstore``), a model defined as ``class Book`` will have
+a database table named ``bookstore_book``.
+
+To override the database table name, use the ``db_table`` parameter in
+``class Meta``.
+
+Automatic primary key fields
+============================
+
+By default, Django gives each model the following field::
+
+ id = models.AutoField(primary_key=True)
+
+This is an auto-incrementing primary key.
+
+If you'd like to specify a custom primary key, just specify ``primary_key=True``
+on one of your fields. If Django sees you've explicitly set ``primary_key``, it
+won't add the automatic ``id`` column.
+
+Each model requires exactly one field to have ``primary_key=True``.
+
+Admin options
+=============
+
+If you want your model to be visible to Django's admin site, give your model an
+inner ``"class Admin"``, like so::
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ # Admin options go here
+ pass
+
+The ``Admin`` class tells Django how to display the model in the admin site.
+
+Here's a list of all possible ``Admin`` options. None of these options are
+required. To use an admin interface without specifying any options, use
+``pass``, like so::
+
+ class Admin:
+ pass
+
+Adding ``class Admin`` to a model is completely optional.
+
+``date_hierarchy``
+------------------
+
+Set ``date_hierarchy`` to the name of a ``DateField`` or ``DateTimeField`` in
+your model, and the change list page will include a date-based drilldown
+navigation by that field.
+
+Example::
+
+ date_hierarchy = 'pub_date'
+
+``fields``
+----------
+
+Set ``fields`` to control the layout of admin "add" and "change" pages.
+
+``fields`` is a list of two-tuples, in which each two-tuple represents a
+``<fieldset>`` on the admin form page. (A ``<fieldset>`` is a "section" of the
+form.)
+
+The two-tuples are in the format ``(name, field_options)``, where ``name`` is a
+string representing the title of the fieldset and ``field_options`` is a
+dictionary of information about the fieldset, including a list of fields to be
+displayed in it.
+
+A full example, taken from the ``django.contrib.flatpages.FlatPage`` model::
+
+ class Admin:
+ fields = (
+ (None, {
+ 'fields': ('url', 'title', 'content', 'sites')
+ }),
+ ('Advanced options', {
+ 'classes': 'collapse',
+ 'fields' : ('enable_comments', 'registration_required', 'template_name')
+ }),
+ )
+
+This results in an admin page that looks like:
+
+ .. image:: http://media.djangoproject.com/img/doc/flatfiles_admin.png
+
+If ``fields`` isn't given, Django will default to displaying each field that
+isn't an ``AutoField`` and has ``editable=True``, in a single fieldset, in
+the same order as the fields are defined in the model.
+
+The ``field_options`` dictionary can have the following keys:
+
+``fields``
+~~~~~~~~~~
+
+A tuple of field names to display in this fieldset. This key is required.
+
+Example::
+
+ {
+ 'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
+ }
+
+To display multiple fields on the same line, wrap those fields in their own
+tuple. In this example, the ``first_name`` and ``last_name`` fields will
+display on the same line::
+
+ {
+ 'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
+ }
+
+``classes``
+~~~~~~~~~~~
+
+A string containing extra CSS classes to apply to the fieldset.
+
+Example::
+
+ {
+ 'classes': 'wide',
+ }
+
+Apply multiple classes by separating them with spaces. Example::
+
+ {
+ 'classes': 'wide extrapretty',
+ }
+
+Two useful classes defined by the default admin-site stylesheet are
+``collapse`` and ``wide``. Fieldsets with the ``collapse`` style will be
+initially collapsed in the admin and replaced with a small "click to expand"
+link. Fieldsets with the ``wide`` style will be given extra horizontal space.
+
+``description``
+~~~~~~~~~~~~~~~
+
+A string of optional extra text to be displayed at the top of each fieldset,
+under the heading of the fieldset. It's used verbatim, so you can use any HTML
+and you must escape any special HTML characters (such as ampersands) yourself.
+
+``js``
+------
+
+A list of strings representing URLs of JavaScript files to link into the admin
+screen via ``<script src="">`` tags. This can be used to tweak a given type of
+admin page in JavaScript or to provide "quick links" to fill in default values
+for certain fields.
+
+If you use relative URLs -- URLs that don't start with ``http://`` or ``/`` --
+then the admin site will automatically prefix these links with
+``settings.ADMIN_MEDIA_PREFIX``.
+
+``list_display``
+----------------
+
+Set ``list_display`` to control which fields are displayed on the change list
+page of the admin.
+
+Example::
+
+ list_display = ('first_name', 'last_name')
+
+If you don't set ``list_display``, the admin site will display a single column
+that displays the ``__str__()`` representation of each object.
+
+A few special cases to note about ``list_display``:
+
+ * If the field is a ``ForeignKey``, Django will display the ``__str__()``
+ of the related object.
+
+ * ``ManyToManyField`` fields aren't supported, because that would entail
+ executing a separate SQL statement for each row in the table. If you
+ want to do this nonetheless, give your model a custom method, and add
+ that method's name to ``list_display``. (See below for more on custom
+ methods in ``list_display``.)
+
+ * If the field is a ``BooleanField`` or ``NullBooleanField``, Django will
+ display a pretty "on" or "off" icon instead of ``True`` or ``False``.
+
+ * If the string given is a method of the model, Django will call it and
+ display the output. This method should have a ``short_description``
+ function attribute, for use as the header for the field.
+
+ Here's a full example model::
+
+ class Person(models.Model):
+ name = models.CharField(maxlength=50)
+ birthday = models.DateField()
+
+ class Admin:
+ list_display = ('name', 'decade_born_in')
+
+ def decade_born_in(self):
+ return self.birthday.strftime('%Y')[:3] + "0's"
+ decade_born_in.short_description = 'Birth decade'
+
+ * If the string given is a method of the model, Django will HTML-escape the
+ output by default. If you'd rather not escape the output of the method,
+ give the method an ``allow_tags`` attribute whose value is ``True``.
+
+ Here's a full example model::
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=50)
+ last_name = models.CharField(maxlength=50)
+ color_code = models.CharField(maxlength=6)
+
+ class Admin:
+ list_display = ('first_name', 'last_name', 'colored_name')
+
+ def colored_name(self):
+ return '<span style="color: #%s;">%s %s</span>' % (self.color_code, self.first_name, self.last_name)
+ colored_name.allow_tags = True
+
+ * If the string given is a method of the model that returns True or False
+ Django will display a pretty "on" or "off" icon if you give the method a
+ ``boolean`` attribute whose value is ``True``.
+
+ Here's a full example model::
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=50)
+ birthday = models.DateField()
+
+ class Admin:
+ list_display = ('name', 'born_in_fifties')
+
+ def born_in_fifties(self):
+ return self.birthday.strftime('%Y')[:3] == 5
+ born_in_fifties.boolean = True
+
+
+ * The ``__str__()`` method is just as valid in ``list_display`` as any
+ other model method, so it's perfectly OK to do this::
+
+ list_display = ('__str__', 'some_other_field')
+
+ * Usually, elements of ``list_display`` that aren't actual database fields
+ can't be used in sorting (because Django does all the sorting at the
+ database level).
+
+ However, if an element of ``list_display`` represents a certain database
+ field, you can indicate this fact by setting the ``admin_order_field``
+ attribute of the item.
+
+ For example::
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=50)
+ color_code = models.CharField(maxlength=6)
+
+ class Admin:
+ list_display = ('first_name', 'colored_first_name')
+
+ def colored_first_name(self):
+ return '<span style="color: #%s;">%s</span>' % (self.color_code, self.first_name)
+ colored_first_name.allow_tags = True
+ colored_first_name.admin_order_field = 'first_name'
+
+ The above will tell Django to order by the ``first_name`` field when
+ trying to sort by ``colored_first_name`` in the admin.
+
+``list_display_links``
+----------------------
+
+Set ``list_display_links`` to control which fields in ``list_display`` should
+be linked to the "change" page for an object.
+
+By default, the change list page will link the first column -- the first field
+specified in ``list_display`` -- to the change page for each item. But
+``list_display_links`` lets you change which columns are linked. Set
+``list_display_links`` to a list or tuple of field names (in the same format as
+``list_display``) to link.
+
+``list_display_links`` can specify one or many field names. As long as the
+field names appear in ``list_display``, Django doesn't care how many (or how
+few) fields are linked. The only requirement is: If you want to use
+``list_display_links``, you must define ``list_display``.
+
+In this example, the ``first_name`` and ``last_name`` fields will be linked on
+the change list page::
+
+ class Admin:
+ list_display = ('first_name', 'last_name', 'birthday')
+ list_display_links = ('first_name', 'last_name')
+
+Finally, note that in order to use ``list_display_links``, you must define
+``list_display``, too.
+
+``list_filter``
+---------------
+
+Set ``list_filter`` to activate filters in the right sidebar of the change list
+page of the admin. This should be a list of field names, and each specified
+field should be either a ``BooleanField``, ``DateField``, ``DateTimeField``
+or ``ForeignKey``.
+
+This example, taken from the ``django.contrib.auth.models.User`` model, shows
+how both ``list_display`` and ``list_filter`` work::
+
+ class Admin:
+ list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
+ list_filter = ('is_staff', 'is_superuser')
+
+The above code results in an admin change list page that looks like this:
+
+ .. image:: http://media.djangoproject.com/img/doc/users_changelist.png
+
+(This example also has ``search_fields`` defined. See below.)
+
+``list_per_page``
+-----------------
+
+Set ``list_per_page`` to control how many items appear on each paginated admin
+change list page. By default, this is set to ``100``.
+
+``list_select_related``
+-----------------------
+
+Set ``list_select_related`` to tell Django to use ``select_related()`` in
+retrieving the list of objects on the admin change list page. This can save you
+a bunch of database queries.
+
+The value should be either ``True`` or ``False``. Default is ``False``.
+
+Note that Django will use ``select_related()``, regardless of this setting,
+if one of the ``list_display`` fields is a ``ForeignKey``.
+
+For more on ``select_related()``, see `the select_related() docs`_.
+
+.. _the select_related() docs: http://www.djangoproject.com/documentation/db_api/#select-related
+
+``ordering``
+------------
+
+Set ``ordering`` to specify how objects on the admin change list page should be
+ordered. This should be a list or tuple in the same format as a model's
+``ordering`` parameter.
+
+If this isn't provided, the Django admin will use the model's default ordering.
+
+``save_as``
+-----------
+
+Set ``save_as`` to enable a "save as" feature on admin change forms.
+
+Normally, objects have three save options: "Save", "Save and continue editing"
+and "Save and add another". If ``save_as`` is ``True``, "Save and add another"
+will be replaced by a "Save as" button.
+
+"Save as" means the object will be saved as a new object (with a new ID),
+rather than the old object.
+
+By default, ``save_as`` is set to ``False``.
+
+``save_on_top``
+---------------
+
+Set ``save_on_top`` to add save buttons across the top of your admin change
+forms.
+
+Normally, the save buttons appear only at the bottom of the forms. If you set
+``save_on_top``, the buttons will appear both on the top and the bottom.
+
+By default, ``save_on_top`` is set to ``False``.
+
+``search_fields``
+-----------------
+
+Set ``search_fields`` to enable a search box on the admin change list page.
+This should be set to a list of field names that will be searched whenever
+somebody submits a search query in that text box.
+
+These fields should be some kind of text field, such as ``CharField`` or
+``TextField``. You can also perform a related lookup on a ``ForeignKey`` with
+the lookup API "follow" notation::
+
+ search_fields = ['foreign_key__related_fieldname']
+
+When somebody does a search in the admin search box, Django splits the search
+query into words and returns all objects that contain each of the words, case
+insensitive, where each word must be in at least one of ``search_fields``. For
+example, if ``search_fields`` is set to ``['first_name', 'last_name']`` and a
+user searches for ``john lennon``, Django will do the equivalent of this SQL
+``WHERE`` clause::
+
+ WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%')
+ AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')
+
+For faster and/or more restrictive searches, prefix the field name
+with an operator:
+
+``^``
+ Matches the beginning of the field. For example, if ``search_fields`` is
+ set to ``['^first_name', '^last_name']`` and a user searches for
+ ``john lennon``, Django will do the equivalent of this SQL ``WHERE``
+ clause::
+
+ WHERE (first_name ILIKE 'john%' OR last_name ILIKE 'john%')
+ AND (first_name ILIKE 'lennon%' OR last_name ILIKE 'lennon%')
+
+ This query is more efficient than the normal ``'%john%'`` query, because
+ the database only needs to check the beginning of a column's data, rather
+ than seeking through the entire column's data. Plus, if the column has an
+ index on it, some databases may be able to use the index for this query,
+ even though it's a ``LIKE`` query.
+
+``=``
+ Matches exactly, case-insensitive. For example, if
+ ``search_fields`` is set to ``['=first_name', '=last_name']`` and
+ a user searches for ``john lennon``, Django will do the equivalent
+ of this SQL ``WHERE`` clause::
+
+ WHERE (first_name ILIKE 'john' OR last_name ILIKE 'john')
+ AND (first_name ILIKE 'lennon' OR last_name ILIKE 'lennon')
+
+ Note that the query input is split by spaces, so, following this example,
+ it's not currently not possible to search for all records in which
+ ``first_name`` is exactly ``'john winston'`` (containing a space).
+
+``@``
+ Performs a full-text match. This is like the default search method but uses
+ an index. Currently this is only available for MySQL.
+
+Managers
+========
+
+A ``Manager`` is the interface through which database query operations are
+provided to Django models. At least one ``Manager`` exists for every model in
+a Django application.
+
+The way ``Manager`` classes work is documented in the `Retrieving objects`_
+section of the database API docs, but this section specifically touches on
+model options that customize ``Manager`` behavior.
+
+.. _Retrieving objects: http://www.djangoproject.com/documentation/db_api/#retrieving-objects
+
+Manager names
+-------------
+
+By default, Django adds a ``Manager`` with the name ``objects`` to every Django
+model class. However, if you want to use ``objects`` as a field name, or if you
+want to use a name other than ``objects`` for the ``Manager``, you can rename
+it on a per-model basis. To rename the ``Manager`` for a given class, define a
+class attribute of type ``models.Manager()`` on that model. For example::
+
+ from django.db import models
+
+ class Person(models.Model):
+ #...
+ people = models.Manager()
+
+Using this example model, ``Person.objects`` will generate an
+``AttributeError`` exception, but ``Person.people.all()`` will provide a list
+of all ``Person`` objects.
+
+Custom Managers
+---------------
+
+You can use a custom ``Manager`` in a particular model by extending the base
+``Manager`` class and instantiating your custom ``Manager`` in your model.
+
+There are two reasons you might want to customize a ``Manager``: to add extra
+``Manager`` methods, and/or to modify the initial ``QuerySet`` the ``Manager``
+returns.
+
+Adding extra Manager methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Adding extra ``Manager`` methods is the preferred way to add "table-level"
+functionality to your models. (For "row-level" functionality -- i.e., functions
+that act on a single instance of a model object -- use _`Model methods`, not
+custom ``Manager`` methods.)
+
+A custom ``Manager`` method can return anything you want. It doesn't have to
+return a ``QuerySet``.
+
+For example, this custom ``Manager`` offers a method ``with_counts()``, which
+returns a list of all ``OpinionPoll`` objects, each with an extra
+``num_responses`` attribute that is the result of an aggregate query::
+
+ class PollManager(models.Manager):
+ def with_counts(self):
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT p.id, p.question, p.poll_date, COUNT(*)
+ FROM polls_opinionpoll p, polls_response r
+ WHERE p.id = r.poll_id
+ GROUP BY 1, 2, 3
+ ORDER BY 3 DESC""")
+ result_list = []
+ for row in cursor.fetchall():
+ p = self.model(id=row[0], question=row[1], poll_date=row[2])
+ p.num_responses = row[3]
+ result_list.append(p)
+ return result_list
+
+ class OpinionPoll(models.Model):
+ question = models.CharField(maxlength=200)
+ poll_date = models.DateField()
+ objects = PollManager()
+
+ class Response(models.Model):
+ poll = models.ForeignKey(Poll)
+ person_name = models.CharField(maxlength=50)
+ response = models.TextField()
+
+With this example, you'd use ``OpinionPoll.objects.with_counts()`` to return
+that list of ``OpinionPoll`` objects with ``num_responses`` attributes.
+
+Another thing to note about this example is that ``Manager`` methods can
+access ``self.model`` to get the model class to which they're attached.
+
+Modifying initial Manager QuerySets
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A ``Manager``'s base ``QuerySet`` returns all objects in the system. For
+example, using this model::
+
+ class Book(models.Model):
+ title = models.CharField(maxlength=100)
+ author = models.CharField(maxlength=50)
+
+...the statement ``Book.objects.all()`` will return all books in the database.
+
+You can override a ``Manager``\'s base ``QuerySet`` by overriding the
+``Manager.get_query_set()`` method. ``get_query_set()`` should return a
+``QuerySet`` with the properties you require.
+
+For example, the following model has *two* ``Manager``\s -- one that returns
+all objects, and one that returns only the books by Roald Dahl::
+
+ # First, define the Manager subclass.
+ class DahlBookManager(models.Manager):
+ def get_query_set(self):
+ return super(DahlBookManager, self).get_query_set().filter(author='Roald Dahl')
+
+ # Then hook it into the Book model explicitly.
+ class Book(models.Model):
+ title = models.CharField(maxlength=100)
+ author = models.CharField(maxlength=50)
+
+ objects = models.Manager() # The default manager.
+ dahl_objects = DahlBookManager() # The Dahl-specific manager.
+
+With this sample model, ``Book.objects.all()`` will return all books in the
+database, but ``Book.dahl_objects.all()`` will only return the ones written by
+Roald Dahl.
+
+Of course, because ``get_query_set()`` returns a ``QuerySet`` object, you can
+use ``filter()``, ``exclude()`` and all the other ``QuerySet`` methods on it.
+So these statements are all legal::
+
+ Book.dahl_objects.all()
+ Book.dahl_objects.filter(title='Matilda')
+ Book.dahl_objects.count()
+
+This example also pointed out another interesting technique: using multiple
+managers on the same model. You can attach as many ``Manager()`` instances to
+a model as you'd like. This is an easy way to define common "filters" for your
+models.
+
+For example::
+
+ class MaleManager(models.Manager):
+ def get_query_set(self):
+ return super(MaleManager, self).get_query_set().filter(sex='M')
+
+ class FemaleManager(models.Manager):
+ def get_query_set(self):
+ return super(FemaleManager, self).get_query_set().filter(sex='F')
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=50)
+ last_name = models.CharField(maxlength=50)
+ sex = models.CharField(maxlength=1, choices=(('M', 'Male'), ('F', 'Female')))
+ people = models.Manager()
+ men = MaleManager()
+ women = FemaleManager()
+
+This example allows you to request ``Person.men.all()``, ``Person.women.all()``,
+and ``Person.people.all()``, yielding predictable results.
+
+If you use custom ``Manager`` objects, take note that the first ``Manager``
+Django encounters (in order by which they're defined in the model) has a
+special status. Django interprets the first ``Manager`` defined in a class as
+the "default" ``Manager``. Certain operations -- such as Django's admin site --
+use the default ``Manager`` to obtain lists of objects, so it's generally a
+good idea for the first ``Manager`` to be relatively unfiltered. In the last
+example, the ``people`` ``Manager`` is defined first -- so it's the default
+``Manager``.
+
+Model methods
+=============
+
+Define custom methods on a model to add custom "row-level" functionality to
+your objects. Whereas ``Manager`` methods are intended to do "table-wide"
+things, model methods should act on a particular model instance.
+
+This is a valuable technique for keeping business logic in one place -- the
+model.
+
+For example, this model has a few custom methods::
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=50)
+ last_name = models.CharField(maxlength=50)
+ birth_date = models.DateField()
+ address = models.CharField(maxlength=100)
+ city = models.CharField(maxlength=50)
+ state = models.USStateField() # Yes, this is America-centric...
+
+ def baby_boomer_status(self):
+ "Returns the person's baby-boomer status."
+ import datetime
+ if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31):
+ return "Baby boomer"
+ if self.birth_date < datetime.date(1945, 8, 1):
+ return "Pre-boomer"
+ return "Post-boomer"
+
+ def is_midwestern(self):
+ "Returns True if this person is from the Midwest."
+ return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO')
+
+ def _get_full_name(self):
+ "Returns the person's full name."
+ return '%s %s' % (self.first_name, self.last_name)
+ full_name = property(_get_full_name)
+
+The last method in this example is a *property*. `Read more about properties`_.
+
+.. _Read more about properties: http://www.python.org/download/releases/2.2/descrintro/#property
+
+A few object methods have special meaning:
+
+``__str__``
+-----------
+
+``__str__()`` is a Python "magic method" that defines what should be returned
+if you call ``str()`` on the object. Django uses ``str(obj)`` in a number of
+places, most notably as the value displayed to render an object in the Django
+admin site and as the value inserted into a template when it displays an
+object. Thus, you should always return a nice, human-readable string for the
+object's ``__str__``. Although this isn't required, it's strongly encouraged.
+
+For example::
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=50)
+ last_name = models.CharField(maxlength=50)
+
+ def __str__(self):
+ return '%s %s' % (self.first_name, self.last_name)
+
+``get_absolute_url``
+--------------------
+
+Define a ``get_absolute_url()`` method to tell Django how to calculate the
+URL for an object. For example::
+
+ def get_absolute_url(self):
+ return "/people/%i/" % self.id
+
+Django uses this in its admin interface. If an object defines
+``get_absolute_url()``, the object-editing page will have a "View on site"
+link that will jump you directly to the object's public view, according to
+``get_absolute_url()``.
+
+Also, a couple of other bits of Django, such as the syndication-feed framework,
+use ``get_absolute_url()`` as a convenience to reward people who've defined the
+method.
+
+It's good practice to use ``get_absolute_url()`` in templates, instead of
+hard-coding your objects' URLs. For example, this template code is bad::
+
+ <a href="/people/{{ object.id }}/">{{ object.name }}</a>
+
+But this template code is good::
+
+ <a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
+
+The ``permalink`` decorator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The problem with the way we wrote ``get_absolute_url()`` above is that it
+slightly violates the DRY principle: the URL for this object is defined both
+in the URLConf file and in the model.
+
+You can further decouple your models from the URLconf using the ``permalink``
+decorator. This decorator is passed the view function and any parameters you
+would use for accessing this instance directly. Django then works out the
+correct full URL path using the URLconf. For example::
+
+ from django.db.models import permalink
+
+ def get_absolute_url(self):
+ return ('people.views.details', str(self.id))
+ get_absolute_url = permalink(get_absolute_url)
+
+In this way, you're tying the model's absolute URL to the view that is used
+to display it, without repeating the URL information anywhere. You can still
+use the ``get_absolute_url`` method in templates, as before.
+
+Executing custom SQL
+--------------------
+
+Feel free to write custom SQL statements in custom model methods and
+module-level methods. The object ``django.db.connection`` represents the
+current database connection. To use it, call ``connection.cursor()`` to get a
+cursor object. Then, call ``cursor.execute(sql, [params])`` to execute the SQL
+and ``cursor.fetchone()`` or ``cursor.fetchall()`` to return the resulting
+rows. Example::
+
+ def my_custom_sql(self):
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
+ row = cursor.fetchone()
+ return row
+
+``connection`` and ``cursor`` simply use the standard `Python DB-API`_. If
+you're not familiar with the Python DB-API, note that the SQL statement in
+``cursor.execute()`` uses placeholders, ``"%s"``, rather than adding parameters
+directly within the SQL. If you use this technique, the underlying database
+library will automatically add quotes and escaping to your parameter(s) as
+necessary. (Also note that Django expects the ``"%s"`` placeholder, *not* the
+``"?"`` placeholder, which is used by the SQLite Python bindings. This is for
+the sake of consistency and sanity.)
+
+A final note: If all you want to do is a custom ``WHERE`` clause, you can just
+just the ``where``, ``tables`` and ``params`` arguments to the standard lookup
+API. See `Other lookup options`_.
+
+.. _Python DB-API: http://www.python.org/peps/pep-0249.html
+.. _Other lookup options: http://www.djangoproject.com/documentation/db_api/#extra-params-select-where-tables
+
+Overriding default model methods
+--------------------------------
+
+As explained in the `database API docs`_, each model gets a few methods
+automatically -- most notably, ``save()`` and ``delete()``. You can override
+these methods to alter behavior.
+
+A classic use-case for overriding the built-in methods is if you want something
+to happen whenever you save an object. For example::
+
+ class Blog(models.Model):
+ name = models.CharField(maxlength=100)
+ tagline = models.TextField()
+
+ def save(self):
+ do_something()
+ super(Blog, self).save() # Call the "real" save() method.
+ do_something_else()
+
+You can also prevent saving::
+
+ class Blog(models.Model):
+ name = models.CharField(maxlength=100)
+ tagline = models.TextField()
+
+ def save(self):
+ if self.name == "Yoko Ono's blog":
+ return # Yoko shall never have her own blog!
+ else:
+ super(Blog, self).save() # Call the "real" save() method.
+
+.. _database API docs: http://www.djangoproject.com/documentation/db_api/
+
+Models across files
+===================
+
+It's perfectly OK to relate a model to one from another app. To do this, just
+import the related model at the top of the model that holds your model. Then,
+just refer to the other model class wherever needed. For example::
+
+ from mysite.geography.models import ZipCode
+
+ class Restaurant(models.Model):
+ # ...
+ zip_code = models.ForeignKey(ZipCode)
+
+Using models
+============
+
+Once you have created your models, the final step is to tell Django you're
+going to *use* those models.
+
+Do this by editing your settings file and changing the ``INSTALLED_APPS``
+setting to add the name of the module that contains your ``models.py``.
+
+For example, if the models for your application live in the module
+``mysite.myapp.models`` (the package structure that is created for an
+application by the ``manage.py startapp`` script), ``INSTALLED_APPS`` should
+read, in part::
+
+ INSTALLED_APPS = (
+ #...
+ 'mysite.myapp',
+ #...
+ )
+
+Providing initial SQL data
+==========================
+
+Django provides a hook for passing the database arbitrary SQL that's executed
+just after the CREATE TABLE statements. Use this hook, for example, if you want
+to populate default records, or create SQL functions, automatically.
+
+The hook is simple: Django just looks for a file called
+``<appname>/sql/<modelname>.sql``, where ``<appname>`` is your app directory and
+``<modelname>`` is the model's name in lowercase.
+
+In the ``Person`` example model at the top of this document, assuming it lives
+in an app called ``myapp``, you could add arbitrary SQL to the file
+``myapp/sql/person.sql``. Here's an example of what the file might contain::
+
+ INSERT INTO myapp_person (first_name, last_name) VALUES ('John', 'Lennon');
+ INSERT INTO myapp_person (first_name, last_name) VALUES ('Paul', 'McCartney');
+
+Each SQL file, if given, is expected to contain valid SQL. The SQL files are
+piped directly into the database after all of the models' table-creation
+statements have been executed.
+
+The SQL files are read by the ``sqlinitialdata``, ``sqlreset``, ``sqlall`` and
+``reset`` commands in ``manage.py``. Refer to the `manage.py documentation`_
+for more information.
+
+Note that if you have multiple SQL data files, there's no guarantee of the
+order in which they're executed. The only thing you can assume is that, by the
+time your custom data files are executed, all the database tables already will
+have been created.
+
+.. _`manage.py documentation`: http://www.djangoproject.com/documentation/django_admin/#sqlinitialdata-appname-appname
+
+Database-backend-specific SQL data
+----------------------------------
+
+There's also a hook for backend-specific SQL data. For example, you can have
+separate initial-data files for PostgreSQL and MySQL. For each app, Django
+looks for a file called ``<appname>/sql/<modelname>.<backend>.sql``, where
+``<appname>`` is your app directory, ``<modelname>`` is the model's name in
+lowercase and ``<backend>`` is the value of ``DATABASE_ENGINE`` in your
+settings file (e.g., ``postgresql``, ``mysql``).
+
+Backend-specific SQL data is executed before non-backend-specific SQL data. For
+example, if your app contains the files ``sql/person.sql`` and
+``sql/person.postgresql.sql`` and you're installing the app on PostgreSQL,
+Django will execute the contents of ``sql/person.postgresql.sql`` first, then
+``sql/person.sql``.
diff --git a/google_appengine/lib/django/docs/modpython.txt b/google_appengine/lib/django/docs/modpython.txt
new file mode 100644
index 0000000..2c99975
--- /dev/null
+++ b/google_appengine/lib/django/docs/modpython.txt
@@ -0,0 +1,244 @@
+=================================
+How to use Django with mod_python
+=================================
+
+Apache_ with `mod_python`_ currently is the preferred setup for using Django
+on a production server.
+
+mod_python is similar to `mod_perl`_ : It embeds Python within Apache and loads
+Python code into memory when the server starts. Code stays in memory throughout
+the life of an Apache process, which leads to significant performance gains over
+other server arrangements.
+
+Django requires Apache 2.x and mod_python 3.x, and you should use Apache's
+`prefork MPM`_, as opposed to the `worker MPM`_.
+
+You may also be interested in `How to use Django with FastCGI`_.
+
+.. _Apache: http://httpd.apache.org/
+.. _mod_python: http://www.modpython.org/
+.. _mod_perl: http://perl.apache.org/
+.. _prefork MPM: http://httpd.apache.org/docs/2.2/mod/prefork.html
+.. _worker MPM: http://httpd.apache.org/docs/2.2/mod/worker.html
+.. _How to use Django with FastCGI: ../fastcgi/
+
+Basic configuration
+===================
+
+To configure Django with mod_python, first make sure you have Apache installed,
+with the mod_python module activated.
+
+Then edit your ``httpd.conf`` file and add the following::
+
+ <Location "/mysite/">
+ SetHandler python-program
+ PythonHandler django.core.handlers.modpython
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ PythonDebug On
+ </Location>
+
+...and replace ``mysite.settings`` with the Python path to your settings file.
+
+This tells Apache: "Use mod_python for any URL at or under '/mysite/', using the
+Django mod_python handler." It passes the value of ``DJANGO_SETTINGS_MODULE``
+so mod_python knows which settings to use.
+
+Note that we're using the ``<Location>`` directive, not the ``<Directory>``
+directive. The latter is used for pointing at places on your filesystem,
+whereas ``<Location>`` points at places in the URL structure of a Web site.
+``<Directory>`` would be meaningless here.
+
+Also, if you've manually altered your ``PYTHONPATH`` to put your Django project
+on it, you'll need to tell mod_python::
+
+ PythonPath "['/path/to/project'] + sys.path"
+
+You can also add directives such as ``PythonAutoReload Off`` for performance.
+See the `mod_python documentation`_ for a full list of options.
+
+Note that you should set ``PythonDebug Off`` on a production server. If you
+leave ``PythonDebug On``, your users would see ugly (and revealing) Python
+tracebacks if something goes wrong within mod_python.
+
+Restart Apache, and any request to /mysite/ or below will be served by Django.
+Note that Django's URLconfs won't trim the "/mysite/" -- they get passed the
+full URL.
+
+When deploying Django sites on mod_python, you'll need to restart Apache each
+time you make changes to your Python code.
+
+Multiple Django installations on the same Apache
+================================================
+
+It's entirely possible to run multiple Django installations on the same Apache
+instance. Just use ``VirtualHost`` for that, like so::
+
+ NameVirtualHost *
+
+ <VirtualHost *>
+ ServerName www.example.com
+ # ...
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ </VirtualHost>
+
+ <VirtualHost *>
+ ServerName www2.example.com
+ # ...
+ SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
+ </VirtualHost>
+
+If you need to put two Django installations within the same ``VirtualHost``,
+you'll need to take a special precaution to ensure mod_python's cache doesn't
+mess things up. Use the ``PythonInterpreter`` directive to give different
+``<Location>`` directives separate interpreters::
+
+ <VirtualHost *>
+ ServerName www.example.com
+ # ...
+ <Location "/something">
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ PythonInterpreter mysite
+ </Location>
+
+ <Location "/otherthing">
+ SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
+ PythonInterpreter mysite_other
+ </Location>
+ </VirtualHost>
+
+The values of ``PythonInterpreter`` don't really matter, as long as they're
+different between the two ``Location`` blocks.
+
+Running a development server with mod_python
+============================================
+
+If you use mod_python for your development server, you can avoid the hassle of
+having to restart the server each time you make code changes. Just set
+``MaxRequestsPerChild 1`` in your ``httpd.conf`` file to force Apache to reload
+everything for each request. But don't do that on a production server, or we'll
+revoke your Django privileges.
+
+If you're the type of programmer who debugs using scattered ``print``
+statements, note that ``print`` statements have no effect in mod_python; they
+don't appear in the Apache log, as one might expect. If you have the need to
+print debugging information in a mod_python setup, either do this::
+
+ assert False, the_value_i_want_to_see
+
+Or add the debugging information to the template of your page.
+
+.. _mod_python documentation: http://modpython.org/live/current/doc-html/directives.html
+
+Serving media files
+===================
+
+Django doesn't serve media files itself; it leaves that job to whichever Web
+server you choose.
+
+We recommend using a separate Web server -- i.e., one that's not also running
+Django -- for serving media. Here are some good choices:
+
+* lighttpd_
+* TUX_
+* A stripped-down version of Apache_
+
+If, however, you have no option but to serve media files on the same Apache
+``VirtualHost`` as Django, here's how you can turn off mod_python for a
+particular part of the site::
+
+ <Location "/media/">
+ SetHandler None
+ </Location>
+
+Just change ``Location`` to the root URL of your media files. You can also use
+``<LocationMatch>`` to match a regular expression.
+
+This example sets up Django at the site root but explicitly disables Django for
+the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or
+``.png``::
+
+ <Location "/">
+ SetHandler python-program
+ PythonHandler django.core.handlers.modpython
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ </Location>
+
+ <Location "media">
+ SetHandler None
+ </Location>
+
+ <LocationMatch "\.(jpg|gif|png)$">
+ SetHandler None
+ </LocationMatch>
+
+
+.. _lighttpd: http://www.lighttpd.net/
+.. _TUX: http://en.wikipedia.org/wiki/TUX_web_server
+.. _Apache: http://httpd.apache.org/
+
+Serving the admin files
+=======================
+
+Note that the Django development server automagically serves admin media files,
+but this is not the case when you use any other server arrangement. You're
+responsible for setting up Apache, or whichever media server you're using, to
+serve the admin files.
+
+The admin files live in (``django/contrib/admin/media``) of the Django
+distribution.
+
+Here are two recommended approaches:
+
+ 1. Create a symbolic link to the admin media files from within your
+ document root. This way, all of your Django-related files -- code
+ **and** templates -- stay in one place, and you'll still be able to
+ ``svn update`` your code to get the latest admin templates, if they
+ change.
+ 2. Or, copy the admin media files so that they live within your Apache
+ document root.
+
+Error handling
+==============
+
+When you use Apache/mod_python, errors will be caught by Django -- in other
+words, they won't propagate to the Apache level and won't appear in the Apache
+``error_log``.
+
+The exception for this is if something is really wonky in your Django setup. In
+that case, you'll see an "Internal Server Error" page in your browser and the
+full Python traceback in your Apache ``error_log`` file. The ``error_log``
+traceback is spread over multiple lines. (Yes, this is ugly and rather hard to
+read, but it's how mod_python does things.)
+
+If you get a segmentation fault
+===============================
+
+If Apache causes a segmentation fault, there are two probable causes, neither
+of which has to do with Django itself.
+
+ 1. It may be because your Python code is importing the "pyexpat" module,
+ which may conflict with the version embedded in Apache. For full
+ information, see `Expat Causing Apache Crash`_.
+ 2. It may be because you're running mod_python and mod_php in the same
+ Apache instance, with MySQL as your database backend. In some cases,
+ this causes a known mod_python issue due to version conflicts in PHP and
+ the Python MySQL backend. There's full information in the
+ `mod_python FAQ entry`_.
+
+If you continue to have problems setting up mod_python, a good thing to do is
+get a barebones mod_python site working, without the Django framework. This is
+an easy way to isolate mod_python-specific problems. `Getting mod_python Working`_
+details this procedure.
+
+The next step should be to edit your test code and add an import of any
+Django-specific code you're using -- your views, your models, your URLconf,
+your RSS configuration, etc. Put these imports in your test handler function
+and access your test URL in a browser. If this causes a crash, you've confirmed
+it's the importing of Django code that causes the problem. Gradually reduce the
+set of imports until it stops crashing, so as to find the specific module that
+causes the problem. Drop down further into modules and look into their imports,
+as necessary.
+
+.. _Expat Causing Apache Crash: http://www.dscpl.com.au/articles/modpython-006.html
+.. _mod_python FAQ entry: http://modpython.org/FAQ/faqw.py?req=show&file=faq02.013.htp
+.. _Getting mod_python Working: http://www.dscpl.com.au/articles/modpython-001.html
diff --git a/google_appengine/lib/django/docs/newforms.txt b/google_appengine/lib/django/docs/newforms.txt
new file mode 100644
index 0000000..d7ef4d2
--- /dev/null
+++ b/google_appengine/lib/django/docs/newforms.txt
@@ -0,0 +1,875 @@
+====================
+The newforms library
+====================
+
+``django.newforms`` is Django's fantastic new form-handling library. It's a
+replacement for ``django.forms``, the old form/manipulator/validation
+framework. This document explains how to use this new library.
+
+Migration plan
+==============
+
+``django.newforms`` currently is only available in Django beginning
+with the 0.96 release. the Django development version -- i.e., it's
+not available in the Django 0.95 release. For the next Django release,
+our plan is to do the following:
+
+ * As of revision [4208], we've copied the current ``django.forms`` to
+ ``django.oldforms``. This allows you to upgrade your code *now* rather
+ than waiting for the backwards-incompatible change and rushing to fix
+ your code after the fact. Just change your import statements like this::
+
+ from django import forms # old
+ from django import oldforms as forms # new
+
+ * At an undecided future date, we will move the current ``django.newforms``
+ to ``django.forms``. This will be a backwards-incompatible change, and
+ anybody who is still using the old version of ``django.forms`` at that
+ time will need to change their import statements, as described in the
+ previous bullet.
+
+ * We will remove ``django.oldforms`` in the release *after* the next Django
+ release -- the release that comes after the release in which we're
+ creating the new ``django.forms``.
+
+With this in mind, we recommend you use the following import statement when
+using ``django.newforms``::
+
+ from django import newforms as forms
+
+This way, your code can refer to the ``forms`` module, and when
+``django.newforms`` is renamed to ``django.forms``, you'll only have to change
+your ``import`` statements.
+
+If you prefer "``import *``" syntax, you can do the following::
+
+ from django.newforms import *
+
+This will import all fields, widgets, form classes and other various utilities
+into your local namespace. Some people find this convenient; others find it
+too messy. The choice is yours.
+
+Overview
+========
+
+As with the ``django.forms`` ("manipulators") system before it,
+``django.newforms`` is intended to handle HTML form display, data processing
+(validation) and redisplay. It's what you use if you want to perform
+server-side validation for an HTML form.
+
+For example, if your Web site has a contact form that visitors can use to
+send you e-mail, you'd use this library to implement the display of the HTML
+form fields, along with the form validation. Any time you need to use an HTML
+``<form>``, you can use this library.
+
+The library deals with these concepts:
+
+ * **Widget** -- A class that corresponds to an HTML form widget, e.g.
+ ``<input type="text">`` or ``<textarea>``. This handles rendering of the
+ widget as HTML.
+
+ * **Field** -- A class that is responsible for doing validation, e.g.
+ an ``EmailField`` that makes sure its data is a valid e-mail address.
+
+ * **Form** -- A collection of fields that knows how to validate itself and
+ display itself as HTML.
+
+The library is decoupled from the other Django components, such as the database
+layer, views and templates. It relies only on Django settings, a couple of
+``django.utils`` helper functions and Django's internationalization hooks (but
+you're not required to be using internationalization features to use this
+library).
+
+Form objects
+============
+
+The primary way of using the ``newforms`` library is to create a form object.
+Do this by subclassing ``django.newforms.Form`` and specifying the form's
+fields, in a declarative style that you'll be familiar with if you've used
+Django database models. In this section, we'll iteratively develop a form
+object that you might use to implement "contact me" functionality on your
+personal Web site.
+
+Start with this basic ``Form`` subclass, which we'll call ``ContactForm``::
+
+ from django import newforms as forms
+
+ class ContactForm(forms.Form):
+ subject = forms.CharField(max_length=100)
+ message = forms.CharField()
+ sender = forms.EmailField()
+ cc_myself = forms.BooleanField()
+
+A form is composed of ``Field`` objects. In this case, our form has four
+fields: ``subject``, ``message``, ``sender`` and ``cc_myself``. We'll explain
+the different types of fields -- e.g., ``CharField`` and ``EmailField`` --
+shortly.
+
+Creating ``Form`` instances
+---------------------------
+
+A ``Form`` instance is either **bound** or **unbound** to a set of data.
+
+ * If it's **bound** to a set of data, it's capable of validating that data
+ and rendering the form as HTML with the data displayed in the HTML.
+
+ * If it's **unbound**, it cannot do validation (because there's no data to
+ validate!), but it can still render the blank form as HTML.
+
+To create an unbound ``Form`` instance, simply instantiate the class::
+
+ >>> f = ContactForm()
+
+To bind data to a form, pass the data as a dictionary as the first parameter to
+your ``Form`` class constructor::
+
+ >>> data = {'subject': 'hello',
+ ... 'message': 'Hi there',
+ ... 'sender': 'foo@example.com',
+ ... 'cc_myself': True}
+ >>> f = ContactForm(data)
+
+In this dictionary, the keys are the field names, which correspond to the
+attributes in your ``Form`` class. The values are the data you're trying
+to validate. These will usually be strings, but there's no requirement that
+they be strings; the type of data you pass depends on the ``Field``, as we'll
+see in a moment.
+
+If you need to distinguish between bound and unbound form instances at runtime,
+check the value of the form's ``is_bound`` attribute::
+
+ >>> f = ContactForm()
+ >>> f.is_bound
+ False
+ >>> f = ContactForm({'subject': 'hello'})
+ >>> f.is_bound
+ True
+
+Note that passing an empty dictionary creates a *bound* form with empty data::
+
+ >>> f = ContactForm({})
+ >>> f.is_bound
+ True
+
+If you have a bound ``Form`` instance and want to change the data somehow, or
+if you want to bind an unbound ``Form`` instance to some data, create another
+``Form`` instance. There is no way to change data in a ``Form`` instance. Once
+a ``Form`` instance has been created, you should consider its data immutable,
+whether it has data or not.
+
+Using forms to validate data
+----------------------------
+
+The primary task of a ``Form`` object is to validate data. With a bound
+``Form`` instance, call the ``is_valid()`` method to run validation and return
+a boolean designating whether the data was valid::
+
+ >>> data = {'subject': 'hello',
+ ... 'message': 'Hi there',
+ ... 'sender': 'foo@example.com',
+ ... 'cc_myself': True}
+ >>> f = ContactForm(data)
+ >>> f.is_valid()
+ True
+
+Let's try with some invalid data. In this case, ``subject`` is blank (an error,
+because all fields are required by default) and ``sender`` is not a valid
+e-mail address::
+
+ >>> data = {'subject': '',
+ ... 'message': 'Hi there',
+ ... 'sender': 'invalid e-mail address',
+ ... 'cc_myself': True}
+ >>> f = ContactForm(data)
+ >>> f.is_valid()
+ False
+
+Access the ``Form`` attribute ``errors`` to get a dictionary of error messages::
+
+ >>> f.errors
+ {'sender': [u'Enter a valid e-mail address.'], 'subject': [u'This field is required.']}
+
+In this dictionary, the keys are the field names, and the values are lists of
+Unicode strings representing the error messages. The error messages are stored
+in lists because a field can have multiple error messages.
+
+You can access ``errors`` without having to call ``is_valid()`` first. The
+form's data will be validated the first time either you call ``is_valid()`` or
+access ``errors``.
+
+Behavior of unbound forms
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's meaningless to validate a form with no data, but, for the record, here's
+what happens with unbound forms::
+
+ >>> f = ContactForm()
+ >>> f.is_valid()
+ False
+ >>> f.errors
+ {}
+
+Accessing "clean" data
+----------------------
+
+Each ``Field`` in a ``Form`` class is responsible not only for validating data,
+but also for "cleaning" it -- normalizing it to a consistent format. This is a
+nice feature, because it allows data for a particular field to be input in
+a variety of ways, always resulting in consistent output.
+
+For example, ``DateField`` normalizes input into a Python ``datetime.date``
+object. Regardless of whether you pass it a string in the format
+``'1994-07-15'``, a ``datetime.date`` object or a number of other formats,
+``DateField`` will always normalize it to a ``datetime.date`` object as long as
+it's valid.
+
+Once you've created a ``Form`` instance with a set of data and validated it,
+you can access the clean data via the ``clean_data`` attribute of the ``Form``
+object::
+
+ >>> data = {'subject': 'hello',
+ ... 'message': 'Hi there',
+ ... 'sender': 'foo@example.com',
+ ... 'cc_myself': True}
+ >>> f = ContactForm(data)
+ >>> f.is_valid()
+ True
+ >>> f.clean_data
+ {'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
+
+Note that any text-based field -- such as ``CharField`` or ``EmailField`` --
+always cleans the input into a Unicode string. We'll cover the encoding
+implications later in this document.
+
+If your data does *not* validate, your ``Form`` instance will not have a
+``clean_data`` attribute::
+
+ >>> data = {'subject': '',
+ ... 'message': 'Hi there',
+ ... 'sender': 'invalid e-mail address',
+ ... 'cc_myself': True}
+ >>> f = ContactForm(data)
+ >>> f.is_valid()
+ False
+ >>> f.clean_data
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'ContactForm' object has no attribute 'clean_data'
+
+``clean_data`` will always *only* contain a key for fields defined in the
+``Form``, even if you pass extra data when you define the ``Form``. In this
+example, we pass a bunch of extra fields to the ``ContactForm`` constructor,
+but ``clean_data`` contains only the form's fields::
+
+ >>> data = {'subject': 'hello',
+ ... 'message': 'Hi there',
+ ... 'sender': 'foo@example.com',
+ ... 'cc_myself': True,
+ ... 'extra_field_1': 'foo',
+ ... 'extra_field_2': 'bar',
+ ... 'extra_field_3': 'baz'}
+ >>> f = ContactForm(data)
+ >>> f.is_valid()
+ True
+ >>> f.clean_data # Doesn't contain extra_field_1, etc.
+ {'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
+
+Behavior of unbound forms
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's meaningless to request "clean" data in a form with no data, but, for the
+record, here's what happens with unbound forms::
+
+ >>> f = ContactForm()
+ >>> f.clean_data
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'ContactForm' object has no attribute 'clean_data'
+
+Outputting forms as HTML
+------------------------
+
+The second task of a ``Form`` object is to render itself as HTML. To do so,
+simply ``print`` it::
+
+ >>> f = ContactForm()
+ >>> print f
+ <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>
+ <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
+ <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>
+ <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
+
+If the form is bound to data, the HTML output will include that data
+appropriately. For example, if a field is represented by an
+``<input type="text">``, the data will be in the ``value`` attribute. If a
+field is represented by an ``<input type="checkbox">``, then that HTML will
+include ``checked="checked"`` if appropriate::
+
+ >>> data = {'subject': 'hello',
+ ... 'message': 'Hi there',
+ ... 'sender': 'foo@example.com',
+ ... 'cc_myself': True}
+ >>> f = ContactForm(data)
+ >>> print f
+ <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" value="hello" /></td></tr>
+ <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" value="Hi there" /></td></tr>
+ <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" value="foo@example.com" /></td></tr>
+ <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" checked="checked" /></td></tr>
+
+This default output is a two-column HTML table, with a ``<tr>`` for each field.
+Notice the following:
+
+ * For flexibility, the output does *not* include the ``<table>`` and
+ ``</table>`` tags, nor does it include the ``<form>`` and ``</form>``
+ tags or an ``<input type="submit">`` tag. It's your job to do that.
+
+ * Each field type has a default HTML representation. ``CharField`` and
+ ``EmailField`` are represented by an ``<input type="text">``.
+ ``BooleanField`` is represented by an ``<input type="checkbox">``. Note
+ these are merely sensible defaults; you can specify which HTML to use for
+ a given field by using widgets, which we'll explain shortly.
+
+ * The HTML ``name`` for each tag is taken directly from its attribute name
+ in the ``ContactForm`` class.
+
+ * The text label for each field -- e.g. ``'Subject:'``, ``'Message:'`` and
+ ``'Cc myself:'`` is generated from the field name by converting all
+ underscores to spaces and upper-casing the first letter. Again, note
+ these are merely sensible defaults; you can also specify labels manually.
+
+ * Each text label is surrounded in an HTML ``<label>`` tag, which points
+ to the appropriate form field via its ``id``. Its ``id``, in turn, is
+ generated by prepending ``'id_'`` to the field name. The ``id``
+ attributes and ``<label>`` tags are included in the output by default, to
+ follow best practices, but you can change that behavior.
+
+Although ``<table>`` output is the default output style when you ``print`` a
+form, other output styles are available. Each style is available as a method on
+a form object, and each rendering method returns a Unicode object.
+
+``as_p()``
+~~~~~~~~~~
+
+``Form.as_p()`` renders the form as a series of ``<p>`` tags, with each ``<p>``
+containing one field::
+
+ >>> f = ContactForm()
+ >>> f.as_p()
+ u'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>'
+ >>> print f.as_p()
+ <p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></p>
+ <p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p>
+ <p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></p>
+ <p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
+
+``as_ul()``
+~~~~~~~~~~~
+
+``Form.as_ul()`` renders the form as a series of ``<li>`` tags, with each
+``<li>`` containing one field. It does *not* include the ``<ul>`` or ``</ul>``,
+so that you can specify any HTML attributes on the ``<ul>`` for flexibility::
+
+ >>> f = ContactForm()
+ >>> f.as_ul()
+ u'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>\n<li><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>'
+ >>> print f.as_ul()
+ <li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" /></li>
+ <li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>
+ <li><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" /></li>
+ <li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself" /></li>
+
+``as_table()``
+~~~~~~~~~~~~~~
+
+Finally, ``Form.as_table()`` outputs the form as an HTML ``<table>``. This is
+exactly the same as ``print``. In fact, when you ``print`` a form object, it
+calls its ``as_table()`` method behind the scenes::
+
+ >>> f = ContactForm()
+ >>> f.as_table()
+ u'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>'
+ >>> print f.as_table()
+ <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" /></td></tr>
+ <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
+ <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>
+ <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
+
+Configuring HTML ``<label>`` tags
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An HTML ``<label>`` tag designates which label text is associated with which
+form element. This small enhancement makes forms more usable and more accessible
+to assistive devices. It's always a good idea to use ``<label>`` tags.
+
+By default, the form rendering methods include HTML ``id`` attributes on the
+form elements and corresponding ``<label>`` tags around the labels. The ``id``
+attribute values are generated by prepending ``id_`` to the form field names.
+This behavior is configurable, though, if you want to change the ``id``
+convention or remove HTML ``id`` attributes and ``<label>`` tags entirely.
+
+Use the ``auto_id`` argument to the ``Form`` constructor to control the label
+and ``id`` behavior. This argument must be ``True``, ``False`` or a string.
+
+If ``auto_id`` is ``False``, then the form output will not include ``<label>``
+tags nor ``id`` attributes::
+
+ >>> f = ContactForm(auto_id=False)
+ >>> print f.as_table()
+ <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" /></td></tr>
+ <tr><th>Message:</th><td><input type="text" name="message" /></td></tr>
+ <tr><th>Sender:</th><td><input type="text" name="sender" /></td></tr>
+ <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr>
+ >>> print f.as_ul()
+ <li>Subject: <input type="text" name="subject" maxlength="100" /></li>
+ <li>Message: <input type="text" name="message" /></li>
+ <li>Sender: <input type="text" name="sender" /></li>
+ <li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
+ >>> print f.as_p()
+ <p>Subject: <input type="text" name="subject" maxlength="100" /></p>
+ <p>Message: <input type="text" name="message" /></p>
+ <p>Sender: <input type="text" name="sender" /></p>
+ <p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
+
+If ``auto_id`` is set to ``True``, then the form output *will* include
+``<label>`` tags and will simply use the field name as its ``id`` for each form
+field::
+
+ >>> f = ContactForm(auto_id=True)
+ >>> print f.as_table()
+ <tr><th><label for="subject">Subject:</label></th><td><input id="subject" type="text" name="subject" maxlength="100" /></td></tr>
+ <tr><th><label for="message">Message:</label></th><td><input type="text" name="message" id="message" /></td></tr>
+ <tr><th><label for="sender">Sender:</label></th><td><input type="text" name="sender" id="sender" /></td></tr>
+ <tr><th><label for="cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="cc_myself" /></td></tr>
+ >>> print f.as_ul()
+ <li><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></li>
+ <li><label for="message">Message:</label> <input type="text" name="message" id="message" /></li>
+ <li><label for="sender">Sender:</label> <input type="text" name="sender" id="sender" /></li>
+ <li><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></li>
+ >>> print f.as_p()
+ <p><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" /></p>
+ <p><label for="message">Message:</label> <input type="text" name="message" id="message" /></p>
+ <p><label for="sender">Sender:</label> <input type="text" name="sender" id="sender" /></p>
+ <p><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself" /></p>
+
+If ``auto_id`` is set to a string containing the format character ``'%s'``,
+then the form output will include ``<label>`` tags, and will generate ``id``
+attributes based on the format string. For example, for a format string
+``'field_%s'``, a field named ``subject`` will get the ``id``
+``'field_subject'``. Continuing our example::
+
+ >>> f = ContactForm(auto_id='id_for_%s')
+ >>> print f.as_table()
+ <tr><th><label for="id_for_subject">Subject:</label></th><td><input id="id_for_subject" type="text" name="subject" maxlength="100" /></td></tr>
+ <tr><th><label for="id_for_message">Message:</label></th><td><input type="text" name="message" id="id_for_message" /></td></tr>
+ <tr><th><label for="id_for_sender">Sender:</label></th><td><input type="text" name="sender" id="id_for_sender" /></td></tr>
+ <tr><th><label for="id_for_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></td></tr>
+ >>> print f.as_ul()
+ <li><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li>
+ <li><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></li>
+ <li><label for="id_for_sender">Sender:</label> <input type="text" name="sender" id="id_for_sender" /></li>
+ <li><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
+ >>> print f.as_p()
+ <p><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></p>
+ <p><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" /></p>
+ <p><label for="id_for_sender">Sender:</label> <input type="text" name="sender" id="id_for_sender" /></p>
+ <p><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></p>
+
+If ``auto_id`` is set to any other true value -- such as a string that doesn't
+include ``%s`` -- then the library will act as if ``auto_id`` is ``True``.
+
+By default, ``auto_id`` is set to the string ``'id_%s'``.
+
+Notes on field ordering
+~~~~~~~~~~~~~~~~~~~~~~~
+
+In the ``as_p()``, ``as_ul()`` and ``as_table()`` shortcuts, the fields are
+displayed in the order in which you define them in your form class. For
+example, in the ``ContactForm`` example, the fields are defined in the order
+``subject``, ``message``, ``sender``, ``cc_myself``. To reorder the HTML
+output, just change the order in which those fields are listed in the class.
+
+How errors are displayed
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you render a bound ``Form`` object, the act of rendering will automatically
+run the form's validation if it hasn't already happened, and the HTML output
+will include the validation errors as a ``<ul>`` near the field. The particular
+positioning of the error messages depends on the output method you're using::
+
+ >>> data = {'subject': '',
+ ... 'message': 'Hi there',
+ ... 'sender': 'invalid e-mail address',
+ ... 'cc_myself': True}
+ >>> f = ContactForm(data, auto_id=False)
+ >>> print f.as_table()
+ <tr><th>Subject:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" /></td></tr>
+ <tr><th>Message:</th><td><input type="text" name="message" value="Hi there" /></td></tr>
+ <tr><th>Sender:</th><td><ul class="errorlist"><li>Enter a valid e-mail address.</li></ul><input type="text" name="sender" value="invalid e-mail address" /></td></tr>
+ <tr><th>Cc myself:</th><td><input checked="checked" type="checkbox" name="cc_myself" /></td></tr>
+ >>> print f.as_ul()
+ <li><ul class="errorlist"><li>This field is required.</li></ul>Subject: <input type="text" name="subject" maxlength="100" /></li>
+ <li>Message: <input type="text" name="message" value="Hi there" /></li>
+ <li><ul class="errorlist"><li>Enter a valid e-mail address.</li></ul>Sender: <input type="text" name="sender" value="invalid e-mail address" /></li>
+ <li>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></li>
+ >>> print f.as_p()
+ <p><ul class="errorlist"><li>This field is required.</li></ul></p>
+ <p>Subject: <input type="text" name="subject" maxlength="100" /></p>
+ <p>Message: <input type="text" name="message" value="Hi there" /></p>
+ <p><ul class="errorlist"><li>Enter a valid e-mail address.</li></ul></p>
+ <p>Sender: <input type="text" name="sender" value="invalid e-mail address" /></p>
+ <p>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></p>
+
+More granular output
+~~~~~~~~~~~~~~~~~~~~
+
+The ``as_p()``, ``as_ul()`` and ``as_table()`` methods are simply shortcuts for
+lazy developers -- they're not the only way a form object can be displayed.
+
+To display the HTML for a single field in your form, use dictionary lookup
+syntax using the field's name as the key, and print the resulting object::
+
+ >>> f = ContactForm()
+ >>> print f['subject']
+ <input id="id_subject" type="text" name="subject" maxlength="100" />
+ >>> print f['message']
+ <input type="text" name="message" id="id_message" />
+ >>> print f['sender']
+ <input type="text" name="sender" id="id_sender" />
+ >>> print f['cc_myself']
+ <input type="checkbox" name="cc_myself" id="id_cc_myself" />
+
+Call ``str()`` or ``unicode()`` on the field to get its rendered HTML as a
+string or Unicode object, respectively::
+
+ >>> str(f['subject'])
+ '<input id="id_subject" type="text" name="subject" maxlength="100" />'
+ >>> unicode(f['subject'])
+ u'<input id="id_subject" type="text" name="subject" maxlength="100" />'
+
+The field-specific output honors the form object's ``auto_id`` setting::
+
+ >>> f = ContactForm(auto_id=False)
+ >>> print f['message']
+ <input type="text" name="message" />
+ >>> f = ContactForm(auto_id='id_%s')
+ >>> print f['message']
+ <input type="text" name="message" id="id_message" />
+
+For a field's list of errors, access the field's ``errors`` attribute. This
+is a list-like object that is displayed as an HTML ``<ul>`` when printed::
+
+ >>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
+ >>> f = ContactForm(data, auto_id=False)
+ >>> print f['message']
+ <input type="text" name="message" />
+ >>> f['message'].errors
+ [u'This field is required.']
+ >>> print f['message'].errors
+ <ul class="errorlist"><li>This field is required.</li></ul>
+ >>> f['subject'].errors
+ []
+ >>> print f['subject'].errors
+
+ >>> str(f['subject'].errors)
+ ''
+
+Subclassing forms
+-----------------
+
+If you subclass a custom ``Form`` class, the resulting ``Form`` class will
+include all fields of the parent class(es), followed by the fields you define
+in the subclass.
+
+In this example, ``ContactFormWithPriority`` contains all the fields from
+``ContactForm``, plus an additional field, ``priority``. The ``ContactForm``
+fields are ordered first::
+
+ >>> class ContactFormWithPriority(ContactForm):
+ ... priority = forms.CharField()
+ >>> f = ContactFormWithPriority(auto_id=False)
+ >>> print f.as_ul()
+ <li>Subject: <input type="text" name="subject" maxlength="100" /></li>
+ <li>Message: <input type="text" name="message" /></li>
+ <li>Sender: <input type="text" name="sender" /></li>
+ <li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
+ <li>Priority: <input type="text" name="priority" /></li>
+
+It's possible to subclass multiple forms, treating forms as "mix-ins." In this
+example, ``BeatleForm`` subclasses both ``PersonForm`` and ``InstrumentForm``
+(in that order), and its field list includes the fields from the parent
+classes::
+
+ >>> class PersonForm(Form):
+ ... first_name = CharField()
+ ... last_name = CharField()
+ >>> class InstrumentForm(Form):
+ ... instrument = CharField()
+ >>> class BeatleForm(PersonForm, InstrumentForm):
+ ... haircut_type = CharField()
+ >>> b = BeatleForm(auto_id=False)
+ >>> print b.as_ul()
+ <li>First name: <input type="text" name="first_name" /></li>
+ <li>Last name: <input type="text" name="last_name" /></li>
+ <li>Instrument: <input type="text" name="instrument" /></li>
+ <li>Haircut type: <input type="text" name="haircut_type" /></li>
+
+Fields
+======
+
+When you create a ``Form`` class, the most important part is defining the
+fields of the form. Each field has custom validation logic, along with a few
+other hooks.
+
+Although the primary way you'll use ``Field`` classes is in ``Form`` classes,
+you can also instantiate them and use them directly to get a better idea of
+how they work. Each ``Field`` instance has a ``clean()`` method, which takes
+a single argument and either raises a ``django.newforms.ValidationError``
+exception or returns the clean value::
+
+ >>> f = forms.EmailField()
+ >>> f.clean('foo@example.com')
+ u'foo@example.com'
+ >>> f.clean(u'foo@example.com')
+ u'foo@example.com'
+ >>> f.clean('invalid e-mail address')
+ Traceback (most recent call last):
+ ...
+ ValidationError: [u'Enter a valid e-mail address.']
+
+If you've used Django's old forms/validation framework, take care in noticing
+this ``ValidationError`` is different than the previous ``ValidationError``.
+This one lives at ``django.newforms.ValidationError`` rather than
+``django.core.validators.ValidationError``.
+
+Core field arguments
+--------------------
+
+Each ``Field`` class constructor takes at least these arguments. Some
+``Field`` classes take additional, field-specific arguments, but the following
+should *always* be available:
+
+``required``
+~~~~~~~~~~~~
+
+By default, each ``Field`` class assumes the value is required, so if you pass
+an empty value -- either ``None`` or the empty string (``""``) -- then
+``clean()`` will raise a ``ValidationError`` exception::
+
+ >>> f = forms.CharField()
+ >>> f.clean('foo')
+ u'foo'
+ >>> f.clean('')
+ Traceback (most recent call last):
+ ...
+ ValidationError: [u'This field is required.']
+ >>> f.clean(None)
+ Traceback (most recent call last):
+ ...
+ ValidationError: [u'This field is required.']
+ >>> f.clean(' ')
+ u' '
+ >>> f.clean(0)
+ u'0'
+ >>> f.clean(True)
+ u'True'
+ >>> f.clean(False)
+ u'False'
+
+To specify that a field is *not* required, pass ``required=False`` to the
+``Field`` constructor::
+
+ >>> f = forms.CharField(required=False)
+ >>> f.clean('foo')
+ u'foo'
+ >>> f.clean('')
+ u''
+ >>> f.clean(None)
+ u''
+ >>> f.clean(0)
+ u'0'
+ >>> f.clean(True)
+ u'True'
+ >>> f.clean(False)
+ u'False'
+
+If a ``Field`` has ``required=False`` and you pass ``clean()`` an empty value,
+then ``clean()`` will return a *normalized* empty value rather than raising
+``ValidationError``. For ``CharField``, this will be a Unicode empty string.
+For other ``Field`` classes, it might be ``None``. (This varies from field to
+field.)
+
+``label``
+~~~~~~~~~
+
+The ``label`` argument lets you specify the "human-friendly" label for this
+field. This is used when the ``Field`` is displayed in a ``Form``.
+
+As explained in _`Outputting forms as HTML` above, the default label for a
+``Field`` is generated from the field name by converting all underscores to
+spaces and upper-casing the first letter. Specify ``label`` if that default
+behavior doesn't result in an adequate label.
+
+Here's a full example ``Form`` that implements ``label`` for two of its fields.
+We've specified ``auto_id=False`` to simplify the output::
+
+ >>> class CommentForm(forms.Form):
+ ... name = forms.CharField(label='Your name')
+ ... url = forms.URLField(label='Your Web site', required=False)
+ ... comment = forms.CharField()
+ >>> f = CommentForm(auto_id=False)
+ >>> print f
+ <tr><th>Your name:</th><td><input type="text" name="name" /></td></tr>
+ <tr><th>Your Web site:</th><td><input type="text" name="url" /></td></tr>
+ <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
+
+``initial``
+~~~~~~~~~~~
+
+The ``initial`` argument lets you specify the initial value to use when
+rendering this ``Field`` in an unbound ``Form``.
+
+The use-case for this is when you want to display an "empty" form in which a
+field is initialized to a particular value. For example::
+
+ >>> class CommentForm(forms.Form):
+ ... name = forms.CharField(initial='Your name')
+ ... url = forms.URLField(initial='http://')
+ ... comment = forms.CharField()
+ >>> f = CommentForm(auto_id=False)
+ >>> print f
+ <tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr>
+ <tr><th>Url:</th><td><input type="text" name="url" value="http://" /></td></tr>
+ <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
+
+You may be thinking, why not just pass a dictionary of the initial values as
+data when displaying the form? Well, if you do that, you'll trigger validation,
+and the HTML output will include any validation errors::
+
+ >>> class CommentForm(forms.Form):
+ ... name = forms.CharField()
+ ... url = forms.URLField()
+ ... comment = forms.CharField()
+ >>> default_data = {'name': 'Your name', 'url': 'http://'}
+ >>> f = CommentForm(default_data, auto_id=False)
+ >>> print f
+ <tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr>
+ <tr><th>Url:</th><td><ul class="errorlist"><li>Enter a valid URL.</li></ul><input type="text" name="url" value="http://" /></td></tr>
+ <tr><th>Comment:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="comment" /></td></tr>
+
+This is why ``initial`` values are only displayed for unbound forms. For bound
+forms, the HTML output will use the bound data.
+
+Also note that ``initial`` values are *not* used as "fallback" data in
+validation if a particular field's value is not given. ``initial`` values are
+*only* intended for initial form display::
+
+ >>> class CommentForm(forms.Form):
+ ... name = forms.CharField(initial='Your name')
+ ... url = forms.URLField(initial='http://')
+ ... comment = forms.CharField()
+ >>> data = {'name': '', 'url': '', 'comment': 'Foo'}
+ >>> f = CommentForm(data)
+ >>> f.is_valid()
+ False
+ # The form does *not* fall back to using the initial values.
+ >>> f.errors
+ {'url': [u'This field is required.'], 'name': [u'This field is required.']}
+
+``widget``
+~~~~~~~~~~
+
+The ``widget`` argument lets you specify a ``Widget`` class to use when
+rendering this ``Field``. See _`Widgets` below for more information.
+
+``help_text``
+~~~~~~~~~~~~~
+
+The ``help_text`` argument lets you specify descriptive text for this
+``Field``. If you provide ``help_text``, it will be displayed next to the
+``Field`` when the ``Field`` is rendered in a ``Form``.
+
+Here's a full example ``Form`` that implements ``help_text`` for two of its
+fields. We've specified ``auto_id=False`` to simplify the output::
+
+ >>> class HelpTextContactForm(forms.Form):
+ ... subject = forms.CharField(max_length=100, help_text='100 characters max.')
+ ... message = forms.CharField()
+ ... sender = forms.EmailField(help_text='A valid e-mail address, please.')
+ ... cc_myself = forms.BooleanField()
+ >>> f = HelpTextContactForm(auto_id=False)
+ >>> print f.as_table()
+ <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" /><br />100 characters max.</td></tr>
+ <tr><th>Message:</th><td><input type="text" name="message" /></td></tr>
+ <tr><th>Sender:</th><td><input type="text" name="sender" /><br />A valid e-mail address, please.</td></tr>
+ <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself" /></td></tr>
+ >>> print f.as_ul()
+ <li>Subject: <input type="text" name="subject" maxlength="100" /> 100 characters max.</li>
+ <li>Message: <input type="text" name="message" /></li>
+ <li>Sender: <input type="text" name="sender" /> A valid e-mail address, please.</li>
+ <li>Cc myself: <input type="checkbox" name="cc_myself" /></li>
+ >>> print f.as_p()
+ <p>Subject: <input type="text" name="subject" maxlength="100" /> 100 characters max.</p>
+ <p>Message: <input type="text" name="message" /></p>
+ <p>Sender: <input type="text" name="sender" /> A valid e-mail address, please.</p>
+ <p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
+
+Dynamic initial values
+----------------------
+
+The ``initial`` argument to ``Field`` (explained above) lets you hard-code the
+initial value for a ``Field`` -- but what if you want to declare the initial
+value at runtime? For example, you might want to fill in a ``username`` field
+with the username of the current session.
+
+To accomplish this, use the ``initial`` argument to a ``Form``. This argument,
+if given, should be a dictionary mapping field names to initial values. Only
+include the fields for which you're specifying an initial value; it's not
+necessary to include every field in your form. For example::
+
+ >>> class CommentForm(forms.Form):
+ ... name = forms.CharField()
+ ... url = forms.URLField()
+ ... comment = forms.CharField()
+ >>> f = CommentForm(initial={'name': 'your username'}, auto_id=False)
+ >>> print f
+ <tr><th>Name:</th><td><input type="text" name="name" value="your username" /></td></tr>
+ <tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
+ <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
+ >>> f = CommentForm(initial={'name': 'another username'}, auto_id=False)
+ >>> print f
+ <tr><th>Name:</th><td><input type="text" name="name" value="another username" /></td></tr>
+ <tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
+ <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
+
+Just like the ``initial`` parameter to ``Field``, these values are only
+displayed for unbound forms, and they're not used as fallback values if a
+particular value isn't provided.
+
+Finally, note that if a ``Field`` defines ``initial`` *and* you include
+``initial`` when instantiating the ``Form``, then the latter ``initial`` will
+have precedence. In this example, ``initial`` is provided both at the field
+level and at the form instance level, and the latter gets precedence::
+
+ >>> class CommentForm(forms.Form):
+ ... name = forms.CharField(initial='class')
+ ... url = forms.URLField()
+ ... comment = forms.CharField()
+ >>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
+ >>> print f
+ <tr><th>Name:</th><td><input type="text" name="name" value="instance" /></td></tr>
+ <tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
+ <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
+
+More coming soon
+================
+
+That's all the documentation for now. For more, see the file
+http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/tests.py
+-- the unit tests for ``django.newforms``. This can give you a good idea of
+what's possible.
+
+If you're really itching to learn and use this library, please be patient.
+We're working hard on finishing both the code and documentation.
+
+Widgets
+=======
diff --git a/google_appengine/lib/django/docs/outputting_csv.txt b/google_appengine/lib/django/docs/outputting_csv.txt
new file mode 100644
index 0000000..d6ec3f6
--- /dev/null
+++ b/google_appengine/lib/django/docs/outputting_csv.txt
@@ -0,0 +1,119 @@
+==========================
+Outputting CSV with Django
+==========================
+
+This document explains how to output CSV (Comma Separated Values) dynamically
+using Django views.
+
+To do this, you can either use the `Python CSV library`_ or the Django template
+system.
+
+.. _Python CSV library: http://www.python.org/doc/current/lib/module-csv.html
+
+Using the Python CSV library
+============================
+
+Python comes with a CSV library, ``csv``. The key to using it with Django is
+that the ``csv`` module's CSV-creation capability acts on file-like objects,
+and Django's ``HttpResponse`` objects are file-like objects.
+
+.. admonition:: Note
+
+ For more information on ``HttpResponse`` objects, see
+ `Request and response objects`_.
+
+ For more information on the CSV library, see the `CSV library docs`_.
+
+ .. _Request and response objects: ../request_response/
+ .. _CSV library docs: http://www.python.org/doc/current/lib/module-csv.html
+
+Here's an example::
+
+ import csv
+ from django.http import HttpResponse
+
+ def some_view(request):
+ # Create the HttpResponse object with the appropriate CSV header.
+ response = HttpResponse(mimetype='text/csv')
+ response['Content-Disposition'] = 'attachment; filename=somefilename.csv'
+
+ writer = csv.writer(response)
+ writer.writerow(['First row', 'Foo', 'Bar', 'Baz'])
+ writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"])
+
+ return response
+
+The code and comments should be self-explanatory, but a few things deserve a
+mention:
+
+ * The response gets a special mimetype, ``text/csv``. This tells
+ browsers that the document is a CSV file, rather than an HTML file. If
+ you leave this off, browsers will probably interpret the output as HTML,
+ which will result in ugly, scary gobbledygook in the browser window.
+
+ * The response gets an additional ``Content-Disposition`` header, which
+ contains the name of the CSV file. This filename is arbitrary; call it
+ whatever you want. It'll be used by browsers in the "Save as..."
+ dialogue, etc.
+
+ * Hooking into the CSV-generation API is easy: Just pass ``response`` as
+ the first argument to ``csv.writer``. The ``csv.writer`` function expects
+ a file-like object, and ``HttpResponse`` objects fit the bill.
+
+ * For each row in your CSV file, call ``writer.writerow``, passing it an
+ iterable object such as a list or tuple.
+
+ * The CSV module takes care of quoting for you, so you don't have to worry
+ about escaping strings with quotes or commas in them. Just pass
+ ``writerow()`` your raw strings, and it'll do the right thing.
+
+Using the template system
+=========================
+
+Alternatively, you can use the `Django template system`_ to generate CSV. This
+is lower-level than using the convenient CSV, but the solution is presented
+here for completeness.
+
+The idea here is to pass a list of items to your template, and have the
+template output the commas in a ``{% for %}`` loop.
+
+Here's an example, which generates the same CSV file as above::
+
+ from django.http import HttpResponse
+ from django.template import loader, Context
+
+ def some_view(request):
+ # Create the HttpResponse object with the appropriate CSV header.
+ response = HttpResponse(mimetype='text/csv')
+ response['Content-Disposition'] = 'attachment; filename=somefilename.csv'
+
+ # The data is hard-coded here, but you could load it from a database or
+ # some other source.
+ csv_data = (
+ ('First row', 'Foo', 'Bar', 'Baz'),
+ ('Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"),
+ )
+
+ t = loader.get_template('my_template_name.txt')
+ c = Context({
+ 'data': csv_data,
+ })
+ response.write(t.render(c))
+ return response
+
+The only difference between this example and the previous example is that this
+one uses template loading instead of the CSV module. The rest of the code --
+such as the ``mimetype='text/csv'`` -- is the same.
+
+Then, create the template ``my_template_name.txt``, with this template code::
+
+ {% for row in data %}"{{ row.0|addslashes }}", "{{ row.1|addslashes }}", "{{ row.2|addslashes }}", "{{ row.3|addslashes }}", "{{ row.4|addslashes }}"
+ {% endfor %}
+
+This template is quite basic. It just iterates over the given data and displays
+a line of CSV for each row. It uses the `addslashes template filter`_ to ensure
+there aren't any problems with quotes. If you can be certain your data doesn't
+have single or double quotes in it, you can remove the ``addslashes`` filters.
+
+.. _Django template system: ../templates/
+.. _addslashes template filter: ../templates/#addslashes
diff --git a/google_appengine/lib/django/docs/outputting_pdf.txt b/google_appengine/lib/django/docs/outputting_pdf.txt
new file mode 100644
index 0000000..bd6ae7a
--- /dev/null
+++ b/google_appengine/lib/django/docs/outputting_pdf.txt
@@ -0,0 +1,153 @@
+===========================
+Outputting PDFs with Django
+===========================
+
+This document explains how to output PDF files dynamically using Django views.
+This is made possible by the excellent, open-source ReportLab_ Python PDF
+library.
+
+The advantage of generating PDF files dynamically is that you can create
+customized PDFs for different purposes -- say, for different users or different
+pieces of content.
+
+For example, Django was used at kusports.com_ to generate customized,
+printer-friendly NCAA tournament brackets, as PDF files, for people
+participating in a March Madness contest.
+
+.. _ReportLab: http://www.reportlab.org/rl_toolkit.html
+.. _kusports.com: http://www.kusports.com/
+
+Install ReportLab
+=================
+
+Download and install the ReportLab library from http://www.reportlab.org/downloads.html.
+The `user guide`_ (not coincidentally, a PDF file) explains how to install it.
+
+Test your installation by importing it in the Python interactive interpreter::
+
+ >>> import reportlab
+
+If that command doesn't raise any errors, the installation worked.
+
+.. _user guide: http://www.reportlab.com/docs/userguide.pdf
+
+Write your view
+===============
+
+The key to generating PDFs dynamically with Django is that the ReportLab API
+acts on file-like objects, and Django's ``HttpResponse`` objects are file-like
+objects.
+
+.. admonition:: Note
+
+ For more information on ``HttpResponse`` objects, see
+ `Request and response objects`_.
+
+ .. _Request and response objects: ../request_response/
+
+Here's a "Hello World" example::
+
+ from reportlab.pdfgen import canvas
+ from django.http import HttpResponse
+
+ def some_view(request):
+ # Create the HttpResponse object with the appropriate PDF headers.
+ response = HttpResponse(mimetype='application/pdf')
+ response['Content-Disposition'] = 'attachment; filename=somefilename.pdf'
+
+ # Create the PDF object, using the response object as its "file."
+ p = canvas.Canvas(response)
+
+ # Draw things on the PDF. Here's where the PDF generation happens.
+ # See the ReportLab documentation for the full list of functionality.
+ p.drawString(100, 100, "Hello world.")
+
+ # Close the PDF object cleanly, and we're done.
+ p.showPage()
+ p.save()
+ return response
+
+The code and comments should be self-explanatory, but a few things deserve a
+mention:
+
+ * The response gets a special mimetype, ``application/pdf``. This tells
+ browsers that the document is a PDF file, rather than an HTML file. If
+ you leave this off, browsers will probably interpret the output as HTML,
+ which would result in ugly, scary gobbledygook in the browser window.
+
+ * The response gets an additional ``Content-Disposition`` header, which
+ contains the name of the PDF file. This filename is arbitrary: Call it
+ whatever you want. It'll be used by browsers in the "Save as..."
+ dialogue, etc.
+
+ * The ``Content-Disposition`` header starts with ``'attachment; '`` in this
+ example. This forces Web browsers to pop-up a dialog box
+ prompting/confirming how to handle the document even if a default is set
+ on the machine. If you leave off ``'attachment;'``, browsers will handle
+ the PDF using whatever program/plugin they've been configured to use for
+ PDFs. Here's what that code would look like::
+
+ response['Content-Disposition'] = 'filename=somefilename.pdf'
+
+ * Hooking into the ReportLab API is easy: Just pass ``response`` as the
+ first argument to ``canvas.Canvas``. The ``Canvas`` class expects a
+ file-like object, and ``HttpResponse`` objects fit the bill.
+
+ * Note that all subsequent PDF-generation methods are called on the PDF
+ object (in this case, ``p``) -- not on ``response``.
+
+ * Finally, it's important to call ``showPage()`` and ``save()`` on the PDF
+ file.
+
+Complex PDFs
+============
+
+If you're creating a complex PDF document with ReportLab, consider using the
+cStringIO_ library as a temporary holding place for your PDF file. The
+cStringIO library provides a file-like object interface that is particularly
+efficient. Here's the above "Hello World" example rewritten to use
+``cStringIO``::
+
+ from cStringIO import StringIO
+ from reportlab.pdfgen import canvas
+ from django.http import HttpResponse
+
+ def some_view(request):
+ # Create the HttpResponse object with the appropriate PDF headers.
+ response = HttpResponse(mimetype='application/pdf')
+ response['Content-Disposition'] = 'attachment; filename=somefilename.pdf'
+
+ buffer = StringIO()
+
+ # Create the PDF object, using the StringIO object as its "file."
+ p = canvas.Canvas(buffer)
+
+ # Draw things on the PDF. Here's where the PDF generation happens.
+ # See the ReportLab documentation for the full list of functionality.
+ p.drawString(100, 100, "Hello world.")
+
+ # Close the PDF object cleanly.
+ p.showPage()
+ p.save()
+
+ # Get the value of the StringIO buffer and write it to the response.
+ pdf = buffer.getvalue()
+ buffer.close()
+ response.write(pdf)
+ return response
+
+.. _cStringIO: http://www.python.org/doc/current/lib/module-cStringIO.html
+
+Further resources
+=================
+
+ * PDFlib_ is another PDF-generation library that has Python bindings. To
+ use it with Django, just use the same concepts explained in this article.
+ * HTMLdoc_ is a command-line script that can convert HTML to PDF. It
+ doesn't have a Python interface, but you can escape out to the shell
+ using ``system`` or ``popen`` and retrieve the output in Python.
+ * `forge_fdf in Python`_ is a library that fills in PDF forms.
+
+.. _PDFlib: http://www.pdflib.org/
+.. _HTMLdoc: http://www.htmldoc.org/
+.. _forge_fdf in Python: http://www.accesspdf.com/article.php/20050421092951834
diff --git a/google_appengine/lib/django/docs/overview.txt b/google_appengine/lib/django/docs/overview.txt
new file mode 100644
index 0000000..35af75b
--- /dev/null
+++ b/google_appengine/lib/django/docs/overview.txt
@@ -0,0 +1,301 @@
+==================
+Django at a glance
+==================
+
+Because Django was developed in a fast-paced newsroom environment, it was
+designed to make common Web-development tasks fast and easy. Here's an informal
+overview of how to write a database-driven Web app with Django.
+
+The goal of this document is to give you enough technical specifics to
+understand how Django works, but this isn't intended to be a tutorial or
+reference. Please see our more-detailed Django documentation_ when you're ready
+to start a project.
+
+.. _documentation: ../
+
+Design your model
+=================
+
+Although you can use Django without a database, it comes with an
+object-relational mapper in which you describe your database layout in Python
+code.
+
+The data-model syntax offers many rich ways of representing your models -- so
+far, it's been solving two years' worth of database-schema problems. Here's a
+quick example::
+
+ class Reporter(models.Model):
+ full_name = models.CharField(maxlength=70)
+
+ def __str__(self):
+ return self.full_name
+
+ class Article(models.Model):
+ pub_date = models.DateTimeField()
+ headline = models.CharField(maxlength=200)
+ article = models.TextField()
+ reporter = models.ForeignKey(Reporter)
+
+ def __str__(self):
+ return self.headline
+
+Install it
+==========
+
+Next, run the Django command-line utility to create the database tables
+automatically::
+
+ manage.py syncdb
+
+The ``syncdb`` command looks at all your available models and creates tables
+in your database for whichever tables don't already exist.
+
+Enjoy the free API
+==================
+
+With that, you've got a free, and rich, Python API to access your data. The API
+is created on the fly: No code generation necessary::
+
+ >>> from mysite.models import Reporter, Article
+
+ # No reporters are in the system yet.
+ >>> Reporter.objects.all()
+ []
+
+ # Create a new Reporter.
+ >>> r = Reporter(full_name='John Smith')
+
+ # Save the object into the database. You have to call save() explicitly.
+ >>> r.save()
+
+ # Now it has an ID.
+ >>> r.id
+ 1
+
+ # Now the new reporter is in the database.
+ >>> Reporter.objects.all()
+ [John Smith]
+
+ # Fields are represented as attributes on the Python object.
+ >>> r.full_name
+ 'John Smith'
+
+ # Django provides a rich database lookup API.
+ >>> Reporter.objects.get(id=1)
+ John Smith
+ >>> Reporter.objects.get(full_name__startswith='John')
+ John Smith
+ >>> Reporter.objects.get(full_name__contains='mith')
+ John Smith
+ >>> Reporter.objects.get(id=2)
+ Traceback (most recent call last):
+ ...
+ DoesNotExist: Reporter does not exist for {'id__exact': 2}
+
+ # Create an article.
+ >>> from datetime import datetime
+ >>> a = Article(pub_date=datetime.now(), headline='Django is cool',
+ ... article='Yeah.', reporter=r)
+ >>> a.save()
+
+ # Now the article is in the database.
+ >>> Article.objects.all()
+ [Django is cool]
+
+ # Article objects get API access to related Reporter objects.
+ >>> r = a.reporter
+ >>> r.full_name
+ 'John Smith'
+
+ # And vice versa: Reporter objects get API access to Article objects.
+ >>> r.article_set.all()
+ [Django is cool]
+
+ # The API follows relationships as far as you need, performing efficient
+ # JOINs for you behind the scenes.
+ # This finds all articles by a reporter whose name starts with "John".
+ >>> Article.objects.filter(reporter__full_name__startswith="John")
+ [Django is cool]
+
+ # Change an object by altering its attributes and calling save().
+ >>> r.full_name = 'Billy Goat'
+ >>> r.save()
+
+ # Delete an object with delete().
+ >>> r.delete()
+
+A dynamic admin interface: It's not just scaffolding -- it's the whole house
+============================================================================
+
+Once your models are defined, Django can automatically create a professional,
+production ready administrative interface -- a Web site that lets authenticated
+users add, change and delete objects. It's as easy as adding a line of code to
+your model classes::
+
+ class Article(models.Model):
+ pub_date = models.DateTimeField()
+ headline = models.CharField(maxlength=200)
+ article = models.TextField()
+ reporter = models.ForeignKey(Reporter)
+ class Admin: pass
+
+The philosophy here is that your site is edited by a staff, or a client, or
+maybe just you -- and you don't want to have to deal with creating backend
+interfaces just to manage content.
+
+One typical workflow in creating Django apps is to create models and get the
+admin sites up and running as fast as possible, so your staff (or clients) can
+start populating data. Then, develop the way data is presented to the public.
+
+Design your URLs
+================
+
+A clean, elegant URL scheme is an important detail in a high-quality Web
+application. Django encourages beautiful URL design and doesn't put any cruft
+in URLs, like ``.php`` or ``.asp``.
+
+To design URLs for an app, you create a Python module called a URLconf. A table
+of contents for your app, it contains a simple mapping between URL patterns and
+Python callback functions. URLconfs also serve to decouple URLs from Python
+code.
+
+Here's what a URLconf might look like for the ``Reporter``/``Article``
+example above::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^/articles/(\d{4})/$', 'mysite.views.year_archive'),
+ (r'^/articles/(\d{4})/(\d{2})/$', 'mysite.views.month_archive'),
+ (r'^/articles/(\d{4})/(\d{2})/(\d+)/$', 'mysite.views.article_detail'),
+ )
+
+The code above maps URLs, as simple regular expressions, to the location of
+Python callback functions ("views"). The regular expressions use parenthesis to
+"capture" values from the URLs. When a user requests a page, Django runs
+through each pattern, in order, and stops at the first one that matches the
+requested URL. (If none of them matches, Django calls a special-case 404 view.)
+This is blazingly fast, because the regular expressions are compiled at load
+time.
+
+Once one of the regexes matches, Django imports and calls the given view, which
+is a simple Python function. Each view gets passed a request object --
+which contains request metadata -- and the values captured in the regex.
+
+For example, if a user requested the URL "/articles/2005/05/39323/", Django
+would call the function ``mysite.views.article_detail(request,
+'2005', '05', '39323')``.
+
+Write your views
+================
+
+Each view is responsible for doing one of two things: Returning an
+``HttpResponse`` object containing the content for the requested page, or
+raising an exception such as ``Http404``. The rest is up to you.
+
+Generally, a view retrieves data according to the parameters, loads a template
+and renders the template with the retrieved data. Here's an example view for
+``year_archive`` from above::
+
+ def year_archive(request, year):
+ a_list = Article.objects.filter(pub_date__year=year)
+ return render_to_response('news/year_archive.html', {'year': year, 'article_list': a_list})
+
+This example uses Django's template system, which has several powerful
+features but strives to stay simple enough for non-programmers to use.
+
+Design your templates
+=====================
+
+The code above loads the ``news/year_archive.html`` template.
+
+Django has a template search path, which allows you to minimize redundancy among
+templates. In your Django settings, you specify a list of directories to check
+for templates. If a template doesn't exist in the first directory, it checks the
+second, and so on.
+
+Let's say the ``news/article_detail.html`` template was found. Here's what that
+might look like::
+
+ {% extends "base.html" %}
+
+ {% block title %}Articles for {{ year }}{% endblock %}
+
+ {% block content %}
+ <h1>Articles for {{ year }}</h1>
+
+ {% for article in article_list %}
+ <p>{{ article.headline }}</p>
+ <p>By {{ article.reporter.full_name }}</p>
+ <p>Published {{ article.pub_date|date:"F j, Y" }}</p>
+ {% endfor %}
+ {% endblock %}
+
+Variables are surrounded by double-curly braces. ``{{ article.headline }}``
+means "Output the value of the article's headline attribute." But dots aren't
+used only for attribute lookup: They also can do dictionary-key lookup, index
+lookup and function calls.
+
+Note ``{{ article.pub_date|date:"F j, Y" }}`` uses a Unix-style "pipe" (the "|"
+character). This is called a template filter, and it's a way to filter the value
+of a variable. In this case, the date filter formats a Python datetime object in
+the given format (as found in PHP's date function; yes, there is one good idea
+in PHP).
+
+You can chain together as many filters as you'd like. You can write custom
+filters. You can write custom template tags, which run custom Python code behind
+the scenes.
+
+Finally, Django uses the concept of "template inheritance": That's what the
+``{% extends "base.html" %}`` does. It means "First load the template called
+'base', which has defined a bunch of blocks, and fill the blocks with the
+following blocks." In short, that lets you dramatically cut down on redundancy
+in templates: Each template has to define only what's unique to that template.
+
+Here's what the "base.html" template might look like::
+
+ <html>
+ <head>
+ <title>{% block title %}{% endblock %}</title>
+ </head>
+ <body>
+ <img src="sitelogo.gif" alt="Logo" />
+ {% block content %}{% endblock %}
+ </body>
+ </html>
+
+Simplistically, it defines the look-and-feel of the site (with the site's logo),
+and provides "holes" for child templates to fill. This makes a site redesign as
+easy as changing a single file -- the base template.
+
+It also lets you create multiple versions of a site, with different base
+templates, while reusing child templates. Django's creators have used this
+technique to create strikingly different cell-phone editions of sites -- simply
+by creating a new base template.
+
+Note that you don't have to use Django's template system if you prefer another
+system. While Django's template system is particularly well-integrated with
+Django's model layer, nothing forces you to use it. For that matter, you don't
+have to use Django's database API, either. You can use another database
+abstraction layer, you can read XML files, you can read files off disk, or
+anything you want. Each piece of Django -- models, views, templates -- is
+decoupled from the next.
+
+This is just the surface
+========================
+
+This has been only a quick overview of Django's functionality. Some more useful
+features:
+
+ * A caching framework that integrates with memcached or other backends.
+ * A syndication framework that makes creating RSS and Atom feeds as easy as
+ writing a small Python class.
+ * More sexy automatically-generated admin features -- this overview barely
+ scratched the surface.
+
+The next obvious steps are for you to `download Django`_, read `the tutorial`_
+and join `the community`_. Thanks for your interest!
+
+.. _download Django: http://www.djangoproject.com/download/
+.. _the tutorial: http://www.djangoproject.com/documentation/tutorial1/
+.. _the community: http://www.djangoproject.com/community/
diff --git a/google_appengine/lib/django/docs/redirects.txt b/google_appengine/lib/django/docs/redirects.txt
new file mode 100644
index 0000000..5f84f28
--- /dev/null
+++ b/google_appengine/lib/django/docs/redirects.txt
@@ -0,0 +1,71 @@
+=================
+The redirects app
+=================
+
+Django comes with an optional redirects application. It lets you store simple
+redirects in a database and handles the redirecting for you.
+
+Installation
+============
+
+To install the redirects app, follow these steps:
+
+ 1. Add ``'django.contrib.redirects'`` to your INSTALLED_APPS_ setting.
+ 2. Add ``'django.contrib.redirects.middleware.RedirectFallbackMiddleware'``
+ to your MIDDLEWARE_CLASSES_ setting.
+ 3. Run the command ``manage.py syncdb``.
+
+.. _INSTALLED_APPS: ../settings/#installed-apps
+.. _MIDDLEWARE_CLASSES: ../settings/#middleware-classes
+
+How it works
+============
+
+``manage.py syncdb`` creates a ``django_redirect`` table in your database. This
+is a simple lookup table with ``site_id``, ``old_path`` and ``new_path`` fields.
+
+The ``RedirectFallbackMiddleware`` does all of the work. Each time any Django
+application raises a 404 error, this middleware checks the redirects database
+for the requested URL as a last resort. Specifically, it checks for a redirect
+with the given ``old_path`` with a site ID that corresponds to the SITE_ID_
+setting.
+
+ * If it finds a match, and ``new_path`` is not empty, it redirects to
+ ``new_path``.
+ * If it finds a match, and ``new_path`` is empty, it sends a 410 ("Gone")
+ HTTP header and empty (content-less) response.
+ * If it doesn't find a match, the request continues to be processed as
+ usual.
+
+The middleware only gets activated for 404s -- not for 500s or responses of any
+other status code.
+
+Note that the order of ``MIDDLEWARE_CLASSES`` matters. Generally, you can put
+``RedirectFallbackMiddleware`` at the end of the list, because it's a last
+resort.
+
+For more on middleware, read the `middleware docs`_.
+
+.. _SITE_ID: ../settings/#site-id
+.. _middleware docs: ../middleware/
+
+How to add, change and delete redirects
+=======================================
+
+Via the admin interface
+-----------------------
+
+If you've activated the automatic Django admin interface, you should see a
+"Redirects" section on the admin index page. Edit redirects as you edit any
+other object in the system.
+
+Via the Python API
+------------------
+
+Redirects are represented by a standard `Django model`_, which lives in
+`django/contrib/redirects/models.py`_. You can access redirect
+objects via the `Django database API`_.
+
+.. _Django model: ../model_api/
+.. _django/contrib/redirects/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/redirects/models.py
+.. _Django database API: ../db_api/
diff --git a/google_appengine/lib/django/docs/release_notes_0.95.txt b/google_appengine/lib/django/docs/release_notes_0.95.txt
new file mode 100644
index 0000000..3709cac
--- /dev/null
+++ b/google_appengine/lib/django/docs/release_notes_0.95.txt
@@ -0,0 +1,126 @@
+=================================
+Django version 0.95 release notes
+=================================
+
+
+Welcome to the Django 0.95 release.
+
+This represents a significant advance in Django development since the 0.91
+release in January 2006. The details of every change in this release would be
+too extensive to list in full, but a summary is presented below.
+
+Suitability and API stability
+=============================
+
+This release is intended to provide a stable reference point for developers
+wanting to work on production-level applications that use Django.
+
+However, it's not the 1.0 release, and we'll be introducing further changes
+before 1.0. For a clear look at which areas of the framework will change (and
+which ones will *not* change) before 1.0, see the api-stability.txt file, which
+lives in the docs/ directory of the distribution.
+
+You may have a need to use some of the features that are marked as
+"subject to API change" in that document, but that's OK with us as long as it's
+OK with you, and as long as you understand APIs may change in the future.
+
+Fortunately, most of Django's core APIs won't be changing before version 1.0.
+There likely won't be as big of a change between 0.95 and 1.0 versions as there
+was between 0.91 and 0.95.
+
+Changes and new features
+========================
+
+The major changes in this release (for developers currently using the 0.91
+release) are a result of merging the 'magic-removal' branch of development.
+This branch removed a number of constraints in the way Django code had to be
+written that were a consequence of decisions made in the early days of Django,
+prior to its open-source release. It's now possible to write more natural,
+Pythonic code that works as expected, and there's less "black magic" happening
+behind the scenes.
+
+Aside from that, another main theme of this release is a dramatic increase in
+usability. We've made countless improvements in error messages, documentation,
+etc., to improve developers' quality of life.
+
+The new features and changes introduced in 0.95 include:
+
+ * Django now uses a more consistent and natural filtering interface for
+ retrieving objects from the database.
+
+ * User-defined models, functions and constants now appear in the module
+ namespace they were defined in. (Previously everything was magically
+ transferred to the django.models.* namespace.)
+
+ * Some optional applications, such as the FlatPage, Sites and Redirects
+ apps, have been decoupled and moved into django.contrib. If you don't
+ want to use these applications, you no longer have to install their
+ database tables.
+
+ * Django now has support for managing database transactions.
+
+ * We've added the ability to write custom authentication and authorization
+ backends for authenticating users against alternate systems, such as
+ LDAP.
+
+ * We've made it easier to add custom table-level functions to models,
+ through a new "Manager" API.
+
+ * It's now possible to use Django without a database. This simply means
+ that the framework no longer requires you to have a working database set
+ up just to serve dynamic pages. In other words, you can just use
+ URLconfs/views on their own. Previously, the framework required that a
+ database be configured, regardless of whether you actually used it.
+
+ * It's now more explicit and natural to override save() and delete()
+ methods on models, rather than needing to hook into the pre_save() and
+ post_save() method hooks.
+
+ * Individual pieces of the framework now can be configured without
+ requiring the setting of an environment variable. This permits use of,
+ for example, the Django templating system inside other applications.
+
+ * More and more parts of the framework have been internationalized, as
+ we've expanded internationalization (i18n) support. The Django
+ codebase, including code and templates, has now been translated, at least
+ in part, into 31 languages. From Arabic to Chinese to Hungarian to Welsh,
+ it is now possible to use Django's admin site in your native language.
+
+The number of changes required to port from 0.91-compatible code to the 0.95
+code base are significant in some cases. However, they are, for the most part,
+reasonably routine and only need to be done once. A list of the necessary
+changes is described in the `Removing The Magic`_ wiki page. There is also an
+easy checklist_ for reference when undertaking the porting operation.
+
+.. _Removing The Magic: http://code.djangoproject.com/wiki/RemovingTheMagic
+.. _checklist: http://code.djangoproject.com/wiki/MagicRemovalCheatSheet1
+
+Problem reports and getting help
+================================
+
+Need help resolving a problem with Django? The documentation in the
+distribution is also available online_ at the `Django website`_. The FAQ_
+document is especially recommended, as it contains a number of issues that
+come up time and again.
+
+For more personalized help, the `django-users`_ mailing list is a very active
+list, with more than 2,000 subscribers who can help you solve any sort of
+Django problem. We recommend you search the archives first, though, because
+many common questions appear with some regularity, and any particular problem
+may already have been answered.
+
+Finally, for those who prefer the more immediate feedback offered by IRC,
+there's a #django channel on irc.freenode.net that is regularly populated by
+Django users and developers from around the world. Friendly people are usually
+available at any hour of the day -- to help, or just to chat.
+
+.. _online: http://www.djangoproject.com/documentation/
+.. _Django website: http://www.djangoproject.com/
+.. _FAQ: http://www.djangoproject.com/documentation/faq/
+.. _django-users: http://groups.google.com/group/django-users
+
+Thanks for using Django!
+
+The Django Team
+July 2006
+
diff --git a/google_appengine/lib/django/docs/release_notes_0.96.txt b/google_appengine/lib/django/docs/release_notes_0.96.txt
new file mode 100644
index 0000000..cc5f282
--- /dev/null
+++ b/google_appengine/lib/django/docs/release_notes_0.96.txt
@@ -0,0 +1,276 @@
+===================================
+Django version 0.96.1 release notes
+===================================
+
+Welcome to Django 0.96.1!
+
+The primary goal for 0.96 is a cleanup and stabilization of the features
+introduced in 0.95. There have been a few small `backwards-incompatible
+changes since 0.95`_, but the upgrade process should be fairly simple
+and should not require major changes to existing applications.
+
+However, we're also releasing 0.96 now because we have a set of
+backwards-incompatible changes scheduled for the near future. Once
+completed, they will involve some code changes for application
+developers, so we recommend that you stick with Django 0.96 until the
+next official release; then you'll be able to upgrade in one step
+instead of needing to make incremental changes to keep up with the
+development version of Django.
+
+Changes since the 0.96 release
+==============================
+
+This release contains fixes for a security vulnerability discovered after the
+initial release of Django 0.96. A bug in the i18n framework could allow an
+attacker to send extremely large strings in the Accept-Language header and
+cause a denial of service by filling available memory.
+
+Because this problems wasn't discovered and fixed until after the 0.96
+release, it's recommended that you use this release rather than the original
+0.96.
+
+Backwards-incompatible changes since 0.95
+=========================================
+
+The following changes may require you to update your code when you switch from
+0.95 to 0.96:
+
+``MySQLdb`` version requirement
+-------------------------------
+
+Due to a bug in older versions of the ``MySQLdb`` Python module (which
+Django uses to connect to MySQL databases), Django's MySQL backend now
+requires version 1.2.1p2 or higher of `MySQLdb`, and will raise
+exceptions if you attempt to use an older version.
+
+If you're currently unable to upgrade your copy of ``MySQLdb`` to meet
+this requirement, a separate, backwards-compatible backend, called
+"mysql_old", has been added to Django. To use this backend, change
+the ``DATABASE_ENGINE`` setting in your Django settings file from
+this::
+
+ DATABASE_ENGINE = "mysql"
+
+to this::
+
+ DATABASE_ENGINE = "mysql_old"
+
+However, we strongly encourage MySQL users to upgrade to a more recent
+version of `MySQLdb` as soon as possible, The "mysql_old" backend is
+provided only to ease this transition, and is considered deprecated;
+aside from any necessary security fixes, it will not be actively
+maintained, and it will be removed in a future release of Django.
+
+Also, note that some features, like the new ``DATABASE_OPTIONS``
+setting (see the `databases documentation`_ for details), are only
+available on the "mysql" backend, and will not be made available for
+"mysql_old".
+
+.. _databases documentation: ../databases/
+
+Database constraint names changed
+---------------------------------
+
+The format of the constraint names Django generates for foreign key
+references have changed slightly. These names are generally only used
+when it is not possible to put the reference directly on the affected
+column, so they is not always visible.
+
+The effect of this change is that running ``manage.py reset`` and
+similar commands against an existing database may generate SQL with
+the new form of constraint name, while the database itself contains
+constraints named in the old form; this will cause the database server
+to raise an error message about modifying non-existent constraints.
+
+If you need to work around this, there are two methods available:
+
+ 1. Redirect the output of ``manage.py`` to a file, and edit the
+ generated SQL to use the correct constraint names before
+ executing it.
+
+ 2. Examine the output of ``manage.py sqlall`` to see the new-style
+ constraint names, and use that as a guide to rename existing
+ constraints in your database.
+
+Name changes in ``manage.py``
+-----------------------------
+
+A few of the options to ``manage.py`` have changed with the addition of fixture
+support:
+
+ * There are new ``dumpdata`` and ``loaddata`` commands which, as
+ you might expect, will dump and load data to/from the
+ database. These commands can operate against any of Django's
+ supported serialization formats.
+
+ * The ``sqlinitialdata`` command has been renamed to ``sqlcustom`` to
+ emphasize that ``loaddata`` should be used for data (and ``sqlcustom`` for
+ other custom SQL -- views, stored procedures, etc.).
+
+ * The vestigial ``install`` command has been removed. Use ``syncdb``.
+
+Backslash escaping changed
+--------------------------
+
+The Django database API now escapes backslashes given as query parameters. If
+you have any database API code that matches backslashes, and it was working before
+(despite the lack of escaping), you'll have to change your code to "unescape" the
+slashes one level.
+
+For example, this used to work::
+
+ # Find text containing a single backslash
+ MyModel.objects.filter(text__contains='\\\\')
+
+The above is now incorrect, and should be rewritten as::
+
+ # Find text containing a single backslash
+ MyModel.objects.filter(text__contains='\\')
+
+Removed ENABLE_PSYCO setting
+----------------------------
+
+The ``ENABLE_PSYCO`` setting no longer exists. If your settings file includes
+``ENABLE_PSYCO`` it will have no effect; to use Psyco_, we recommend
+writing a middleware class to activate it.
+
+.. _psyco: http://psyco.sourceforge.net/
+
+What's new in 0.96?
+===================
+
+This revision represents over a thousand source commits and over four hundred
+bug fixes, so we can't possibly catalog all the changes. Here, we describe the
+most notable changes in this release.
+
+New forms library
+-----------------
+
+``django.newforms`` is Django's new form-handling library. It's a
+replacement for ``django.forms``, the old form/manipulator/validation
+framework. Both APIs are available in 0.96, but over the next two
+releases we plan to switch completely to the new forms system, and
+deprecate and remove the old system.
+
+There are three elements to this transition:
+
+ * We've copied the current ``django.forms`` to
+ ``django.oldforms``. This allows you to upgrade your code *now*
+ rather than waiting for the backwards-incompatible change and
+ rushing to fix your code after the fact. Just change your
+ import statements like this::
+
+ from django import forms # 0.95-style
+ from django import oldforms as forms # 0.96-style
+
+ * The next official release of Django will move the current
+ ``django.newforms`` to ``django.forms``. This will be a
+ backwards-incompatible change, and anyone still using the old
+ version of ``django.forms`` at that time will need to change
+ their import statements as described above.
+
+ * The next release after that will completely remove
+ ``django.oldforms``.
+
+Although the ``newforms`` library will continue to evolve, it's ready for use
+for most common cases. We recommend that anyone new to form handling skip the
+old forms system and start with the new.
+
+For more information about ``django.newforms``, read the `newforms
+documentation`_.
+
+.. _newforms documentation: ../newforms/
+
+URLconf improvements
+--------------------
+
+You can now use any callable as the callback in URLconfs (previously, only
+strings that referred to callables were allowed). This allows a much more
+natural use of URLconfs. For example, this URLconf::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ ('^myview/$', 'mysite.myapp.views.myview')
+ )
+
+can now be rewritten as::
+
+ from django.conf.urls.defaults import *
+ from mysite.myapp.views import myview
+
+ urlpatterns = patterns('',
+ ('^myview/$', myview)
+ )
+
+One useful application of this can be seen when using decorators; this
+change allows you to apply decorators to views *in your
+URLconf*. Thus, you can make a generic view require login very
+easily::
+
+ from django.conf.urls.defaults import *
+ from django.contrib.auth.decorators import login_required
+ from django.views.generic.list_detail import object_list
+ from mysite.myapp.models import MyModel
+
+ info = {
+ "queryset" : MyModel.objects.all(),
+ }
+
+ urlpatterns = patterns('',
+ ('^myview/$', login_required(object_list), info)
+ )
+
+Note that both syntaxes (strings and callables) are valid, and will continue to
+be valid for the foreseeable future.
+
+The test framework
+------------------
+
+Django now includes a test framework so you can start transmuting fear into
+boredom (with apologies to Kent Beck). You can write tests based on doctest_
+or unittest_ and test your views with a simple test client.
+
+There is also new support for "fixtures" -- initial data, stored in any of the
+supported `serialization formats`_, that will be loaded into your database at the
+start of your tests. This makes testing with real data much easier.
+
+See `the testing documentation`_ for the full details.
+
+.. _doctest: http://docs.python.org/lib/module-doctest.html
+.. _unittest: http://docs.python.org/lib/module-unittest.html
+.. _the testing documentation: ../testing/
+.. _serialization formats: ../serialization/
+
+Improvements to the admin interface
+-----------------------------------
+
+A small change, but a very nice one: dedicated views for adding and
+updating users have been added to the admin interface, so you no
+longer need to worry about working with hashed passwords in the admin.
+
+Thanks
+======
+
+Since 0.95, a number of people have stepped forward and taken a major
+new role in Django's development. We'd like to thank these people for
+all their hard work:
+
+ * Russell Keith-Magee and Malcolm Tredinnick for their major code
+ contributions. This release wouldn't have been possible without them.
+
+ * Our new release manager, James Bennett, for his work in getting out
+ 0.95.1, 0.96, and (hopefully) future release.
+
+ * Our ticket managers Chris Beaven (aka SmileyChris), Simon Greenhill,
+ Michael Radziej, and Gary Wilson. They agreed to take on the monumental
+ task of wrangling our tickets into nicely cataloged submission. Figuring
+ out what to work on is now about a million times easier; thanks again,
+ guys.
+
+ * Everyone who submitted a bug report, patch or ticket comment. We can't
+ possibly thank everyone by name -- over 200 developers submitted patches
+ that went into 0.96 -- but everyone who's contributed to Django is listed
+ in AUTHORS_.
+
+.. _AUTHORS: http://code.djangoproject.com/browser/django/trunk/AUTHORS \ No newline at end of file
diff --git a/google_appengine/lib/django/docs/request_response.txt b/google_appengine/lib/django/docs/request_response.txt
new file mode 100644
index 0000000..2b79903
--- /dev/null
+++ b/google_appengine/lib/django/docs/request_response.txt
@@ -0,0 +1,548 @@
+============================
+Request and response objects
+============================
+
+Quick overview
+==============
+
+Django uses request and response objects to pass state through the system.
+
+When a page is requested, Django creates an ``HttpRequest`` object that
+contains metadata about the request. Then Django loads the appropriate view,
+passing the ``HttpRequest`` as the first argument to the view function. Each
+view is responsible for returning an ``HttpResponse`` object.
+
+This document explains the APIs for ``HttpRequest`` and ``HttpResponse``
+objects.
+
+HttpRequest objects
+===================
+
+Attributes
+----------
+
+All attributes except ``session`` should be considered read-only.
+
+``path``
+ A string representing the full path to the requested page, not including
+ the domain.
+
+ Example: ``"/music/bands/the_beatles/"``
+
+``method``
+ A string representing the HTTP method used in the request. This is
+ guaranteed to be uppercase. Example::
+
+ if request.method == 'GET':
+ do_something()
+ elif request.method == 'POST':
+ do_something_else()
+
+``GET``
+ A dictionary-like object containing all given HTTP GET parameters. See the
+ ``QueryDict`` documentation below.
+
+``POST``
+ A dictionary-like object containing all given HTTP POST parameters. See the
+ ``QueryDict`` documentation below.
+
+ It's possible that a request can come in via POST with an empty ``POST``
+ dictionary -- if, say, a form is requested via the POST HTTP method but
+ does not include form data. Therefore, you shouldn't use ``if request.POST``
+ to check for use of the POST method; instead, use ``if request.method ==
+ "POST"`` (see above).
+
+ Note: ``POST`` does *not* include file-upload information. See ``FILES``.
+
+``REQUEST``
+ For convenience, a dictionary-like object that searches ``POST`` first,
+ then ``GET``. Inspired by PHP's ``$_REQUEST``.
+
+ For example, if ``GET = {"name": "john"}`` and ``POST = {"age": '34'}``,
+ ``REQUEST["name"]`` would be ``"john"``, and ``REQUEST["age"]`` would be
+ ``"34"``.
+
+ It's strongly suggested that you use ``GET`` and ``POST`` instead of
+ ``REQUEST``, because the former are more explicit.
+
+``COOKIES``
+ A standard Python dictionary containing all cookies. Keys and values are
+ strings.
+
+``FILES``
+ A dictionary-like object containing all uploaded files. Each key in
+ ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each
+ value in ``FILES`` is a standard Python dictionary with the following three
+ keys:
+
+ * ``filename`` -- The name of the uploaded file, as a Python string.
+ * ``content-type`` -- The content type of the uploaded file.
+ * ``content`` -- The raw content of the uploaded file.
+
+ Note that ``FILES`` will only contain data if the request method was POST
+ and the ``<form>`` that posted to the request had
+ ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank
+ dictionary-like object.
+
+``META``
+ A standard Python dictionary containing all available HTTP headers.
+ Available headers depend on the client and server, but here are some
+ examples:
+
+ * ``CONTENT_LENGTH``
+ * ``CONTENT_TYPE``
+ * ``HTTP_ACCEPT_ENCODING``
+ * ``HTTP_ACCEPT_LANGUAGE``
+ * ``HTTP_REFERER`` -- The referring page, if any.
+ * ``HTTP_USER_AGENT`` -- The client's user-agent string.
+ * ``QUERY_STRING`` -- The query string, as a single (unparsed) string.
+ * ``REMOTE_ADDR`` -- The IP address of the client.
+ * ``REMOTE_HOST`` -- The hostname of the client.
+ * ``REQUEST_METHOD`` -- A string such as ``"GET"`` or ``"POST"``.
+ * ``SERVER_NAME`` -- The hostname of the server.
+ * ``SERVER_PORT`` -- The port of the server.
+
+``user``
+ A ``django.contrib.auth.models.User`` object representing the currently
+ logged-in user. If the user isn't currently logged in, ``user`` will be set
+ to an instance of ``django.contrib.auth.models.AnonymousUser``. You
+ can tell them apart with ``is_authenticated()``, like so::
+
+ if request.user.is_authenticated():
+ # Do something for logged-in users.
+ else:
+ # Do something for anonymous users.
+
+ ``user`` is only available if your Django installation has the
+ ``AuthenticationMiddleware`` activated. For more, see
+ `Authentication in Web requests`_.
+
+ .. _Authentication in Web requests: ../authentication/#authentication-in-web-requests
+
+``session``
+ A readable-and-writable, dictionary-like object that represents the current
+ session. This is only available if your Django installation has session
+ support activated. See the `session documentation`_ for full details.
+
+ .. _`session documentation`: ../sessions/
+
+``raw_post_data``
+ The raw HTTP POST data. This is only useful for advanced processing. Use
+ ``POST`` instead.
+
+Methods
+-------
+
+``__getitem__(key)``
+ Returns the GET/POST value for the given key, checking POST first, then
+ GET. Raises ``KeyError`` if the key doesn't exist.
+
+ This lets you use dictionary-accessing syntax on an ``HttpRequest``
+ instance. Example: ``request["foo"]`` would return ``True`` if either
+ ``request.POST`` or ``request.GET`` had a ``"foo"`` key.
+
+``has_key()``
+ Returns ``True`` or ``False``, designating whether ``request.GET`` or
+ ``request.POST`` has the given key.
+
+``get_full_path()``
+ Returns the ``path``, plus an appended query string, if applicable.
+
+ Example: ``"/music/bands/the_beatles/?print=true"``
+
+``is_secure()``
+ Returns ``True`` if the request is secure; that is, if it was made with
+ HTTPS.
+
+QueryDict objects
+-----------------
+
+In an ``HttpRequest`` object, the ``GET`` and ``POST`` attributes are instances
+of ``django.http.QueryDict``. ``QueryDict`` is a dictionary-like
+class customized to deal with multiple values for the same key. This is
+necessary because some HTML form elements, notably
+``<select multiple="multiple">``, pass multiple values for the same key.
+
+``QueryDict`` instances are immutable, unless you create a ``copy()`` of them.
+That means you can't change attributes of ``request.POST`` and ``request.GET``
+directly.
+
+``QueryDict`` implements the all standard dictionary methods, because it's a
+subclass of dictionary. Exceptions are outlined here:
+
+ * ``__getitem__(key)`` -- Returns the value for the given key. If the key
+ has more than one value, ``__getitem__()`` returns the last value.
+
+ * ``__setitem__(key, value)`` -- Sets the given key to ``[value]``
+ (a Python list whose single element is ``value``). Note that this, as
+ other dictionary functions that have side effects, can only be called on
+ a mutable ``QueryDict`` (one that was created via ``copy()``).
+
+ * ``__contains__(key)`` -- Returns ``True`` if the given key is set. This
+ lets you do, e.g., ``if "foo" in request.GET``.
+
+ * ``get(key, default)`` -- Uses the same logic as ``__getitem__()`` above,
+ with a hook for returning a default value if the key doesn't exist.
+
+ * ``has_key(key)``
+
+ * ``setdefault(key, default)`` -- Just like the standard dictionary
+ ``setdefault()`` method, except it uses ``__setitem__`` internally.
+
+ * ``update(other_dict)`` -- Takes either a ``QueryDict`` or standard
+ dictionary. Just like the standard dictionary ``update()`` method, except
+ it *appends* to the current dictionary items rather than replacing them.
+ For example::
+
+ >>> q = QueryDict('a=1')
+ >>> q = q.copy() # to make it mutable
+ >>> q.update({'a': '2'})
+ >>> q.getlist('a')
+ ['1', '2']
+ >>> q['a'] # returns the last
+ ['2']
+
+ * ``items()`` -- Just like the standard dictionary ``items()`` method,
+ except this uses the same last-value logic as ``__getitem()__``. For
+ example::
+
+ >>> q = QueryDict('a=1&a=2&a=3')
+ >>> q.items()
+ [('a', '3')]
+
+ * ``values()`` -- Just like the standard dictionary ``values()`` method,
+ except this uses the same last-value logic as ``__getitem()__``. For
+ example::
+
+ >>> q = QueryDict('a=1&a=2&a=3')
+ >>> q.values()
+ ['3']
+
+In addition, ``QueryDict`` has the following methods:
+
+ * ``copy()`` -- Returns a copy of the object, using ``copy.deepcopy()``
+ from the Python standard library. The copy will be mutable -- that is,
+ you can change its values.
+
+ * ``getlist(key)`` -- Returns the data with the requested key, as a Python
+ list. Returns an empty list if the key doesn't exist. It's guaranteed to
+ return a list of some sort.
+
+ * ``setlist(key, list_)`` -- Sets the given key to ``list_`` (unlike
+ ``__setitem__()``).
+
+ * ``appendlist(key, item)`` -- Appends an item to the internal list
+ associated with key.
+
+ * ``setlistdefault(key, default_list)`` -- Just like ``setdefault``, except
+ it takes a list of values instead of a single value.
+
+ * ``lists()`` -- Like ``items()``, except it includes all values, as a list,
+ for each member of the dictionary. For example::
+
+ >>> q = QueryDict('a=1&a=2&a=3')
+ >>> q.lists()
+ [('a', ['1', '2', '3'])]
+
+ * ``urlencode()`` -- Returns a string of the data in query-string format.
+ Example: ``"a=2&b=3&b=5"``.
+
+Examples
+--------
+
+Here's an example HTML form and how Django would treat the input::
+
+ <form action="/foo/bar/" method="post">
+ <input type="text" name="your_name" />
+ <select multiple="multiple" name="bands">
+ <option value="beatles">The Beatles</option>
+ <option value="who">The Who</option>
+ <option value="zombies">The Zombies</option>
+ </select>
+ <input type="submit" />
+ </form>
+
+If the user enters ``"John Smith"`` in the ``your_name`` field and selects both
+"The Beatles" and "The Zombies" in the multiple select box, here's what
+Django's request object would have::
+
+ >>> request.GET
+ {}
+ >>> request.POST
+ {'your_name': ['John Smith'], 'bands': ['beatles', 'zombies']}
+ >>> request.POST['your_name']
+ 'John Smith'
+ >>> request.POST['bands']
+ 'zombies'
+ >>> request.POST.getlist('bands')
+ ['beatles', 'zombies']
+ >>> request.POST.get('your_name', 'Adrian')
+ 'John Smith'
+ >>> request.POST.get('nonexistent_field', 'Nowhere Man')
+ 'Nowhere Man'
+
+Implementation notes
+--------------------
+
+The ``GET``, ``POST``, ``COOKIES``, ``FILES``, ``META``, ``REQUEST``,
+``raw_post_data`` and ``user`` attributes are all lazily loaded. That means
+Django doesn't spend resources calculating the values of those attributes until
+your code requests them.
+
+HttpResponse objects
+====================
+
+In contrast to ``HttpRequest`` objects, which are created automatically by
+Django, ``HttpResponse`` objects are your responsibility. Each view you write
+is responsible for instantiating, populating and returning an ``HttpResponse``.
+
+The ``HttpResponse`` class lives at ``django.http.HttpResponse``.
+
+Usage
+-----
+
+Passing strings
+~~~~~~~~~~~~~~~
+
+Typical usage is to pass the contents of the page, as a string, to the
+``HttpResponse`` constructor::
+
+ >>> response = HttpResponse("Here's the text of the Web page.")
+ >>> response = HttpResponse("Text only, please.", mimetype="text/plain")
+
+But if you want to add content incrementally, you can use ``response`` as a
+file-like object::
+
+ >>> response = HttpResponse()
+ >>> response.write("<p>Here's the text of the Web page.</p>")
+ >>> response.write("<p>Here's another paragraph.</p>")
+
+You can add and delete headers using dictionary syntax::
+
+ >>> response = HttpResponse()
+ >>> response['X-DJANGO'] = "It's the best."
+ >>> del response['X-PHP']
+ >>> response['X-DJANGO']
+ "It's the best."
+
+Note that ``del`` doesn't raise ``KeyError`` if the header doesn't exist.
+
+Passing iterators
+~~~~~~~~~~~~~~~~~
+
+Finally, you can pass ``HttpResponse`` an iterator rather than passing it
+hard-coded strings. If you use this technique, follow these guidelines:
+
+ * The iterator should return strings.
+ * If an ``HttpResponse`` has been initialized with an iterator as its
+ content, you can't use the ``HttpResponse`` instance as a file-like
+ object. Doing so will raise ``Exception``.
+
+Methods
+-------
+
+``__init__(content='', mimetype=DEFAULT_CONTENT_TYPE)``
+ Instantiates an ``HttpResponse`` object with the given page content (a
+ string) and MIME type. The ``DEFAULT_CONTENT_TYPE`` is ``'text/html'``.
+
+ ``content`` can be an iterator or a string. If it's an iterator, it should
+ return strings, and those strings will be joined together to form the
+ content of the response.
+
+``__setitem__(header, value)``
+ Sets the given header name to the given value. Both ``header`` and
+ ``value`` should be strings.
+
+``__delitem__(header)``
+ Deletes the header with the given name. Fails silently if the header
+ doesn't exist. Case-sensitive.
+
+``__getitem__(header)``
+ Returns the value for the given header name. Case-sensitive.
+
+``has_header(header)``
+ Returns ``True`` or ``False`` based on a case-insensitive check for a
+ header with the given name.
+
+``set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None)``
+ Sets a cookie. The parameters are the same as in the `cookie Morsel`_
+ object in the Python standard library.
+
+ * ``max_age`` should be a number of seconds, or ``None`` (default) if
+ the cookie should last only as long as the client's browser session.
+ * ``expires`` should be a string in the format
+ ``"Wdy, DD-Mon-YY HH:MM:SS GMT"``.
+ * Use ``domain`` if you want to set a cross-domain cookie. For example,
+ ``domain=".lawrence.com"`` will set a cookie that is readable by
+ the domains www.lawrence.com, blogs.lawrence.com and
+ calendars.lawrence.com. Otherwise, a cookie will only be readable by
+ the domain that set it.
+
+ .. _`cookie Morsel`: http://www.python.org/doc/current/lib/morsel-objects.html
+
+``delete_cookie(key, path='/', domain=None)``
+ Deletes the cookie with the given key. Fails silently if the key doesn't
+ exist.
+
+ Due to the way cookies work, ``path`` and ``domain`` should be the same
+ values you used in ``set_cookie()`` -- otherwise the cookie may not be deleted.
+
+``content``
+ Returns the content as a Python string, encoding it from a Unicode object
+ if necessary. Note this is a property, not a method, so use ``r.content``
+ instead of ``r.content()``.
+
+``write(content)``, ``flush()`` and ``tell()``
+ These methods make an ``HttpResponse`` instance a file-like object.
+
+HttpResponse subclasses
+-----------------------
+
+Django includes a number of ``HttpResponse`` subclasses that handle different
+types of HTTP responses. Like ``HttpResponse``, these subclasses live in
+``django.http``.
+
+``HttpResponseRedirect``
+ The constructor takes a single argument -- the path to redirect to. This
+ can be a fully qualified URL (e.g. ``'http://www.yahoo.com/search/'``) or an
+ absolute URL with no domain (e.g. ``'/search/'``). Note that this returns
+ an HTTP status code 302.
+
+``HttpResponsePermanentRedirect``
+ Like ``HttpResponseRedirect``, but it returns a permanent redirect (HTTP
+ status code 301) instead of a "found" redirect (status code 302).
+
+``HttpResponseNotModified``
+ The constructor doesn't take any arguments. Use this to designate that a
+ page hasn't been modified since the user's last request.
+
+``HttpResponseNotFound``
+ Acts just like ``HttpResponse`` but uses a 404 status code.
+
+``HttpResponseForbidden``
+ Acts just like ``HttpResponse`` but uses a 403 status code.
+
+``HttpResponseNotAllowed``
+ Like ``HttpResponse``, but uses a 405 status code. Takes a single,
+ required argument: a list of permitted methods (e.g. ``['GET', 'POST']``).
+
+``HttpResponseGone``
+ Acts just like ``HttpResponse`` but uses a 410 status code.
+
+``HttpResponseServerError``
+ Acts just like ``HttpResponse`` but uses a 500 status code.
+
+Returning errors
+================
+
+Returning HTTP error codes in Django is easy. We've already mentioned the
+``HttpResponseNotFound``, ``HttpResponseForbidden``,
+``HttpResponseServerError``, etc., subclasses; just return an instance of one
+of those subclasses instead of a normal ``HttpResponse`` in order to signify
+an error. For example::
+
+ def my_view(request):
+ # ...
+ if foo:
+ return HttpResponseNotFound('<h1>Page not found</h1>')
+ else:
+ return HttpResponse('<h1>Page was found</h1>')
+
+Because 404 errors are by far the most common HTTP error, there's an easier way
+to handle those errors.
+
+The Http404 exception
+---------------------
+
+When you return an error such as ``HttpResponseNotFound``, you're responsible
+for defining the HTML of the resulting error page::
+
+ return HttpResponseNotFound('<h1>Page not found</h1>')
+
+For convenience, and because it's a good idea to have a consistent 404 error page
+across your site, Django provides an ``Http404`` exception. If you raise
+``Http404`` at any point in a view function, Django will catch it and return the
+standard error page for your application, along with an HTTP error code 404.
+
+Example usage::
+
+ from django.http import Http404
+
+ def detail(request, poll_id):
+ try:
+ p = Poll.objects.get(pk=poll_id)
+ except Poll.DoesNotExist:
+ raise Http404
+ return render_to_response('polls/detail.html', {'poll': p})
+
+In order to use the ``Http404`` exception to its fullest, you should create a
+template that is displayed when a 404 error is raised. This template should be
+called ``404.html`` and located in the top level of your template tree.
+
+Customing error views
+---------------------
+
+The 404 (page not found) view
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you raise an ``Http404`` exception, Django loads a special view devoted
+to handling 404 errors. By default, it's the view
+``django.views.defaults.page_not_found``, which loads and renders the template
+``404.html``.
+
+This means you need to define a ``404.html`` template in your root template
+directory. This template will be used for all 404 errors.
+
+This ``page_not_found`` view should suffice for 99% of Web applications, but if
+you want to override the 404 view, you can specify ``handler404`` in your
+URLconf, like so::
+
+ handler404 = 'mysite.views.my_custom_404_view'
+
+Behind the scenes, Django determines the 404 view by looking for ``handler404``.
+By default, URLconfs contain the following line::
+
+ from django.conf.urls.defaults import *
+
+That takes care of setting ``handler404`` in the current module. As you can see
+in ``django/conf/urls/defaults.py``, ``handler404`` is set to
+``'django.views.defaults.page_not_found'`` by default.
+
+Three things to note about 404 views:
+
+ * The 404 view is also called if Django doesn't find a match after checking
+ every regular expression in the URLconf.
+
+ * If you don't define your own 404 view -- and simply use the default,
+ which is recommended -- you still have one obligation: To create a
+ ``404.html`` template in the root of your template directory. The default
+ 404 view will use that template for all 404 errors.
+
+ * If ``DEBUG`` is set to ``True`` (in your settings module) then your 404
+ view will never be used, and the traceback will be displayed instead.
+
+The 500 (server error) view
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Similarly, Django executes special-case behavior in the case of runtime errors
+in view code. If a view results in an exception, Django will, by default, call
+the view ``django.views.defaults.server_error``, which loads and renders the
+template ``500.html``.
+
+This means you need to define a ``500.html`` template in your root template
+directory. This template will be used for all server errors.
+
+This ``server_error`` view should suffice for 99% of Web applications, but if
+you want to override the view, you can specify ``handler500`` in your
+URLconf, like so::
+
+ handler500 = 'mysite.views.my_custom_error_view'
+
+Behind the scenes, Django determines the error view by looking for ``handler500``.
+By default, URLconfs contain the following line::
+
+ from django.conf.urls.defaults import *
+
+That takes care of setting ``handler500`` in the current module. As you can see
+in ``django/conf/urls/defaults.py``, ``handler500`` is set to
+``'django.views.defaults.server_error'`` by default.
diff --git a/google_appengine/lib/django/docs/serialization.txt b/google_appengine/lib/django/docs/serialization.txt
new file mode 100644
index 0000000..48ab46f
--- /dev/null
+++ b/google_appengine/lib/django/docs/serialization.txt
@@ -0,0 +1,118 @@
+==========================
+Serializing Django objects
+==========================
+
+.. note::
+
+ This API is currently under heavy development and may change --
+ perhaps drastically -- in the future.
+
+ You have been warned.
+
+Django's serialization framework provides a mechanism for "translating" Django
+objects into other formats. Usually these other formats will be text-based and
+used for sending Django objects over a wire, but it's possible for a
+serializer to handle any format (text-based or not).
+
+Serializing data
+----------------
+
+At the highest level, serializing data is a very simple operation::
+
+ from django.core import serializers
+ data = serializers.serialize("xml", SomeModel.objects.all())
+
+The arguments to the ``serialize`` function are the format to serialize the
+data to (see `Serialization formats`_) and a QuerySet_ to serialize.
+(Actually, the second argument can be any iterator that yields Django objects,
+but it'll almost always be a QuerySet).
+
+.. _QuerySet: ../db_api/#retrieving-objects
+
+You can also use a serializer object directly::
+
+ XMLSerializer = serializers.get_serializer("xml")
+ xml_serializer = XMLSerializer()
+ xml_serializer.serialize(queryset)
+ data = xml_serializer.getvalue()
+
+This is useful if you want to serialize data directly to a file-like object
+(which includes a HTTPResponse_)::
+
+ out = open("file.xml", "w")
+ xml_serializer.serialize(SomeModel.objects.all(), stream=out)
+
+.. _HTTPResponse: ../request_response/#httpresponse-objects
+
+Deserializing data
+------------------
+
+Deserializing data is also a fairly simple operation::
+
+ for obj in serializers.deserialize("xml", data):
+ do_something_with(obj)
+
+As you can see, the ``deserialize`` function takes the same format argument as
+``serialize``, a string or stream of data, and returns an iterator.
+
+However, here it gets slightly complicated. The objects returned by the
+``deserialize`` iterator *aren't* simple Django objects. Instead, they are
+special ``DeserializedObject`` instances that wrap a created -- but unsaved --
+object and any associated relationship data.
+
+Calling ``DeserializedObject.save()`` saves the object to the database.
+
+This ensures that deserializing is a non-destructive operation even if the
+data in your serialized representation doesn't match what's currently in the
+database. Usually, working with these ``DeserializedObject`` instances looks
+something like::
+
+ for deserialized_object in serializers.deserialize("xml", data):
+ if object_should_be_saved(deserialized_object):
+ obj.save()
+
+In other words, the usual use is to examine the deserialized objects to make
+sure that they are "appropriate" for saving before doing so. Of course, if you trust your data source you could just save the object and move on.
+
+The Django object itself can be inspected as ``deserialized_object.object``.
+
+Serialization formats
+---------------------
+
+Django "ships" with a few included serializers:
+
+ ========== ==============================================================
+ Identifier Information
+ ========== ==============================================================
+ ``xml`` Serializes to and from a simple XML dialect.
+
+ ``json`` Serializes to and from JSON_ (using a version of simplejson_
+ bundled with Django).
+
+ ``python`` Translates to and from "simple" Python objects (lists, dicts,
+ strings, etc.). Not really all that useful on its own, but
+ used as a base for other serializers.
+ ========== ==============================================================
+
+.. _json: http://json.org/
+.. _simplejson: http://undefined.org/python/#simplejson
+
+Notes for specific serialization formats
+----------------------------------------
+
+json
+~~~~
+
+If you're using UTF-8 (or any other non-ASCII encoding) data with the JSON
+serializer, you must pass ``ensure_ascii=False`` as a parameter to the
+``serialize()`` call. Otherwise, the output won't be encoded correctly.
+
+For example::
+
+ json_serializer = serializers.get_serializer("json")
+ json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
+
+Writing custom serializers
+``````````````````````````
+
+XXX ...
diff --git a/google_appengine/lib/django/docs/sessions.txt b/google_appengine/lib/django/docs/sessions.txt
new file mode 100644
index 0000000..660718b
--- /dev/null
+++ b/google_appengine/lib/django/docs/sessions.txt
@@ -0,0 +1,313 @@
+===================
+How to use sessions
+===================
+
+Django provides full support for anonymous sessions. The session framework lets
+you store and retrieve arbitrary data on a per-site-visitor basis. It stores
+data on the server side and abstracts the sending and receiving of cookies.
+Cookies contain a session ID -- not the data itself.
+
+Enabling sessions
+=================
+
+Sessions are implemented via a piece of middleware_ and a Django model.
+
+To enable session functionality, do these two things:
+
+ * Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
+ ``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
+ The default ``settings.py`` created by ``django-admin.py startproject`` has
+ ``SessionMiddleware`` activated.
+
+ * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
+ run ``manage.py syncdb`` to install the single database table that stores
+ session data.
+
+If you don't want to use sessions, you might as well remove the
+``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
+from your ``INSTALLED_APPS``. It'll save you a small bit of overhead.
+
+.. _middleware: ../middleware/
+
+Using sessions in views
+=======================
+
+When ``SessionMiddleware`` is activated, each ``HttpRequest`` object -- the
+first argument to any Django view function -- will have a ``session``
+attribute, which is a dictionary-like object. You can read it and write to it.
+
+It implements the following standard dictionary methods:
+
+ * ``__getitem__(key)``
+ Example: ``fav_color = request.session['fav_color']``
+
+ * ``__setitem__(key, value)``
+ Example: ``request.session['fav_color'] = 'blue'``
+
+ * ``__delitem__(key)``
+ Example: ``del request.session['fav_color']``. This raises ``KeyError``
+ if the given ``key`` isn't already in the session.
+
+ * ``__contains__(key)``
+ Example: ``'fav_color' in request.session``
+
+ * ``get(key, default=None)``
+ Example: ``fav_color = request.session.get('fav_color', 'red')``
+
+ * ``keys()``
+
+ * ``items()``
+
+It also has these three methods:
+
+ * ``set_test_cookie()``
+ Sets a test cookie to determine whether the user's browser supports
+ cookies. Due to the way cookies work, you won't be able to test this
+ until the user's next page request. See "Setting test cookies" below for
+ more information.
+
+ * ``test_cookie_worked()``
+ Returns either ``True`` or ``False``, depending on whether the user's
+ browser accepted the test cookie. Due to the way cookies work, you'll
+ have to call ``set_test_cookie()`` on a previous, separate page request.
+ See "Setting test cookies" below for more information.
+
+ * ``delete_test_cookie()``
+ Deletes the test cookie. Use this to clean up after yourself.
+
+You can edit ``request.session`` at any point in your view. You can edit it
+multiple times.
+
+Session object guidelines
+-------------------------
+
+ * Use normal Python strings as dictionary keys on ``request.session``. This
+ is more of a convention than a hard-and-fast rule.
+
+ * Session dictionary keys that begin with an underscore are reserved for
+ internal use by Django.
+
+ * Don't override ``request.session`` with a new object, and don't access or
+ set its attributes. Use it like a Python dictionary.
+
+Examples
+--------
+
+This simplistic view sets a ``has_commented`` variable to ``True`` after a user
+posts a comment. It doesn't let a user post a comment more than once::
+
+ def post_comment(request, new_comment):
+ if request.session.get('has_commented', False):
+ return HttpResponse("You've already commented.")
+ c = comments.Comment(comment=new_comment)
+ c.save()
+ request.session['has_commented'] = True
+ return HttpResponse('Thanks for your comment!')
+
+This simplistic view logs in a "member" of the site::
+
+ def login(request):
+ m = members.get_object(username__exact=request.POST['username'])
+ if m.password == request.POST['password']:
+ request.session['member_id'] = m.id
+ return HttpResponse("You're logged in.")
+ else:
+ return HttpResponse("Your username and password didn't match.")
+
+...And this one logs a member out, according to ``login()`` above::
+
+ def logout(request):
+ try:
+ del request.session['member_id']
+ except KeyError:
+ pass
+ return HttpResponse("You're logged out.")
+
+Setting test cookies
+====================
+
+As a convenience, Django provides an easy way to test whether the user's
+browser accepts cookies. Just call ``request.session.set_test_cookie()`` in a
+view, and call ``request.session.test_cookie_worked()`` in a subsequent view --
+not in the same view call.
+
+This awkward split between ``set_test_cookie()`` and ``test_cookie_worked()``
+is necessary due to the way cookies work. When you set a cookie, you can't
+actually tell whether a browser accepted it until the browser's next request.
+
+It's good practice to use ``delete_test_cookie()`` to clean up after yourself.
+Do this after you've verified that the test cookie worked.
+
+Here's a typical usage example::
+
+ def login(request):
+ if request.method == 'POST':
+ if request.session.test_cookie_worked():
+ request.session.delete_test_cookie()
+ return HttpResponse("You're logged in.")
+ else:
+ return HttpResponse("Please enable cookies and try again.")
+ request.session.set_test_cookie()
+ return render_to_response('foo/login_form.html')
+
+Using sessions out of views
+===========================
+
+Internally, each session is just a normal Django model. The ``Session`` model
+is defined in ``django/contrib/sessions/models.py``. Because it's a normal
+model, you can access sessions using the normal Django database API::
+
+ >>> from django.contrib.sessions.models import Session
+ >>> s = Session.objects.get_object(pk='2b1189a188b44ad18c35e113ac6ceead')
+ >>> s.expire_date
+ datetime.datetime(2005, 8, 20, 13, 35, 12)
+
+Note that you'll need to call ``get_decoded()`` to get the session dictionary.
+This is necessary because the dictionary is stored in an encoded format::
+
+ >>> s.session_data
+ 'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
+ >>> s.get_decoded()
+ {'user_id': 42}
+
+When sessions are saved
+=======================
+
+By default, Django only saves to the session database when the session has been
+modified -- that is if any of its dictionary values have been assigned or
+deleted::
+
+ # Session is modified.
+ request.session['foo'] = 'bar'
+
+ # Session is modified.
+ del request.session['foo']
+
+ # Session is modified.
+ request.session['foo'] = {}
+
+ # Gotcha: Session is NOT modified, because this alters
+ # request.session['foo'] instead of request.session.
+ request.session['foo']['bar'] = 'baz'
+
+To change this default behavior, set the ``SESSION_SAVE_EVERY_REQUEST`` setting
+to ``True``. If ``SESSION_SAVE_EVERY_REQUEST`` is ``True``, Django will save
+the session to the database on every single request.
+
+Note that the session cookie is only sent when a session has been created or
+modified. If ``SESSION_SAVE_EVERY_REQUEST`` is ``True``, the session cookie
+will be sent on every request.
+
+Similarly, the ``expires`` part of a session cookie is updated each time the
+session cookie is sent.
+
+Browser-length sessions vs. persistent sessions
+===============================================
+
+You can control whether the session framework uses browser-length sessions vs.
+persistent sessions with the ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` setting.
+
+By default, ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``False``, which
+means session cookies will be stored in users' browsers for as long as
+``SESSION_COOKIE_AGE``. Use this if you don't want people to have to log in
+every time they open a browser.
+
+If ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``True``, Django will use
+browser-length cookies -- cookies that expire as soon as the user closes his or
+her browser. Use this if you want people to have to log in every time they open
+a browser.
+
+Clearing the session table
+==========================
+
+Note that session data can accumulate in the ``django_session`` database table
+and Django does *not* provide automatic purging. Therefore, it's your job to
+purge expired sessions on a regular basis.
+
+To understand this problem, consider what happens when a user uses a session.
+When a user logs in, Django adds a row to the ``django_session`` database
+table. Django updates this row each time the session data changes. If the user
+logs out manually, Django deletes the row. But if the user does *not* log out,
+the row never gets deleted.
+
+Django provides a sample clean-up script in ``django/bin/daily_cleanup.py``.
+That script deletes any session in the session table whose ``expire_date`` is
+in the past -- but your application may have different requirements.
+
+Settings
+========
+
+A few `Django settings`_ give you control over session behavior:
+
+SESSION_COOKIE_AGE
+------------------
+
+Default: ``1209600`` (2 weeks, in seconds)
+
+The age of session cookies, in seconds.
+
+SESSION_COOKIE_DOMAIN
+---------------------
+
+Default: ``None``
+
+The domain to use for session cookies. Set this to a string such as
+``".lawrence.com"`` for cross-domain cookies, or use ``None`` for a standard
+domain cookie.
+
+SESSION_COOKIE_NAME
+-------------------
+
+Default: ``'sessionid'``
+
+The name of the cookie to use for sessions. This can be whatever you want.
+
+SESSION_COOKIE_SECURE
+---------------------
+
+Default: ``False``
+
+Whether to use a secure cookie for the session cookie. If this is set to
+``True``, the cookie will be marked as "secure," which means browsers may
+ensure that the cookie is only sent under an HTTPS connection.
+
+SESSION_EXPIRE_AT_BROWSER_CLOSE
+-------------------------------
+
+Default: ``False``
+
+Whether to expire the session when the user closes his or her browser. See
+"Browser-length sessions vs. persistent sessions" above.
+
+SESSION_SAVE_EVERY_REQUEST
+--------------------------
+
+Default: ``False``
+
+Whether to save the session data on every request. If this is ``False``
+(default), then the session data will only be saved if it has been modified --
+that is, if any of its dictionary values have been assigned or deleted.
+
+.. _Django settings: ../settings/
+
+Technical details
+=================
+
+ * The session dictionary should accept any pickleable Python object. See
+ `the pickle module`_ for more information.
+
+ * Session data is stored in a database table named ``django_session`` .
+
+ * Django only sends a cookie if it needs to. If you don't set any session
+ data, it won't send a session cookie.
+
+.. _`the pickle module`: http://www.python.org/doc/current/lib/module-pickle.html
+
+Session IDs in URLs
+===================
+
+The Django sessions framework is entirely, and solely, cookie-based. It does
+not fall back to putting session IDs in URLs as a last resort, as PHP does.
+This is an intentional design decision. Not only does that behavior make URLs
+ugly, it makes your site vulnerable to session-ID theft via the "Referer"
+header.
diff --git a/google_appengine/lib/django/docs/settings.txt b/google_appengine/lib/django/docs/settings.txt
new file mode 100644
index 0000000..63b5cce
--- /dev/null
+++ b/google_appengine/lib/django/docs/settings.txt
@@ -0,0 +1,1046 @@
+===============
+Django settings
+===============
+
+A Django settings file contains all the configuration of your Django
+installation. This document explains how settings work and which settings are
+available.
+
+The basics
+==========
+
+A settings file is just a Python module with module-level variables.
+
+Here are a couple of example settings::
+
+ DEBUG = False
+ DEFAULT_FROM_EMAIL = 'webmaster@example.com'
+ TEMPLATE_DIRS = ('/home/templates/mike', '/home/templates/john')
+
+Because a settings file is a Python module, the following apply:
+
+ * It doesn't allow for Python syntax errors.
+ * It can assign settings dynamically using normal Python syntax.
+ For example::
+
+ MY_SETTING = [str(i) for i in range(30)]
+
+ * It can import values from other settings files.
+
+Designating the settings
+========================
+
+When you use Django, you have to tell it which settings you're using. Do this
+by using an environment variable, ``DJANGO_SETTINGS_MODULE``.
+
+The value of ``DJANGO_SETTINGS_MODULE`` should be in Python path syntax, e.g.
+``mysite.settings``. Note that the settings module should be on the
+Python `import search path`_.
+
+.. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html
+
+The django-admin.py utility
+---------------------------
+
+When using `django-admin.py`_, you can either set the environment variable
+once, or explicitly pass in the settings module each time you run the utility.
+
+Example (Unix Bash shell)::
+
+ export DJANGO_SETTINGS_MODULE=mysite.settings
+ django-admin.py runserver
+
+Example (Windows shell)::
+
+ set DJANGO_SETTINGS_MODULE=mysite.settings
+ django-admin.py runserver
+
+Use the ``--settings`` command-line argument to specify the settings manually::
+
+ django-admin.py runserver --settings=mysite.settings
+
+.. _django-admin.py: ../django_admin/
+
+On the server (mod_python)
+--------------------------
+
+In your live server environment, you'll need to tell Apache/mod_python which
+settings file to use. Do that with ``SetEnv``::
+
+ <Location "/mysite/">
+ SetHandler python-program
+ PythonHandler django.core.handlers.modpython
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ </Location>
+
+Read the `Django mod_python documentation`_ for more information.
+
+.. _Django mod_python documentation: ../modpython/
+
+Default settings
+================
+
+A Django settings file doesn't have to define any settings if it doesn't need
+to. Each setting has a sensible default value. These defaults live in the file
+``django/conf/global_settings.py``.
+
+Here's the algorithm Django uses in compiling settings:
+
+ * Load settings from ``global_settings.py``.
+ * Load settings from the specified settings file, overriding the global
+ settings as necessary.
+
+Note that a settings file should *not* import from ``global_settings``, because
+that's redundant.
+
+Seeing which settings you've changed
+------------------------------------
+
+There's an easy way to view which of your settings deviate from the default
+settings. The command ``python manage.py diffsettings`` displays differences
+between the current settings file and Django's default settings.
+
+For more, see the `diffsettings documentation`_.
+
+.. _diffsettings documentation: ../django_admin/#diffsettings
+
+Using settings in Python code
+=============================
+
+In your Django apps, use settings by importing the object
+``django.conf.settings``. Example::
+
+ from django.conf import settings
+
+ if settings.DEBUG:
+ # Do something
+
+Note that ``django.conf.settings`` isn't a module -- it's an object. So
+importing individual settings is not possible::
+
+ from django.conf.settings import DEBUG # This won't work.
+
+Also note that your code should *not* import from either ``global_settings`` or
+your own settings file. ``django.conf.settings`` abstracts the concepts of
+default settings and site-specific settings; it presents a single interface.
+It also decouples the code that uses settings from the location of your
+settings.
+
+Altering settings at runtime
+============================
+
+You shouldn't alter settings in your applications at runtime. For example,
+don't do this in a view::
+
+ from django.conf import settings
+
+ settings.DEBUG = True # Don't do this!
+
+The only place you should assign to settings is in a settings file.
+
+Security
+========
+
+Because a settings file contains sensitive information, such as the database
+password, you should make every attempt to limit access to it. For example,
+change its file permissions so that only you and your Web server's user can
+read it. This is especially important in a shared-hosting environment.
+
+Available settings
+==================
+
+Here's a full list of all available settings, in alphabetical order, and their
+default values.
+
+ABSOLUTE_URL_OVERRIDES
+----------------------
+
+Default: ``{}`` (Empty dictionary)
+
+A dictionary mapping ``"app_label.model_name"`` strings to functions that take
+a model object and return its URL. This is a way of overriding
+``get_absolute_url()`` methods on a per-installation basis. Example::
+
+ ABSOLUTE_URL_OVERRIDES = {
+ 'blogs.weblog': lambda o: "/blogs/%s/" % o.slug,
+ 'news.story': lambda o: "/stories/%s/%s/" % (o.pub_year, o.slug),
+ }
+
+Note that the model name used in this setting should be all lower-case, regardless
+of the case of the actual model class name.
+
+ADMIN_FOR
+---------
+
+Default: ``()`` (Empty list)
+
+Used for admin-site settings modules, this should be a tuple of settings
+modules (in the format ``'foo.bar.baz'``) for which this site is an admin.
+
+The admin site uses this in its automatically-introspected documentation of
+models, views and template tags.
+
+ADMIN_MEDIA_PREFIX
+------------------
+
+Default: ``'/media/'``
+
+The URL prefix for admin media -- CSS, JavaScript and images. Make sure to use
+a trailing slash.
+
+ADMINS
+------
+
+Default: ``()`` (Empty tuple)
+
+A tuple that lists people who get code error notifications. When
+``DEBUG=False`` and a view raises an exception, Django will e-mail these people
+with the full exception information. Each member of the tuple should be a tuple
+of (Full name, e-mail address). Example::
+
+ (('John', 'john@example.com'), ('Mary', 'mary@example.com'))
+
+Note that Django will e-mail *all* of these people whenever an error happens. See the
+section on `error reporting via e-mail`_ for more information.
+
+ALLOWED_INCLUDE_ROOTS
+---------------------
+
+Default: ``()`` (Empty tuple)
+
+A tuple of strings representing allowed prefixes for the ``{% ssi %}`` template
+tag. This is a security measure, so that template authors can't access files
+that they shouldn't be accessing.
+
+For example, if ``ALLOWED_INCLUDE_ROOTS`` is ``('/home/html', '/var/www')``,
+then ``{% ssi /home/html/foo.txt %}`` would work, but ``{% ssi /etc/passwd %}``
+wouldn't.
+
+APPEND_SLASH
+------------
+
+Default: ``True``
+
+Whether to append trailing slashes to URLs. This is only used if
+``CommonMiddleware`` is installed (see the `middleware docs`_). See also
+``PREPEND_WWW``.
+
+CACHE_BACKEND
+-------------
+
+Default: ``'simple://'``
+
+The cache backend to use. See the `cache docs`_.
+
+CACHE_MIDDLEWARE_KEY_PREFIX
+
+Default: ``''`` (Empty string)
+
+The cache key prefix that the cache middleware should use. See the
+`cache docs`_.
+
+DATABASE_ENGINE
+---------------
+
+Default: ``''`` (Empty string)
+
+Which database backend to use. Either ``'postgresql_psycopg2'``,
+``'postgresql'``, ``'mysql'``, ``'mysql_old'``, ``'sqlite3'`` or
+``'ado_mssql'``.
+
+DATABASE_HOST
+-------------
+
+Default: ``''`` (Empty string)
+
+Which host to use when connecting to the database. An empty string means
+localhost. Not used with SQLite.
+
+If this value starts with a forward slash (``'/'``) and you're using MySQL,
+MySQL will connect via a Unix socket to the specified socket. For example::
+
+ DATABASE_HOST = '/var/run/mysql'
+
+If you're using MySQL and this value *doesn't* start with a forward slash, then
+this value is assumed to be the host.
+
+DATABASE_NAME
+-------------
+
+Default: ``''`` (Empty string)
+
+The name of the database to use. For SQLite, it's the full path to the database
+file.
+
+DATABASE_OPTIONS
+----------------
+
+Default: ``{}`` (Empty dictionary)
+
+Extra parameters to use when connecting to the database. Consult backend
+module's document for available keywords.
+
+DATABASE_PASSWORD
+-----------------
+
+Default: ``''`` (Empty string)
+
+The password to use when connecting to the database. Not used with SQLite.
+
+DATABASE_PORT
+-------------
+
+Default: ``''`` (Empty string)
+
+The port to use when connecting to the database. An empty string means the
+default port. Not used with SQLite.
+
+DATABASE_USER
+-------------
+
+Default: ``''`` (Empty string)
+
+The username to use when connecting to the database. Not used with SQLite.
+
+DATE_FORMAT
+-----------
+
+Default: ``'N j, Y'`` (e.g. ``Feb. 4, 2003``)
+
+The default formatting to use for date fields on Django admin change-list
+pages -- and, possibly, by other parts of the system. See
+`allowed date format strings`_.
+
+See also DATETIME_FORMAT, TIME_FORMAT, YEAR_MONTH_FORMAT and MONTH_DAY_FORMAT.
+
+.. _allowed date format strings: ../templates/#now
+
+DATETIME_FORMAT
+---------------
+
+Default: ``'N j, Y, P'`` (e.g. ``Feb. 4, 2003, 4 p.m.``)
+
+The default formatting to use for datetime fields on Django admin change-list
+pages -- and, possibly, by other parts of the system. See
+`allowed date format strings`_.
+
+See also DATE_FORMAT, DATETIME_FORMAT, TIME_FORMAT, YEAR_MONTH_FORMAT and MONTH_DAY_FORMAT.
+
+.. _allowed date format strings: ../templates/#now
+
+DEBUG
+-----
+
+Default: ``False``
+
+A boolean that turns on/off debug mode.
+
+If you define custom settings, django/views/debug.py has a ``HIDDEN_SETTINGS``
+regular expression which will hide from the DEBUG view anything that contins
+``'SECRET``, ``PASSWORD``, or ``PROFANITIES'``. This allows untrusted users to
+be able to give backtraces without seeing sensitive (or offensive) settings.
+
+Still, note that there are always going to be sections of your debug output that
+are inapporpriate for public consumption. File paths, configuration options, and
+the like all give attackers extra information about your server. Never deploy a
+site with ``DEBUG`` turned on.
+
+DEFAULT_CHARSET
+---------------
+
+Default: ``'utf-8'``
+
+Default charset to use for all ``HttpResponse`` objects, if a MIME type isn't
+manually specified. Used with ``DEFAULT_CONTENT_TYPE`` to construct the
+``Content-Type`` header.
+
+DEFAULT_CONTENT_TYPE
+--------------------
+
+Default: ``'text/html'``
+
+Default content type to use for all ``HttpResponse`` objects, if a MIME type
+isn't manually specified. Used with ``DEFAULT_CHARSET`` to construct the
+``Content-Type`` header.
+
+DEFAULT_FROM_EMAIL
+------------------
+
+Default: ``'webmaster@localhost'``
+
+Default e-mail address to use for various automated correspondence from the
+site manager(s).
+
+DISALLOWED_USER_AGENTS
+----------------------
+
+Default: ``()`` (Empty tuple)
+
+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. This is only used if ``CommonMiddleware`` is installed (see
+the `middleware docs`_).
+
+EMAIL_HOST
+----------
+
+Default: ``'localhost'``
+
+The host to use for sending e-mail.
+
+See also ``EMAIL_PORT``.
+
+EMAIL_HOST_PASSWORD
+-------------------
+
+Default: ``''`` (Empty string)
+
+Username to use for the SMTP server defined in ``EMAIL_HOST``. If empty,
+Django won't attempt authentication.
+
+See also ``EMAIL_HOST_USER``.
+
+EMAIL_HOST_USER
+---------------
+
+Default: ``''`` (Empty string)
+
+Username to use for the SMTP server defined in ``EMAIL_HOST``. If empty,
+Django won't attempt authentication.
+
+See also ``EMAIL_HOST_PASSWORD``.
+
+EMAIL_PORT
+----------
+
+Default: ``25``
+
+Port to use for the SMTP server defined in ``EMAIL_HOST``.
+
+EMAIL_SUBJECT_PREFIX
+--------------------
+
+Default: ``'[Django] '``
+
+Subject-line prefix for e-mail messages sent with ``django.core.mail.mail_admins``
+or ``django.core.mail.mail_managers``. You'll probably want to include the
+trailing space.
+
+FIXTURE_DIRS
+-------------
+
+Default: ``()`` (Empty tuple)
+
+List of locations of the fixture data files, in search order. Note that
+these paths should use Unix-style forward slashes, even on Windows. See
+`Testing Django Applications`_.
+
+.. _Testing Django Applications: ../testing/
+
+IGNORABLE_404_ENDS
+------------------
+
+Default: ``('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'favicon.ico', '.php')``
+
+See also ``IGNORABLE_404_STARTS`` and ``Error reporting via e-mail``.
+
+IGNORABLE_404_STARTS
+--------------------
+
+Default: ``('/cgi-bin/', '/_vti_bin', '/_vti_inf')``
+
+A tuple of strings that specify beginnings of URLs that should be ignored by
+the 404 e-mailer. See ``SEND_BROKEN_LINK_EMAILS``, ``IGNORABLE_404_ENDS`` and
+the section on `error reporting via e-mail`_.
+
+INSTALLED_APPS
+--------------
+
+Default: ``()`` (Empty tuple)
+
+A tuple of strings designating all applications that are enabled in this Django
+installation. Each string should be a full Python path to a Python package that
+contains a Django application, as created by `django-admin.py startapp`_.
+
+.. _django-admin.py startapp: ../django_admin/#startapp-appname
+
+INTERNAL_IPS
+------------
+
+Default: ``()`` (Empty tuple)
+
+A tuple of IP addresses, as strings, that:
+
+ * See debug comments, when ``DEBUG`` is ``True``
+ * Receive X headers if the ``XViewMiddleware`` is installed (see the
+ `middleware docs`_)
+
+JING_PATH
+---------
+
+Default: ``'/usr/bin/jing'``
+
+Path to the "Jing" executable. Jing is a RELAX NG validator, and Django uses it
+to validate each ``XMLField`` in your models.
+See http://www.thaiopensource.com/relaxng/jing.html .
+
+LANGUAGE_CODE
+-------------
+
+Default: ``'en-us'``
+
+A string representing the language code for this installation. This should be
+in standard language format. For example, U.S. English is ``"en-us"``. See the
+`internationalization docs`_.
+
+.. _internationalization docs: ../i18n/
+
+LANGUAGES
+---------
+
+Default: A tuple of all available languages. Currently, this is::
+
+ LANGUAGES = (
+ ('ar', _('Arabic')),
+ ('bn', _('Bengali')),
+ ('cs', _('Czech')),
+ ('cy', _('Welsh')),
+ ('da', _('Danish')),
+ ('de', _('German')),
+ ('el', _('Greek')),
+ ('en', _('English')),
+ ('es', _('Spanish')),
+ ('es_AR', _('Argentinean Spanish')),
+ ('fr', _('French')),
+ ('gl', _('Galician')),
+ ('hu', _('Hungarian')),
+ ('he', _('Hebrew')),
+ ('is', _('Icelandic')),
+ ('it', _('Italian')),
+ ('ja', _('Japanese')),
+ ('nl', _('Dutch')),
+ ('no', _('Norwegian')),
+ ('pt-br', _('Brazilian')),
+ ('ro', _('Romanian')),
+ ('ru', _('Russian')),
+ ('sk', _('Slovak')),
+ ('sl', _('Slovenian')),
+ ('sr', _('Serbian')),
+ ('sv', _('Swedish')),
+ ('ta', _('Tamil')),
+ ('uk', _('Ukrainian')),
+ ('zh-cn', _('Simplified Chinese')),
+ ('zh-tw', _('Traditional Chinese')),
+ )
+
+A tuple of two-tuples in the format (language code, language name). This
+specifies which languages are available for language selection. See the
+`internationalization docs`_ for details.
+
+Generally, the default value should suffice. Only set this setting if you want
+to restrict language selection to a subset of the Django-provided languages.
+
+If you define a custom ``LANGUAGES`` setting, it's OK to mark the languages as
+translation strings (as in the default value displayed above) -- but use a
+"dummy" ``gettext()`` function, not the one in ``django.utils.translation``.
+You should *never* import ``django.utils.translation`` from within your
+settings file, because that module in itself depends on the settings, and that
+would cause a circular import.
+
+The solution is to use a "dummy" ``gettext()`` function. Here's a sample
+settings file::
+
+ gettext = lambda s: s
+
+ LANGUAGES = (
+ ('de', gettext('German')),
+ ('en', gettext('English')),
+ )
+
+With this arrangement, ``make-messages.py`` will still find and mark these
+strings for translation, but the translation won't happen at runtime -- so
+you'll have to remember to wrap the languages in the *real* ``gettext()`` in
+any code that uses ``LANGUAGES`` at runtime.
+
+MANAGERS
+--------
+
+Default: ``()`` (Empty tuple)
+
+A tuple in the same format as ``ADMINS`` that specifies who should get
+broken-link notifications when ``SEND_BROKEN_LINK_EMAILS=True``.
+
+MEDIA_ROOT
+----------
+
+Default: ``''`` (Empty string)
+
+Absolute path to the directory that holds media for this installation.
+Example: ``"/home/media/media.lawrence.com/"`` See also ``MEDIA_URL``.
+
+MEDIA_URL
+---------
+
+Default: ``''`` (Empty string)
+
+URL that handles the media served from ``MEDIA_ROOT``.
+Example: ``"http://media.lawrence.com"``
+
+Note that this should have a trailing slash if it has a path component.
+
+Good: ``"http://www.example.com/static/"``
+Bad: ``"http://www.example.com/static"``
+
+MIDDLEWARE_CLASSES
+------------------
+
+Default::
+
+ ("django.contrib.sessions.middleware.SessionMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.doc.XViewMiddleware")
+
+A tuple of middleware classes to use. See the `middleware docs`_.
+
+MONTH_DAY_FORMAT
+----------------
+
+Default: ``'F j'``
+
+The default formatting to use for date fields on Django admin change-list
+pages -- and, possibly, by other parts of the system -- in cases when only the
+month and day are displayed.
+
+For example, when a Django admin change-list page is being filtered by a date
+drilldown, the header for a given day displays the day and month. Different
+locales have different formats. For example, U.S. English would say
+"January 1," whereas Spanish might say "1 Enero."
+
+See `allowed date format strings`_. See also DATE_FORMAT, DATETIME_FORMAT,
+TIME_FORMAT and YEAR_MONTH_FORMAT.
+
+PREPEND_WWW
+-----------
+
+Default: ``False``
+
+Whether to prepend the "www." subdomain to URLs that don't have it. This is
+only used if ``CommonMiddleware`` is installed (see the `middleware docs`_).
+See also ``APPEND_SLASH``.
+
+PROFANITIES_LIST
+----------------
+
+A tuple of profanities, as strings, that will trigger a validation error when
+the ``hasNoProfanities`` validator is called.
+
+We don't list the default values here, because that would be profane. To see
+the default values, see the file ``django/conf/global_settings.py``.
+
+ROOT_URLCONF
+------------
+
+Default: Not defined
+
+A string representing the full Python import path to your root URLconf. For example:
+``"mydjangoapps.urls"``. See `How Django processes a request`_.
+
+.. _How Django processes a request: ../url_dispatch/#how-django-processes-a-request
+
+SECRET_KEY
+----------
+
+Default: ``''`` (Empty string)
+
+A secret key for this particular Django installation. Used to provide a seed in
+secret-key hashing algorithms. Set this to a random string -- the longer, the
+better. ``django-admin.py startproject`` creates one automatically.
+
+SEND_BROKEN_LINK_EMAILS
+-----------------------
+
+Default: ``False``
+
+Whether to send an e-mail to the ``MANAGERS`` each time somebody visits a
+Django-powered page that is 404ed with a non-empty referer (i.e., a broken
+link). This is only used if ``CommonMiddleware`` is installed (see the
+`middleware docs`_). See also ``IGNORABLE_404_STARTS``,
+``IGNORABLE_404_ENDS`` and the section on `error reporting via e-mail`_
+
+SERIALIZATION_MODULES
+---------------------
+
+Default: Not defined.
+
+A dictionary of modules containing serializer definitions (provided as
+strings), keyed by a string identifier for that serialization type. For
+example, to define a YAML serializer, use::
+
+ SERIALIZATION_MODULES = { 'yaml' : 'path.to.yaml_serializer' }
+
+SERVER_EMAIL
+------------
+
+Default: ``'root@localhost'``
+
+The e-mail address that error messages come from, such as those sent to
+``ADMINS`` and ``MANAGERS``.
+
+SESSION_COOKIE_AGE
+------------------
+
+Default: ``1209600`` (2 weeks, in seconds)
+
+The age of session cookies, in seconds. See the `session docs`_.
+
+SESSION_COOKIE_DOMAIN
+---------------------
+
+Default: ``None``
+
+The domain to use for session cookies. Set this to a string such as
+``".lawrence.com"`` for cross-domain cookies, or use ``None`` for a standard
+domain cookie. See the `session docs`_.
+
+SESSION_COOKIE_NAME
+-------------------
+
+Default: ``'sessionid'``
+
+The name of the cookie to use for sessions. This can be whatever you want.
+See the `session docs`_.
+
+SESSION_COOKIE_SECURE
+---------------------
+
+Default: ``False``
+
+Whether to use a secure cookie for the session cookie. If this is set to
+``True``, the cookie will be marked as "secure," which means browsers may
+ensure that the cookie is only sent under an HTTPS connection.
+See the `session docs`_.
+
+SESSION_EXPIRE_AT_BROWSER_CLOSE
+-------------------------------
+
+Default: ``False``
+
+Whether to expire the session when the user closes his or her browser.
+See the `session docs`_.
+
+SESSION_SAVE_EVERY_REQUEST
+--------------------------
+
+Default: ``False``
+
+Whether to save the session data on every request. See the `session docs`_.
+
+SITE_ID
+-------
+
+Default: Not defined
+
+The ID, as an integer, of the current site in the ``django_site`` database
+table. This is used so that application data can hook into specific site(s)
+and a single database can manage content for multiple sites.
+
+See the `site framework docs`_.
+
+.. _site framework docs: ../sites/
+
+TEMPLATE_CONTEXT_PROCESSORS
+---------------------------
+
+Default::
+
+ ("django.core.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n")
+
+A tuple of callables that are used to populate the context in ``RequestContext``.
+These callables take a request object as their argument and return a dictionary
+of items to be merged into the context.
+
+TEMPLATE_DEBUG
+--------------
+
+Default: ``False``
+
+A boolean that turns on/off template debug mode. If this is ``True``, the fancy
+error page will display a detailed report for any ``TemplateSyntaxError``. This
+report contains the relevant snippet of the template, with the appropriate line
+highlighted.
+
+Note that Django only displays fancy error pages if ``DEBUG`` is ``True``, so
+you'll want to set that to take advantage of this setting.
+
+See also DEBUG.
+
+TEMPLATE_DIRS
+-------------
+
+Default: ``()`` (Empty tuple)
+
+List of locations of the template source files, in search order. Note that
+these paths should use Unix-style forward slashes, even on Windows.
+
+See the `template documentation`_.
+
+TEMPLATE_LOADERS
+----------------
+
+Default: ``('django.template.loaders.filesystem.load_template_source',)``
+
+A tuple of callables (as strings) that know how to import templates from
+various sources. See the `template documentation`_.
+
+TEMPLATE_STRING_IF_INVALID
+--------------------------
+
+Default: ``''`` (Empty string)
+
+Output, as a string, that the template system should use for invalid (e.g.
+misspelled) variables. See `How invalid variables are handled`_.
+
+.. _How invalid variables are handled: ../templates_python/#how-invalid-variables-are-handled
+
+TEST_RUNNER
+-----------
+
+Default: ``'django.test.simple.run_tests'``
+
+The name of the method to use for starting the test suite. See
+`Testing Django Applications`_.
+
+.. _Testing Django Applications: ../testing/
+
+TEST_DATABASE_NAME
+------------------
+
+Default: ``None``
+
+The name of database to use when running the test suite. If a value of
+``None`` is specified, the test database will use the name ``'test_' + settings.DATABASE_NAME``. See `Testing Django Applications`_.
+
+.. _Testing Django Applications: ../testing/
+
+TIME_FORMAT
+-----------
+
+Default: ``'P'`` (e.g. ``4 p.m.``)
+
+The default formatting to use for time fields on Django admin change-list
+pages -- and, possibly, by other parts of the system. See
+`allowed date format strings`_.
+
+See also DATE_FORMAT, DATETIME_FORMAT, TIME_FORMAT, YEAR_MONTH_FORMAT and
+MONTH_DAY_FORMAT.
+
+.. _allowed date format strings: ../templates/#now
+
+TIME_ZONE
+---------
+
+Default: ``'America/Chicago'``
+
+A string representing the time zone for this installation. `See available choices`_.
+(Note that list of available choices lists more than one on the same line;
+you'll want to use just one of the choices for a given time zone. For instance,
+one line says ``'Europe/London GB GB-Eire'``, but you should use the first bit
+of that -- ``'Europe/London'`` -- as your ``TIME_ZONE`` setting.)
+
+Note that this is the time zone to which Django will convert all dates/times --
+not necessarily the timezone of the server. For example, one server may serve
+multiple Django-powered sites, each with a separate time-zone setting.
+
+Normally, Django sets the ``os.environ['TZ']`` variable to the time zone you
+specify in the ``TIME_ZONE`` setting. Thus, all your views and models will
+automatically operate in the correct time zone. However, if you're using the
+manual configuration option (see below), Django will *not* touch the ``TZ``
+environment variable, and it'll be up to you to ensure your processes are
+running in the correct environment.
+
+.. note::
+ Django cannot reliably use alternate time zones in a Windows environment.
+ If you're running Django on Windows, this variable must be set to match the
+ system timezone.
+
+URL_VALIDATOR_USER_AGENT
+------------------------
+
+Default: ``Django/<version> (http://www.djangoproject.com/)``
+
+The string to use as the ``User-Agent`` header when checking to see if URLs
+exist (see the ``verify_exists`` option on URLField_).
+
+.. _URLField: ../model_api/#urlfield
+
+USE_ETAGS
+---------
+
+Default: ``False``
+
+A boolean that specifies whether to output the "Etag" header. This saves
+bandwidth but slows down performance. This is only used if ``CommonMiddleware``
+is installed (see the `middleware docs`_).
+
+USE_I18N
+--------
+
+Default: ``True``
+
+A boolean that specifies whether Django's internationalization system should be
+enabled. This provides an easy way to turn it off, for performance. If this is
+set to ``False``, Django will make some optimizations so as not to load the
+internationalization machinery.
+
+YEAR_MONTH_FORMAT
+-----------------
+
+Default: ``'F Y'``
+
+The default formatting to use for date fields on Django admin change-list
+pages -- and, possibly, by other parts of the system -- in cases when only the
+year and month are displayed.
+
+For example, when a Django admin change-list page is being filtered by a date
+drilldown, the header for a given month displays the month and the year.
+Different locales have different formats. For example, U.S. English would say
+"January 2006," whereas another locale might say "2006/January."
+
+See `allowed date format strings`_. See also DATE_FORMAT, DATETIME_FORMAT,
+TIME_FORMAT and MONTH_DAY_FORMAT.
+
+.. _cache docs: ../cache/
+.. _middleware docs: ../middleware/
+.. _session docs: ../sessions/
+.. _See available choices: http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+.. _template documentation: ../templates_python/
+
+Creating your own settings
+==========================
+
+There's nothing stopping you from creating your own settings, for your own
+Django apps. Just follow these conventions:
+
+ * Setting names are in all uppercase.
+ * For settings that are sequences, use tuples instead of lists. This is
+ purely for performance.
+ * Don't reinvent an already-existing setting.
+
+Using settings without setting DJANGO_SETTINGS_MODULE
+=====================================================
+
+In some cases, you might want to bypass the ``DJANGO_SETTINGS_MODULE``
+environment variable. For example, if you're using the template system by
+itself, you likely don't want to have to set up an environment variable
+pointing to a settings module.
+
+In these cases, you can configure Django's settings manually. Do this by
+calling ``django.conf.settings.configure()``.
+
+Example::
+
+ from django.conf import settings
+
+ settings.configure(DEBUG=True, TEMPLATE_DEBUG=True,
+ TEMPLATE_DIRS=('/home/web-apps/myapp', '/home/web-apps/base'))
+
+Pass ``configure()`` as many keyword arguments as you'd like, with each keyword
+argument representing a setting and its value. Each argument name should be all
+uppercase, with the same name as the settings described above. If a particular
+setting is not passed to ``configure()`` and is needed at some later point,
+Django will use the default setting value.
+
+Configuring Django in this fashion is mostly necessary -- and, indeed,
+recommended -- when you're using a piece of the framework inside a larger
+application.
+
+Consequently, when configured via ``settings.configure()``, Django will not
+make any modifications to the process environment variables. (See the
+explanation of ``TIME_ZONE``, above, for why this would normally occur.) It's
+assumed that you're already in full control of your environment in these cases.
+
+Custom default settings
+-----------------------
+
+If you'd like default values to come from somewhere other than
+``django.conf.global_settings``, you can pass in a module or class that
+provides the default settings as the ``default_settings`` argument (or as the
+first positional argument) in the call to ``configure()``.
+
+In this example, default settings are taken from ``myapp_defaults``, and the
+``DEBUG`` setting is set to ``True``, regardless of its value in
+``myapp_defaults``::
+
+ from django.conf import settings
+ from myapp import myapp_defaults
+
+ settings.configure(default_settings=myapp_defaults, DEBUG=True)
+
+The following example, which uses ``myapp_defaults`` as a positional argument,
+is equivalent::
+
+ settings.configure(myapp_defaults, DEBUG = True)
+
+Normally, you will not need to override the defaults in this fashion. The
+Django defaults are sufficiently tame that you can safely use them. Be aware
+that if you do pass in a new default module, it entirely *replaces* the Django
+defaults, so you must specify a value for every possible setting that might be
+used in that code you are importing. Check in
+``django.conf.settings.global_settings`` for the full list.
+
+Either configure() or DJANGO_SETTINGS_MODULE is required
+--------------------------------------------------------
+
+If you're not setting the ``DJANGO_SETTINGS_MODULE`` environment variable, you
+*must* call ``configure()`` at some point before using any code that reads
+settings.
+
+If you don't set ``DJANGO_SETTINGS_MODULE`` and don't call ``configure()``,
+Django will raise an ``EnvironmentError`` exception the first time a setting
+is accessed.
+
+If you set ``DJANGO_SETTINGS_MODULE``, access settings values somehow, *then*
+call ``configure()``, Django will raise an ``EnvironmentError`` saying settings
+have already been configured.
+
+Also, it's an error to call ``configure()`` more than once, or to call
+``configure()`` after any setting has been accessed.
+
+It boils down to this: Use exactly one of either ``configure()`` or
+``DJANGO_SETTINGS_MODULE``. Not both, and not neither.
+
+Error reporting via e-mail
+==========================
+
+Server errors
+-------------
+
+When ``DEBUG`` is ``False``, Django will e-mail the users listed in the
+``ADMIN`` setting whenever your code raises an unhandled exception and results
+in an internal server error (HTTP status code 500). This gives the
+administrators immediate notification of any errors.
+
+To disable this behavior, just remove all entries from the ``ADMINS`` setting.
+
+404 errors
+----------
+
+When ``DEBUG`` is ``False`` and your ``MIDDLEWARE_CLASSES`` setting includes
+``CommonMiddleware``, Django will e-mail the users listed in the ``MANAGERS``
+setting whenever your code raises a 404 and the request has a referer.
+(It doesn't bother to e-mail for 404s that don't have a referer.)
+
+You can tell Django to stop reporting particular 404s by tweaking the
+``IGNORABLE_404_ENDS`` and ``IGNORABLE_404_STARTS`` settings. Both should be a
+tuple of strings. For example::
+
+ IGNORABLE_404_ENDS = ('.php', '.cgi')
+ IGNORABLE_404_STARTS = ('/phpmyadmin/')
+
+In this example, a 404 to any URL ending with ``.php`` or ``.cgi`` will *not*
+be reported. Neither will any URL starting with ``/phpmyadmin/``.
+
+To disable this behavior, just remove all entries from the ``MANAGERS`` setting.
diff --git a/google_appengine/lib/django/docs/sitemaps.txt b/google_appengine/lib/django/docs/sitemaps.txt
new file mode 100644
index 0000000..dafc009
--- /dev/null
+++ b/google_appengine/lib/django/docs/sitemaps.txt
@@ -0,0 +1,321 @@
+=====================
+The sitemap framework
+=====================
+
+**New in Django development version**.
+
+Django comes with a high-level sitemap-generating framework that makes
+creating sitemap_ XML files easy.
+
+.. _sitemap: http://www.sitemaps.org/
+
+Overview
+========
+
+A sitemap is an XML file on your Web site that tells search-engine indexers how
+frequently your pages change and how "important" certain pages are in relation
+to other pages on your site. This information helps search engines index your
+site.
+
+The Django sitemap framework automates the creation of this XML file by letting
+you express this information in Python code.
+
+It works much like Django's `syndication framework`_. To create a sitemap, just
+write a ``Sitemap`` class and point to it in your URLconf_.
+
+.. _syndication framework: ../syndication/
+.. _URLconf: ../url_dispatch/
+
+Installation
+============
+
+To install the sitemap app, follow these steps:
+
+ 1. Add ``'django.contrib.sitemaps'`` to your INSTALLED_APPS_ setting.
+ 2. Make sure ``'django.template.loaders.app_directories.load_template_source'``
+ is in your TEMPLATE_LOADERS_ setting. It's in there by default, so
+ you'll only need to change this if you've changed that setting.
+ 3. Make sure you've installed the `sites framework`_.
+
+(Note: The sitemap application doesn't install any database tables. The only
+reason it needs to go into ``INSTALLED_APPS`` is so that the
+``load_template_source`` template loader can find the default templates.)
+
+.. _INSTALLED_APPS: ../settings/#installed-apps
+.. _TEMPLATE_LOADERS: ../settings/#template-loaders
+.. _sites framework: ../sites/
+
+Initialization
+==============
+
+To activate sitemap generation on your Django site, add this line to your
+URLconf_:
+
+ (r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})
+
+This tells Django to build a sitemap when a client accesses ``/sitemap.xml``.
+
+The name of the sitemap file is not important, but the location is. Search
+engines will only index links in your sitemap for the current URL level and
+below. For instance, if ``sitemap.xml`` lives in your root directory, it may
+reference any URL in your site. However, if your sitemap lives at
+``/content/sitemap.xml``, it may only reference URLs that begin with
+``/content/``.
+
+The sitemap view takes an extra, required argument: ``{'sitemaps': sitemaps}``.
+``sitemaps`` should be a dictionary that maps a short section label (e.g.,
+``blog`` or ``news``) to its ``Sitemap`` class (e.g., ``BlogSitemap`` or
+``NewsSitemap``). It may also map to an *instance* of a ``Sitemap`` class
+(e.g., ``BlogSitemap(some_var)``).
+
+.. _URLconf: ../url_dispatch/
+
+Sitemap classes
+===============
+
+A ``Sitemap`` class is a simple Python class that represents a "section" of
+entries in your sitemap. For example, one ``Sitemap`` class could represent all
+the entries of your weblog, while another could represent all of the events in
+your events calendar.
+
+In the simplest case, all these sections get lumped together into one
+``sitemap.xml``, but it's also possible to use the framework to generate a
+sitemap index that references individual sitemap files, one per section. (See
+`Creating a sitemap index`_ below.)
+
+``Sitemap`` classes must subclass ``django.contrib.sitemaps.Sitemap``. They can
+live anywhere in your codebase.
+
+A simple example
+================
+
+Let's assume you have a blog system, with an ``Entry`` model, and you want your
+sitemap to include all the links to your individual blog entries. Here's how
+your sitemap class might look::
+
+ from django.contrib.sitemaps import Sitemap
+ from mysite.blog.models import Entry
+
+ class BlogSitemap(Sitemap):
+ changefreq = "never"
+ priority = 0.5
+
+ def items(self):
+ return Entry.objects.filter(is_draft=False)
+
+ def lastmod(self, obj):
+ return obj.pub_date
+
+Note:
+
+ * ``changefreq`` and ``priority`` are class attributes corresponding to
+ ``<changefreq>`` and ``<priority>`` elements, respectively. They can be
+ made callable as functions, as ``lastmod`` was in the example.
+ * ``items()`` is simply a method that returns a list of objects. The objects
+ returned will get passed to any callable methods corresponding to a
+ sitemap property (``location``, ``lastmod``, ``changefreq``, and
+ ``priority``).
+ * ``lastmod`` should return a Python ``datetime`` object.
+ * There is no ``location`` method in this example, but you can provide it
+ in order to specify the URL for your object. By default, ``location()``
+ calls ``get_absolute_url()`` on each object and returns the result.
+
+Sitemap class reference
+=======================
+
+A ``Sitemap`` class can define the following methods/attributes:
+
+``items``
+---------
+
+**Required.** A method that returns a list of objects. The framework doesn't
+care what *type* of objects they are; all that matters is that these objects
+get passed to the ``location()``, ``lastmod()``, ``changefreq()`` and
+``priority()`` methods.
+
+``location``
+------------
+
+**Optional.** Either a method or attribute.
+
+If it's a method, it should return the absolute URL for a given object as
+returned by ``items()``.
+
+If it's an attribute, its value should be a string representing an absolute URL
+to use for *every* object returned by ``items()``.
+
+In both cases, "absolute URL" means a URL that doesn't include the protocol or
+domain. Examples:
+
+ * Good: ``'/foo/bar/'``
+ * Bad: ``'example.com/foo/bar/'``
+ * Bad: ``'http://example.com/foo/bar/'``
+
+If ``location`` isn't provided, the framework will call the
+``get_absolute_url()`` method on each object as returned by ``items()``.
+
+``lastmod``
+-----------
+
+**Optional.** Either a method or attribute.
+
+If it's a method, it should take one argument -- an object as returned by
+``items()`` -- and return that object's last-modified date/time, as a Python
+``datetime.datetime`` object.
+
+If it's an attribute, its value should be a Python ``datetime.datetime`` object
+representing the last-modified date/time for *every* object returned by
+``items()``.
+
+``changefreq``
+--------------
+
+**Optional.** Either a method or attribute.
+
+If it's a method, it should take one argument -- an object as returned by
+``items()`` -- and return that object's change frequency, as a Python string.
+
+If it's an attribute, its value should be a string representing the change
+frequency of *every* object returned by ``items()``.
+
+Possible values for ``changefreq``, whether you use a method or attribute, are:
+
+ * ``'always'``
+ * ``'hourly'``
+ * ``'daily'``
+ * ``'weekly'``
+ * ``'monthly'``
+ * ``'yearly'``
+ * ``'never'``
+
+``priority``
+------------
+
+**Optional.** Either a method or attribute.
+
+If it's a method, it should take one argument -- an object as returned by
+``items()`` -- and return that object's priority, as either a string or float.
+
+If it's an attribute, its value should be either a string or float representing
+the priority of *every* object returned by ``items()``.
+
+Example values for ``priority``: ``0.4``, ``1.0``. The default priority of a
+page is ``0.5``. See the `sitemaps.org documentation`_ for more.
+
+.. _sitemaps.org documentation: http://www.sitemaps.org/protocol.html#prioritydef
+
+Shortcuts
+=========
+
+The sitemap framework provides a couple convenience classes for common cases:
+
+``FlatPageSitemap``
+-------------------
+
+The ``django.contrib.sitemaps.FlatPageSitemap`` class looks at all flatpages_
+defined for the current ``SITE_ID`` (see the `sites documentation`_) and
+creates an entry in the sitemap. These entries include only the ``location``
+attribute -- not ``lastmod``, ``changefreq`` or ``priority``.
+
+.. _flatpages: ../flatpages/
+.. _sites documentation: ../sites/
+
+``GenericSitemap``
+------------------
+
+The ``GenericSitemap`` class works with any `generic views`_ you already have.
+To use it, create an instance, passing in the same ``info_dict`` you pass to
+the generic views. The only requirement is that the dictionary have a
+``queryset`` entry. It may also have a ``date_field`` entry that specifies a
+date field for objects retrieved from the ``queryset``. This will be used for
+the ``lastmod`` attribute in the generated sitemap. You may also pass
+``priority`` and ``changefreq`` keyword arguments to the ``GenericSitemap``
+constructor to specify these attributes for all URLs.
+
+.. _generic views: ../generic_views/
+
+Example
+-------
+
+Here's an example of a URLconf_ using both::
+
+ from django.conf.urls.defaults import *
+ from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap
+ from mysite.blog.models import Entry
+
+ info_dict = {
+ 'queryset': Entry.objects.all(),
+ 'date_field': 'pub_date',
+ }
+
+ sitemaps = {
+ 'flatpages': FlatPageSitemap,
+ 'blog': GenericSitemap(info_dict, priority=0.6),
+ }
+
+ urlpatterns = patterns('',
+ # some generic view using info_dict
+ # ...
+
+ # the sitemap
+ (r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})
+ )
+
+.. _URLconf: ../url_dispatch/
+
+Creating a sitemap index
+========================
+
+The sitemap framework also has the ability to create a sitemap index that
+references individual sitemap files, one per each section defined in your
+``sitemaps`` dictionary. The only differences in usage are:
+
+ * You use two views in your URLconf: ``django.contrib.sitemaps.views.index``
+ and ``django.contrib.sitemaps.views.sitemap``.
+ * The ``django.contrib.sitemaps.views.sitemap`` view should take a
+ ``section`` keyword argument.
+
+Here is what the relevant URLconf lines would look like for the example above::
+
+ (r'^sitemap.xml$', 'django.contrib.sitemaps.views.index', {'sitemaps': sitemaps})
+ (r'^sitemap-(?P<section>.+).xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})
+
+This will automatically generate a ``sitemap.xml`` file that references
+both ``sitemap-flatpages.xml`` and ``sitemap-blog.xml``. The ``Sitemap``
+classes and the ``sitemaps`` dict don't change at all.
+
+Pinging Google
+==============
+
+You may want to "ping" Google when your sitemap changes, to let it know to
+reindex your site. The framework provides a function to do just that:
+``django.contrib.sitemaps.ping_google()``.
+
+``ping_google()`` takes an optional argument, ``sitemap_url``, which should be
+the absolute URL of your site's sitemap (e.g., ``'/sitemap.xml'``). If this
+argument isn't provided, ``ping_google()`` will attempt to figure out your
+sitemap by performing a reverse looking in your URLconf.
+
+``ping_google()`` raises the exception
+``django.contrib.sitemaps.SitemapNotFound`` if it cannot determine your sitemap
+URL.
+
+One useful way to call ``ping_google()`` is from a model's ``save()`` method::
+
+ from django.contrib.sitemaps import ping_google
+
+ class Entry(models.Model):
+ # ...
+ def save(self):
+ super(Entry, self).save()
+ try:
+ ping_google()
+ except Exception:
+ # Bare 'except' because we could get a variety
+ # of HTTP-related exceptions.
+ pass
+
+A more efficient solution, however, would be to call ``ping_google()`` from a
+cron script, or some other scheduled task. The function makes an HTTP request
+to Google's servers, so you may not want to introduce that network overhead
+each time you call ``save()``.
diff --git a/google_appengine/lib/django/docs/sites.txt b/google_appengine/lib/django/docs/sites.txt
new file mode 100644
index 0000000..7497d7d
--- /dev/null
+++ b/google_appengine/lib/django/docs/sites.txt
@@ -0,0 +1,322 @@
+=====================
+The "sites" framework
+=====================
+
+Django comes with an optional "sites" framework. It's a hook for associating
+objects and functionality to particular Web sites, and it's a holding place for
+the domain names and "verbose" names of your Django-powered sites.
+
+Use it if your single Django installation powers more than one site and you
+need to differentiate between those sites in some way.
+
+The whole sites framework is based on two simple concepts:
+
+ * The ``Site`` model, found in ``django.contrib.sites``, has ``domain`` and
+ ``name`` fields.
+ * The ``SITE_ID`` setting specifies the database ID of the ``Site`` object
+ associated with that particular settings file.
+
+How you use this is up to you, but Django uses it in a couple of ways
+automatically via simple conventions.
+
+Example usage
+=============
+
+Why would you use sites? It's best explained through examples.
+
+Associating content with multiple sites
+---------------------------------------
+
+The Django-powered sites LJWorld.com_ and Lawrence.com_ are operated by the
+same news organization -- the Lawrence Journal-World newspaper in Lawrence,
+Kansas. LJWorld.com focuses on news, while Lawrence.com focuses on local
+entertainment. But sometimes editors want to publish an article on *both*
+sites.
+
+The brain-dead way of solving the problem would be to require site producers to
+publish the same story twice: once for LJWorld.com and again for Lawrence.com.
+But that's inefficient for site producers, and it's redundant to store
+multiple copies of the same story in the database.
+
+The better solution is simple: Both sites use the same article database, and an
+article is associated with one or more sites. In Django model terminology,
+that's represented by a ``ManyToManyField`` in the ``Article`` model::
+
+ from django.db import models
+ from django.contrib.sites.models import Site
+
+ class Article(models.Model):
+ headline = models.CharField(maxlength=200)
+ # ...
+ sites = models.ManyToManyField(Site)
+
+This accomplishes several things quite nicely:
+
+ * It lets the site producers edit all content -- on both sites -- in a
+ single interface (the Django admin).
+
+ * It means the same story doesn't have to be published twice in the
+ database; it only has a single record in the database.
+
+ * It lets the site developers use the same Django view code for both sites.
+ The view code that displays a given story just checks to make sure the
+ requested story is on the current site. It looks something like this::
+
+ from django.conf import settings
+
+ def article_detail(request, article_id):
+ try:
+ a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID)
+ except Article.DoesNotExist:
+ raise Http404
+ # ...
+
+.. _ljworld.com: http://www.ljworld.com/
+.. _lawrence.com: http://www.lawrence.com/
+
+Associating content with a single site
+--------------------------------------
+
+Similarly, you can associate a model to the ``Site`` model in a many-to-one
+relationship, using ``ForeignKey``.
+
+For example, if an article is only allowed on a single site, you'd use a model
+like this::
+
+ from django.db import models
+ from django.contrib.sites.models import Site
+
+ class Article(models.Model):
+ headline = models.CharField(maxlength=200)
+ # ...
+ site = models.ForeignKey(Site)
+
+This has the same benefits as described in the last section.
+
+Hooking into the current site from views
+----------------------------------------
+
+On a lower level, you can use the sites framework in your Django views to do
+particular things based on what site in which the view is being called.
+For example::
+
+ from django.conf import settings
+
+ def my_view(request):
+ if settings.SITE_ID == 3:
+ # Do something.
+ else:
+ # Do something else.
+
+Of course, it's ugly to hard-code the site IDs like that. This sort of
+hard-coding is best for hackish fixes that you need done quickly. A slightly
+cleaner way of accomplishing the same thing is to check the current site's
+domain::
+
+ from django.conf import settings
+ from django.contrib.sites.models import Site
+
+ def my_view(request):
+ current_site = Site.objects.get(id=settings.SITE_ID)
+ if current_site.domain == 'foo.com':
+ # Do something
+ else:
+ # Do something else.
+
+The idiom of retrieving the ``Site`` object for the value of
+``settings.SITE_ID`` is quite common, so the ``Site`` model's manager has a
+``get_current()`` method. This example is equivalent to the previous one::
+
+ from django.contrib.sites.models import Site
+
+ def my_view(request):
+ current_site = Site.objects.get_current()
+ if current_site.domain == 'foo.com':
+ # Do something
+ else:
+ # Do something else.
+
+Getting the current domain for display
+--------------------------------------
+
+LJWorld.com and Lawrence.com both have e-mail alert functionality, which lets
+readers sign up to get notifications when news happens. It's pretty basic: A
+reader signs up on a Web form, and he immediately gets an e-mail saying,
+"Thanks for your subscription."
+
+It'd be inefficient and redundant to implement this signup-processing code
+twice, so the sites use the same code behind the scenes. But the "thank you for
+signing up" notice needs to be different for each site. By using ``Site``
+objects, we can abstract the "thank you" notice to use the values of the
+current site's ``name`` and ``domain``.
+
+Here's an example of what the form-handling view looks like::
+
+ from django.contrib.sites.models import Site
+ from django.core.mail import send_mail
+
+ def register_for_newsletter(request):
+ # Check form values, etc., and subscribe the user.
+ # ...
+
+ current_site = Site.objects.get_current()
+ send_mail('Thanks for subscribing to %s alerts' % current_site.name,
+ 'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name,
+ 'editor@%s' % current_site.domain,
+ [user.email])
+
+ # ...
+
+On Lawrence.com, this e-mail has the subject line "Thanks for subscribing to
+lawrence.com alerts." On LJWorld.com, the e-mail has the subject "Thanks for
+subscribing to LJWorld.com alerts." Same goes for the e-mail's message body.
+
+Note that an even more flexible (but more heavyweight) way of doing this would
+be to use Django's template system. Assuming Lawrence.com and LJWorld.com have
+different template directories (``TEMPLATE_DIRS``), you could simply farm out
+to the template system like so::
+
+ from django.core.mail import send_mail
+ from django.template import loader, Context
+
+ def register_for_newsletter(request):
+ # Check form values, etc., and subscribe the user.
+ # ...
+
+ subject = loader.get_template('alerts/subject.txt').render(Context({}))
+ message = loader.get_template('alerts/message.txt').render(Context({}))
+ send_mail(subject, message, 'editor@ljworld.com', [user.email])
+
+ # ...
+
+In this case, you'd have to create ``subject.txt`` and ``message.txt`` template
+files for both the LJWorld.com and Lawrence.com template directories. That
+gives you more flexibility, but it's also more complex.
+
+It's a good idea to exploit the ``Site`` objects as much as possible, to remove
+unneeded complexity and redundancy.
+
+Getting the current domain for full URLs
+----------------------------------------
+
+Django's ``get_absolute_url()`` convention is nice for getting your objects'
+URL without the domain name, but in some cases you might want to display the
+full URL -- with ``http://`` and the domain and everything -- for an object.
+To do this, you can use the sites framework. A simple example::
+
+ >>> from django.contrib.sites.models import Site
+ >>> obj = MyModel.objects.get(id=3)
+ >>> obj.get_absolute_url()
+ '/mymodel/objects/3/'
+ >>> Site.objects.get_current().domain
+ 'example.com'
+ >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
+ 'http://example.com/mymodel/objects/3/'
+
+The ``CurrentSiteManager``
+==========================
+
+If ``Site``\s play a key role in your application, consider using the helpful
+``CurrentSiteManager`` in your model(s). It's a model manager_ that
+automatically filters its queries to include only objects associated with the
+current ``Site``.
+
+Use ``CurrentSiteManager`` by adding it to your model explicitly. For example::
+
+ from django.db import models
+ from django.contrib.sites.models import Site
+ from django.contrib.sites.managers import CurrentSiteManager
+
+ class Photo(models.Model):
+ photo = models.FileField(upload_to='/home/photos')
+ photographer_name = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+ site = models.ForeignKey(Site)
+ objects = models.Manager()
+ on_site = CurrentSiteManager()
+
+With this model, ``Photo.objects.all()`` will return all ``Photo`` objects in
+the database, but ``Photo.on_site.all()`` will return only the ``Photo``
+objects associated with the current site, according to the ``SITE_ID`` setting.
+
+Put another way, these two statements are equivalent::
+
+ Photo.objects.filter(site=settings.SITE_ID)
+ Photo.on_site.all()
+
+How did ``CurrentSiteManager`` know which field of ``Photo`` was the ``Site``?
+It defaults to looking for a field called ``site``. If your model has a
+``ForeignKey`` or ``ManyToManyField`` called something *other* than ``site``,
+you need to explicitly pass that as the parameter to ``CurrentSiteManager``.
+The following model, which has a field called ``publish_on``, demonstrates
+this::
+
+ from django.db import models
+ from django.contrib.sites.models import Site
+ from django.contrib.sites.managers import CurrentSiteManager
+
+ class Photo(models.Model):
+ photo = models.FileField(upload_to='/home/photos')
+ photographer_name = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+ publish_on = models.ForeignKey(Site)
+ objects = models.Manager()
+ on_site = CurrentSiteManager('publish_on')
+
+If you attempt to use ``CurrentSiteManager`` and pass a field name that doesn't
+exist, Django will raise a ``ValueError``.
+
+Finally, note that you'll probably want to keep a normal (non-site-specific)
+``Manager`` on your model, even if you use ``CurrentSiteManager``. As explained
+in the `manager documentation`_, if you define a manager manually, then Django
+won't create the automatic ``objects = models.Manager()`` manager for you.
+Also, note that certain parts of Django -- namely, the Django admin site and
+generic views -- use whichever manager is defined *first* in the model, so if
+you want your admin site to have access to all objects (not just site-specific
+ones), put ``objects = models.Manager()`` in your model, before you define
+``CurrentSiteManager``.
+
+.. _manager: ../model_api/#managers
+.. _manager documentation: ../model_api/#managers
+
+How Django uses the sites framework
+===================================
+
+Although it's not required that you use the sites framework, it's strongly
+encouraged, because Django takes advantage of it in a few places. Even if your
+Django installation is powering only a single site, you should take the two
+seconds to create the site object with your ``domain`` and ``name``, and point
+to its ID in your ``SITE_ID`` setting.
+
+Here's how Django uses the sites framework:
+
+ * In the `redirects framework`_, each redirect object is associated with a
+ particular site. When Django searches for a redirect, it takes into
+ account the current ``SITE_ID``.
+
+ * In the comments framework, each comment is associated with a particular
+ site. When a comment is posted, its ``site`` is set to the current
+ ``SITE_ID``, and when comments are listed via the appropriate template
+ tag, only the comments for the current site are displayed.
+
+ * In the `flatpages framework`_, each flatpage is associated with a
+ particular site. When a flatpage is created, you specify its ``site``,
+ and the ``FlatpageFallbackMiddleware`` checks the current ``SITE_ID`` in
+ retrieving flatpages to display.
+
+ * In the `syndication framework`_, the templates for ``title`` and
+ ``description`` automatically have access to a variable ``{{{ site }}}``,
+ which is the ``Site`` object representing the current site. Also, the
+ hook for providing item URLs will use the ``domain`` from the current
+ ``Site`` object if you don't specify a fully-qualified domain.
+
+ * In the `authentication framework`_, the ``django.contrib.auth.views.login``
+ view passes the current ``Site`` name to the template as ``{{{ site_name }}}``.
+
+ * The shortcut view (``django.views.defaults.shortcut``) uses the domain of
+ the current ``Site`` object when calculating an object's URL.
+
+.. _redirects framework: ../redirects/
+.. _flatpages framework: ../flatpages/
+.. _syndication framework: ../syndication/
+.. _authentication framework: ../authentication/
diff --git a/google_appengine/lib/django/docs/static_files.txt b/google_appengine/lib/django/docs/static_files.txt
new file mode 100644
index 0000000..b6a1d27
--- /dev/null
+++ b/google_appengine/lib/django/docs/static_files.txt
@@ -0,0 +1,125 @@
+=========================
+How to serve static files
+=========================
+
+Django itself doesn't serve static (media) files, such as images, style sheets,
+or video. It leaves that job to whichever Web server you choose.
+
+The reasoning here is that standard Web servers, such as Apache_ and lighttpd_,
+are much more fine-tuned at serving static files than a Web application
+framework.
+
+With that said, Django does support static files **during development**. Use
+the view ``django.views.static.serve`` to serve media files.
+
+.. _Apache: http://httpd.apache.org/
+.. _lighttpd: http://www.lighttpd.net/
+
+The big, fat disclaimer
+=======================
+
+Using this method is **inefficient** and **insecure**. Do not use this in a
+production setting. Use this only for development.
+
+For information on serving static files in an Apache production environment,
+see the `Django mod_python documentation`_.
+
+.. _Django mod_python documentation: ../modpython/#serving-media-files
+
+How to do it
+============
+
+Just put this in your URLconf_::
+
+ (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media'}),
+
+...where ``site_media`` is the URL where your media will be rooted, and
+``/path/to/media`` is the filesystem root for your media.
+
+You must pass a ``document_root`` parameter to indicate the filesystem root.
+
+Examples:
+
+ * The file ``/path/to/media/foo.jpg`` will be made available at the URL
+ ``/site_media/foo.jpg``.
+
+ * The file ``/path/to/media/css/mystyles.css`` will be made available
+ at the URL ``/site_media/css/mystyles.css``.
+
+ * The file ``/path/bar.jpg`` will not be accessible, because it doesn't
+ fall under the document root.
+
+.. _URLconf: ../url_dispatch/
+
+Directory listings
+==================
+
+Optionally, you can pass a ``show_indexes`` parameter to the ``static.serve``
+view. This is ``False`` by default. If it's ``True``, Django will display file
+listings for directories.
+
+Example::
+
+ (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media', 'show_indexes': True}),
+
+You can customize the index view by creating a template called
+``static/directory_index``. That template gets two objects in its context:
+
+ * ``directory`` -- the directory name (a string)
+ * ``file_list`` -- a list of file names (as strings) in the directory
+
+Here's the default ``static/directory_index`` template::
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Language" content="en-us" />
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ <title>Index of {{ directory }}</title>
+ </head>
+ <body>
+ <h1>Index of {{ directory }}</h1>
+ <ul>
+ {% for f in file_list %}
+ <li><a href="{{ f }}">{{ f }}</a></li>
+ {% endfor %}
+ </ul>
+ </body>
+ </html>
+
+Limiting use to DEBUG=True
+==========================
+
+Because URLconfs are just plain Python modules, you can use Python logic to
+make the static-media view available only in development mode. This is a handy
+trick to make sure the static-serving view doesn't slip into a production
+setting by mistake.
+
+Do this by wrapping an ``if DEBUG`` statement around the
+``django.views.static.serve`` inclusion. Here's a full example URLconf::
+
+ from django.conf.urls.defaults import *
+ from django.conf import settings
+
+ urlpatterns = patterns('',
+ (r'^/articles/2003/$', 'news.views.special_case_2003'),
+ (r'^/articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
+ (r'^/articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'news.views.month_archive'),
+ (r'^/articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'news.views.article_detail'),
+ )
+
+ if settings.DEBUG:
+ urlpatterns += patterns('',
+ (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media'}),
+ )
+
+This code is straightforward. It imports the settings and checks the value of
+the ``DEBUG`` setting. If it evaluates to ``True``, then ``site_media`` will be
+associated with the ``django.views.static.serve`` view. If not
+(``DEBUG == False``), then the view won't be made available.
+
+Of course, the catch here is that you'll have to remember to set ``DEBUG=False``
+in your production settings file. But you should be doing that anyway.
+
+.. _DEBUG setting: ../settings/#debug
diff --git a/google_appengine/lib/django/docs/syndication_feeds.txt b/google_appengine/lib/django/docs/syndication_feeds.txt
new file mode 100644
index 0000000..a64914d
--- /dev/null
+++ b/google_appengine/lib/django/docs/syndication_feeds.txt
@@ -0,0 +1,767 @@
+==============================
+The syndication feed framework
+==============================
+
+Django comes with a high-level syndication-feed-generating framework that makes
+creating RSS_ and Atom_ feeds easy.
+
+To create any syndication feed, all you have to do is write a short Python
+class. You can create as many feeds as you want.
+
+Django also comes with a lower-level feed-generating API. Use this if you want
+to generate feeds outside of a Web context, or in some other lower-level way.
+
+.. _RSS: http://www.whatisrss.com/
+.. _Atom: http://www.atomenabled.org/
+
+The high-level framework
+========================
+
+Overview
+--------
+
+The high-level feed-generating framework is a view that's hooked to ``/feeds/``
+by default. Django uses the remainder of the URL (everything after ``/feeds/``)
+to determine which feed to output.
+
+To create a feed, just write a ``Feed`` class and point to it in your URLconf_.
+
+.. _URLconf: ../url_dispatch/
+
+Initialization
+--------------
+
+To activate syndication feeds on your Django site, add this line to your
+URLconf_::
+
+ (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
+
+This tells Django to use the RSS framework to handle all URLs starting with
+``"feeds/"``. (You can change that ``"feeds/"`` prefix to fit your own needs.)
+
+This URLconf line has an extra argument: ``{'feed_dict': feeds}``. Use this
+extra argument to pass the syndication framework the feeds that should be
+published under that URL.
+
+Specifically, ``feed_dict`` should be a dictionary that maps a feed's slug
+(short URL label) to its ``Feed`` class.
+
+You can define the ``feed_dict`` in the URLconf itself. Here's a full example
+URLconf::
+
+ from django.conf.urls.defaults import *
+ from myproject.feeds import LatestEntries, LatestEntriesByCategory
+
+ feeds = {
+ 'latest': LatestEntries,
+ 'categories': LatestEntriesByCategory,
+ }
+
+ urlpatterns = patterns('',
+ # ...
+ (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
+ {'feed_dict': feeds}),
+ # ...
+ )
+
+The above example registers two feeds:
+
+ * The feed represented by ``LatestEntries`` will live at ``feeds/latest/``.
+ * The feed represented by ``LatestEntriesByCategory`` will live at
+ ``feeds/categories/``.
+
+Once that's set up, you just need to define the ``Feed`` classes themselves.
+
+.. _URLconf: ../url_dispatch/
+.. _settings file: ../settings/
+
+Feed classes
+------------
+
+A ``Feed`` class is a simple Python class that represents a syndication feed.
+A feed can be simple (e.g., a "site news" feed, or a basic feed displaying
+the latest entries of a blog) or more complex (e.g., a feed displaying all the
+blog entries in a particular category, where the category is variable).
+
+``Feed`` classes must subclass ``django.contrib.syndication.feeds.Feed``. They
+can live anywhere in your codebase.
+
+A simple example
+----------------
+
+This simple example, taken from `chicagocrime.org`_, describes a feed of the
+latest five news items::
+
+ from django.contrib.syndication.feeds import Feed
+ from chicagocrime.models import NewsItem
+
+ class LatestEntries(Feed):
+ title = "Chicagocrime.org site news"
+ link = "/sitenews/"
+ description = "Updates on changes and additions to chicagocrime.org."
+
+ def items(self):
+ return NewsItem.objects.order_by('-pub_date')[:5]
+
+Note:
+
+ * The class subclasses ``django.contrib.syndication.feeds.Feed``.
+ * ``title``, ``link`` and ``description`` correspond to the standard
+ RSS ``<title>``, ``<link>`` and ``<description>`` elements, respectively.
+ * ``items()`` is, simply, a method that returns a list of objects that
+ should be included in the feed as ``<item>`` elements. Although this
+ example returns ``NewsItem`` objects using Django's
+ `object-relational mapper`_, ``items()`` doesn't have to return model
+ instances. Although you get a few bits of functionality "for free" by
+ using Django models, ``items()`` can return any type of object you want.
+
+One thing's left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
+``<link>`` and ``<description>``. We need to tell the framework what data to
+put into those elements.
+
+ * To specify the contents of ``<title>`` and ``<description>``, create
+ `Django templates`_ called ``feeds/latest_title.html`` and
+ ``feeds/latest_description.html``, where ``latest`` is the ``slug``
+ specified in the URLconf for the given feed. Note the ``.html`` extension
+ is required. The RSS system renders that template for each item, passing
+ it two template context variables:
+
+ * ``{{ obj }}`` -- The current object (one of whichever objects you
+ returned in ``items()``).
+ * ``{{ site }}`` -- A ``django.models.core.sites.Site`` object
+ representing the current site. This is useful for
+ ``{{ site.domain }}`` or ``{{ site.name }}``.
+
+ If you don't create a template for either the title or description, the
+ framework will use the template ``"{{ obj }}"`` by default -- that is,
+ the normal string representation of the object. You can also change the
+ names of these two templates by specifying ``title_template`` and
+ ``description_template`` as attributes of your ``Feed`` class.
+ * To specify the contents of ``<link>``, you have two options. For each
+ item in ``items()``, Django first tries executing a
+ ``get_absolute_url()`` method on that object. If that method doesn't
+ exist, it tries calling a method ``item_link()`` in the ``Feed`` class,
+ passing it a single parameter, ``item``, which is the object itself.
+ Both ``get_absolute_url()`` and ``item_link()`` should return the item's
+ URL as a normal Python string.
+
+ * For the LatestEntries example above, we could have very simple feed templates:
+
+ * latest_title.html::
+
+ {{ obj.title }}
+
+ * latest_description.html::
+
+ {{ obj.description }}
+
+.. _chicagocrime.org: http://www.chicagocrime.org/
+.. _object-relational mapper: ../db_api/
+.. _Django templates: ../templates/
+
+A complex example
+-----------------
+
+The framework also supports more complex feeds, via parameters.
+
+For example, `chicagocrime.org`_ offers an RSS feed of recent crimes for every
+police beat in Chicago. It'd be silly to create a separate ``Feed`` class for
+each police beat; that would violate the `DRY principle`_ and would couple data
+to programming logic. Instead, the syndication framework lets you make generic
+feeds that output items based on information in the feed's URL.
+
+On chicagocrime.org, the police-beat feeds are accessible via URLs like this:
+
+ * ``/rss/beats/0613/`` -- Returns recent crimes for beat 0613.
+ * ``/rss/beats/1424/`` -- Returns recent crimes for beat 1424.
+
+The slug here is ``"beats"``. The syndication framework sees the extra URL bits
+after the slug -- ``0613`` and ``1424`` -- and gives you a hook to tell it what
+those URL bits mean, and how they should influence which items get published in
+the feed.
+
+An example makes this clear. Here's the code for these beat-specific feeds::
+
+ class BeatFeed(Feed):
+ def get_object(self, bits):
+ # In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
+ # check that bits has only one member.
+ if len(bits) != 1:
+ raise ObjectDoesNotExist
+ return Beat.objects.get(beat__exact=bits[0])
+
+ def title(self, obj):
+ return "Chicagocrime.org: Crimes for beat %s" % obj.beat
+
+ def link(self, obj):
+ return obj.get_absolute_url()
+
+ def description(self, obj):
+ return "Crimes recently reported in police beat %s" % obj.beat
+
+ def items(self, obj):
+ return Crime.objects.filter(beat__id__exact=obj.id).order_by('-crime_date')[:30]
+
+Here's the basic algorithm the RSS framework follows, given this class and a
+request to the URL ``/rss/beats/0613/``:
+
+ * The framework gets the URL ``/rss/beats/0613/`` and notices there's
+ an extra bit of URL after the slug. It splits that remaining string by
+ the slash character (``"/"``) and calls the ``Feed`` class'
+ ``get_object()`` method, passing it the bits. In this case, bits is
+ ``['0613']``. For a request to ``/rss/beats/0613/foo/bar/``, bits would
+ be ``['0613', 'foo', 'bar']``.
+ * ``get_object()`` is responsible for retrieving the given beat, from the
+ given ``bits``. In this case, it uses the Django database API to retrieve
+ the beat. Note that ``get_object()`` should raise
+ ``django.core.exceptions.ObjectDoesNotExist`` if given invalid
+ parameters. There's no ``try``/``except`` around the
+ ``Beat.objects.get()`` call, because it's not necessary; that function
+ raises ``Beat.DoesNotExist`` on failure, and ``Beat.DoesNotExist`` is a
+ subclass of ``ObjectDoesNotExist``. Raising ``ObjectDoesNotExist`` in
+ ``get_object()`` tells Django to produce a 404 error for that request.
+ * To generate the feed's ``<title>``, ``<link>`` and ``<description>``,
+ Django uses the ``title()``, ``link()`` and ``description()`` methods. In
+ the previous example, they were simple string class attributes, but this
+ example illustrates that they can be either strings *or* methods. For
+ each of ``title``, ``link`` and ``description``, Django follows this
+ algorithm:
+
+ * First, it tries to call a method, passing the ``obj`` argument, where
+ ``obj`` is the object returned by ``get_object()``.
+ * Failing that, it tries to call a method with no arguments.
+ * Failing that, it uses the class attribute.
+
+ * Finally, note that ``items()`` in this example also takes the ``obj``
+ argument. The algorithm for ``items`` is the same as described in the
+ previous step -- first, it tries ``items(obj)``, then ``items()``, then
+ finally an ``items`` class attribute (which should be a list).
+
+The ``ExampleFeed`` class below gives full documentation on methods and
+attributes of ``Feed`` classes.
+
+.. _DRY principle: http://c2.com/cgi/wiki?DontRepeatYourself
+
+Specifying the type of feed
+---------------------------
+
+By default, feeds produced in this framework use RSS 2.0.
+
+To change that, add a ``feed_type`` attribute to your ``Feed`` class, like so::
+
+ from django.utils.feedgenerator import Atom1Feed
+
+ class MyFeed(Feed):
+ feed_type = Atom1Feed
+
+Note that you set ``feed_type`` to a class object, not an instance.
+
+Currently available feed types are:
+
+ * ``django.utils.feedgenerator.Rss201rev2Feed`` (RSS 2.01. Default.)
+ * ``django.utils.feedgenerator.RssUserland091Feed`` (RSS 0.91.)
+ * ``django.utils.feedgenerator.Atom1Feed`` (Atom 1.0.)
+
+Enclosures
+----------
+
+To specify enclosures, such as those used in creating podcast feeds, use the
+``item_enclosure_url``, ``item_enclosure_length`` and
+``item_enclosure_mime_type`` hooks. See the ``ExampleFeed`` class below for
+usage examples.
+
+Language
+--------
+
+Feeds created by the syndication framework automatically include the
+appropriate ``<language>`` tag (RSS 2.0) or ``xml:lang`` attribute (Atom). This
+comes directly from your `LANGUAGE_CODE setting`_.
+
+.. _LANGUAGE_CODE setting: ../settings/#language-code
+
+URLs
+----
+
+The ``link`` method/attribute can return either an absolute URL (e.g.
+``"/blog/"``) or a URL with the fully-qualified domain and protocol (e.g.
+``"http://www.example.com/blog/"``). If ``link`` doesn't return the domain,
+the syndication framework will insert the domain of the current site, according
+to your `SITE_ID setting`_.
+
+Atom feeds require a ``<link rel="self">`` that defines the feed's current
+location. The syndication framework populates this automatically, using the
+domain of the current site according to the SITE_ID setting.
+
+.. _SITE_ID setting: ../settings/#site-id
+
+Publishing Atom and RSS feeds in tandem
+---------------------------------------
+
+Some developers like to make available both Atom *and* RSS versions of their
+feeds. That's easy to do with Django: Just create a subclass of your ``feed``
+class and set the ``feed_type`` to something different. Then update your
+URLconf to add the extra versions.
+
+Here's a full example::
+
+ from django.contrib.syndication.feeds import Feed
+ from chicagocrime.models import NewsItem
+ from django.utils.feedgenerator import Atom1Feed
+
+ class RssSiteNewsFeed(Feed):
+ title = "Chicagocrime.org site news"
+ link = "/sitenews/"
+ description = "Updates on changes and additions to chicagocrime.org."
+
+ def items(self):
+ return NewsItem.objects.order_by('-pub_date')[:5]
+
+ class AtomSiteNewsFeed(RssSiteNewsFeed):
+ feed_type = Atom1Feed
+
+And the accompanying URLconf::
+
+ from django.conf.urls.defaults import *
+ from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed
+
+ feeds = {
+ 'rss': RssSiteNewsFeed,
+ 'atom': AtomSiteNewsFeed,
+ }
+
+ urlpatterns = patterns('',
+ # ...
+ (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
+ {'feed_dict': feeds}),
+ # ...
+ )
+
+Feed class reference
+--------------------
+
+This example illustrates all possible attributes and methods for a ``Feed`` class::
+
+ from django.contrib.syndication.feeds import Feed
+ from django.utils import feedgenerator
+
+ class ExampleFeed(Feed):
+
+ # FEED TYPE -- Optional. This should be a class that subclasses
+ # django.utils.feedgenerator.SyndicationFeed. This designates which
+ # type of feed this should be: RSS 2.0, Atom 1.0, etc.
+ # If you don't specify feed_type, your feed will be RSS 2.0.
+ # This should be a class, not an instance of the class.
+
+ feed_type = feedgenerator.Rss201rev2Feed
+
+ # TEMPLATE NAMES -- Optional. These should be strings representing
+ # names of Django templates that the system should use in rendering the
+ # title and description of your feed items. Both are optional.
+ # If you don't specify one, or either, Django will use the template
+ # 'feeds/SLUG_title.html' and 'feeds/SLUG_description.html', where SLUG
+ # is the slug you specify in the URL.
+
+ title_template = None
+ description_template = None
+
+ # TITLE -- One of the following three is required. The framework looks
+ # for them in this order.
+
+ def title(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ title as a normal Python string.
+ """
+
+ def title(self):
+ """
+ Returns the feed's title as a normal Python string.
+ """
+
+ title = 'foo' # Hard-coded title.
+
+ # LINK -- One of the following three is required. The framework looks
+ # for them in this order.
+
+ def link(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ link as a normal Python string.
+ """
+
+ def link(self):
+ """
+ Returns the feed's link as a normal Python string.
+ """
+
+ link = '/foo/bar/' # Hard-coded link.
+
+ # DESCRIPTION -- One of the following three is required. The framework
+ # looks for them in this order.
+
+ def description(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ description as a normal Python string.
+ """
+
+ def description(self):
+ """
+ Returns the feed's description as a normal Python string.
+ """
+
+ description = 'Foo bar baz.' # Hard-coded description.
+
+ # AUTHOR NAME --One of the following three is optional. The framework
+ # looks for them in this order.
+
+ def author_name(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ author's name as a normal Python string.
+ """
+
+ def author_name(self):
+ """
+ Returns the feed's author's name as a normal Python string.
+ """
+
+ author_name = 'Sally Smith' # Hard-coded author name.
+
+ # AUTHOR E-MAIL --One of the following three is optional. The framework
+ # looks for them in this order.
+
+ def author_email(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ author's e-mail as a normal Python string.
+ """
+
+ def author_email(self):
+ """
+ Returns the feed's author's e-mail as a normal Python string.
+ """
+
+ author_email = 'test@example.com' # Hard-coded author e-mail.
+
+ # AUTHOR LINK --One of the following three is optional. The framework
+ # looks for them in this order. In each case, the URL should include
+ # the "http://" and domain name.
+
+ def author_link(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ author's URL as a normal Python string.
+ """
+
+ def author_link(self):
+ """
+ Returns the feed's author's URL as a normal Python string.
+ """
+
+ author_link = 'http://www.example.com/' # Hard-coded author URL.
+
+ # CATEGORIES -- One of the following three is optional. The framework
+ # looks for them in this order. In each case, the method/attribute
+ # should return an iterable object that returns strings.
+
+ def categories(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ categories as iterable over strings.
+ """
+
+ def categories(self):
+ """
+ Returns the feed's categories as iterable over strings.
+ """
+
+ categories = ("python", "django") # Hard-coded list of categories.
+
+ # COPYRIGHT NOTICE -- One of the following three is optional. The
+ # framework looks for them in this order.
+
+ def copyright(self, obj):
+ """
+ Takes the object returned by get_object() and returns the feed's
+ copyright notice as a normal Python string.
+ """
+
+ def copyright(self):
+ """
+ Returns the feed's copyright notice as a normal Python string.
+ """
+
+ copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
+
+ # ITEMS -- One of the following three is required. The framework looks
+ # for them in this order.
+
+ def items(self, obj):
+ """
+ Takes the object returned by get_object() and returns a list of
+ items to publish in this feed.
+ """
+
+ def items(self):
+ """
+ Returns a list of items to publish in this feed.
+ """
+
+ items = ('Item 1', 'Item 2') # Hard-coded items.
+
+ # GET_OBJECT -- This is required for feeds that publish different data
+ # for different URL parameters. (See "A complex example" above.)
+
+ def get_object(self, bits):
+ """
+ Takes a list of strings gleaned from the URL and returns an object
+ represented by this feed. Raises
+ django.core.exceptions.ObjectDoesNotExist on error.
+ """
+
+ # ITEM LINK -- One of these three is required. The framework looks for
+ # them in this order.
+
+ # First, the framework tries the get_absolute_url() method on each item
+ # returned by items(). Failing that, it tries these two methods:
+
+ def item_link(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's URL.
+ """
+
+ def item_link(self):
+ """
+ Returns the URL for every item in the feed.
+ """
+
+ # ITEM AUTHOR NAME --One of the following three is optional. The
+ # framework looks for them in this order.
+
+ def item_author_name(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ author's name as a normal Python string.
+ """
+
+ def item_author_name(self):
+ """
+ Returns the author name for every item in the feed.
+ """
+
+ item_author_name = 'Sally Smith' # Hard-coded author name.
+
+ # ITEM AUTHOR E-MAIL --One of the following three is optional. The
+ # framework looks for them in this order.
+ #
+ # If you specify this, you must specify item_author_name.
+
+ def item_author_email(self, obj):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ author's e-mail as a normal Python string.
+ """
+
+ def item_author_email(self):
+ """
+ Returns the author e-mail for every item in the feed.
+ """
+
+ item_author_email = 'test@example.com' # Hard-coded author e-mail.
+
+ # ITEM AUTHOR LINK --One of the following three is optional. The
+ # framework looks for them in this order. In each case, the URL should
+ # include the "http://" and domain name.
+ #
+ # If you specify this, you must specify item_author_name.
+
+ def item_author_link(self, obj):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ author's URL as a normal Python string.
+ """
+
+ def item_author_link(self):
+ """
+ Returns the author URL for every item in the feed.
+ """
+
+ item_author_link = 'http://www.example.com/' # Hard-coded author URL.
+
+ # ITEM ENCLOSURE URL -- One of these three is required if you're
+ # publishing enclosures. The framework looks for them in this order.
+
+ def item_enclosure_url(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ enclosure URL.
+ """
+
+ def item_enclosure_url(self):
+ """
+ Returns the enclosure URL for every item in the feed.
+ """
+
+ item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link.
+
+ # ITEM ENCLOSURE LENGTH -- One of these three is required if you're
+ # publishing enclosures. The framework looks for them in this order.
+ # In each case, the returned value should be either an integer, or a
+ # string representation of the integer, in bytes.
+
+ def item_enclosure_length(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ enclosure length.
+ """
+
+ def item_enclosure_length(self):
+ """
+ Returns the enclosure length for every item in the feed.
+ """
+
+ item_enclosure_length = 32000 # Hard-coded enclosure length.
+
+ # ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're
+ # publishing enclosures. The framework looks for them in this order.
+
+ def item_enclosure_mime_type(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ enclosure mime type.
+ """
+
+ def item_enclosure_mime_type(self):
+ """
+ Returns the enclosure length, in bytes, for every item in the feed.
+ """
+
+ item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure mime-type.
+
+ # ITEM PUBDATE -- It's optional to use one of these three. This is a
+ # hook that specifies how to get the pubdate for a given item.
+ # In each case, the method/attribute should return a Python
+ # datetime.datetime object.
+
+ def item_pubdate(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ pubdate.
+ """
+
+ def item_pubdate(self):
+ """
+ Returns the pubdate for every item in the feed.
+ """
+
+ item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate.
+
+ # ITEM CATEGORIES -- It's optional to use one of these three. This is
+ # a hook that specifies how to get the list of categories for a given
+ # item. In each case, the method/attribute should return an iterable
+ # object that returns strings.
+
+ def item_categories(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ categories.
+ """
+
+ def item_categories(self):
+ """
+ Returns the categories for every item in the feed.
+ """
+
+ item_categories = ("python", "django") # Hard-coded categories.
+
+ # ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the
+ # following three is optional. The framework looks for them in this
+ # order.
+
+ def item_copyright(self, obj):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ copyright notice as a normal Python string.
+ """
+
+ def item_copyright(self):
+ """
+ Returns the copyright notice for every item in the feed.
+ """
+
+ item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice.
+
+
+The low-level framework
+=======================
+
+Behind the scenes, the high-level RSS framework uses a lower-level framework
+for generating feeds' XML. This framework lives in a single module:
+`django/utils/feedgenerator.py`_.
+
+Feel free to use this framework on your own, for lower-level tasks.
+
+The ``feedgenerator`` module contains a base class ``SyndicationFeed`` and
+several subclasses:
+
+ * ``RssUserland091Feed``
+ * ``Rss201rev2Feed``
+ * ``Atom1Feed``
+
+Each of these three classes knows how to render a certain type of feed as XML.
+They share this interface:
+
+``__init__(title, link, description, language=None, author_email=None,``
+``author_name=None, author_link=None, subtitle=None, categories=None,``
+``feed_url=None)``
+
+Initializes the feed with the given metadata, which applies to the entire feed
+(i.e., not just to a specific item in the feed).
+
+All parameters, if given, should be Unicode objects, except ``categories``,
+which should be a sequence of Unicode objects.
+
+``add_item(title, link, description, author_email=None, author_name=None,``
+``pubdate=None, comments=None, unique_id=None, enclosure=None, categories=())``
+
+Add an item to the feed with the given parameters. All parameters, if given,
+should be Unicode objects, except:
+
+ * ``pubdate`` should be a `Python datetime object`_.
+ * ``enclosure`` should be an instance of ``feedgenerator.Enclosure``.
+ * ``categories`` should be a sequence of Unicode objects.
+
+``write(outfile, encoding)``
+
+Outputs the feed in the given encoding to outfile, which is a file-like object.
+
+``writeString(encoding)``
+
+Returns the feed as a string in the given encoding.
+
+Example usage
+-------------
+
+This example creates an Atom 1.0 feed and prints it to standard output::
+
+ >>> from django.utils import feedgenerator
+ >>> f = feedgenerator.Atom1Feed(
+ ... title=u"My Weblog",
+ ... link=u"http://www.example.com/",
+ ... description=u"In which I write about what I ate today.",
+ ... language=u"en")
+ >>> f.add_item(title=u"Hot dog today",
+ ... link=u"http://www.example.com/entries/1/",
+ ... description=u"<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>")
+ >>> print f.writeString('utf8')
+ <?xml version="1.0" encoding="utf8"?>
+ <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title>My Weblog</title>
+ <link href="http://www.example.com/"></link><id>http://www.example.com/</id>
+ <updated>Sat, 12 Nov 2005 00:28:43 -0000</updated><entry><title>Hot dog today</title>
+ <link>http://www.example.com/entries/1/</link><id>tag:www.example.com/entries/1/</id>
+ <summary type="html">&lt;p&gt;Today I had a Vienna Beef hot dog. It was pink, plump and perfect.&lt;/p&gt;</summary>
+ </entry></feed>
+
+.. _django/utils/feedgenerator.py: http://code.djangoproject.com/browser/django/trunk/django/utils/feedgenerator.py
+.. _Python datetime object: http://www.python.org/doc/current/lib/module-datetime.html
diff --git a/google_appengine/lib/django/docs/templates.txt b/google_appengine/lib/django/docs/templates.txt
new file mode 100644
index 0000000..db748ae
--- /dev/null
+++ b/google_appengine/lib/django/docs/templates.txt
@@ -0,0 +1,1277 @@
+==================================================
+The Django template language: For template authors
+==================================================
+
+Django's template language is designed to strike a balance between power and
+ease. It's designed to feel comfortable to those used to working with HTML. If
+you have any exposure to other text-based template languages, such as Smarty_
+or CheetahTemplate_, you should feel right at home with Django's templates.
+
+.. _Smarty: http://smarty.php.net/
+.. _CheetahTemplate: http://www.cheetahtemplate.org/
+
+Templates
+=========
+
+A template is simply a text file. It can generate any text-based format (HTML,
+XML, CSV, etc.).
+
+A template contains **variables**, which get replaced with values when the
+template is evaluated, and **tags**, which control the logic of the template.
+
+Below is a minimal template that illustrates a few basics. Each element will be
+explained later in this document.::
+
+ {% extends "base_generic.html" %}
+
+ {% block title %}{{ section.title }}{% endblock %}
+
+ {% block content %}
+ <h1>{{ section.title }}</h1>
+
+ {% for story in story_list %}
+ <h2>
+ <a href="{{ story.get_absolute_url }}">
+ {{ story.headline|upper }}
+ </a>
+ </h2>
+ <p>{{ story.tease|truncatewords:"100" }}</p>
+ {% endfor %}
+ {% endblock %}
+
+.. admonition:: Philosophy
+
+ Why use a text-based template instead of an XML-based one (like Zope's
+ TAL)? We wanted Django's template language to be usable for more than
+ just XML/HTML templates. At World Online, we use it for e-mails,
+ JavaScript and CSV. You can use the template language for any text-based
+ format.
+
+ Oh, and one more thing: Making humans edit XML is sadistic!
+
+Variables
+=========
+
+Variables look like this: ``{{ variable }}``. When the template engine
+encounters a variable, it evaluates that variable and replaces it with the
+result.
+
+Use a dot (``.``) to access attributes of a variable.
+
+.. admonition:: Behind the scenes
+
+ Technically, when the template system encounters a dot, it tries the
+ following lookups, in this order:
+
+ * Dictionary lookup
+ * Attribute lookup
+ * Method call
+ * List-index lookup
+
+In the above example, ``{{ section.title }}`` will be replaced with the
+``title`` attribute of the ``section`` object.
+
+If you use a variable that doesn't exist, the template system will insert
+the value of the ``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''``
+(the empty string) by default.
+
+See `Using the built-in reference`_, below, for help on finding what variables
+are available in a given template.
+
+Filters
+=======
+
+You can modify variables for display by using **filters**.
+
+Filters look like this: ``{{ name|lower }}``. This displays the value of the
+``{{ name }}`` variable after being filtered through the ``lower`` filter,
+which converts text to lowercase. Use a pipe (``|``) to apply a filter.
+
+Filters can be "chained." The output of one filter is applied to the next.
+``{{ text|escape|linebreaks }}`` is a common idiom for escaping text contents,
+then converting line breaks to ``<p>`` tags.
+
+Some filters take arguments. A filter argument looks like this:
+``{{ bio|truncatewords:"30" }}``. This will display the first 30 words of the
+``bio`` variable. Filter arguments always are in double quotes.
+
+The `Built-in filter reference`_ below describes all the built-in filters.
+
+Tags
+====
+
+Tags look like this: ``{% tag %}``. Tags are more complex than variables: Some
+create text in the output, some control flow by performing loops or logic, and
+some load external information into the template to be used by later variables.
+
+Some tags require beginning and ending tags (i.e.
+``{% tag %} ... tag contents ... {% endtag %}``). The `Built-in tag reference`_
+below describes all the built-in tags. You can create your own tags, if you
+know how to write Python code.
+
+Comments
+========
+
+To comment-out part of a template, use the comment syntax: ``{# #}``.
+
+For example, this template would render as ``'hello'``::
+
+ {# greeting #}hello
+
+A comment can contain any template code, invalid or not. For example::
+
+ {# {% if foo %}bar{% else %} #}
+
+Template inheritance
+====================
+
+The most powerful -- and thus the most complex -- part of Django's template
+engine is template inheritance. Template inheritance allows you to build a base
+"skeleton" template that contains all the common elements of your site and
+defines **blocks** that child templates can override.
+
+It's easiest to understand template inheritance by starting with an example::
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" href="style.css" />
+ <title>{% block title %}My amazing site{% endblock %}</title>
+ </head>
+
+ <body>
+ <div id="sidebar">
+ {% block sidebar %}
+ <ul>
+ <li><a href="/">Home</a></li>
+ <li><a href="/blog/">Blog</a></li>
+ </ul>
+ {% endblock %}
+ </div>
+
+ <div id="content">
+ {% block content %}{% endblock %}
+ </div>
+ </body>
+ </html>
+
+This template, which we'll call ``base.html``, defines a simple HTML skeleton
+document that you might use for a simple two-column page. It's the job of
+"child" templates to fill the empty blocks with content.
+
+In this example, the ``{% block %}`` tag defines three blocks that child
+templates can fill in. All the ``block`` tag does is to tell the template
+engine that a child template may override those portions of the template.
+
+A child template might look like this::
+
+ {% extends "base.html" %}
+
+ {% block title %}My amazing blog{% endblock %}
+
+ {% block content %}
+ {% for entry in blog_entries %}
+ <h2>{{ entry.title }}</h2>
+ <p>{{ entry.body }}</p>
+ {% endfor %}
+ {% endblock %}
+
+The ``{% extends %}`` tag is the key here. It tells the template engine that
+this template "extends" another template. When the template system evaluates
+this template, first it locates the parent -- in this case, "base.html".
+
+At that point, the template engine will notice the three ``{% block %}`` tags
+in ``base.html`` and replace those blocks with the contents of the child
+template. Depending on the value of ``blog_entries``, the output might look
+like::
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" href="style.css" />
+ <title>My amazing blog</title>
+ </head>
+
+ <body>
+ <div id="sidebar">
+ <ul>
+ <li><a href="/">Home</a></li>
+ <li><a href="/blog/">Blog</a></li>
+ </ul>
+ </div>
+
+ <div id="content">
+ <h2>Entry one</h2>
+ <p>This is my first entry.</p>
+
+ <h2>Entry two</h2>
+ <p>This is my second entry.</p>
+ </div>
+ </body>
+ </html>
+
+Note that since the child template didn't define the ``sidebar`` block, the
+value from the parent template is used instead. Content within a ``{% block %}``
+tag in a parent template is always used as a fallback.
+
+You can use as many levels of inheritance as needed. One common way of using
+inheritance is the following three-level approach:
+
+ * Create a ``base.html`` template that holds the main look-and-feel of your
+ site.
+ * Create a ``base_SECTIONNAME.html`` template for each "section" of your
+ site. For example, ``base_news.html``, ``base_sports.html``. These
+ templates all extend ``base.html`` and include section-specific
+ styles/design.
+ * Create individual templates for each type of page, such as a news
+ article or blog entry. These templates extend the appropriate section
+ template.
+
+This approach maximizes code reuse and makes it easy to add items to shared
+content areas, such as section-wide navigation.
+
+Here are some tips for working with inheritance:
+
+ * If you use ``{% extends %}`` in a template, it must be the first template
+ tag in that template. Template inheritance won't work, otherwise.
+
+ * More ``{% block %}`` tags in your base templates are better. Remember,
+ child templates don't have to define all parent blocks, so you can fill
+ in reasonable defaults in a number of blocks, then only define the ones
+ you need later. It's better to have more hooks than fewer hooks.
+
+ * If you find yourself duplicating content in a number of templates, it
+ probably means you should move that content to a ``{% block %}`` in a
+ parent template.
+
+ * If you need to get the content of the block from the parent template,
+ the ``{{ block.super }}`` variable will do the trick. This is useful
+ if you want to add to the contents of a parent block instead of
+ completely overriding it.
+
+ * For extra readability, you can optionally give a *name* to your
+ ``{% endblock %}`` tag. For example::
+
+ {% block content %}
+ ...
+ {% endblock content %}
+
+ In larger templates, this technique helps you see which ``{% block %}``
+ tags are being closed.
+
+Finally, note that you can't define multiple ``{% block %}`` tags with the same
+name in the same template. This limitation exists because a block tag works in
+"both" directions. That is, a block tag doesn't just provide a hole to fill --
+it also defines the content that fills the hole in the *parent*. If there were
+two similarly-named ``{% block %}`` tags in a template, that template's parent
+wouldn't know which one of the blocks' content to use.
+
+Using the built-in reference
+============================
+
+Django's admin interface includes a complete reference of all template tags and
+filters available for a given site. To see it, go to your admin interface and
+click the "Documentation" link in the upper right of the page.
+
+The reference is divided into 4 sections: tags, filters, models, and views.
+
+The **tags** and **filters** sections describe all the built-in tags (in fact,
+the tag and filter references below come directly from those pages) as well as
+any custom tag or filter libraries available.
+
+The **views** page is the most valuable. Each URL in your site has a separate
+entry here, and clicking on a URL will show you:
+
+ * The name of the view function that generates that view.
+ * A short description of what the view does.
+ * The **context**, or a list of variables available in the view's template.
+ * The name of the template or templates that are used for that view.
+
+Each view documentation page also has a bookmarklet that you can use to jump
+from any page to the documentation page for that view.
+
+Because Django-powered sites usually use database objects, the **models**
+section of the documentation page describes each type of object in the system
+along with all the fields available on that object.
+
+Taken together, the documentation pages should tell you every tag, filter,
+variable and object available to you in a given template.
+
+Custom tag and filter libraries
+===============================
+
+Certain applications provide custom tag and filter libraries. To access them in
+a template, use the ``{% load %}`` tag::
+
+ {% load comments %}
+
+ {% comment_form for blogs.entries entry.id with is_public yes %}
+
+In the above, the ``load`` tag loads the ``comments`` tag library, which then
+makes the ``comment_form`` tag available for use. Consult the documentation
+area in your admin to find the list of custom libraries in your installation.
+
+The ``{% load %}`` tag can take multiple library names, separated by spaces.
+Example::
+
+ {% load comments i18n %}
+
+Custom libraries and template inheritance
+-----------------------------------------
+
+When you load a custom tag or filter library, the tags/filters are only made
+available to the current template -- not any parent or child templates along
+the template-inheritance path.
+
+For example, if a template ``foo.html`` has ``{% load comments %}``, a child
+template (e.g., one that has ``{% extends "foo.html" %}``) will *not* have
+access to the comments template tags and filters. The child template is
+responsible for its own ``{% load comments %}``.
+
+This is a feature for the sake of maintainability and sanity.
+
+Built-in tag and filter reference
+=================================
+
+For those without an admin site available, reference for the stock tags and
+filters follows. Because Django is highly customizable, the reference in your
+admin should be considered the final word on what tags and filters are
+available, and what they do.
+
+Built-in tag reference
+----------------------
+
+block
+~~~~~
+
+Define a block that can be overridden by child templates. See
+`Template inheritance`_ for more information.
+
+comment
+~~~~~~~
+
+Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
+
+cycle
+~~~~~
+
+Cycle among the given strings each time this tag is encountered.
+
+Within a loop, cycles among the given strings each time through the loop::
+
+ {% for o in some_list %}
+ <tr class="{% cycle row1,row2 %}">
+ ...
+ </tr>
+ {% endfor %}
+
+Outside of a loop, give the values a unique name the first time you call it,
+then use that name each successive time through::
+
+ <tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
+ <tr class="{% cycle rowcolors %}">...</tr>
+ <tr class="{% cycle rowcolors %}">...</tr>
+
+You can use any number of values, separated by commas. Make sure not to put
+spaces between the values -- only commas.
+
+debug
+~~~~~
+
+Output a whole load of debugging information, including the current context and
+imported modules.
+
+extends
+~~~~~~~
+
+Signal that this template extends a parent template.
+
+This tag can be used in two ways:
+
+ * ``{% extends "base.html" %}`` (with quotes) uses the literal value
+ ``"base.html"`` as the name of the parent template to extend.
+
+ * ``{% extends variable %}`` uses the value of ``variable``. If the variable
+ evaluates to a string, Django will use that string as the name of the
+ parent template. If the variable evaluates to a ``Template`` object,
+ Django will use that object as the parent template.
+
+See `Template inheritance`_ for more information.
+
+filter
+~~~~~~
+
+Filter the contents of the variable through variable filters.
+
+Filters can also be piped through each other, and they can have arguments --
+just like in variable syntax.
+
+Sample usage::
+
+ {% filter escape|lower %}
+ This text will be HTML-escaped, and will appear in all lowercase.
+ {% endfilter %}
+
+firstof
+~~~~~~~
+
+Outputs the first variable passed that is not False. Outputs nothing if all the
+passed variables are False.
+
+Sample usage::
+
+ {% firstof var1 var2 var3 %}
+
+This is equivalent to::
+
+ {% if var1 %}
+ {{ var1 }}
+ {% else %}{% if var2 %}
+ {{ var2 }}
+ {% else %}{% if var3 %}
+ {{ var3 }}
+ {% endif %}{% endif %}{% endif %}
+
+for
+~~~
+
+Loop over each item in an array. For example, to display a list of athletes
+given ``athlete_list``::
+
+ <ul>
+ {% for athlete in athlete_list %}
+ <li>{{ athlete.name }}</li>
+ {% endfor %}
+ </ul>
+
+You can also loop over a list in reverse by using ``{% for obj in list reversed %}``.
+
+The for loop sets a number of variables available within the loop:
+
+ ========================== ================================================
+ Variable Description
+ ========================== ================================================
+ ``forloop.counter`` The current iteration of the loop (1-indexed)
+ ``forloop.counter0`` The current iteration of the loop (0-indexed)
+ ``forloop.revcounter`` The number of iterations from the end of the
+ loop (1-indexed)
+ ``forloop.revcounter0`` The number of iterations from the end of the
+ loop (0-indexed)
+ ``forloop.first`` True if this is the first time through the loop
+ ``forloop.last`` True if this is the last time through the loop
+ ``forloop.parentloop`` For nested loops, this is the loop "above" the
+ current one
+ ========================== ================================================
+
+if
+~~
+
+The ``{% if %}`` tag evaluates a variable, and if that variable is "true" (i.e.
+exists, is not empty, and is not a false boolean value) the contents of the
+block are output::
+
+ {% if athlete_list %}
+ Number of athletes: {{ athlete_list|length }}
+ {% else %}
+ No athletes.
+ {% endif %}
+
+In the above, if ``athlete_list`` is not empty, the number of athletes will be
+displayed by the ``{{ athlete_list|length }}`` variable.
+
+As you can see, the ``if`` tag can take an optional ``{% else %}`` clause that
+will be displayed if the test fails.
+
+``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
+to negate a given variable::
+
+ {% if athlete_list and coach_list %}
+ Both athletes and coaches are available.
+ {% endif %}
+
+ {% if not athlete_list %}
+ There are no athletes.
+ {% endif %}
+
+ {% if athlete_list or coach_list %}
+ There are some athletes or some coaches.
+ {% endif %}
+
+ {% if not athlete_list or coach_list %}
+ There are no athletes or there are some coaches (OK, so
+ writing English translations of boolean logic sounds
+ stupid; it's not our fault).
+ {% endif %}
+
+ {% if athlete_list and not coach_list %}
+ There are some athletes and absolutely no coaches.
+ {% endif %}
+
+``if`` tags don't allow ``and`` and ``or`` clauses within the same tag, because
+the order of logic would be ambiguous. For example, this is invalid::
+
+ {% if athlete_list and coach_list or cheerleader_list %}
+
+If you need to combine ``and`` and ``or`` to do advanced logic, just use nested
+``if`` tags. For example::
+
+ {% if athlete_list %}
+ {% if coach_list or cheerleader_list %}
+ We have athletes, and either coaches or cheerleaders!
+ {% endif %}
+ {% endif %}
+
+Multiple uses of the same logical operator are fine, as long as you use the
+same operator. For example, this is valid::
+
+ {% if athlete_list or coach_list or parent_list or teacher_list %}
+
+ifchanged
+~~~~~~~~~
+
+Check if a value has changed from the last iteration of a loop.
+
+The 'ifchanged' block tag is used within a loop. It has two possible uses.
+
+1. Checks its own rendered contents against its previous state and only
+ displays the content if it has changed. For example, this displays a list of
+ days, only displaying the month if it changes::
+
+ <h1>Archive for {{ year }}</h1>
+
+ {% for date in days %}
+ {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
+ <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
+ {% endfor %}
+
+2. If given a variable, check whether that variable has changed. For
+ example, the following shows the date every time it changes, but
+ only shows the hour if both the hour and the date has changed::
+
+ {% for date in days %}
+ {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
+ {% ifchanged date.hour date.date %}
+ {{ date.hour }}
+ {% endifchanged %}
+ {% endfor %}
+
+ifequal
+~~~~~~~
+
+Output the contents of the block if the two arguments equal each other.
+
+Example::
+
+ {% ifequal user.id comment.user_id %}
+ ...
+ {% endifequal %}
+
+As in the ``{% if %}`` tag, an ``{% else %}`` clause is optional.
+
+The arguments can be hard-coded strings, so the following is valid::
+
+ {% ifequal user.username "adrian" %}
+ ...
+ {% endifequal %}
+
+It is only possible to compare an argument to template variables or strings.
+You cannot check for equality with Python objects such as ``True`` or
+``False``. If you need to test if something is true or false, use the ``if``
+tag instead.
+
+ifnotequal
+~~~~~~~~~~
+
+Just like ``ifequal``, except it tests that the two arguments are not equal.
+
+include
+~~~~~~~
+
+Loads a template and renders it with the current context. This is a way of
+"including" other templates within a template.
+
+The template name can either be a variable or a hard-coded (quoted) string,
+in either single or double quotes.
+
+This example includes the contents of the template ``"foo/bar.html"``::
+
+ {% include "foo/bar.html" %}
+
+This example includes the contents of the template whose name is contained in
+the variable ``template_name``::
+
+ {% include template_name %}
+
+An included template is rendered with the context of the template that's
+including it. This example produces the output ``"Hello, John"``:
+
+ * Context: variable ``person`` is set to ``"john"``.
+ * Template::
+
+ {% include "name_snippet.html" %}
+
+ * The ``name_snippet.html`` template::
+
+ Hello, {{ person }}
+
+See also: ``{% ssi %}``.
+
+load
+~~~~
+
+Load a custom template tag set.
+
+See `Custom tag and filter libraries`_ for more information.
+
+now
+~~~
+
+Display the date, formatted according to the given string.
+
+Uses the same format as PHP's ``date()`` function (http://php.net/date)
+with some custom extensions.
+
+Available format strings:
+
+ ================ ======================================== =====================
+ Format character Description Example output
+ ================ ======================================== =====================
+ a ``'a.m.'`` or ``'p.m.'`` (Note that ``'a.m.'``
+ this is slightly different than PHP's
+ output, because this includes periods
+ to match Associated Press style.)
+ A ``'AM'`` or ``'PM'``. ``'AM'``
+ b Month, textual, 3 letters, lowercase. ``'jan'``
+ B Not implemented.
+ d Day of the month, 2 digits with ``'01'`` to ``'31'``
+ leading zeros.
+ D Day of the week, textual, 3 letters. ``'Fri'``
+ f Time, in 12-hour hours and minutes, ``'1'``, ``'1:30'``
+ with minutes left off if they're zero.
+ Proprietary extension.
+ F Month, textual, long. ``'January'``
+ g Hour, 12-hour format without leading ``'1'`` to ``'12'``
+ zeros.
+ G Hour, 24-hour format without leading ``'0'`` to ``'23'``
+ zeros.
+ h Hour, 12-hour format. ``'01'`` to ``'12'``
+ H Hour, 24-hour format. ``'00'`` to ``'23'``
+ i Minutes. ``'00'`` to ``'59'``
+ I Not implemented.
+ j Day of the month without leading ``'1'`` to ``'31'``
+ zeros.
+ l Day of the week, textual, long. ``'Friday'``
+ L Boolean for whether it's a leap year. ``True`` or ``False``
+ m Month, 2 digits with leading zeros. ``'01'`` to ``'12'``
+ M Month, textual, 3 letters. ``'Jan'``
+ n Month without leading zeros. ``'1'`` to ``'12'``
+ N Month abbreviation in Associated Press ``'Jan.'``, ``'Feb.'``, ``'March'``, ``'May'``
+ style. Proprietary extension.
+ O Difference to Greenwich time in hours. ``'+0200'``
+ P Time, in 12-hour hours, minutes and ``'1 a.m.'``, ``'1:30 p.m.'``, ``'midnight'``, ``'noon'``, ``'12:30 p.m.'``
+ 'a.m.'/'p.m.', with minutes left off
+ if they're zero and the special-case
+ strings 'midnight' and 'noon' if
+ appropriate. Proprietary extension.
+ r RFC 822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'``
+ s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'``
+ S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'``
+ month, 2 characters.
+ t Number of days in the given month. ``28`` to ``31``
+ T Time zone of this machine. ``'EST'``, ``'MDT'``
+ U Not implemented.
+ w Day of the week, digits without ``'0'`` (Sunday) to ``'6'`` (Saturday)
+ leading zeros.
+ W ISO-8601 week number of year, with ``1``, ``23``
+ weeks starting on Monday.
+ y Year, 2 digits. ``'99'``
+ Y Year, 4 digits. ``'1999'``
+ z Day of the year. ``0`` to ``365``
+ Z Time zone offset in seconds. The ``-43200`` to ``43200``
+ offset for timezones west of UTC is
+ always negative, and for those east of
+ UTC is always positive.
+ ================ ======================================== =====================
+
+Example::
+
+ It is {% now "jS F Y H:i" %}
+
+Note that you can backslash-escape a format string if you want to use the
+"raw" value. In this example, "f" is backslash-escaped, because otherwise
+"f" is a format string that displays the time. The "o" doesn't need to be
+escaped, because it's not a format character.::
+
+ It is the {% now "jS o\f F" %}
+
+(Displays "It is the 4th of September" %}
+
+regroup
+~~~~~~~
+
+Regroup a list of alike objects by a common attribute.
+
+This complex tag is best illustrated by use of an example: say that ``people``
+is a list of ``Person`` objects that have ``first_name``, ``last_name``, and
+``gender`` attributes, and you'd like to display a list that looks like:
+
+ * Male:
+ * George Bush
+ * Bill Clinton
+ * Female:
+ * Margaret Thatcher
+ * Condoleezza Rice
+ * Unknown:
+ * Pat Smith
+
+The following snippet of template code would accomplish this dubious task::
+
+ {% regroup people by gender as grouped %}
+ <ul>
+ {% for group in grouped %}
+ <li>{{ group.grouper }}
+ <ul>
+ {% for item in group.list %}
+ <li>{{ item }}</li>
+ {% endfor %}
+ </ul>
+ {% endfor %}
+ </ul>
+
+As you can see, ``{% regroup %}`` populates a variable with a list of objects
+with ``grouper`` and ``list`` attributes. ``grouper`` contains the item that
+was grouped by; ``list`` contains the list of objects that share that
+``grouper``. In this case, ``grouper`` would be ``Male``, ``Female`` and
+``Unknown``, and ``list`` is the list of people with those genders.
+
+Note that ``{% regroup %}`` does not work when the list to be grouped is not
+sorted by the key you are grouping by! This means that if your list of people
+was not sorted by gender, you'd need to make sure it is sorted before using it,
+i.e.::
+
+ {% regroup people|dictsort:"gender" by gender as grouped %}
+
+spaceless
+~~~~~~~~~
+
+Normalizes whitespace between HTML tags to a single space. This includes tab
+characters and newlines.
+
+Example usage::
+
+ {% spaceless %}
+ <p>
+ <a href="foo/">Foo</a>
+ </p>
+ {% endspaceless %}
+
+This example would return this HTML::
+
+ <p> <a href="foo/">Foo</a> </p>
+
+Only space between *tags* is normalized -- not space between tags and text. In
+this example, the space around ``Hello`` won't be stripped::
+
+ {% spaceless %}
+ <strong>
+ Hello
+ </strong>
+ {% endspaceless %}
+
+ssi
+~~~
+
+Output the contents of a given file into the page.
+
+Like a simple "include" tag, ``{% ssi %}`` includes the contents of another
+file -- which must be specified using an absolute path -- in the current
+page::
+
+ {% ssi /home/html/ljworld.com/includes/right_generic.html %}
+
+If the optional "parsed" parameter is given, the contents of the included
+file are evaluated as template code, within the current context::
+
+ {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
+
+Note that if you use ``{% ssi %}``, you'll need to define
+`ALLOWED_INCLUDE_ROOTS`_ in your Django settings, as a security measure.
+
+See also: ``{% include %}``.
+
+.. _ALLOWED_INCLUDE_ROOTS: ../settings/#allowed-include-roots
+
+templatetag
+~~~~~~~~~~~
+
+Output one of the syntax characters used to compose template tags.
+
+Since the template system has no concept of "escaping", to display one of the
+bits used in template tags, you must use the ``{% templatetag %}`` tag.
+
+The argument tells which template bit to output:
+
+ ================== =======
+ Argument Outputs
+ ================== =======
+ ``openblock`` ``{%``
+ ``closeblock`` ``%}``
+ ``openvariable`` ``{{``
+ ``closevariable`` ``}}``
+ ``openbrace`` ``{``
+ ``closebrace`` ``}``
+ ``opencomment`` ``{#``
+ ``closecomment`` ``#}``
+ ================== =======
+
+url
+~~~
+
+**Note that the syntax for this tag may change in the future, as we make it more robust.**
+
+Returns an absolute URL (i.e., a URL without the domain name) matching a given
+view function and optional parameters. This is a way to output links without
+violating the DRY principle by having to hard-code URLs in your templates::
+
+ {% url path.to.some_view arg1,arg2,name1=value1 %}
+
+The first argument is a path to a view function in the format
+``package.package.module.function``. Additional arguments are optional and
+should be comma-separated values that will be used as positional and keyword
+arguments in the URL. All arguments required by the URLconf should be present.
+
+For example, suppose you have a view, ``app_name.client``, whose URLconf takes
+a client ID. The URLconf line might look like this::
+
+ ('^client/(\d+)/$', 'app_name.client')
+
+If this app's URLconf is included into the project's URLconf under a path
+such as this::
+
+ ('^clients/', include('project_name.app_name.urls'))
+
+...then, in a template, you can create a link to this view like this::
+
+ {% url app_name.client client.id %}
+
+The template tag will output the string ``/clients/client/123/``.
+
+widthratio
+~~~~~~~~~~
+
+For creating bar charts and such, this tag calculates the ratio of a given value
+to a maximum value, and then applies that ratio to a constant.
+
+For example::
+
+ <img src="bar.gif" height="10" width="{% widthratio this_value max_value 100 %}" />
+
+Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in the
+above example will be 88 pixels wide (because 175/200 = .875; .875 * 100 = 87.5
+which is rounded up to 88).
+
+Built-in filter reference
+-------------------------
+
+add
+~~~
+
+Adds the arg to the value.
+
+addslashes
+~~~~~~~~~~
+
+Adds slashes. Useful for passing strings to JavaScript, for example.
+
+
+capfirst
+~~~~~~~~
+
+Capitalizes the first character of the value.
+
+center
+~~~~~~
+
+Centers the value in a field of a given width.
+
+cut
+~~~
+
+Removes all values of arg from the given string.
+
+date
+~~~~
+
+Formats a date according to the given format (same as the ``now`` tag).
+
+default
+~~~~~~~
+
+If value is unavailable, use given default.
+
+default_if_none
+~~~~~~~~~~~~~~~
+
+If value is ``None``, use given default.
+
+dictsort
+~~~~~~~~
+
+Takes a list of dicts, returns that list sorted by the property given in the
+argument.
+
+dictsortreversed
+~~~~~~~~~~~~~~~~
+
+Takes a list of dicts, returns that list sorted in reverse order by the
+property given in the argument.
+
+divisibleby
+~~~~~~~~~~~
+
+Returns true if the value is divisible by the argument.
+
+escape
+~~~~~~
+
+Escapes a string's HTML. Specifically, it makes these replacements:
+
+ * ``"&"`` to ``"&amp;"``
+ * ``<`` to ``"&lt;"``
+ * ``>`` to ``"&gt;"``
+ * ``'"'`` (double quote) to ``'&quot;'``
+ * ``"'"`` (single quote) to ``'&#39;'``
+
+filesizeformat
+~~~~~~~~~~~~~~
+
+Format the value like a 'human-readable' file size (i.e. ``'13 KB'``,
+``'4.1 MB'``, ``'102 bytes'``, etc).
+
+first
+~~~~~
+
+Returns the first item in a list.
+
+fix_ampersands
+~~~~~~~~~~~~~~
+
+Replaces ampersands with ``&amp;`` entities.
+
+floatformat
+~~~~~~~~~~~
+
+When used without an argument, rounds a floating-point number to one decimal
+place -- but only if there's a decimal part to be displayed. For example:
+
+ * ``36.123`` gets converted to ``36.1``
+ * ``36.15`` gets converted to ``36.2``
+ * ``36`` gets converted to ``36``
+
+If used with a numeric integer argument, ``floatformat`` rounds a number to that
+many decimal places. For example:
+
+ * ``36.1234`` with floatformat:3 gets converted to ``36.123``
+ * ``36`` with floatformat:4 gets converted to ``36.0000``
+
+If the argument passed to ``floatformat`` is negative, it will round a number to
+that many decimal places -- but only if there's a decimal part to be displayed.
+For example:
+
+ * ``36.1234`` with floatformat:-3 gets converted to ``36.123``
+ * ``36`` with floatformat:-4 gets converted to ``36``
+
+Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with
+an argument of ``-1``.
+
+get_digit
+~~~~~~~~~
+
+Given a whole number, returns the requested digit of it, where 1 is the
+right-most digit, 2 is the second-right-most digit, etc. Returns the original
+value for invalid input (if input or argument is not an integer, or if argument
+is less than 1). Otherwise, output is always an integer.
+
+join
+~~~~
+
+Joins a list with a string, like Python's ``str.join(list)``.
+
+length
+~~~~~~
+
+Returns the length of the value. Useful for lists.
+
+length_is
+~~~~~~~~~
+
+Returns a boolean of whether the value's length is the argument.
+
+linebreaks
+~~~~~~~~~~
+
+Converts newlines into ``<p>`` and ``<br />`` tags.
+
+linebreaksbr
+~~~~~~~~~~~~
+
+Converts newlines into ``<br />`` tags.
+
+linenumbers
+~~~~~~~~~~~
+
+Displays text with line numbers.
+
+ljust
+~~~~~
+
+Left-aligns the value in a field of a given width.
+
+**Argument:** field size
+
+lower
+~~~~~
+
+Converts a string into all lowercase.
+
+make_list
+~~~~~~~~~
+
+Returns the value turned into a list. For an integer, it's a list of
+digits. For a string, it's a list of characters.
+
+phone2numeric
+~~~~~~~~~~~~~
+
+Converts a phone number (possibly containing letters) to its numerical
+equivalent. For example, ``'800-COLLECT'`` will be converted to
+``'800-2655328'``.
+
+The input doesn't have to be a valid phone number. This will happily convert
+any string.
+
+pluralize
+~~~~~~~~~
+
+Returns a plural suffix if the value is not 1. By default, this suffix is ``'s'``.
+
+Example::
+
+ You have {{ num_messages }} message{{ num_messages|pluralize }}.
+
+For words that require a suffix other than ``'s'``, you can provide an alternate
+suffix as a parameter to the filter.
+
+Example::
+
+ You have {{ num_walruses }} walrus{{ num_walrus|pluralize:"es" }}.
+
+For words that don't pluralize by simple suffix, you can specify both a
+singular and plural suffix, separated by a comma.
+
+Example::
+
+ You have {{ num_cherries }} cherr{{ num_cherries|pluralize:"y,ies" }}.
+
+pprint
+~~~~~~
+
+A wrapper around pprint.pprint -- for debugging, really.
+
+random
+~~~~~~
+
+Returns a random item from the list.
+
+removetags
+~~~~~~~~~~
+
+Removes a space separated list of [X]HTML tags from the output.
+
+rjust
+~~~~~
+
+Right-aligns the value in a field of a given width.
+
+**Argument:** field size
+
+slice
+~~~~~
+
+Returns a slice of the list.
+
+Uses the same syntax as Python's list slicing. See
+http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
+for an introduction.
+
+Example: ``{{ some_list|slice:":2" }}``
+
+slugify
+~~~~~~~
+
+Converts to lowercase, removes non-word characters (alphanumerics and
+underscores) and converts spaces to hyphens. Also strips leading and trailing
+whitespace.
+
+stringformat
+~~~~~~~~~~~~
+
+Formats the variable according to the argument, a string formatting specifier.
+This specifier uses Python string formating syntax, with the exception that
+the leading "%" is dropped.
+
+See http://docs.python.org/lib/typesseq-strings.html for documentation of
+Python string formatting
+
+striptags
+~~~~~~~~~
+
+Strips all [X]HTML tags.
+
+time
+~~~~
+
+Formats a time according to the given format (same as the ``now`` tag).
+
+timesince
+~~~~~~~~~
+
+Formats a date as the time since that date (i.e. "4 days, 6 hours").
+
+Takes an optional argument that is a variable containing the date to use as
+the comparison point (without the argument, the comparison point is *now*).
+For example, if ``blog_date`` is a date instance representing midnight on 1
+June 2006, and ``comment_date`` is a date instance for 08:00 on 1 June 2006,
+then ``{{ comment_date|timesince:blog_date }}`` would return "8 hours".
+
+timeuntil
+~~~~~~~~~
+
+Similar to ``timesince``, except that it measures the time from now until the
+given date or datetime. For example, if today is 1 June 2006 and
+``conference_date`` is a date instance holding 29 June 2006, then
+``{{ conference_date|timeuntil }}`` will return "28 days".
+
+Takes an optional argument that is a variable containing the date to use as
+the comparison point (instead of *now*). If ``from_date`` contains 22 June
+2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "7 days".
+
+title
+~~~~~
+
+Converts a string into titlecase.
+
+truncatewords
+~~~~~~~~~~~~~
+
+Truncates a string after a certain number of words.
+
+**Argument:** Number of words to truncate after
+
+truncatewords_html
+~~~~~~~~~~~~~~~~~~
+
+Similar to ``truncatewords``, except that it is aware of HTML tags. Any tags
+that are opened in the string and not closed before the truncation point, are
+closed immediately after the truncation.
+
+This is less efficient than ``truncatewords``, so should only be used when it
+is being passed HTML text.
+
+unordered_list
+~~~~~~~~~~~~~~
+
+Recursively takes a self-nested list and returns an HTML unordered list --
+WITHOUT opening and closing <ul> tags.
+
+The list is assumed to be in the proper format. For example, if ``var`` contains
+``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
+then ``{{ var|unordered_list }}`` would return::
+
+ <li>States
+ <ul>
+ <li>Kansas
+ <ul>
+ <li>Lawrence</li>
+ <li>Topeka</li>
+ </ul>
+ </li>
+ <li>Illinois</li>
+ </ul>
+ </li>
+
+upper
+~~~~~
+
+Converts a string into all uppercase.
+
+urlencode
+~~~~~~~~~
+
+Escapes a value for use in a URL.
+
+urlize
+~~~~~~
+
+Converts URLs in plain text into clickable links.
+
+urlizetrunc
+~~~~~~~~~~~
+
+Converts URLs into clickable links, truncating URLs to the given character limit.
+
+**Argument:** Length to truncate URLs to
+
+wordcount
+~~~~~~~~~
+
+Returns the number of words.
+
+wordwrap
+~~~~~~~~
+
+Wraps words at specified line length.
+
+**Argument:** number of characters at which to wrap the text
+
+yesno
+~~~~~
+
+Given a string mapping values for true, false and (optionally) None,
+returns one of those strings according to the value:
+
+========== ====================== ==================================
+Value Argument Outputs
+========== ====================== ==================================
+``True`` ``"yeah,no,maybe"`` ``yeah``
+``False`` ``"yeah,no,maybe"`` ``no``
+``None`` ``"yeah,no,maybe"`` ``maybe``
+``None`` ``"yeah,no"`` ``"no"`` (converts None to False
+ if no mapping for None is given)
+========== ====================== ==================================
+
+Other tags and filter libraries
+===============================
+
+Django comes with a couple of other template-tag libraries that you have to
+enable explicitly in your ``INSTALLED_APPS`` setting and enable in your
+template with the ``{% load %}`` tag.
+
+django.contrib.humanize
+-----------------------
+
+A set of Django template filters useful for adding a "human touch" to data. See
+the `humanize documentation`_.
+
+.. _humanize documentation: ../add_ons/#humanize
+
+django.contrib.markup
+---------------------
+
+A collection of template filters that implement these common markup languages:
+
+ * Textile
+ * Markdown
+ * ReST (ReStructured Text)
diff --git a/google_appengine/lib/django/docs/templates_python.txt b/google_appengine/lib/django/docs/templates_python.txt
new file mode 100644
index 0000000..5dd8e4f
--- /dev/null
+++ b/google_appengine/lib/django/docs/templates_python.txt
@@ -0,0 +1,1195 @@
+====================================================
+The Django template language: For Python programmers
+====================================================
+
+This document explains the Django template system from a technical
+perspective -- how it works and how to extend it. If you're just looking for
+reference on the language syntax, see
+`The Django template language: For template authors`_.
+
+If you're looking to use the Django template system as part of another
+application -- i.e., without the rest of the framework -- make sure to read
+the `configuration`_ section later in this document.
+
+.. _`The Django template language: For template authors`: ../templates/
+
+Basics
+======
+
+A **template** is a text document, or a normal Python string, that is marked-up
+using the Django template language. A template can contain **block tags** or
+**variables**.
+
+A **block tag** is a symbol within a template that does something.
+
+This definition is deliberately vague. For example, a block tag can output
+content, serve as a control structure (an "if" statement or "for" loop), grab
+content from a database or enable access to other template tags.
+
+Block tags are surrounded by ``"{%"`` and ``"%}"``.
+
+Example template with block tags::
+
+ {% if is_logged_in %}Thanks for logging in!{% else %}Please log in.{% endif %}
+
+A **variable** is a symbol within a template that outputs a value.
+
+Variable tags are surrounded by ``"{{"`` and ``"}}"``.
+
+Example template with variables::
+
+ My first name is {{ first_name }}. My last name is {{ last_name }}.
+
+A **context** is a "variable name" -> "variable value" mapping that is passed
+to a template.
+
+A template **renders** a context by replacing the variable "holes" with values
+from the context and executing all block tags.
+
+Using the template system
+=========================
+
+Using the template system in Python is a two-step process:
+
+ * First, you compile the raw template code into a ``Template`` object.
+ * Then, you call the ``render()`` method of the ``Template`` object with a
+ given context.
+
+Compiling a string
+------------------
+
+The easiest way to create a ``Template`` object is by instantiating it
+directly. The class lives at ``django.template.Template``. The constructor
+takes one argument -- the raw template code::
+
+ >>> from django.template import Template
+ >>> t = Template("My name is {{ my_name }}.")
+ >>> print t
+ <django.template.Template instance>
+
+.. admonition:: Behind the scenes
+
+ The system only parses your raw template code once -- when you create the
+ ``Template`` object. From then on, it's stored internally as a "node"
+ structure for performance.
+
+ Even the parsing itself is quite fast. Most of the parsing happens via a
+ single call to a single, short, regular expression.
+
+Rendering a context
+-------------------
+
+Once you have a compiled ``Template`` object, you can render a context -- or
+multiple contexts -- with it. The ``Context`` class lives at
+``django.template.Context``, and the constructor takes one (optional)
+argument: a dictionary mapping variable names to variable values. Call the
+``Template`` object's ``render()`` method with the context to "fill" the
+template::
+
+ >>> from django.template import Context, Template
+ >>> t = Template("My name is {{ my_name }}.")
+
+ >>> c = Context({"my_name": "Adrian"})
+ >>> t.render(c)
+ "My name is Adrian."
+
+ >>> c = Context({"my_name": "Dolores"})
+ >>> t.render(c)
+ "My name is Dolores."
+
+Variable names must consist of any letter (A-Z), any digit (0-9), an underscore
+or a dot.
+
+Dots have a special meaning in template rendering. A dot in a variable name
+signifies **lookup**. Specifically, when the template system encounters a dot
+in a variable name, it tries the following lookups, in this order:
+
+ * Dictionary lookup. Example: ``foo["bar"]``
+ * Attribute lookup. Example: ``foo.bar``
+ * Method call. Example: ``foo.bar()``
+ * List-index lookup. Example: ``foo[bar]``
+
+The template system uses the first lookup type that works. It's short-circuit
+logic.
+
+Here are a few examples::
+
+ >>> from django.template import Context, Template
+ >>> t = Template("My name is {{ person.first_name }}.")
+ >>> d = {"person": {"first_name": "Joe", "last_name": "Johnson"}}
+ >>> t.render(Context(d))
+ "My name is Joe."
+
+ >>> class PersonClass: pass
+ >>> p = PersonClass()
+ >>> p.first_name = "Ron"
+ >>> p.last_name = "Nasty"
+ >>> t.render(Context({"person": p}))
+ "My name is Ron."
+
+ >>> class PersonClass2:
+ ... def first_name(self):
+ ... return "Samantha"
+ >>> p = PersonClass2()
+ >>> t.render(Context({"person": p}))
+ "My name is Samantha."
+
+ >>> t = Template("The first stooge in the list is {{ stooges.0 }}.")
+ >>> c = Context({"stooges": ["Larry", "Curly", "Moe"]})
+ >>> t.render(c)
+ "The first stooge in the list is Larry."
+
+Method lookups are slightly more complex than the other lookup types. Here are
+some things to keep in mind:
+
+ * If, during the method lookup, a method raises an exception, the exception
+ will be propagated, unless the exception has an attribute
+ ``silent_variable_failure`` whose value is ``True``. If the exception
+ *does* have a ``silent_variable_failure`` attribute, the variable will
+ render as an empty string. Example::
+
+ >>> t = Template("My name is {{ person.first_name }}.")
+ >>> class PersonClass3:
+ ... def first_name(self):
+ ... raise AssertionError, "foo"
+ >>> p = PersonClass3()
+ >>> t.render(Context({"person": p}))
+ Traceback (most recent call last):
+ ...
+ AssertionError: foo
+
+ >>> class SilentAssertionError(Exception):
+ ... silent_variable_failure = True
+ >>> class PersonClass4:
+ ... def first_name(self):
+ ... raise SilentAssertionError
+ >>> p = PersonClass4()
+ >>> t.render(Context({"person": p}))
+ "My name is ."
+
+ Note that ``django.core.exceptions.ObjectDoesNotExist``, which is the
+ base class for all Django database API ``DoesNotExist`` exceptions, has
+ ``silent_variable_failure = True``. So if you're using Django templates
+ with Django model objects, any ``DoesNotExist`` exception will fail
+ silently.
+
+ * A method call will only work if the method has no required arguments.
+ Otherwise, the system will move to the next lookup type (list-index
+ lookup).
+
+ * Obviously, some methods have side effects, and it'd be either foolish or
+ a security hole to allow the template system to access them.
+
+ A good example is the ``delete()`` method on each Django model object.
+ The template system shouldn't be allowed to do something like this::
+
+ I will now delete this valuable data. {{ data.delete }}
+
+ To prevent this, set a function attribute ``alters_data`` on the method.
+ The template system won't execute a method if the method has
+ ``alters_data=True`` set. The dynamically-generated ``delete()`` and
+ ``save()`` methods on Django model objects get ``alters_data=True``
+ automatically. Example::
+
+ def sensitive_function(self):
+ self.database_record.delete()
+ sensitive_function.alters_data = True
+
+How invalid variables are handled
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Generally, if a variable doesn't exist, the template system inserts the
+value of the ``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''``
+(the empty string) by default.
+
+Filters that are applied to an invalid variable will only be applied if
+``TEMPLATE_STRING_IF_INVALID`` is set to ``''`` (the empty string). If
+``TEMPLATE_STRING_IF_INVALID`` is set to any other value, variable
+filters will be ignored.
+
+This behavior is slightly different for the ``if``, ``for`` and ``regroup``
+template tags. If an invalid variable is provided to one of these template
+tags, the variable will be interpreted as ``None``. Filters are always
+applied to invalid variables within these template tags.
+
+.. admonition:: For debug purposes only!
+
+ While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
+ it is a bad idea to turn it on as a 'development default'.
+
+ Many templates, including those in the Admin site, rely upon the
+ silence of the template system when a non-existent variable is
+ encountered. If you assign a value other than ``''`` to
+ ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
+ problems with these templates and sites.
+
+ Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
+ in order to debug a specific template problem, then cleared
+ once debugging is complete.
+
+Playing with Context objects
+----------------------------
+
+Most of the time, you'll instantiate ``Context`` objects by passing in a
+fully-populated dictionary to ``Context()``. But you can add and delete items
+from a ``Context`` object once it's been instantiated, too, using standard
+dictionary syntax::
+
+ >>> c = Context({"foo": "bar"})
+ >>> c['foo']
+ 'bar'
+ >>> del c['foo']
+ >>> c['foo']
+ ''
+ >>> c['newvariable'] = 'hello'
+ >>> c['newvariable']
+ 'hello'
+
+A ``Context`` object is a stack. That is, you can ``push()`` and ``pop()`` it.
+If you ``pop()`` too much, it'll raise
+``django.template.ContextPopException``::
+
+ >>> c = Context()
+ >>> c['foo'] = 'first level'
+ >>> c.push()
+ >>> c['foo'] = 'second level'
+ >>> c['foo']
+ 'second level'
+ >>> c.pop()
+ >>> c['foo']
+ 'first level'
+ >>> c['foo'] = 'overwritten'
+ >>> c['foo']
+ 'overwritten'
+ >>> c.pop()
+ Traceback (most recent call last):
+ ...
+ django.template.ContextPopException
+
+Using a ``Context`` as a stack comes in handy in some custom template tags, as
+you'll see below.
+
+Subclassing Context: RequestContext
+-----------------------------------
+
+Django comes with a special ``Context`` class,
+``django.template.RequestContext``, that acts slightly differently than
+the normal ``django.template.Context``. The first difference is that takes
+an `HttpRequest object`_ as its first argument. For example::
+
+ c = RequestContext(request, {
+ 'foo': 'bar',
+ }
+
+The second difference is that it automatically populates the context with a few
+variables, according to your `TEMPLATE_CONTEXT_PROCESSORS setting`_.
+
+The ``TEMPLATE_CONTEXT_PROCESSORS`` setting is a tuple of callables -- called
+**context processors** -- that take a request object as their argument and
+return a dictionary of items to be merged into the context. By default,
+``TEMPLATE_CONTEXT_PROCESSORS`` is set to::
+
+ ("django.core.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n")
+
+Each processor is applied in order. That means, if one processor adds a
+variable to the context and a second processor adds a variable with the same
+name, the second will override the first. The default processors are explained
+below.
+
+Also, you can give ``RequestContext`` a list of additional processors, using the
+optional, third positional argument, ``processors``. In this example, the
+``RequestContext`` instance gets a ``ip_address`` variable::
+
+ def ip_address_processor(request):
+ return {'ip_address': request.META['REMOTE_ADDR']}
+
+ def some_view(request):
+ # ...
+ return RequestContext(request, {
+ 'foo': 'bar',
+ }, [ip_address_processor])
+
+Note::
+ If you're using Django's ``render_to_response()`` shortcut to populate a
+ template with the contents of a dictionary, your template will be passed a
+ ``Context`` instance by default (not a ``RequestContext``). To use a
+ ``RequestContext`` in your template rendering, pass an optional third
+ argument to ``render_to_response()``: a ``RequestContext``
+ instance. Your code might look like this::
+
+ def some_view(request):
+ # ...
+ return render_to_response('my_template.html',
+ my_data_dictionary,
+ context_instance=RequestContext(request))
+
+Here's what each of the default processors does:
+
+.. _HttpRequest object: ../request_response/#httprequest-objects
+.. _TEMPLATE_CONTEXT_PROCESSORS setting: ../settings/#template-context-processors
+
+django.core.context_processors.auth
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
+``RequestContext`` will contain these three variables:
+
+ * ``user`` -- An ``auth.User`` instance representing the currently
+ logged-in user (or an ``AnonymousUser`` instance, if the client isn't
+ logged in). See the `user authentication docs`.
+
+ * ``messages`` -- A list of messages (as strings) for the currently
+ logged-in user. Behind the scenes, this calls
+ ``request.user.get_and_delete_messages()`` for every request. That method
+ collects the user's messages and deletes them from the database.
+
+ Note that messages are set with ``user.add_message()``. See the
+ `message docs`_ for more.
+
+ * ``perms`` -- An instance of
+ ``django.core.context_processors.PermWrapper``, representing the
+ permissions that the currently logged-in user has. See the `permissions
+ docs`_.
+
+.. _user authentication docs: ../authentication/#users
+.. _message docs: ../authentication/#messages
+.. _permissions docs: ../authentication/#permissions
+
+django.core.context_processors.debug
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
+``RequestContext`` will contain these two variables -- but only if your
+``DEBUG`` setting is set to ``True`` and the request's IP address
+(``request.META['REMOTE_ADDR']``) is in the ``INTERNAL_IPS`` setting:
+
+ * ``debug`` -- ``True``. You can use this in templates to test whether
+ you're in ``DEBUG`` mode.
+ * ``sql_queries`` -- A list of ``{'sql': ..., 'time': ...}`` dictionaries,
+ representing every SQL query that has happened so far during the request
+ and how long it took. The list is in order by query.
+
+django.core.context_processors.i18n
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
+``RequestContext`` will contain these two variables:
+
+ * ``LANGUAGES`` -- The value of the `LANGUAGES setting`_.
+ * ``LANGUAGE_CODE`` -- ``request.LANGUAGE_CODE``, if it exists. Otherwise,
+ the value of the `LANGUAGE_CODE setting`_.
+
+See the `internationalization docs`_ for more.
+
+.. _LANGUAGES setting: ../settings/#languages
+.. _LANGUAGE_CODE setting: ../settings/#language-code
+.. _internationalization docs: ../i18n/
+
+django.core.context_processors.request
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
+``RequestContext`` will contain a variable ``request``, which is the current
+`HttpRequest object`_. Note that this processor is not enabled by default;
+you'll have to activate it.
+
+Writing your own context processors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A context processor has a very simple interface: It's just a Python function
+that takes one argument, an ``HttpRequest`` object, and returns a dictionary
+that gets added to the template context. Each context processor *must* return
+a dictionary.
+
+Custom context processors can live anywhere in your code base. All Django cares
+about is that your custom context processors are pointed-to by your
+``TEMPLATE_CONTEXT_PROCESSORS`` setting.
+
+Loading templates
+-----------------
+
+Generally, you'll store templates in files on your filesystem rather than using
+the low-level ``Template`` API yourself. Save templates in a directory
+specified as a **template directory**.
+
+Django searches for template directories in a number of places, depending on
+your template-loader settings (see "Loader types" below), but the most basic
+way of specifying template directories is by using the ``TEMPLATE_DIRS``
+setting.
+
+The TEMPLATE_DIRS setting
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Tell Django what your template directories are by using the ``TEMPLATE_DIRS``
+setting in your settings file. This should be set to a list or tuple of strings
+that contain full paths to your template directory(ies). Example::
+
+ TEMPLATE_DIRS = (
+ "/home/html/templates/lawrence.com",
+ "/home/html/templates/default",
+ )
+
+Your templates can go anywhere you want, as long as the directories and
+templates are readable by the Web server. They can have any extension you want,
+such as ``.html`` or ``.txt``, or they can have no extension at all.
+
+Note that these paths should use Unix-style forward slashes, even on Windows.
+
+The Python API
+~~~~~~~~~~~~~~
+
+Django has two ways to load templates from files:
+
+``django.template.loader.get_template(template_name)``
+ ``get_template`` returns the compiled template (a ``Template`` object) for
+ the template with the given name. If the template doesn't exist, it raises
+ ``django.template.TemplateDoesNotExist``.
+
+``django.template.loader.select_template(template_name_list)``
+ ``select_template`` is just like ``get_template``, except it takes a list
+ of template names. Of the list, it returns the first template that exists.
+
+For example, if you call ``get_template('story_detail.html')`` and have the
+above ``TEMPLATE_DIRS`` setting, here are the files Django will look for, in
+order:
+
+ * ``/home/html/templates/lawrence.com/story_detail.html``
+ * ``/home/html/templates/default/story_detail.html``
+
+If you call ``select_template(['story_253_detail.html', 'story_detail.html'])``,
+here's what Django will look for:
+
+ * ``/home/html/templates/lawrence.com/story_253_detail.html``
+ * ``/home/html/templates/default/story_253_detail.html``
+ * ``/home/html/templates/lawrence.com/story_detail.html``
+ * ``/home/html/templates/default/story_detail.html``
+
+When Django finds a template that exists, it stops looking.
+
+.. admonition:: Tip
+
+ You can use ``select_template()`` for super-flexible "templatability." For
+ example, if you've written a news story and want some stories to have
+ custom templates, use something like
+ ``select_template(['story_%s_detail.html' % story.id, 'story_detail.html'])``.
+ That'll allow you to use a custom template for an individual story, with a
+ fallback template for stories that don't have custom templates.
+
+Using subdirectories
+~~~~~~~~~~~~~~~~~~~~
+
+It's possible -- and preferable -- to organize templates in subdirectories of
+the template directory. The convention is to make a subdirectory for each
+Django app, with subdirectories within those subdirectories as needed.
+
+Do this for your own sanity. Storing all templates in the root level of a
+single directory gets messy.
+
+To load a template that's within a subdirectory, just use a slash, like so::
+
+ get_template('news/story_detail.html')
+
+Using the same ``TEMPLATE_DIRS`` setting from above, this example
+``get_template()`` call will attempt to load the following templates:
+
+ * ``/home/html/templates/lawrence.com/news/story_detail.html``
+ * ``/home/html/templates/default/news/story_detail.html``
+
+Loader types
+~~~~~~~~~~~~
+
+By default, Django uses a filesystem-based template loader, but Django comes
+with a few other template loaders, which know how to load templates from other
+sources.
+
+These other loaders are disabled by default, but you can activate them by
+editing your ``TEMPLATE_LOADERS`` setting. ``TEMPLATE_LOADERS`` should be a
+tuple of strings, where each string represents a template loader. Here are the
+template loaders that come with Django:
+
+``django.template.loaders.filesystem.load_template_source``
+ Loads templates from the filesystem, according to ``TEMPLATE_DIRS``.
+
+``django.template.loaders.app_directories.load_template_source``
+ Loads templates from Django apps on the filesystem. For each app in
+ ``INSTALLED_APPS``, the loader looks for a ``templates`` subdirectory. If
+ the directory exists, Django looks for templates in there.
+
+ This means you can store templates with your individual apps. This also
+ makes it easy to distribute Django apps with default templates.
+
+ For example, for this setting::
+
+ INSTALLED_APPS = ('myproject.polls', 'myproject.music')
+
+ ...then ``get_template('foo.html')`` will look for templates in these
+ directories, in this order:
+
+ * ``/path/to/myproject/polls/templates/foo.html``
+ * ``/path/to/myproject/music/templates/foo.html``
+
+ Note that the loader performs an optimization when it is first imported:
+ It caches a list of which ``INSTALLED_APPS`` packages have a ``templates``
+ subdirectory.
+
+``django.template.loaders.eggs.load_template_source``
+ Just like ``app_directories`` above, but it loads templates from Python
+ eggs rather than from the filesystem.
+
+Django uses the template loaders in order according to the ``TEMPLATE_LOADERS``
+setting. It uses each loader until a loader finds a match.
+
+Extending the template system
+=============================
+
+Although the Django template language comes with several default tags and
+filters, you might want to write your own. It's easy to do.
+
+First, create a ``templatetags`` package in the appropriate Django app's
+package. It should be on the same level as ``models.py``, ``views.py``, etc. For
+example::
+
+ polls/
+ models.py
+ templatetags/
+ views.py
+
+Add two files to the ``templatetags`` package: an ``__init__.py`` file and a
+file that will contain your custom tag/filter definitions. The name of the
+latter file is the name you'll use to load the tags later. For example, if your
+custom tags/filters are in a file called ``poll_extras.py``, you'd do the
+following in a template::
+
+ {% load poll_extras %}
+
+The ``{% load %}`` tag looks at your ``INSTALLED_APPS`` setting and only allows
+the loading of template libraries within installed Django apps. This is a
+security feature: It allows you to host Python code for many template libraries
+on a single computer without enabling access to all of them for every Django
+installation.
+
+If you write a template library that isn't tied to any particular models/views,
+it's perfectly OK to have a Django app package that only contains a
+``templatetags`` package.
+
+There's no limit on how many modules you put in the ``templatetags`` package.
+Just keep in mind that a ``{% load %}`` statement will load tags/filters for
+the given Python module name, not the name of the app.
+
+Once you've created that Python module, you'll just have to write a bit of
+Python code, depending on whether you're writing filters or tags.
+
+To be a valid tag library, the module contain a module-level variable named
+``register`` that is a ``template.Library`` instance, in which all the tags and
+filters are registered. So, near the top of your module, put the following::
+
+ from django import template
+
+ register = template.Library()
+
+.. admonition:: Behind the scenes
+
+ For a ton of examples, read the source code for Django's default filters
+ and tags. They're in ``django/template/defaultfilters.py`` and
+ ``django/template/defaulttags.py``, respectively.
+
+Writing custom template filters
+-------------------------------
+
+Custom filters are just Python functions that take one or two arguments:
+
+ * The value of the variable (input) -- not necessarily a string.
+ * The value of the argument -- this can have a default value, or be left
+ out altogether.
+
+For example, in the filter ``{{ var|foo:"bar" }}``, the filter ``foo`` would be
+passed the variable ``var`` and the argument ``"bar"``.
+
+Filter functions should always return something. They shouldn't raise
+exceptions. They should fail silently. In case of error, they should return
+either the original input or an empty string -- whichever makes more sense.
+
+Here's an example filter definition::
+
+ def cut(value, arg):
+ "Removes all values of arg from the given string"
+ return value.replace(arg, '')
+
+And here's an example of how that filter would be used::
+
+ {{ somevariable|cut:"0" }}
+
+Most filters don't take arguments. In this case, just leave the argument out of
+your function. Example::
+
+ def lower(value): # Only one argument.
+ "Converts a string into all lowercase"
+ return value.lower()
+
+When you've written your filter definition, you need to register it with
+your ``Library`` instance, to make it available to Django's template language::
+
+ register.filter('cut', cut)
+ register.filter('lower', lower)
+
+The ``Library.filter()`` method takes two arguments:
+
+ 1. The name of the filter -- a string.
+ 2. The compilation function -- a Python function (not the name of the
+ function as a string).
+
+If you're using Python 2.4 or above, you can use ``register.filter()`` as a
+decorator instead::
+
+ @register.filter(name='cut')
+ def cut(value, arg):
+ return value.replace(arg, '')
+
+ @register.filter
+ def lower(value):
+ return value.lower()
+
+If you leave off the ``name`` argument, as in the second example above, Django
+will use the function's name as the filter name.
+
+Template filters which expect strings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you are writing a template filter which only expects a string as the first
+argument, you should use the included decorator ``stringfilter`` which will convert
+an object to it's string value before being passed to your function::
+
+ from django import template
+
+ @template.stringfilter
+ def lower(value):
+ return value.lower()
+
+Writing custom template tags
+----------------------------
+
+Tags are more complex than filters, because tags can do anything.
+
+A quick overview
+~~~~~~~~~~~~~~~~
+
+Above, this document explained that the template system works in a two-step
+process: compiling and rendering. To define a custom template tag, you specify
+how the compilation works and how the rendering works.
+
+When Django compiles a template, it splits the raw template text into
+''nodes''. Each node is an instance of ``django.template.Node`` and has
+a ``render()`` method. A compiled template is, simply, a list of ``Node``
+objects. When you call ``render()`` on a compiled template object, the template
+calls ``render()`` on each ``Node`` in its node list, with the given context.
+The results are all concatenated together to form the output of the template.
+
+Thus, to define a custom template tag, you specify how the raw template tag is
+converted into a ``Node`` (the compilation function), and what the node's
+``render()`` method does.
+
+Writing the compilation function
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For each template tag the template parser encounters, it calls a Python
+function with the tag contents and the parser object itself. This function is
+responsible for returning a ``Node`` instance based on the contents of the tag.
+
+For example, let's write a template tag, ``{% current_time %}``, that displays
+the current date/time, formatted according to a parameter given in the tag, in
+`strftime syntax`_. It's a good idea to decide the tag syntax before anything
+else. In our case, let's say the tag should be used like this::
+
+ <p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
+
+.. _`strftime syntax`: http://www.python.org/doc/current/lib/module-time.html#l2h-1941
+
+The parser for this function should grab the parameter and create a ``Node``
+object::
+
+ from django import template
+ def do_current_time(parser, token):
+ try:
+ # split_contents() knows not to split quoted strings.
+ tag_name, format_string = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents[0]
+ if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
+ raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
+ return CurrentTimeNode(format_string[1:-1])
+
+Notes:
+
+ * ``parser`` is the template parser object. We don't need it in this
+ example.
+
+ * ``token.contents`` is a string of the raw contents of the tag. In our
+ example, it's ``'current_time "%Y-%m-%d %I:%M %p"'``.
+
+ * The ``token.split_contents()`` method separates the arguments on spaces
+ while keeping quoted strings together. The more straightforward
+ ``token.contents.split()`` wouldn't be as robust, as it would naively
+ split on *all* spaces, including those within quoted strings. It's a good
+ idea to always use ``token.split_contents()``.
+
+ * This function is responsible for raising
+ ``django.template.TemplateSyntaxError``, with helpful messages, for
+ any syntax error.
+
+ * The ``TemplateSyntaxError`` exceptions use the ``tag_name`` variable.
+ Don't hard-code the tag's name in your error messages, because that
+ couples the tag's name to your function. ``token.contents.split()[0]``
+ will ''always'' be the name of your tag -- even when the tag has no
+ arguments.
+
+ * The function returns a ``CurrentTimeNode`` with everything the node needs
+ to know about this tag. In this case, it just passes the argument --
+ ``"%Y-%m-%d %I:%M %p"``. The leading and trailing quotes from the
+ template tag are removed in ``format_string[1:-1]``.
+
+ * The parsing is very low-level. The Django developers have experimented
+ with writing small frameworks on top of this parsing system, using
+ techniques such as EBNF grammars, but those experiments made the template
+ engine too slow. It's low-level because that's fastest.
+
+Writing the renderer
+~~~~~~~~~~~~~~~~~~~~
+
+The second step in writing custom tags is to define a ``Node`` subclass that
+has a ``render()`` method.
+
+Continuing the above example, we need to define ``CurrentTimeNode``::
+
+ from django import template
+ import datetime
+ class CurrentTimeNode(template.Node):
+ def __init__(self, format_string):
+ self.format_string = format_string
+ def render(self, context):
+ return datetime.datetime.now().strftime(self.format_string)
+
+Notes:
+
+ * ``__init__()`` gets the ``format_string`` from ``do_current_time()``.
+ Always pass any options/parameters/arguments to a ``Node`` via its
+ ``__init__()``.
+
+ * The ``render()`` method is where the work actually happens.
+
+ * ``render()`` should never raise ``TemplateSyntaxError`` or any other
+ exception. It should fail silently, just as template filters should.
+
+Ultimately, this decoupling of compilation and rendering results in an
+efficient template system, because a template can render multiple context
+without having to be parsed multiple times.
+
+Registering the tag
+~~~~~~~~~~~~~~~~~~~
+
+Finally, register the tag with your module's ``Library`` instance, as explained
+in "Writing custom template filters" above. Example::
+
+ register.tag('current_time', do_current_time)
+
+The ``tag()`` method takes two arguments:
+
+ 1. The name of the template tag -- a string. If this is left out, the
+ name of the compilation function will be used.
+ 2. The compilation function -- a Python function (not the name of the
+ function as a string).
+
+As with filter registration, it is also possible to use this as a decorator, in
+Python 2.4 and above::
+
+ @register.tag(name="current_time")
+ def do_current_time(parser, token):
+ # ...
+
+ @register.tag
+ def shout(parser, token):
+ # ...
+
+If you leave off the ``name`` argument, as in the second example above, Django
+will use the function's name as the tag name.
+
+Passing template variables to the tag
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Although you can pass any number of arguments to a template tag using
+``token.split_contents()``, the arguments are all unpacked as
+string literals. A little more work is required in order to dynamic content (a
+template variable) to a template tag as an argument.
+
+While the previous examples have formatted the current time into a string and
+returned the string, suppose you wanted to pass in a ``DateTimeField`` from an
+object and have the template tag format that date-time::
+
+ <p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>
+
+Initially, ``token.split_contents()`` will return three values:
+
+ 1. The tag name ``format_time``.
+ 2. The string "blog_entry.date_updated" (without the surrounding quotes).
+ 3. The formatting string "%Y-%m-%d %I:%M %p". The return value from
+ ``split_contents()`` will include the leading and trailing quotes for
+ string literals like this.
+
+Now your tag should begin to look like this::
+
+ from django import template
+ def do_format_time(parser, token):
+ try:
+ # split_contents() knows not to split quoted strings.
+ tag_name, date_to_be_formatted, format_string = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents[0]
+ if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
+ raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
+ return FormatTimeNode(date_to_be_formatted, format_string[1:-1])
+
+You also have to change the renderer to retrieve the actual contents of the
+``date_updated`` property of the ``blog_entry`` object. This can be
+accomplished by using the ``resolve_variable()`` function in
+``django.template``. You pass ``resolve_variable()`` the variable name and the
+current context, available in the ``render`` method::
+
+ from django import template
+ from django.template import resolve_variable
+ import datetime
+ class FormatTimeNode(template.Node):
+ def __init__(self, date_to_be_formatted, format_string):
+ self.date_to_be_formatted = date_to_be_formatted
+ self.format_string = format_string
+
+ def render(self, context):
+ try:
+ actual_date = resolve_variable(self.date_to_be_formatted, context)
+ return actual_date.strftime(self.format_string)
+ except VariableDoesNotExist:
+ return ''
+
+``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
+format it accordingly.
+
+.. note::
+ The ``resolve_variable()`` function will throw a ``VariableDoesNotExist``
+ exception if it cannot resolve the string passed to it in the current
+ context of the page.
+
+Shortcut for simple tags
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Many template tags take a number of arguments -- strings or a template variables
+-- and return a string after doing some processing based solely on
+the input argument and some external information. For example, the
+``current_time`` tag we wrote above is of this variety: we give it a format
+string, it returns the time as a string.
+
+To ease the creation of the types of tags, Django provides a helper function,
+``simple_tag``. This function, which is a method of
+``django.template.Library``, takes a function that accepts any number of
+arguments, wraps it in a ``render`` function and the other necessary bits
+mentioned above and registers it with the template system.
+
+Our earlier ``current_time`` function could thus be written like this::
+
+ def current_time(format_string):
+ return datetime.datetime.now().strftime(format_string)
+
+ register.simple_tag(current_time)
+
+In Python 2.4, the decorator syntax also works::
+
+ @register.simple_tag
+ def current_time(token):
+ ...
+
+A couple of things to note about the ``simple_tag`` helper function:
+ * Checking for the required number of arguments, etc, has already been
+ done by the time our function is called, so we don't need to do that.
+ * The quotes around the argument (if any) have already been stripped away,
+ so we just receive a plain string.
+ * If the argument was a template variable, our function is passed the
+ current value of the variable, not the variable itself.
+
+When your template tag does not need access to the current context, writing a
+function to work with the input values and using the ``simple_tag`` helper is
+the easiest way to create a new tag.
+
+Inclusion tags
+~~~~~~~~~~~~~~
+
+Another common type of template tag is the type that displays some data by
+rendering *another* template. For example, Django's admin interface uses custom
+template tags to display the buttons along the bottom of the "add/change" form
+pages. Those buttons always look the same, but the link targets change depending
+on the object being edited -- so they're a perfect case for using a small
+template that is filled with details from the current object. (In the admin's
+case, this is the ``submit_row`` tag.)
+
+These sorts of tags are called `inclusion tags`.
+
+Writing inclusion tags is probably best demonstrated by example. Let's write a
+tag that outputs a list of choices for a given ``Poll`` object, such as was
+created in the tutorials_. We'll use the tag like this::
+
+ {% show_results poll %}
+
+...and the output will be something like this::
+
+ <ul>
+ <li>First choice</li>
+ <li>Second choice</li>
+ <li>Third choice</li>
+ </ul>
+
+First, define the function that takes the argument and produces a dictionary of
+data for the result. The important point here is we only need to return a
+dictionary, not anything more complex. This will be used as a template context
+for the template fragment. Example::
+
+ def show_results(poll):
+ choices = poll.choice_set.all()
+ return {'choices': choices}
+
+Next, create the template used to render the tag's output. This template is a
+fixed feature of the tag: the tag writer specifies it, not the template
+designer. Following our example, the template is very simple::
+
+ <ul>
+ {% for choice in choices %}
+ <li> {{ choice }} </li>
+ {% endfor %}
+ </ul>
+
+Now, create and register the inclusion tag by calling the ``inclusion_tag()``
+method on a ``Library`` object. Following our example, if the above template is
+in a file called ``results.html`` in a directory that's searched by the template
+loader, we'd register the tag like this::
+
+ # Here, register is a django.template.Library instance, as before
+ register.inclusion_tag('results.html')(show_results)
+
+As always, Python 2.4 decorator syntax works as well, so we could have
+written::
+
+ @register.inclusion_tag('results.html')
+ def show_results(poll):
+ ...
+
+...when first creating the function.
+
+Sometimes, your inclusion tags might require a large number of arguments,
+making it a pain for template authors to pass in all the arguments and remember
+their order. To solve this, Django provides a ``takes_context`` option for
+inclusion tags. If you specify ``takes_context`` in creating a template tag,
+the tag will have no required arguments, and the underlying Python function
+will have one argument -- the template context as of when the tag was called.
+
+For example, say you're writing an inclusion tag that will always be used in a
+context that contains ``home_link`` and ``home_title`` variables that point
+back to the main page. Here's what the Python function would look like::
+
+ # The first argument *must* be called "context" here.
+ def jump_link(context):
+ return {
+ 'link': context['home_link'],
+ 'title': context['home_title'],
+ }
+ # Register the custom tag as an inclusion tag with takes_context=True.
+ register.inclusion_tag('link.html', takes_context=True)(jump_link)
+
+(Note that the first parameter to the function *must* be called ``context``.)
+
+In that ``register.inclusion_tag()`` line, we specified ``takes_context=True``
+and the name of the template. Here's what the template ``link.html`` might look
+like::
+
+ Jump directly to <a href="{{ link }}">{{ title }}</a>.
+
+Then, any time you want to use that custom tag, load its library and call it
+without any arguments, like so::
+
+ {% jump_link %}
+
+Note that when you're using ``takes_context=True``, there's no need to pass
+arguments to the template tag. It automatically gets access to the context.
+
+The ``takes_context`` parameter defaults to ``False``. When it's set to *True*,
+the tag is passed the context object, as in this example. That's the only
+difference between this case and the previous ``inclusion_tag`` example.
+
+.. _tutorials: ../tutorial1/#creating-models
+
+Setting a variable in the context
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The above example simply output a value. Generally, it's more flexible if your
+template tags set template variables instead of outputting values. That way,
+template authors can reuse the values that your template tags create.
+
+To set a variable in the context, just use dictionary assignment on the context
+object in the ``render()`` method. Here's an updated version of
+``CurrentTimeNode`` that sets a template variable ``current_time`` instead of
+outputting it::
+
+ class CurrentTimeNode2(template.Node):
+ def __init__(self, format_string):
+ self.format_string = format_string
+ def render(self, context):
+ context['current_time'] = datetime.datetime.now().strftime(self.format_string)
+ return ''
+
+Note that ``render()`` returns the empty string. ``render()`` should always
+return string output. If all the template tag does is set a variable,
+``render()`` should return the empty string.
+
+Here's how you'd use this new version of the tag::
+
+ {% current_time "%Y-%M-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
+
+But, there's a problem with ``CurrentTimeNode2``: The variable name
+``current_time`` is hard-coded. This means you'll need to make sure your
+template doesn't use ``{{ current_time }}`` anywhere else, because the
+``{% current_time %}`` will blindly overwrite that variable's value. A cleaner
+solution is to make the template tag specify the name of the output variable,
+like so::
+
+ {% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
+ <p>The current time is {{ my_current_time }}.</p>
+
+To do that, you'll need to refactor both the compilation function and ``Node``
+class, like so::
+
+ class CurrentTimeNode3(template.Node):
+ def __init__(self, format_string, var_name):
+ self.format_string = format_string
+ self.var_name = var_name
+ def render(self, context):
+ context[self.var_name] = datetime.datetime.now().strftime(self.format_string)
+ return ''
+
+ import re
+ def do_current_time(parser, token):
+ # This version uses a regular expression to parse tag contents.
+ try:
+ # Splitting by None == splitting by spaces.
+ tag_name, arg = token.contents.split(None, 1)
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents[0]
+ m = re.search(r'(.*?) as (\w+)', arg)
+ if not m:
+ raise template.TemplateSyntaxError, "%r tag had invalid arguments" % tag_name
+ format_string, var_name = m.groups()
+ if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
+ raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
+ return CurrentTimeNode3(format_string[1:-1], var_name)
+
+The difference here is that ``do_current_time()`` grabs the format string and
+the variable name, passing both to ``CurrentTimeNode3``.
+
+Parsing until another block tag
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Template tags can work in tandem. For instance, the standard ``{% comment %}``
+tag hides everything until ``{% endcomment %}``. To create a template tag such
+as this, use ``parser.parse()`` in your compilation function.
+
+Here's how the standard ``{% comment %}`` tag is implemented::
+
+ def do_comment(parser, token):
+ nodelist = parser.parse(('endcomment',))
+ parser.delete_first_token()
+ return CommentNode()
+
+ class CommentNode(template.Node):
+ def render(self, context):
+ return ''
+
+``parser.parse()`` takes a tuple of names of block tags ''to parse until''. It
+returns an instance of ``django.template.NodeList``, which is a list of
+all ``Node`` objects that the parser encountered ''before'' it encountered
+any of the tags named in the tuple.
+
+In ``"nodelist = parser.parse(('endcomment',))"`` in the above example,
+``nodelist`` is a list of all nodes between the ``{% comment %}`` and
+``{% endcomment %}``, not counting ``{% comment %}`` and ``{% endcomment %}``
+themselves.
+
+After ``parser.parse()`` is called, the parser hasn't yet "consumed" the
+``{% endcomment %}`` tag, so the code needs to explicitly call
+``parser.delete_first_token()``.
+
+``CommentNode.render()`` simply returns an empty string. Anything between
+``{% comment %}`` and ``{% endcomment %}`` is ignored.
+
+Parsing until another block tag, and saving contents
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the previous example, ``do_comment()`` discarded everything between
+``{% comment %}`` and ``{% endcomment %}``. Instead of doing that, it's
+possible to do something with the code between block tags.
+
+For example, here's a custom template tag, ``{% upper %}``, that capitalizes
+everything between itself and ``{% endupper %}``.
+
+Usage::
+
+ {% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}
+
+As in the previous example, we'll use ``parser.parse()``. But this time, we
+pass the resulting ``nodelist`` to the ``Node``::
+
+ def do_upper(parser, token):
+ nodelist = parser.parse(('endupper',))
+ parser.delete_first_token()
+ return UpperNode(nodelist)
+
+ class UpperNode(template.Node):
+ def __init__(self, nodelist):
+ self.nodelist = nodelist
+ def render(self, context):
+ output = self.nodelist.render(context)
+ return output.upper()
+
+The only new concept here is the ``self.nodelist.render(context)`` in
+``UpperNode.render()``.
+
+For more examples of complex rendering, see the source code for ``{% if %}``,
+``{% for %}``, ``{% ifequal %}`` and ``{% ifchanged %}``. They live in
+``django/template/defaulttags.py``.
+
+.. _configuration:
+
+Configuring the template system in standalone mode
+==================================================
+
+.. note::
+
+ This section is only of interest to people trying to use the template
+ system as an output component in another application. If you're using the
+ template system as part of a Django application, nothing here applies to
+ you.
+
+Normally, Django will load all the configuration information it needs from its
+own default configuration file, combined with the settings in the module given
+in the ``DJANGO_SETTINGS_MODULE`` environment variable. But if you're using the
+template system independently of the rest of Django, the environment variable
+approach isn't very convenient, because you probably want to configure the
+template system in line with the rest of your application rather than dealing
+with settings files and pointing to them via environment variables.
+
+To solve this problem, you need to use the manual configuration option
+described in the `settings file`_ documentation. Simply import the appropriate
+pieces of the templating system and then, *before* you call any of the
+templating functions, call ``django.conf.settings.configure()`` with any
+settings you wish to specify. You might want to consider setting at least
+``TEMPLATE_DIRS`` (if you're going to use template loaders),
+``DEFAULT_CHARSET`` (although the default of ``utf-8`` is probably fine) and
+``TEMPLATE_DEBUG``. All available settings are described in the
+`settings documentation`_, and any setting starting with *TEMPLATE_*
+is of obvious interest.
+
+.. _settings file: ../settings/#using-settings-without-the-django-settings-module-environment-variable
+.. _settings documentation: ../settings/
diff --git a/google_appengine/lib/django/docs/testing.txt b/google_appengine/lib/django/docs/testing.txt
new file mode 100644
index 0000000..31cea79
--- /dev/null
+++ b/google_appengine/lib/django/docs/testing.txt
@@ -0,0 +1,550 @@
+===========================
+Testing Django applications
+===========================
+
+Automated testing is an extremely useful weapon in the bug-killing arsenal
+of the modern developer. When initially writing code, a test suite can be
+used to validate that code behaves as expected. When refactoring or
+modifying code, tests serve as a guide to ensure that behavior hasn't
+changed unexpectedly as a result of the refactor.
+
+Testing a web application is a complex task, as there are many
+components of a web application that must be validated and tested. To
+help you test your application, Django provides a test execution
+framework, and range of utilities that can be used to simulate and
+inspect various facets of a web application.
+
+ This testing framework is currently under development, and may change
+ slightly before the next official Django release.
+
+ (That's *no* excuse not to write tests, though!)
+
+Writing tests
+=============
+
+Tests in Django come in two forms: doctests and unit tests.
+
+Writing doctests
+----------------
+
+Doctests use Python's standard doctest_ module, which searches for tests in
+your docstrings. Django's test runner looks for doctests in your ``models.py``
+file, and executes any that it finds. Django will also search for a file
+called ``tests.py`` in the application directory (i.e., the directory that
+holds ``models.py``). If a ``tests.py`` is found, it will also be searched
+for doctests.
+
+.. admonition:: What's a **docstring**?
+
+ A good explanation of docstrings (and some guidlines for using them
+ effectively) can be found in :PEP:`257`:
+
+ A docstring is a string literal that occurs as the first statement in
+ a module, function, class, or method definition. Such a docstring
+ becomes the ``__doc__`` special attribute of that object.
+
+ Since tests often make great documentation, doctest lets you put your
+ tests directly in your docstrings.
+
+You can put doctest strings on any object in your ``models.py``, but it's
+common practice to put application-level doctests in the module docstring, and
+model-level doctests in the docstring for each model.
+
+For example::
+
+ from django.db import model
+
+ class Animal(models.Model):
+ """
+ An animal that knows how to make noise
+
+ # Create some animals
+ >>> lion = Animal.objects.create(name="lion", sound="roar")
+ >>> cat = Animal.objects.create(name="cat", sound="meow")
+
+ # Make 'em speak
+ >>> lion.speak()
+ 'The lion says "roar"'
+ >>> cat.speak()
+ 'The cat says "meow"'
+ """
+
+ name = models.CharField(maxlength=20)
+ sound = models.CharField(maxlength=20)
+
+ def speak(self):
+ return 'The %s says "%s"' % (self.name, self.sound)
+
+When you `run your tests`_, the test utility will find this docstring, notice
+that portions of it look like an interactive Python session, and execute those
+lines while checking that the results match.
+
+For more details about how doctest works, see the `standard library
+documentation for doctest`_
+
+.. _doctest: http://docs.python.org/lib/module-doctest.html
+.. _standard library documentation for doctest: doctest_
+
+Writing unittests
+-----------------
+
+Like doctests, Django's unit tests use a standard library module: unittest_.
+As with doctests, Django's test runner looks for any unit test cases defined
+in ``models.py``, or in a ``tests.py`` file stored in the application
+directory.
+
+An equivalent unittest test case for the above example would look like::
+
+ import unittest
+ from myapp.models import Animal
+
+ class AnimalTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.lion = Animal.objects.create(name="lion", sound="roar")
+ self.cat = Animal.objects.create(name="cat", sound="meow")
+
+ def testSpeaking(self):
+ self.assertEquals(self.lion.speak(), 'The lion says "roar"')
+ self.assertEquals(self.cat.speak(), 'The cat says "meow"')
+
+When you `run your tests`_, the test utility will find all the test cases
+(that is, subclasses of ``unittest.TestCase``) in ``models.py`` and
+``tests.py``, automatically build a test suite out of those test cases,
+and run that suite.
+
+For more details about ``unittest``, see the `standard library unittest
+documentation`_.
+
+.. _unittest: http://docs.python.org/lib/module-unittest.html
+.. _standard library unittest documentation: unittest_
+.. _run your tests: `Running tests`_
+
+Which should I use?
+-------------------
+
+Choosing a test framework is often contentious, so Django simply supports
+both of the standard Python test frameworks. Choosing one is up to each
+developer's personal tastes; each is supported equally. Since each test
+system has different benefits, the best approach is probably to use both
+together, picking the test system to match the type of tests you need to
+write.
+
+For developers new to testing, however, this choice can seem
+confusing, so here are a few key differences to help you decide whether
+doctests or unit tests are right for you.
+
+If you've been using Python for a while, ``doctest`` will probably feel more
+"pythonic". It's designed to make writing tests as easy as possible, so
+there's no overhead of writing classes or methods; you simply put tests in
+docstrings. This gives the added advantage of given your modules automatic
+documentation -- well-written doctests can kill both the documentation and the
+testing bird with a single stone.
+
+For developers just getting started with testing, using doctests will probably
+get you started faster.
+
+The ``unittest`` framework will probably feel very familiar to developers
+coming from Java. Since ``unittest`` is inspired by Java's JUnit, if
+you've used testing frameworks in other languages that similarly were
+inspired by JUnit, ``unittest`` should also feel pretty familiar.
+
+Since ``unittest`` is organized around classes and methods, if you need
+to write a bunch of tests that all share similar code, you can easily use
+subclass to abstract common tasks; this makes test code shorter and cleaner.
+There's also support for explicit setup and/or cleanup routines, which give
+you a high level of control over the environment your test cases run in.
+
+Again, remember that you can use both systems side-by-side (even in the same
+app). In the end, most projects will eventually end up using both; each shines
+in different circumstances.
+
+Testing Tools
+=============
+
+To assist in testing various features of your application, Django provides
+tools that can be used to establish tests and test conditions.
+
+* `Test Client`_
+* Fixtures_
+
+Test Client
+-----------
+
+The Test Client is a simple dummy browser. It allows you to simulate
+GET and POST requests on a URL, and observe the response that is received.
+This allows you to test that the correct view is executed for a given URL,
+and that the view constructs the correct response.
+
+As the response is generated, the Test Client gathers details on the
+Template and Context objects that were used to generate the response. These
+Templates and Contexts are then provided as part of the response, and can be
+used as test conditions.
+
+.. admonition:: Test Client vs Browser Automation?
+
+ The Test Client is not intended as a replacement for Twill_, Selenium_,
+ or other browser automation frameworks - it is intended to allow
+ testing of the contexts and templates produced by a view,
+ rather than the HTML rendered to the end-user.
+
+ A comprehensive test suite should use a combination of both: Test Client
+ tests to establish that the correct view is being called and that
+ the view is collecting the correct context data, and Browser Automation
+ tests to check that user interface behaves as expected.
+
+.. _Twill: http://twill.idyll.org/
+.. _Selenium: http://www.openqa.org/selenium/
+
+Making requests
+~~~~~~~~~~~~~~~
+
+Creating an instance of ``Client`` (``django.test.client.Client``) requires
+no arguments at time of construction. Once constructed, the following methods
+can be invoked on the ``Client`` instance.
+
+``get(path, data={})``
+ Make a GET request on the provided ``path``. The key-value pairs in the
+ data dictionary will be used to create a GET data payload. For example::
+
+ c = Client()
+ c.get('/customers/details/', {'name':'fred', 'age':7})
+
+ will result in the evaluation of a GET request equivalent to::
+
+ http://yoursite.com/customers/details/?name=fred&age=7
+
+``post(path, data={}, content_type=MULTIPART_CONTENT)``
+ Make a POST request on the provided ``path``. If you provide a content type
+ (e.g., ``text/xml`` for an XML payload), the contents of ``data`` will be
+ sent as-is in the POST request, using the content type in the HTTP
+ ``Content-Type`` header.
+
+ If you do not provide a value for ``content_type``, the values in
+ ``data`` will be transmitted with a content type of ``multipart/form-data``.
+ The key-value pairs in the data dictionary will be encoded as a multipart
+ message and used to create the POST data payload.
+
+ To submit multiple values for a given key (for example, to specify
+ the selections for a multiple selection list), provide the values as a
+ list or tuple for the required key. For example, a data dictionary of
+ ``{'choices': ('a','b','d')}`` would submit three selected rows for the
+ field named ``choices``.
+
+ Submitting files is a special case. To POST a file, you need only
+ provide the file field name as a key, and a file handle to the file you wish to
+ upload as a value. The Test Client will populate the two POST fields (i.e.,
+ ``field`` and ``field_file``) required by Django's FileField. For example::
+
+ c = Client()
+ f = open('wishlist.doc')
+ c.post('/customers/wishes/', {'name':'fred', 'attachment':f})
+ f.close()
+
+ will result in the evaluation of a POST request on ``/customers/wishes/``,
+ with a POST dictionary that contains `name`, `attachment` (containing the
+ file name), and `attachment_file` (containing the file data). Note that you
+ need to manually close the file after it has been provided to the POST.
+
+``login(path, username, password)``
+ In a production site, it is likely that some views will be protected with
+ the @login_required decorator provided by ``django.contrib.auth``. Interacting
+ with a URL that has been login protected is a slightly complex operation,
+ so the Test Client provides a simple method to automate the login process. A
+ call to ``login()`` stimulates the series of GET and POST calls required
+ to log a user into a @login_required protected view.
+
+ If login is possible, the final return value of ``login()`` is the response
+ that is generated by issuing a GET request on the protected URL. If login
+ is not possible, ``login()`` returns False.
+
+ Note that since the test suite will be executed using the test database,
+ which contains no users by default. As a result, logins for your production
+ site will not work. You will need to create users as part of the test suite
+ to be able to test logins to your application.
+
+Testing Responses
+~~~~~~~~~~~~~~~~~
+
+The ``get()``, ``post()`` and ``login()`` methods all return a Response
+object. This Response object has the following properties that can be used
+for testing purposes:
+
+ =============== ==========================================================
+ Property Description
+ =============== ==========================================================
+ ``status_code`` The HTTP status of the response. See RFC2616_ for a
+ full list of HTTP status codes.
+
+ ``content`` The body of the response. The is the final page
+ content as rendered by the view, or any error message
+ (such as the URL for a 302 redirect).
+
+ ``template`` The Template instance that was used to render the final
+ content. Testing ``template.name`` can be particularly
+ useful; if the template was loaded from a file,
+ ``template.name`` will be the file name that was loaded.
+
+ If multiple templates were rendered, (e.g., if one
+ template includes another template),``template`` will
+ be a list of Template objects, in the order in which
+ they were rendered.
+
+ ``context`` The Context that was used to render the template that
+ produced the response content.
+
+ As with ``template``, if multiple templates were rendered
+ ``context`` will be a list of Context objects, stored in
+ the order in which they were rendered.
+ =============== ==========================================================
+
+.. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+
+Exceptions
+~~~~~~~~~~
+
+If you point the Test Client at a view that raises an exception, that exception
+will be visible in the test case. You can then use a standard ``try...catch``
+block, or ``unittest.TestCase.assertRaises()`` to test for exceptions.
+
+The only exceptions that are not visible in a Test Case are ``Http404``,
+``PermissionDenied`` and ``SystemExit``. Django catches these exceptions
+internally and converts them into the appropriate HTTP responses codes.
+
+Persistent state
+~~~~~~~~~~~~~~~~
+
+The Test Client is stateful; if a cookie is returned as part of a response,
+that cookie is provided as part of the next request issued by that Client
+instance. Expiry policies for these cookies are not followed; if you want
+a cookie to expire, either delete it manually or create a new Client
+instance (which will effectively delete all cookies).
+
+There are two properties of the Test Client which are used to store persistent
+state information. If necessary, these properties can be interrogated as
+part of a test condition.
+
+ =============== ==========================================================
+ Property Description
+ =============== ==========================================================
+ ``cookies`` A Python ``SimpleCookie`` object, containing the current
+ values of all the client cookies.
+
+ ``session`` A dictionary-like object containing session information.
+ See the `session documentation`_ for full details.
+ =============== ==========================================================
+
+.. _`session documentation`: ../sessions/
+
+Example
+~~~~~~~
+
+The following is a simple unit test using the Test Client::
+
+ import unittest
+ from django.test.client import Client
+
+ class SimpleTest(unittest.TestCase):
+ def setUp(self):
+ # Every test needs a client
+ self.client = Client()
+ def test_details(self):
+ # Issue a GET request
+ response = self.client.get('/customer/details/')
+
+ # Check that the respose is 200 OK
+ self.failUnlessEqual(response.status_code, 200)
+ # Check that the rendered context contains 5 customers
+ self.failUnlessEqual(len(response.context['customers']), 5)
+
+Fixtures
+--------
+
+A test case for a database-backed website isn't much use if there isn't any
+data in the database. To make it easy to put test data into the database,
+Django provides a fixtures framework.
+
+A *Fixture* is a collection of files that contain the serialized contents of
+the database. Each fixture has a unique name; however, the files that
+comprise the fixture can be distributed over multiple directories, in
+multiple applications.
+
+.. note::
+ If you have synchronized a Django project, you have already experienced
+ the use of one fixture -- the ``initial_data`` fixture. Every time you
+ synchronize the database, Django installs the ``initial_data`` fixture.
+ This provides a mechanism to populate a new database with any initial
+ data (such as a default set of categories). Fixtures with other names
+ can be installed manually using ``django-admin.py loaddata``.
+
+
+However, for the purposes of unit testing, each test must be able to
+guarantee the contents of the database at the start of each and every
+test. To do this, Django provides a TestCase baseclass that can integrate
+with fixtures.
+
+Moving from a normal unittest TestCase to a Django TestCase is easy - just
+change the base class of your test, and define a list of fixtures
+to be used. For example, the test case from `Writing unittests`_ would
+look like::
+
+ from django.test import TestCase
+ from myapp.models import Animal
+
+ class AnimalTestCase(TestCase):
+ fixtures = ['mammals.json', 'birds']
+
+ def setUp(self):
+ # test definitions as before
+
+At the start of each test case, before ``setUp()`` is run, Django will
+flush the database, returning the database the state it was in directly
+after ``syncdb`` was called. Then, all the named fixtures are installed.
+In this example, any JSON fixture called ``mammals``, and any fixture
+named ``birds`` will be installed. See the documentation on
+`loading fixtures`_ for more details on defining and installing fixtures.
+
+.. _`loading fixtures`: ../django_admin/#loaddata-fixture-fixture
+
+This flush/load procedure is repeated for each test in the test case, so you
+can be certain that the outcome of a test will not be affected by
+another test, or the order of test execution.
+
+Running tests
+=============
+
+Run your tests using your project's ``manage.py`` utility::
+
+ $ ./manage.py test
+
+If you only want to run tests for a particular application, add the
+application name to the command line. For example, if your
+``INSTALLED_APPS`` contains ``myproject.polls`` and ``myproject.animals``,
+but you only want to run the animals unit tests, run::
+
+ $ ./manage.py test animals
+
+When you run your tests, you'll see a bunch of text flow by as the test
+database is created and models are initialized. This test database is
+created from scratch every time you run your tests.
+
+By default, the test database gets its name by prepending ``test_`` to
+the database name specified by the ``DATABASE_NAME`` setting; all other
+database settings will the same as they would be for the project normally.
+If you wish to use a name other than the default for the test database,
+you can use the ``TEST_DATABASE_NAME`` setting to provide a name.
+
+Once the test database has been established, Django will run your tests.
+If everything goes well, at the end you'll see::
+
+ ----------------------------------------------------------------------
+ Ran 22 tests in 0.221s
+
+ OK
+
+If there are test failures, however, you'll see full details about what tests
+failed::
+
+ ======================================================================
+ FAIL: Doctest: ellington.core.throttle.models
+ ----------------------------------------------------------------------
+ Traceback (most recent call last):
+ File "/dev/django/test/doctest.py", line 2153, in runTest
+ raise self.failureException(self.format_failure(new.getvalue()))
+ AssertionError: Failed doctest test for myapp.models
+ File "/dev/myapp/models.py", line 0, in models
+
+ ----------------------------------------------------------------------
+ File "/dev/myapp/models.py", line 14, in myapp.models
+ Failed example:
+ throttle.check("actor A", "action one", limit=2, hours=1)
+ Expected:
+ True
+ Got:
+ False
+
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.048s
+
+ FAILED (failures=1)
+
+The return code for the script will indicate the number of tests that failed.
+
+Regardless of whether the tests pass or fail, the test database is destroyed when
+all the tests have been executed.
+
+Using a different testing framework
+===================================
+
+Doctest and Unittest are not the only Python testing frameworks. While
+Django doesn't provide explicit support these alternative frameworks,
+it does provide a mechanism to allow you to invoke tests constructed for
+an alternative framework as if they were normal Django tests.
+
+When you run ``./manage.py test``, Django looks at the ``TEST_RUNNER``
+setting to determine what to do. By default, ``TEST_RUNNER`` points to
+``django.test.simple.run_tests``. This method defines the default Django
+testing behavior. This behavior involves:
+
+#. Performing global pre-test setup
+#. Creating the test database
+#. Running ``syncdb`` to install models and initial data into the test database
+#. Looking for Unit Tests and Doctests in ``models.py`` and ``tests.py`` file for each installed application
+#. Running the Unit Tests and Doctests that are found
+#. Destroying the test database
+#. Performing global post-test teardown
+
+If you define your own test runner method and point ``TEST_RUNNER``
+at that method, Django will execute your test runner whenever you run
+``./manage.py test``. In this way, it is possible to use any test
+framework that can be executed from Python code.
+
+Defining a test runner
+----------------------
+By convention, a test runner should be called ``run_tests``; however, you
+can call it anything you want. The only requirement is that it accept two
+arguments:
+
+``run_tests(module_list, verbosity=1)``
+ The module list is the list of Python modules that contain the models to be
+ tested. This is the same format returned by ``django.db.models.get_apps()``
+
+ Verbosity determines the amount of notification and debug information that
+ will be printed to the console; `0` is no output, `1` is normal output,
+ and `2` is verbose output.
+
+ This method should return the number of tests that failed.
+
+Testing utilities
+-----------------
+
+To assist in the creation of your own test runner, Django provides
+a number of utility methods in the ``django.test.utils`` module.
+
+``setup_test_environment()``
+ Performs any global pre-test setup, such as the installing the
+ instrumentation of the template rendering system.
+
+``teardown_test_environment()``
+ Performs any global post-test teardown, such as removing the instrumentation
+ of the template rendering system.
+
+``create_test_db(verbosity=1, autoclobber=False)``
+ Creates a new test database, and run ``syncdb`` against it.
+
+ ``verbosity`` has the same behavior as in the test runner.
+
+ ``Autoclobber`` describes the behavior that will occur if a database with
+ the same name as the test database is discovered. If ``autoclobber`` is False,
+ the user will be asked to approve destroying the existing database. ``sys.exit``
+ is called if the user does not approve. If autoclobber is ``True``, the database
+ will be destroyed without consulting the user.
+
+ ``create_test_db()`` has the side effect of modifying
+ ``settings.DATABASE_NAME`` to match the name of the test database.
+
+``destroy_test_db(old_database_name, verbosity=1)``
+ Destroys the database with the name ``settings.DATABASE_NAME`` matching,
+ and restores the value of ``settings.DATABASE_NAME`` to the provided name.
+
+ ``verbosity`` has the same behavior as in the test runner.
diff --git a/google_appengine/lib/django/docs/transactions.txt b/google_appengine/lib/django/docs/transactions.txt
new file mode 100644
index 0000000..2b0755a
--- /dev/null
+++ b/google_appengine/lib/django/docs/transactions.txt
@@ -0,0 +1,163 @@
+==============================
+Managing database transactions
+==============================
+
+Django gives you a few ways to control how database transactions are managed,
+if you're using a database that supports transactions.
+
+Django's default transaction behavior
+=====================================
+
+Django's default behavior is to commit automatically when any built-in,
+data-altering model function is called. For example, if you call
+``model.save()`` or ``model.delete()``, the change will be committed
+immediately.
+
+This is much like the auto-commit setting for most databases. As soon as you
+perform an action that needs to write to the database, Django produces the
+``INSERT``/``UPDATE``/``DELETE`` statements and then does the ``COMMIT``.
+There's no implicit ``ROLLBACK``.
+
+Tying transactions to HTTP requests
+===================================
+
+The recommended way to handle transactions in Web requests is to tie them to
+the request and response phases via Django's ``TransactionMiddleware``.
+
+It works like this: When a request starts, Django starts a transaction. If the
+response is produced without problems, Django commits any pending transactions.
+If the view function produces an exception, Django rolls back any pending
+transactions.
+
+To activate this feature, just add the ``TransactionMiddleware`` middleware to
+your ``MIDDLEWARE_CLASSES`` setting::
+
+ MIDDLEWARE_CLASSES = (
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.cache.CacheMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+ )
+
+The order is quite important. The transaction middleware applies not only to
+view functions, but also for all middleware modules that come after it. So if
+you use the session middleware after the transaction middleware, session
+creation will be part of the transaction.
+
+An exception is ``CacheMiddleware``, which is never affected. The cache
+middleware uses its own database cursor (which is mapped to its own database
+connection internally).
+
+Controlling transaction management in views
+===========================================
+
+For most people, implicit request-based transactions work wonderfully. However,
+if you need more fine-grained control over how transactions are managed, you
+can use Python decorators to change the way transactions are handled by a
+particular view function.
+
+.. note::
+
+ Although the examples below use view functions as examples, these
+ decorators can be applied to non-view functions as well.
+
+``django.db.transaction.autocommit``
+------------------------------------
+
+Use the ``autocommit`` decorator to switch a view function to Django's default
+commit behavior, regardless of the global transaction setting.
+
+Example::
+
+ from django.db import transaction
+
+ @transaction.autocommit
+ def viewfunc(request):
+ ....
+
+Within ``viewfunc()``, transactions will be committed as soon as you call
+``model.save()``, ``model.delete()``, or any other function that writes to the
+database.
+
+``django.db.transaction.commit_on_success``
+-------------------------------------------
+
+Use the ``commit_on_success`` decorator to use a single transaction for
+all the work done in a function::
+
+ from django.db import transaction
+
+ @transaction.commit_on_success
+ def viewfunc(request):
+ ....
+
+If the function returns successfully, then Django will commit all work done
+within the function at that point. If the function raises an exception, though,
+Django will roll back the transaction.
+
+``django.db.transaction.commit_manually``
+-----------------------------------------
+
+Use the ``commit_manually`` decorator if you need full control over
+transactions. It tells Django you'll be managing the transaction on your own.
+
+If your view changes data and doesn't ``commit()`` or ``rollback()``, Django
+will raise a ``TransactionManagementError`` exception.
+
+Manual transaction management looks like this::
+
+ from django.db import transaction
+
+ @transaction.commit_manually
+ def viewfunc(request):
+ ...
+ # You can commit/rollback however and whenever you want
+ transaction.commit()
+ ...
+
+ # But you've got to remember to do it yourself!
+ try:
+ ...
+ except:
+ transaction.rollback()
+ else:
+ transaction.commit()
+
+.. admonition:: An important note to users of earlier Django releases:
+
+ The database ``connection.commit()`` and ``connection.rollback()`` methods
+ (called ``db.commit()`` and ``db.rollback()`` in 0.91 and earlier) no longer
+ exist. They've been replaced by ``transaction.commit()`` and
+ ``transaction.rollback()``.
+
+How to globally deactivate transaction management
+=================================================
+
+Control freaks can totally disable all transaction management by setting
+``DISABLE_TRANSACTION_MANAGEMENT`` to ``True`` in the Django settings file.
+
+If you do this, Django won't provide any automatic transaction management
+whatsoever. Middleware will no longer implicitly commit transactions, and
+you'll need to roll management yourself. This even requires you to commit
+changes done by middleware somewhere else.
+
+Thus, this is best used in situations where you want to run your own
+transaction-controlling middleware or do something really strange. In almost
+all situations, you'll be better off using the default behavior, or the
+transaction middleware, and only modify selected functions as needed.
+
+Transactions in MySQL
+=====================
+
+If you're using MySQL, your tables may or may not support transactions; it
+depends on your MySQL version and the table types you're using. (By
+"table types," we mean something like "InnoDB" or "MyISAM".) MySQL transaction
+peculiarities are outside the scope of this article, but the MySQL site has
+`information on MySQL transactions`_.
+
+If your MySQL setup does *not* support transactions, then Django will function
+in auto-commit mode: Statements will be executed and committed as soon as
+they're called. If your MySQL setup *does* support transactions, Django will
+handle transactions as explained in this document.
+
+.. _information on MySQL transactions: http://dev.mysql.com/books/mysqlpress/mysql-tutorial/ch10.html
diff --git a/google_appengine/lib/django/docs/tutorial01.txt b/google_appengine/lib/django/docs/tutorial01.txt
new file mode 100644
index 0000000..56c5fa7
--- /dev/null
+++ b/google_appengine/lib/django/docs/tutorial01.txt
@@ -0,0 +1,575 @@
+=====================================
+Writing your first Django app, part 1
+=====================================
+
+Let's learn by example.
+
+Throughout this tutorial, we'll walk you through the creation of a basic
+poll application.
+
+It'll consist of two parts:
+
+ * A public site that lets people view polls and vote in them.
+ * An admin site that lets you add, change and delete poll.
+
+We'll assume you have `Django installed`_ already. You can tell Django is
+installed by running the Python interactive interpreter and typing
+``import django``. If that command runs successfully, with no errors, Django is
+installed.
+
+.. _`Django installed`: ../install/
+
+.. admonition:: Where to get help:
+
+ If you're having trouble going through this tutorial, please post a message
+ to `django-users`_ or drop by `#django`_ on ``irc.freenode.net`` and we'll
+ try to help.
+
+.. _django-users: http://groups.google.com/group/django-users
+.. _#django: irc://irc.freenode.net/django
+
+Creating a project
+==================
+
+If this is your first time using Django, you'll have to take care of some
+initial setup. Namely, you'll need to auto-generate some code that establishes
+a Django *project* -- a collection of settings for an instance of Django,
+including database configuration, Django-specific options and
+application-specific settings.
+
+From the command line, ``cd`` into a directory where you'd like to store your
+code, then run the command ``django-admin.py startproject mysite``. This
+will create a ``mysite`` directory in your current directory.
+
+.. note::
+
+ You'll need to avoid naming projects after built-in Python or Django
+ components. In particular, this means you should avoid using names like
+ ``django`` (which will conflict with Django itself) or ``site`` (which
+ conflicts with a built-in Python package).
+
+(``django-admin.py`` should be on your system path if you installed Django via
+``python setup.py``. If it's not on your path, you can find it in
+``site-packages/django/bin``, where ``site-packages`` is a directory within
+your Python installation. Consider symlinking to ``django-admin.py`` from some
+place on your path, such as ``/usr/local/bin``.)
+
+.. admonition:: Where should this code live?
+
+ If your background is in PHP, you're probably used to putting code under the
+ Web server's document root (in a place such as ``/var/www``). With Django,
+ you don't do that. It's not a good idea to put any of this Python code within
+ your Web server's document root, because it risks the possibility that
+ people may be able to view your code over the Web. That's not good for
+ security.
+
+ Put your code in some directory **outside** of the document root, such as
+ ``/home/mycode``.
+
+Let's look at what ``startproject`` created::
+
+ mysite/
+ __init__.py
+ manage.py
+ settings.py
+ urls.py
+
+These files are:
+
+ * ``__init__.py``: An empty file that tells Python that this directory
+ should be considered a Python package. (Read `more about packages`_ in the
+ official Python docs if you're a Python beginner.)
+ * ``manage.py``: A command-line utility that lets you interact with this
+ Django project in various ways.
+ * ``settings.py``: Settings/configuration for this Django project.
+ * ``urls.py``: The URL declarations for this Django project; a "table of
+ contents" of your Django-powered site.
+
+.. _more about packages: http://docs.python.org/tut/node8.html#packages
+
+The development server
+----------------------
+
+Let's verify this worked. Change into the ``mysite`` directory, if you
+haven't already, and run the command ``python manage.py runserver``. You'll see
+the following output on the command line::
+
+ Validating models...
+ 0 errors found.
+
+ Django version 0.95, using settings 'mysite.settings'
+ Development server is running at http://127.0.0.1:8000/
+ Quit the server with CONTROL-C (Unix) or CTRL-BREAK (Windows).
+
+You've started the Django development server, a lightweight Web server written
+purely in Python. We've included this with Django so you can develop things
+rapidly, without having to deal with configuring a production server -- such as
+Apache -- until you're ready for production.
+
+Now's a good time to note: DON'T use this server in anything resembling a
+production environment. It's intended only for use while developing. (We're in
+the business of making Web frameworks, not Web servers.)
+
+Now that the server's running, visit http://127.0.0.1:8000/ with your Web
+browser. You'll see a "Welcome to Django" page, in pleasant, light-blue pastel.
+It worked!
+
+.. admonition:: Changing the port
+
+ By default, the ``runserver`` command starts the development server on port
+ 8000. If you want to change the server's port, pass it as a command-line
+ argument. For instance, this command starts the server on port 8080::
+
+ python manage.py runserver 8080
+
+ Full docs for the development server are at `django-admin documentation`_.
+
+.. _django-admin documentation: ../django_admin/
+
+Database setup
+--------------
+
+Now, edit ``settings.py``. It's a normal Python module with module-level
+variables representing Django settings. Change these settings to match your
+database's connection parameters:
+
+ * ``DATABASE_ENGINE`` -- Either 'postgresql', 'mysql' or 'sqlite3'.
+ More coming soon.
+ * ``DATABASE_NAME`` -- The name of your database, or the full (absolute)
+ path to the database file if you're using SQLite.
+ * ``DATABASE_USER`` -- Your database username (not used for SQLite).
+ * ``DATABASE_PASSWORD`` -- Your database password (not used for SQLite).
+ * ``DATABASE_HOST`` -- The host your database is on. Leave this as an
+ empty string if your database server is on the same physical machine
+ (not used for SQLite).
+
+.. admonition:: Note
+
+ If you're using PostgreSQL or MySQL, make sure you've created a database by
+ this point. Do that with "``CREATE DATABASE database_name;``" within your
+ database's interactive prompt.
+
+While you're editing ``settings.py``, take note of the ``INSTALLED_APPS``
+setting towards the bottom of the file. That variable holds the names of all
+Django applications that are activated in this Django instance. Apps can be
+used in multiple projects, and you can package and distribute them for use
+by others in their projects.
+
+By default, ``INSTALLED_APPS`` contains the following apps, all of which come
+with Django:
+
+ * ``django.contrib.auth`` -- An authentication system.
+ * ``django.contrib.contenttypes`` -- A framework for content types.
+ * ``django.contrib.sessions`` -- A session framework.
+ * ``django.contrib.sites`` -- A framework for managing multiple sites
+ with one Django installation.
+
+These applications are included by default as a convenience for the common
+case.
+
+Each of these applications makes use of at least one database table, though,
+so we need to create the tables in the database before we can use them. To do
+that, run the following command::
+
+ python manage.py syncdb
+
+The ``syncdb`` command looks at the ``INSTALLED_APPS`` setting and creates any
+necessary database tables according to the database settings in your
+``settings.py`` file. You'll see a message for each database table it creates,
+and you'll get a prompt asking you if you'd like to create a superuser account
+for the authentication system. Go ahead and do that.
+
+If you're interested, run the command-line client for your database and type
+``\dt`` (PostgreSQL), ``SHOW TABLES;`` (MySQL), or ``.schema`` (SQLite) to
+display the tables Django created.
+
+.. admonition:: For the minimalists
+
+ Like we said above, the default applications are included for the common
+ case, but not everybody needs them. If you don't need any or all of them,
+ feel free to comment-out or delete the appropriate line(s) from
+ ``INSTALLED_APPS`` before running ``syncdb``. The ``syncdb`` command will
+ only create tables for apps in ``INSTALLED_APPS``.
+
+Creating models
+===============
+
+Now that your environment -- a "project" -- is set up, you're set to start
+doing work.
+
+Each application you write in Django consists of a Python package, somewhere
+on your `Python path`_, that follows a certain convention. Django comes with a
+utility that automatically generates the basic directory structure of an app,
+so you can focus on writing code rather than creating directories.
+
+.. admonition:: Projects vs. apps
+
+ What's the difference between a project and an app? An app is a Web
+ application that does something -- e.g., a weblog system, a database of
+ public records or a simple poll app. A project is a collection of
+ configuration and apps for a particular Web site. A project can contain
+ multiple apps. An app can be in multiple projects.
+
+In this tutorial, we'll create our poll app in the ``mysite`` directory,
+for simplicity. As a consequence, the app will be coupled to the project --
+that is, Python code within the poll app will refer to ``mysite.polls``.
+Later in this tutorial, we'll discuss decoupling your apps for distribution.
+
+To create your app, make sure you're in the ``mysite`` directory and type
+this command::
+
+ python manage.py startapp polls
+
+That'll create a directory ``polls``, which is laid out like this::
+
+ polls/
+ __init__.py
+ models.py
+ views.py
+
+This directory structure will house the poll application.
+
+The first step in writing a database Web app in Django is to define your models
+-- essentially, your database layout, with additional metadata.
+
+.. admonition:: Philosophy
+
+ A model is the single, definitive source of data about your
+ data. It contains the essential fields and behaviors of the data you're
+ storing. Django follows the `DRY Principle`_. The goal is to define your
+ data model in one place and automatically derive things from it.
+
+In our simple poll app, we'll create two models: polls and choices. A poll has
+a question and a publication date. A choice has two fields: the text of the
+choice and a vote tally. Each choice is associated with a poll.
+
+These concepts are represented by simple Python classes. Edit the
+``polls/models.py`` file so it looks like this::
+
+ from django.db import models
+
+ class Poll(models.Model):
+ question = models.CharField(maxlength=200)
+ pub_date = models.DateTimeField('date published')
+
+ class Choice(models.Model):
+ poll = models.ForeignKey(Poll)
+ choice = models.CharField(maxlength=200)
+ votes = models.IntegerField()
+
+The code is straightforward. Each model is represented by a class that
+subclasses ``django.db.models.Model``. Each model has a number of class
+variables, each of which represents a database field in the model.
+
+Each field is represented by an instance of a ``models.*Field`` class -- e.g.,
+``models.CharField`` for character fields and ``models.DateTimeField`` for
+datetimes. This tells Django what type of data each field holds.
+
+The name of each ``models.*Field`` instance (e.g. ``question`` or ``pub_date`` )
+is the field's name, in machine-friendly format. You'll use this value in your
+Python code, and your database will use it as the column name.
+
+You can use an optional first positional argument to a ``Field`` to designate a
+human-readable name. That's used in a couple of introspective parts of Django,
+and it doubles as documentation. If this field isn't provided, Django will use
+the machine-readable name. In this example, we've only defined a human-readable
+name for ``Poll.pub_date``. For all other fields in this model, the field's
+machine-readable name will suffice as its human-readable name.
+
+Some ``Field`` classes have required elements. ``CharField``, for example,
+requires that you give it a ``maxlength``. That's used not only in the database
+schema, but in validation, as we'll soon see.
+
+Finally, note a relationship is defined, using ``models.ForeignKey``. That tells
+Django each Choice is related to a single Poll. Django supports all the common
+database relationships: many-to-ones, many-to-manys and one-to-ones.
+
+.. _`Python path`: http://docs.python.org/tut/node8.html#SECTION008110000000000000000
+.. _DRY Principle: http://c2.com/cgi/wiki?DontRepeatYourself
+
+Activating models
+=================
+
+That small bit of model code gives Django a lot of information. With it, Django
+is able to:
+
+ * Create a database schema (``CREATE TABLE`` statements) for this app.
+ * Create a Python database-access API for accessing Poll and Choice objects.
+
+But first we need to tell our project that the ``polls`` app is installed.
+
+.. admonition:: Philosophy
+
+ Django apps are "pluggable": You can use an app in multiple projects, and
+ you can distribute apps, because they don't have to be tied to a given
+ Django installation.
+
+Edit the ``settings.py`` file again, and change the ``INSTALLED_APPS`` setting
+to include the string ``'mysite.polls'``. So it'll look like this::
+
+ INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'mysite.polls'
+ )
+
+Now Django knows ``mysite`` includes the ``polls`` app. Let's run another command::
+
+ python manage.py sql polls
+
+You should see the following (the CREATE TABLE SQL statements for the polls app)::
+
+ BEGIN;
+ CREATE TABLE "polls_poll" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "question" varchar(200) NOT NULL,
+ "pub_date" timestamp with time zone NOT NULL
+ );
+ CREATE TABLE "polls_choice" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "poll_id" integer NOT NULL REFERENCES "polls_poll" ("id"),
+ "choice" varchar(200) NOT NULL,
+ "votes" integer NOT NULL
+ );
+ COMMIT;
+
+Note the following:
+
+ * Table names are automatically generated by combining the name of the app
+ (``polls``) and the lowercase name of the model -- ``poll`` and
+ ``choice``. (You can override this behavior.)
+
+ * Primary keys (IDs) are added automatically. (You can override this, too.)
+
+ * By convention, Django appends ``"_id"`` to the foreign key field name.
+ Yes, you can override this, as well.
+
+ * The foreign key relationship is made explicit by a ``REFERENCES`` statement.
+
+ * It's tailored to the database you're using, so database-specific field
+ types such as ``auto_increment`` (MySQL), ``serial`` (PostgreSQL), or
+ ``integer primary key`` (SQLite) are handled for you automatically. Same
+ goes for quoting of field names -- e.g., using double quotes or single
+ quotes. The author of this tutorial runs PostgreSQL, so the example
+ output is in PostgreSQL syntax.
+
+ * The `sql` command doesn't actually run the SQL in your database - it just
+ prints it to the screen so that you can see what SQL Django thinks is required.
+ If you wanted to, you could copy and paste this SQL into your database prompt.
+ However, as we will see shortly, Django provides an easier way of committing
+ the SQL to the database.
+
+If you're interested, also run the following commands:
+ * ``python manage.py validate polls`` -- Checks for any errors in the
+ construction of your models.
+
+ * ``python manage.py sqlinitialdata polls`` -- Outputs any initial data
+ required for Django's admin framework and your models.
+
+ * ``python manage.py sqlclear polls`` -- Outputs the necessary ``DROP
+ TABLE`` statements for this app, according to which tables already exist
+ in your database (if any).
+
+ * ``python manage.py sqlindexes polls`` -- Outputs the ``CREATE INDEX``
+ statements for this app.
+
+ * ``python manage.py sqlall polls`` -- A combination of all the SQL from
+ the 'sql', 'sqlinitialdata', and 'sqlindexes' commands.
+
+Looking at the output of those commands can help you understand what's actually
+happening under the hood.
+
+Now, run ``syncdb`` again to create those model tables in your database::
+
+ python manage.py syncdb
+
+The ``syncdb`` command runs the sql from 'sqlall' on your database for all apps
+in ``INSTALLED_APPS`` that don't already exist in your database. This creates
+all the tables, initial data and indexes for any apps you have added to your
+project since the last time you ran syncdb. ``syncdb`` can be called as often
+as you like, and it will only ever create the tables that don't exist.
+
+Read the `django-admin.py documentation`_ for full information on what the
+``manage.py`` utility can do.
+
+.. _django-admin.py documentation: ../django_admin/
+
+Playing with the API
+====================
+
+Now, let's hop into the interactive Python shell and play around with the free
+API Django gives you. To invoke the Python shell, use this command::
+
+ python manage.py shell
+
+We're using this instead of simply typing "python", because ``manage.py`` sets
+up the project's environment for you. "Setting up the environment" involves two
+things:
+
+ * Putting ``mysite`` on ``sys.path``. For flexibility, several pieces of
+ Django refer to projects in Python dotted-path notation (e.g.
+ ``'mysite.polls.models'``). In order for this to work, the
+ ``mysite`` package has to be on ``sys.path``.
+
+ We've already seen one example of this: the ``INSTALLED_APPS`` setting is
+ a list of packages in dotted-path notation.
+
+ * Setting the ``DJANGO_SETTINGS_MODULE`` environment variable, which gives
+ Django the path to your ``settings.py`` file.
+
+.. admonition:: Bypassing manage.py
+
+ If you'd rather not use ``manage.py``, no problem. Just make sure
+ ``mysite`` is at the root level on the Python path (i.e.,
+ ``import mysite`` works) and set the ``DJANGO_SETTINGS_MODULE``
+ environment variable to ``mysite.settings``.
+
+ For more information on all of this, see the `django-admin.py documentation`_.
+
+Once you're in the shell, explore the database API::
+
+ # Import the model classes we just wrote.
+ >>> from mysite.polls.models import Poll, Choice
+
+ # No polls are in the system yet.
+ >>> Poll.objects.all()
+ []
+
+ # Create a new Poll.
+ >>> from datetime import datetime
+ >>> p = Poll(question="What's up?", pub_date=datetime.now())
+
+ # Save the object into the database. You have to call save() explicitly.
+ >>> p.save()
+
+ # Now it has an ID. Note that this might say "1L" instead of "1", depending
+ # on which database you're using. That's no biggie; it just means your
+ # database backend prefers to return integers as Python long integer
+ # objects.
+ >>> p.id
+ 1
+
+ # Access database columns via Python attributes.
+ >>> p.question
+ "What's up?"
+ >>> p.pub_date
+ datetime.datetime(2005, 7, 15, 12, 00, 53)
+
+ # Change values by changing the attributes, then calling save().
+ >>> p.pub_date = datetime(2005, 4, 1, 0, 0)
+ >>> p.save()
+
+ # objects.all() displays all the polls in the database.
+ >>> Poll.objects.all()
+ [<Poll: Poll object>]
+
+
+Wait a minute. ``<Poll: Poll object>`` is, utterly, an unhelpful
+representation of this object. Let's fix that by editing the polls model (in
+the ``polls/models.py`` file) and adding a ``__str__()`` method to both
+``Poll`` and ``Choice``::
+
+ class Poll(models.Model):
+ # ...
+ def __str__(self):
+ return self.question
+
+ class Choice(models.Model):
+ # ...
+ def __str__(self):
+ return self.choice
+
+It's important to add ``__str__()`` methods to your models, not only for your
+own sanity when dealing with the interactive prompt, but also because objects'
+representations are used throughout Django's automatically-generated admin.
+
+Note these are normal Python methods. Let's add a custom method, just for
+demonstration::
+
+ import datetime
+ # ...
+ class Poll(models.Model):
+ # ...
+ def was_published_today(self):
+ return self.pub_date.date() == datetime.date.today()
+
+Note the addition of ``import datetime`` to reference Python's standard
+``datetime`` module.
+
+Let's jump back into the Python interactive shell by running
+``python manage.py shell`` again::
+
+ >>> from mysite.polls.models import Poll, Choice
+
+ # Make sure our __str__() addition worked.
+ >>> Poll.objects.all()
+ [<Poll: What's up?>]
+
+ # Django provides a rich database lookup API that's entirely driven by
+ # keyword arguments.
+ >>> Poll.objects.filter(id=1)
+ [<Poll: What's up?>]
+ >>> Poll.objects.filter(question__startswith='What')
+ [<Poll: What's up?>]
+
+ # Get the poll whose year is 2005. Of course, if you're going through this
+ # tutorial in another year, change as appropriate.
+ >>> Poll.objects.get(pub_date__year=2005)
+ <Poll: What's up?>
+
+ >>> Poll.objects.get(id=2)
+ Traceback (most recent call last):
+ ...
+ DoesNotExist: Poll matching query does not exist.
+
+ # Lookup by a primary key is the most common case, so Django provides a
+ # shortcut for primary-key exact lookups.
+ # The following is identical to Poll.objects.get(id=1).
+ >>> Poll.objects.get(pk=1)
+ <Poll: What's up?>
+
+ # Make sure our custom method worked.
+ >>> p = Poll.objects.get(pk=1)
+ >>> p.was_published_today()
+ False
+
+ # Give the Poll a couple of Choices. The create call constructs a new
+ # choice object, does the INSERT statement, adds the choice to the set
+ # of available choices and returns the new Choice object.
+ >>> p = Poll.objects.get(pk=1)
+ >>> p.choice_set.create(choice='Not much', votes=0)
+ <Choice: Not much>
+ >>> p.choice_set.create(choice='The sky', votes=0)
+ <Choice: The sky>
+ >>> c = p.choice_set.create(choice='Just hacking again', votes=0)
+
+ # Choice objects have API access to their related Poll objects.
+ >>> c.poll
+ <Poll: What's up?>
+
+ # And vice versa: Poll objects get access to Choice objects.
+ >>> p.choice_set.all()
+ [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
+ >>> p.choice_set.count()
+ 3
+
+ # The API automatically follows relationships as far as you need.
+ # Use double underscores to separate relationships.
+ # This works as many levels deep as you want. There's no limit.
+ # Find all Choices for any poll whose pub_date is in 2005.
+ >>> Choice.objects.filter(poll__pub_date__year=2005)
+ [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
+
+ # Let's delete one of the choices. Use delete() for that.
+ >>> c = p.choice_set.filter(choice__startswith='Just hacking')
+ >>> c.delete()
+
+For full details on the database API, see our `Database API reference`_.
+
+When you're comfortable with the API, read `part 2 of this tutorial`_ to get
+Django's automatic admin working.
+
+.. _Database API reference: ../db_api/
+.. _part 2 of this tutorial: ../tutorial2/
diff --git a/google_appengine/lib/django/docs/tutorial02.txt b/google_appengine/lib/django/docs/tutorial02.txt
new file mode 100644
index 0000000..2eabae9
--- /dev/null
+++ b/google_appengine/lib/django/docs/tutorial02.txt
@@ -0,0 +1,437 @@
+=====================================
+Writing your first Django app, part 2
+=====================================
+
+This tutorial begins where `Tutorial 1`_ left off. We're continuing the Web-poll
+application and will focus on Django's automatically-generated admin site.
+
+.. _Tutorial 1: ../tutorial1/
+
+.. admonition:: Philosophy
+
+ Generating admin sites for your staff or clients to add, change and delete
+ content is tedious work that doesn't require much creativity. For that reason,
+ Django entirely automates creation of admin interfaces for models.
+
+ Django was written in a newsroom environment, with a very clear separation
+ between "content publishers" and the "public" site. Site managers use the
+ system to add news stories, events, sports scores, etc., and that content is
+ displayed on the public site. Django solves the problem of creating a unified
+ interface for site administrators to edit content.
+
+ The admin isn't necessarily intended to be used by site visitors; it's for site
+ managers.
+
+Activate the admin site
+=======================
+
+The Django admin site is not activated by default -- it's an opt-in thing. To
+activate the admin site for your installation, do these three things:
+
+ * Add ``"django.contrib.admin"`` to your ``INSTALLED_APPS`` setting.
+ * Run ``python manage.py syncdb``. Since you have added a new application
+ to ``INSTALLED_APPS``, the database tables need to be updated.
+ * Edit your ``mysite/urls.py`` file and uncomment the line below
+ "Uncomment this for admin:". This file is a URLconf; we'll dig into
+ URLconfs in the next tutorial. For now, all you need to know is that it
+ maps URL roots to applications.
+
+Start the development server
+============================
+
+Let's start the development server and explore the admin site.
+
+Recall from Tutorial 1 that you start the development server like so::
+
+ python manage.py runserver
+
+Now, open a Web browser and go to "/admin/" on your local domain -- e.g.,
+http://127.0.0.1:8000/admin/. You should see the admin's login screen:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin01.png
+ :alt: Django admin login screen
+
+Enter the admin site
+====================
+
+Now, try logging in. (You created a superuser account in the first part of this
+tutorial, remember?) You should see the Django admin index page:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin02t.png
+ :alt: Django admin index page
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin02.png
+
+By default, you should see two types of editable content: groups and users.
+These are core features Django ships with by default.
+
+.. _"I can't log in" questions: ../faq/#the-admin-site
+
+Make the poll app modifiable in the admin
+=========================================
+
+But where's our poll app? It's not displayed on the admin index page.
+
+Just one thing to do: We need to specify in the ``Poll`` model that ``Poll``
+objects have an admin interface. Edit the ``mysite/polls/models.py`` file and
+make the following change to add an inner ``Admin`` class::
+
+ class Poll(models.Model):
+ # ...
+ class Admin:
+ pass
+
+The ``class Admin`` will contain all the settings that control how this model
+appears in the Django admin. All the settings are optional, however, so
+creating an empty class means "give this object an admin interface using
+all the default options."
+
+Now reload the Django admin page to see your changes. Note that you don't have
+to restart the development server -- the server will auto-reload your project,
+so any modifications code will be seen immediately in your browser.
+
+Explore the free admin functionality
+====================================
+
+Now that ``Poll`` has the inner ``Admin`` class, Django knows that it should be
+displayed on the admin index page:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin03t.png
+ :alt: Django admin index page, now with polls displayed
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin03.png
+
+Click "Polls." Now you're at the "change list" page for polls. This page
+displays all the polls in the database and lets you choose one to change it.
+There's the "What's up?" poll we created in the first tutorial:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin04t.png
+ :alt: Polls change list page
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin04.png
+
+Click the "What's up?" poll to edit it:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin05t.png
+ :alt: Editing form for poll object
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin05.png
+
+Things to note here:
+
+* The form is automatically generated from the Poll model.
+* The different model field types (``models.DateTimeField``, ``models.CharField``)
+ correspond to the appropriate HTML input widget. Each type of field knows
+ how to display itself in the Django admin.
+* Each ``DateTimeField`` gets free JavaScript shortcuts. Dates get a "Today"
+ shortcut and calendar popup, and times get a "Now" shortcut and a convenient
+ popup that lists commonly entered times.
+
+The bottom part of the page gives you a couple of options:
+
+* Save -- Saves changes and returns to the change-list page for this type of
+ object.
+* Save and continue editing -- Saves changes and reloads the admin page for
+ this object.
+* Save and add another -- Saves changes and loads a new, blank form for this
+ type of object.
+* Delete -- Displays a delete confirmation page.
+
+Change the "Date published" by clicking the "Today" and "Now" shortcuts. Then
+click "Save and continue editing." Then click "History" in the upper right.
+You'll see a page listing all changes made to this object via the Django admin,
+with the timestamp and username of the person who made the change:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin06t.png
+ :alt: History page for poll object
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin06.png
+
+Customize the admin form
+========================
+
+Take a few minutes to marvel at all the code you didn't have to write.
+
+Let's customize this a bit. We can reorder the fields by explicitly adding a
+``fields`` parameter to ``Admin``::
+
+ class Admin:
+ fields = (
+ (None, {'fields': ('pub_date', 'question')}),
+ )
+
+That made the "Publication date" show up first instead of second:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin07.png
+ :alt: Fields have been reordered
+
+This isn't impressive with only two fields, but for admin forms with dozens
+of fields, choosing an intuitive order is an important usability detail.
+
+And speaking of forms with dozens of fields, you might want to split the form
+up into fieldsets::
+
+ class Admin:
+ fields = (
+ (None, {'fields': ('question',)}),
+ ('Date information', {'fields': ('pub_date',)}),
+ )
+
+The first element of each tuple in ``fields`` is the title of the fieldset.
+Here's what our form looks like now:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin08t.png
+ :alt: Form has fieldsets now
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin08.png
+
+You can assign arbitrary HTML classes to each fieldset. Django provides a
+``"collapse"`` class that displays a particular fieldset initially collapsed.
+This is useful when you have a long form that contains a number of fields that
+aren't commonly used::
+
+ class Admin:
+ fields = (
+ (None, {'fields': ('question',)}),
+ ('Date information', {'fields': ('pub_date',), 'classes': 'collapse'}),
+ )
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin09.png
+ :alt: Fieldset is initially collapsed
+
+Adding related objects
+======================
+
+OK, we have our Poll admin page. But a ``Poll`` has multiple ``Choices``, and
+the admin page doesn't display choices.
+
+Yet.
+
+There are two ways to solve this problem. The first is to give the ``Choice``
+model its own inner ``Admin`` class, just as we did with ``Poll``. Here's what
+that would look like::
+
+ class Choice(models.Model):
+ # ...
+ class Admin:
+ pass
+
+Now "Choices" is an available option in the Django admin. The "Add choice" form
+looks like this:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin10.png
+ :alt: Choice admin page
+
+In that form, the "Poll" field is a select box containing every poll in the
+database. Django knows that a ``ForeignKey`` should be represented in the admin
+as a ``<select>`` box. In our case, only one poll exists at this point.
+
+Also note the "Add Another" link next to "Poll." Every object with a ForeignKey
+relationship to another gets this for free. When you click "Add Another," you'll
+get a popup window with the "Add poll" form. If you add a poll in that window
+and click "Save," Django will save the poll to the database and dynamically add
+it as the selected choice on the "Add choice" form you're looking at.
+
+But, really, this is an inefficient way of adding Choice objects to the system.
+It'd be better if you could add a bunch of Choices directly when you create the
+Poll object. Let's make that happen.
+
+Remove the ``Admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)``
+field like so::
+
+ poll = models.ForeignKey(Poll, edit_inline=models.STACKED, num_in_admin=3)
+
+This tells Django: "Choice objects are edited on the Poll admin page. By
+default, provide enough fields for 3 Choices."
+
+Then change the other fields in ``Choice`` to give them ``core=True``::
+
+ choice = models.CharField(maxlength=200, core=True)
+ votes = models.IntegerField(core=True)
+
+This tells Django: "When you edit a Choice on the Poll admin page, the 'choice'
+and 'votes' fields are required. The presence of at least one of them signifies
+the addition of a new Choice object, and clearing both of them signifies the
+deletion of that existing Choice object."
+
+Load the "Add poll" page to see how that looks:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin11t.png
+ :alt: Add poll page now has choices on it
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin11.png
+
+It works like this: There are three slots for related Choices -- as specified
+by ``num_in_admin`` -- but each time you come back to the "Change" page for an
+already-created object, you get one extra slot. (This means there's no
+hard-coded limit on how many related objects can be added.) If you wanted space
+for three extra Choices each time you changed the poll, you'd use
+``num_extra_on_change=3``.
+
+One small problem, though. It takes a lot of screen space to display all the
+fields for entering related Choice objects. For that reason, Django offers an
+alternate way of displaying inline related objects::
+
+ poll = models.ForeignKey(Poll, edit_inline=models.TABULAR, num_in_admin=3)
+
+With that ``edit_inline=models.TABULAR`` (instead of ``models.STACKED``), the
+related objects are displayed in a more compact, table-based format:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin12.png
+ :alt: Add poll page now has more compact choices
+
+Customize the admin change list
+===============================
+
+Now that the Poll admin page is looking good, let's make some tweaks to the
+"change list" page -- the one that displays all the polls in the system.
+
+Here's what it looks like at this point:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin04t.png
+ :alt: Polls change list page
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin04.png
+
+By default, Django displays the ``str()`` of each object. But sometimes it'd
+be more helpful if we could display individual fields. To do that, use the
+``list_display`` option, which is a tuple of field names to display, as columns,
+on the change list page for the object::
+
+ class Poll(models.Model):
+ # ...
+ class Admin:
+ # ...
+ list_display = ('question', 'pub_date')
+
+Just for good measure, let's also include the ``was_published_today`` custom
+method from Tutorial 1::
+
+ list_display = ('question', 'pub_date', 'was_published_today')
+
+Now the poll change list page looks like this:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin13t.png
+ :alt: Polls change list page, updated
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin13.png
+
+You can click on the column headers to sort by those values -- except in the
+case of the ``was_published_today`` header, because sorting by the output of
+an arbitrary method is not supported. Also note that the column header for
+``was_published_today`` is, by default, the name of the method (with
+underscores replaced with spaces). But you can change that by giving that
+method a ``short_description`` attribute::
+
+ def was_published_today(self):
+ return self.pub_date.date() == datetime.date.today()
+ was_published_today.short_description = 'Published today?'
+
+
+Let's add another improvement to the Poll change list page: Filters. Add the
+following line to ``Poll.admin``::
+
+ list_filter = ['pub_date']
+
+That adds a "Filter" sidebar that lets people filter the change list by the
+``pub_date`` field:
+
+.. image:: http://media.djangoproject.com/img/doc/tutorial/admin14t.png
+ :alt: Polls change list page, updated
+ :target: http://media.djangoproject.com/img/doc/tutorial/admin14.png
+
+The type of filter displayed depends on the type of field you're filtering on.
+Because ``pub_date`` is a DateTimeField, Django knows to give the default
+filter options for DateTimeFields: "Any date," "Today," "Past 7 days,"
+"This month," "This year."
+
+This is shaping up well. Let's add some search capability::
+
+ search_fields = ['question']
+
+That adds a search box at the top of the change list. When somebody enters
+search terms, Django will search the ``question`` field. You can use as many
+fields as you'd like -- although because it uses a ``LIKE`` query behind the
+scenes, keep it reasonable, to keep your database happy.
+
+Finally, because Poll objects have dates, it'd be convenient to be able to
+drill down by date. Add this line::
+
+ date_hierarchy = 'pub_date'
+
+That adds hierarchical navigation, by date, to the top of the change list page.
+At top level, it displays all available years. Then it drills down to months
+and, ultimately, days.
+
+Now's also a good time to note that change lists give you free pagination. The
+default is to display 50 items per page. Change-list pagination, search boxes,
+filters, date-hierarchies and column-header-ordering all work together like you
+think they should.
+
+Customize the admin look and feel
+=================================
+
+Clearly, having "Django administration" and "example.com" at the top of each
+admin page is ridiculous. It's just placeholder text.
+
+That's easy to change, though, using Django's template system. The Django admin
+is powered by Django itself, and its interfaces use Django's own template
+system. (How meta!)
+
+Open your settings file (``mysite/settings.py``, remember) and look at the
+``TEMPLATE_DIRS`` setting. ``TEMPLATE_DIRS`` is a tuple of filesystem
+directories to check when loading Django templates. It's a search path.
+
+By default, ``TEMPLATE_DIRS`` is empty. So, let's add a line to it, to tell
+Django where our templates live::
+
+ TEMPLATE_DIRS = (
+ "/home/my_username/mytemplates", # Change this to your own directory.
+ )
+
+Now copy the template ``admin/base_site.html`` from within the default Django
+admin template directory (``django/contrib/admin/templates``) into an ``admin``
+subdirectory of whichever directory you're using in ``TEMPLATE_DIRS``. For
+example, if your ``TEMPLATE_DIRS`` includes ``"/home/my_username/mytemplates"``,
+as above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to
+``/home/my_username/mytemplates/admin/base_site.html``. Don't forget that
+``admin`` subdirectory.
+
+Then, just edit the file and replace the generic Django text with your own
+site's name and URL as you see fit.
+
+Note that any of Django's default admin templates can be overridden. To
+override a template, just do the same thing you did with ``base_site.html`` --
+copy it from the default directory into your custom directory, and make
+changes.
+
+Astute readers will ask: But if ``TEMPLATE_DIRS`` was empty by default, how was
+Django finding the default admin templates? The answer is that, by default,
+Django automatically looks for a ``templates/`` subdirectory within each app
+package, for use as a fallback. See the `loader types documentation`_ for full
+information.
+
+.. _loader types documentation: ../templates_python/#loader-types
+
+Customize the admin index page
+==============================
+
+On a similar note, you might want to customize the look and feel of the Django
+admin index page.
+
+By default, it displays all available apps, according to your ``INSTALLED_APPS``
+setting. But the order in which it displays things is random, and you may want
+to make significant changes to the layout. After all, the index is probably the
+most important page of the admin, and it should be easy to use.
+
+The template to customize is ``admin/index.html``. (Do the same as with
+``admin/base_site.html`` in the previous section -- copy it from the default
+directory to your custom template directory.) Edit the file, and you'll see it
+uses a template tag called ``{% get_admin_app_list as app_list %}``. That's the
+magic that retrieves every installed Django app. Instead of using that, you can
+hard-code links to object-specific admin pages in whatever way you think is
+best.
+
+Django offers another shortcut in this department. Run the command
+``python manage.py adminindex polls`` to get a chunk of template code for
+inclusion in the admin index template. It's a useful starting point.
+
+For full details on customizing the look and feel of the Django admin site in
+general, see the `Django admin CSS guide`_.
+
+When you're comfortable with the admin site, read `part 3 of this tutorial`_ to
+start working on public poll views.
+
+.. _Django admin CSS guide: ../admin_css/
+.. _part 3 of this tutorial: ../tutorial3/
diff --git a/google_appengine/lib/django/docs/tutorial03.txt b/google_appengine/lib/django/docs/tutorial03.txt
new file mode 100644
index 0000000..17b6ec0
--- /dev/null
+++ b/google_appengine/lib/django/docs/tutorial03.txt
@@ -0,0 +1,466 @@
+=====================================
+Writing your first Django app, part 3
+=====================================
+
+This tutorial begins where `Tutorial 2`_ left off. We're continuing the Web-poll
+application and will focus on creating the public interface -- "views."
+
+.. _Tutorial 2: ../tutorial2/
+
+Philosophy
+==========
+
+A view is a "type" of Web page in your Django application that generally serves
+a specific function and has a specific template. For example, in a weblog
+application, you might have the following views:
+
+ * Blog homepage -- displays the latest few entries.
+ * Entry "detail" page -- permalink page for a single entry.
+ * Year-based archive page -- displays all months with entries in the
+ given year.
+ * Month-based archive page -- displays all days with entries in the
+ given month.
+ * Day-based archive page -- displays all entries in the given day.
+ * Comment action -- handles posting comments to a given entry.
+
+In our poll application, we'll have the following four views:
+
+ * Poll "archive" page -- displays the latest few polls.
+ * Poll "detail" page -- displays a poll question, with no results but
+ with a form to vote.
+ * Poll "results" page -- displays results for a particular poll.
+ * Vote action -- handles voting for a particular choice in a particular
+ poll.
+
+In Django, each view is represented by a simple Python function.
+
+Design your URLs
+================
+
+The first step of writing views is to design your URL structure. You do this by
+creating a Python module, called a URLconf. URLconfs are how Django associates
+a given URL with given Python code.
+
+When a user requests a Django-powered page, the system looks at the
+``ROOT_URLCONF`` setting, which contains a string in Python dotted syntax.
+Django loads that module and looks for a module-level variable called
+``urlpatterns``, which is a sequence of tuples in the following format::
+
+ (regular expression, Python callback function [, optional dictionary])
+
+Django starts at the first regular expression and makes its way down the list,
+comparing the requested URL against each regular expression until it finds one
+that matches.
+
+When it finds a match, Django calls the Python callback function, with an
+``HTTPRequest`` object as the first argument, any "captured" values from the
+regular expression as keyword arguments, and, optionally, arbitrary keyword
+arguments from the dictionary (an optional third item in the tuple).
+
+For more on ``HTTPRequest`` objects, see the `request and response documentation`_.
+For more details on URLconfs, see the `URLconf documentation`_.
+
+When you ran ``python manage.py startproject mysite`` at the beginning of
+Tutorial 1, it created a default URLconf in ``mysite/urls.py``. It also
+automatically set your ``ROOT_URLCONF`` setting to point at that file::
+
+ ROOT_URLCONF = 'mysite.urls'
+
+Time for an example. Edit ``mysite/urls.py`` so it looks like this::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^polls/$', 'mysite.polls.views.index'),
+ (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
+ (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
+ (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
+ )
+
+This is worth a review. When somebody requests a page from your Web site --
+say, "/polls/23/", Django will load this Python module, because it's pointed to
+by the ``ROOT_URLCONF`` setting. It finds the variable named ``urlpatterns``
+and traverses the regular expressions in order. When it finds a regular
+expression that matches -- ``r'^polls/(?P<poll_id>\d+)/$'`` -- it loads the
+associated Python package/module: ``mysite.polls.views.detail``. That
+corresponds to the function ``detail()`` in ``mysite/polls/views.py``.
+Finally, it calls that ``detail()`` function like so::
+
+ detail(request=<HttpRequest object>, poll_id='23')
+
+The ``poll_id='23'`` part comes from ``(?P<poll_id>\d+)``. Using parenthesis around a
+pattern "captures" the text matched by that pattern and sends it as an argument
+to the view function; the ``?P<poll_id>`` defines the name that will be used to
+identify the matched pattern; and ``\d+`` is a regular expression to match a sequence of
+digits (i.e., a number).
+
+Because the URL patterns are regular expressions, there really is no limit on
+what you can do with them. And there's no need to add URL cruft such as
+``.php`` -- unless you have a sick sense of humor, in which case you can do
+something like this::
+
+ (r'^polls/latest\.php$', 'mysite.polls.views.index'),
+
+But, don't do that. It's silly.
+
+Note that these regular expressions do not search GET and POST parameters, or
+the domain name. For example, in a request to ``http://www.example.com/myapp/``,
+the URLconf will look for ``/myapp/``. In a request to
+``http://www.example.com/myapp/?page=3``, the URLconf will look for ``/myapp/``.
+
+If you need help with regular expressions, see `Wikipedia's entry`_ and the
+`Python documentation`_. Also, the O'Reilly book "Mastering Regular
+Expressions" by Jeffrey Friedl is fantastic.
+
+Finally, a performance note: these regular expressions are compiled the first
+time the URLconf module is loaded. They're super fast.
+
+.. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression
+.. _Python documentation: http://www.python.org/doc/current/lib/module-re.html
+.. _request and response documentation: ../request_response/
+.. _URLconf documentation: ../url_dispatch/
+
+Write your first view
+=====================
+
+Well, we haven't created any views yet -- we just have the URLconf. But let's
+make sure Django is following the URLconf properly.
+
+Fire up the Django development Web server::
+
+ python manage.py runserver
+
+Now go to "http://localhost:8000/polls/" on your domain in your Web browser.
+You should get a pleasantly-colored error page with the following message::
+
+ ViewDoesNotExist at /polls/
+
+ Tried index in module mysite.polls.views. Error was: 'module'
+ object has no attribute 'index'
+
+This error happened because you haven't written a function ``index()`` in the
+module ``mysite/polls/views.py``.
+
+Try "/polls/23/", "/polls/23/results/" and "/polls/23/vote/". The error
+messages tell you which view Django tried (and failed to find, because you
+haven't written any views yet).
+
+Time to write the first view. Open the file ``mysite/polls/views.py``
+and put the following Python code in it::
+
+ from django.http import HttpResponse
+
+ def index(request):
+ return HttpResponse("Hello, world. You're at the poll index.")
+
+This is the simplest view possible. Go to "/polls/" in your browser, and you
+should see your text.
+
+Now add the following view. It's slightly different, because it takes an
+argument (which, remember, is passed in from whatever was captured by the
+regular expression in the URLconf)::
+
+ def detail(request, poll_id):
+ return HttpResponse("You're looking at poll %s." % poll_id)
+
+Take a look in your browser, at "/polls/34/". It'll display whatever ID you
+provide in the URL.
+
+Write views that actually do something
+======================================
+
+Each view is responsible for doing one of two things: Returning an ``HttpResponse``
+object containing the content for the requested page, or raising an exception
+such as ``Http404``. The rest is up to you.
+
+Your view can read records from a database, or not. It can use a template
+system such as Django's -- or a third-party Python template system -- or not.
+It can generate a PDF file, output XML, create a ZIP file on the fly, anything
+you want, using whatever Python libraries you want.
+
+All Django wants is that ``HttpResponse``. Or an exception.
+
+Because it's convenient, let's use Django's own database API, which we covered
+in Tutorial 1. Here's one stab at the ``index()`` view, which displays the
+latest 5 poll questions in the system, separated by commas, according to
+publication date::
+
+ from mysite.polls.models import Poll
+ from django.http import HttpResponse
+
+ def index(request):
+ latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
+ output = ', '.join([p.question for p in latest_poll_list])
+ return HttpResponse(output)
+
+There's a problem here, though: The page's design is hard-coded in the view. If
+you want to change the way the page looks, you'll have to edit this Python code.
+So let's use Django's template system to separate the design from Python::
+
+ from django.template import Context, loader
+ from mysite.polls.models import Poll
+ from django.http import HttpResponse
+
+ def index(request):
+ latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
+ t = loader.get_template('polls/index.html')
+ c = Context({
+ 'latest_poll_list': latest_poll_list,
+ })
+ return HttpResponse(t.render(c))
+
+That code loads the template called "polls/index.html" and passes it a context. The
+context is a dictionary mapping template variable names to Python objects.
+
+Reload the page. Now you'll see an error::
+
+ TemplateDoesNotExist at /polls/
+ polls/index.html
+
+Ah. There's no template yet. First, create a directory, somewhere on your
+filesystem, whose contents Django can access. (Django runs as whatever user
+your server runs.) Don't put them under your document root, though. You
+probably shouldn't make them public, just for security's sake.
+
+Then edit ``TEMPLATE_DIRS`` in your ``settings.py`` to tell Django where it can
+find templates -- just as you did in the "Customize the admin look and feel"
+section of Tutorial 2.
+
+When you've done that, create a directory ``polls`` in your template directory.
+Within that, create a file called ``index.html``. Note that our
+``loader.get_template('polls/index.html')`` code from above maps to
+"[template_directory]/polls/index.html" on the filesystem.
+
+Put the following code in that template::
+
+ {% if latest_poll_list %}
+ <ul>
+ {% for poll in latest_poll_list %}
+ <li>{{ poll.question }}</li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <p>No polls are available.</p>
+ {% endif %}
+
+Load the page in your Web browser, and you should see a bulleted-list
+containing the "What's up" poll from Tutorial 1.
+
+A shortcut: render_to_response()
+--------------------------------
+
+It's a very common idiom to load a template, fill a context and return an
+``HttpResponse`` object with the result of the rendered template. Django
+provides a shortcut. Here's the full ``index()`` view, rewritten::
+
+ from django.shortcuts import render_to_response
+ from mysite.polls.models import Poll
+
+ def index(request):
+ latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
+ return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
+
+Note that once we've done this in all these views, we no longer need to import ``loader``, ``Context`` and ``HttpResponse``.
+
+The ``render_to_response()`` function takes a template name as its first
+argument and a dictionary as its optional second argument. It returns an
+``HttpResponse`` object of the given template rendered with the given context.
+
+Raising 404
+===========
+
+Now, let's tackle the poll detail view -- the page that displays the question
+for a given poll. Here's the view::
+
+ from django.http import Http404
+ # ...
+ def detail(request, poll_id):
+ try:
+ p = Poll.objects.get(pk=poll_id)
+ except Poll.DoesNotExist:
+ raise Http404
+ return render_to_response('polls/detail.html', {'poll': p})
+
+The new concept here: The view raises the ``django.http.Http404``
+exception if a poll with the requested ID doesn't exist.
+
+A shortcut: get_object_or_404()
+-------------------------------
+
+It's a very common idiom to use ``get()`` and raise ``Http404`` if the
+object doesn't exist. Django provides a shortcut. Here's the ``detail()`` view,
+rewritten::
+
+ from django.shortcuts import render_to_response, get_object_or_404
+ # ...
+ def detail(request, poll_id):
+ p = get_object_or_404(Poll, pk=poll_id)
+ return render_to_response('polls/detail.html', {'poll': p})
+
+The ``get_object_or_404()`` function takes a Django model module as its first
+argument and an arbitrary number of keyword arguments, which it passes to the
+module's ``get()`` function. It raises ``Http404`` if the object doesn't
+exist.
+
+.. admonition:: Philosophy
+
+ Why do we use a helper function ``get_object_or_404()`` instead of
+ automatically catching the ``DoesNotExist`` exceptions at a higher level,
+ or having the model API raise ``Http404`` instead of ``DoesNotExist``?
+
+ Because that would couple the model layer to the view layer. One of the
+ foremost design goals of Django is to maintain loose coupling.
+
+There's also a ``get_list_or_404()`` function, which works just as
+``get_object_or_404()`` -- except using ``filter()`` instead of
+``get()``. It raises ``Http404`` if the list is empty.
+
+Write a 404 (page not found) view
+=================================
+
+When you raise ``Http404`` from within a view, Django will load a special view
+devoted to handling 404 errors. It finds it by looking for the variable
+``handler404``, which is a string in Python dotted syntax -- the same format
+the normal URLconf callbacks use. A 404 view itself has nothing special: It's
+just a normal view.
+
+You normally won't have to bother with writing 404 views. By default, URLconfs
+have the following line up top::
+
+ from django.conf.urls.defaults import *
+
+That takes care of setting ``handler404`` in the current module. As you can see
+in ``django/conf/urls/defaults.py``, ``handler404`` is set to
+``'django.views.defaults.page_not_found'`` by default.
+
+Three more things to note about 404 views:
+
+ * The 404 view is also called if Django doesn't find a match after checking
+ every regular expression in the URLconf.
+ * If you don't define your own 404 view -- and simply use the default,
+ which is recommended -- you still have one obligation: To create a
+ ``404.html`` template in the root of your template directory. The default
+ 404 view will use that template for all 404 errors.
+ * If ``DEBUG`` is set to ``True`` (in your settings module) then your 404
+ view will never be used, and the traceback will be displayed instead.
+
+Write a 500 (server error) view
+===============================
+
+Similarly, URLconfs may define a ``handler500``, which points to a view to call
+in case of server errors. Server errors happen when you have runtime errors in
+view code.
+
+Use the template system
+=======================
+
+Back to our ``polls.detail`` view. Given the context variable ``poll``, here's
+what the template might look like::
+
+ <h1>{{ poll.question }}</h1>
+ <ul>
+ {% for choice in poll.choice_set.all %}
+ <li>{{ choice.choice }}</li>
+ {% endfor %}
+ </ul>
+
+The template system uses dot-lookup syntax to access variable attributes. In
+the example of ``{{ poll.question }}``, first Django does a dictionary lookup
+on the object ``poll``. Failing that, it tries attribute lookup -- which works,
+in this case. If attribute lookup had failed, it would've tried calling the
+method ``question()`` on the poll object.
+
+Method-calling happens in the ``{% for %}`` loop: ``poll.choice_set.all`` is
+interpreted as the Python code ``poll.choice_set.all()``, which returns an
+iterable of Choice objects and is suitable for use in the ``{% for %}`` tag.
+
+See the `template guide`_ for full details on how templates work.
+
+.. _template guide: ../templates/
+
+Simplifying the URLconfs
+========================
+
+Take some time to play around with the views and template system. As you edit
+the URLconf, you may notice there's a fair bit of redundancy in it::
+
+ urlpatterns = patterns('',
+ (r'^polls/$', 'mysite.polls.views.index'),
+ (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
+ (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
+ (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
+ )
+
+Namely, ``mysite.polls.views`` is in every callback.
+
+Because this is a common case, the URLconf framework provides a shortcut for
+common prefixes. You can factor out the common prefixes and add them as the
+first argument to ``patterns()``, like so::
+
+ urlpatterns = patterns('mysite.polls.views',
+ (r'^polls/$', 'index'),
+ (r'^polls/(?P<poll_id>\d+)/$', 'detail'),
+ (r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
+ (r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
+ )
+
+This is functionally identical to the previous formatting. It's just a bit
+tidier.
+
+Decoupling the URLconfs
+=======================
+
+While we're at it, we should take the time to decouple our poll-app URLs from
+our Django project configuration. Django apps are meant to be pluggable -- that
+is, each particular app should be transferrable to another Django installation
+with minimal fuss.
+
+Our poll app is pretty decoupled at this point, thanks to the strict directory
+structure that ``python manage.py startapp`` created, but one part of it is
+coupled to the Django settings: The URLconf.
+
+We've been editing the URLs in ``mysite/urls.py``, but the URL design of an
+app is specific to the app, not to the Django installation -- so let's move the
+URLs within the app directory.
+
+Copy the file ``mysite/urls.py`` to ``mysite/polls/urls.py``. Then,
+change ``mysite/urls.py`` to remove the poll-specific URLs and insert an
+``include()``::
+
+ (r'^polls/', include('mysite.polls.urls')),
+
+``include()``, simply, references another URLconf. Note that the regular
+expression doesn't have a ``$`` (end-of-string match character) but has the
+trailing slash. Whenever Django encounters ``include()``, it chops off whatever
+part of the URL matched up to that point and sends the remaining string to the
+included URLconf for further processing.
+
+Here's what happens if a user goes to "/polls/34/" in this system:
+
+* Django will find the match at ``'^polls/'``
+* It will strip off the matching text (``"polls/"``) and send the remaining
+ text -- ``"34/"`` -- to the 'mysite.polls.urls' urlconf for
+ further processing.
+
+Now that we've decoupled that, we need to decouple the
+'mysite.polls.urls' urlconf by removing the leading "polls/" from each
+line::
+
+ urlpatterns = patterns('mysite.polls.views',
+ (r'^$', 'index'),
+ (r'^(?P<poll_id>\d+)/$', 'detail'),
+ (r'^(?P<poll_id>\d+)/results/$', 'results'),
+ (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
+ )
+
+The idea behind ``include()`` and URLconf decoupling is to make it easy to
+plug-and-play URLs. Now that polls are in their own URLconf, they can be placed
+under "/polls/", or under "/fun_polls/", or under "/content/polls/", or any
+other URL root, and the app will still work.
+
+All the poll app cares about is its relative URLs, not its absolute URLs.
+
+When you're comfortable with writing views, read `part 4 of this tutorial`_ to
+learn about simple form processing and generic views.
+
+.. _part 4 of this tutorial: ../tutorial4/
diff --git a/google_appengine/lib/django/docs/tutorial04.txt b/google_appengine/lib/django/docs/tutorial04.txt
new file mode 100644
index 0000000..b1c8c7d
--- /dev/null
+++ b/google_appengine/lib/django/docs/tutorial04.txt
@@ -0,0 +1,259 @@
+=====================================
+Writing your first Django app, part 4
+=====================================
+
+This tutorial begins where `Tutorial 3`_ left off. We're continuing the Web-poll
+application and will focus on simple form processing and cutting down our code.
+
+Write a simple form
+===================
+
+Let's update our poll detail template from the last tutorial, so that the
+template contains an HTML ``<form>`` element::
+
+ <h1>{{ poll.question }}</h1>
+
+ {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
+
+ <form action="/polls/{{ poll.id }}/vote/" method="post">
+ {% for choice in poll.choice_set.all %}
+ <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
+ <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
+ {% endfor %}
+ <input type="submit" value="Vote" />
+ </form>
+
+A quick rundown:
+
+ * The above template displays a radio button for each poll choice. The
+ ``value`` of each radio button is the associated poll choice's ID. The
+ ``name`` of each radio button is ``"choice"``. That means, when somebody
+ selects one of the radio buttons and submits the form, it'll send the
+ POST data ``choice=3``. This is HTML Forms 101.
+
+ * We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, and we
+ set ``method="post"``. Using ``method="post"`` (as opposed to
+ ``method="get"``) is very important, because the act of submitting this
+ form will alter data server-side. Whenever you create a form that alters
+ data server-side, use ``method="post"``. This tip isn't specific to
+ Django; it's just good Web development practice.
+
+Now, let's create a Django view that handles the submitted data and does
+something with it. Remember, in `Tutorial 3`_, we created a URLconf for the
+polls application that includes this line::
+
+ (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
+
+So let's create a ``vote()`` function in ``mysite/polls/views.py``::
+
+ from django.shortcuts import get_object_or_404, render_to_response
+ from django.http import HttpResponseRedirect
+ from mysite.polls.models import Choice, Poll
+ # ...
+ def vote(request, poll_id):
+ p = get_object_or_404(Poll, pk=poll_id)
+ try:
+ selected_choice = p.choice_set.get(pk=request.POST['choice'])
+ except (KeyError, Choice.DoesNotExist):
+ # Redisplay the poll voting form.
+ return render_to_response('polls/detail.html', {
+ 'poll': p,
+ 'error_message': "You didn't select a choice.",
+ })
+ else:
+ selected_choice.votes += 1
+ selected_choice.save()
+ # Always return an HttpResponseRedirect after successfully dealing
+ # with POST data. This prevents data from being posted twice if a
+ # user hits the Back button.
+ return HttpResponseRedirect('/polls/%s/results/' % p.id)
+
+This code includes a few things we haven't covered yet in this tutorial:
+
+ * ``request.POST`` is a dictionary-like object that lets you access
+ submitted data by key name. In this case, ``request.POST['choice']``
+ returns the ID of the selected choice, as a string. ``request.POST``
+ values are always strings.
+
+ Note that Django also provides ``request.GET`` for accessing GET data
+ in the same way -- but we're explicitly using ``request.POST`` in our
+ code, to ensure that data is only altered via a POST call.
+
+ * ``request.POST['choice']`` will raise ``KeyError`` if ``choice`` wasn't
+ provided in POST data. The above code checks for ``KeyError`` and
+ redisplays the poll form with an error message if ``choice`` isn't given.
+
+ * After incrementing the choice count, the code returns an
+ ``HttpResponseRedirect`` rather than a normal ``HttpResponse``.
+ ``HttpResponseRedirect`` takes a single argument: the URL to which the
+ user will be redirected. You should leave off the "http://" and domain
+ name if you can. That helps your app become portable across domains.
+
+ As the Python comment above points out, you should always return an
+ ``HttpResponseRedirect`` after successfully dealing with POST data. This
+ tip isn't specific to Django; it's just good Web development practice.
+
+As mentioned in Tutorial 3, ``request`` is a ``HTTPRequest`` object. For more
+on ``HTTPRequest`` objects, see the `request and response documentation`_.
+
+After somebody votes in a poll, the ``vote()`` view redirects to the results
+page for the poll. Let's write that view::
+
+ def results(request, poll_id):
+ p = get_object_or_404(Poll, pk=poll_id)
+ return render_to_response('polls/results.html', {'poll': p})
+
+This is almost exactly the same as the ``detail()`` view from `Tutorial 3`_.
+The only difference is the template name. We'll fix this redundancy later.
+
+Now, create a ``results.html`` template::
+
+ <h1>{{ poll.question }}</h1>
+
+ <ul>
+ {% for choice in poll.choice_set.all %}
+ <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
+ {% endfor %}
+ </ul>
+
+Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a
+results page that gets updated each time you vote. If you submit the form
+without having chosen a choice, you should see the error message.
+
+.. _request and response documentation: ../request_response/
+
+Use generic views: Less code is better
+======================================
+
+The ``detail()`` (from `Tutorial 3`_) and ``results()`` views are stupidly
+simple -- and, as mentioned above, redundant. The ``index()`` view (also from
+Tutorial 3), which displays a list of polls, is similar.
+
+These views represent a common case of basic Web development: getting data from
+the database according to a parameter passed in the URL, loading a template and
+returning the rendered template. Because this is so common, Django provides a
+shortcut, called the "generic views" system.
+
+Generic views abstract common patterns to the point where you don't even need
+to write Python code to write an app.
+
+Let's convert our poll app to use the generic views system, so we can delete a
+bunch of our own code. We'll just have to take a few steps to make the
+conversion.
+
+.. admonition:: Why the code-shuffle?
+
+ Generally, when writing a Django app, you'll evaluate whether generic views
+ are a good fit for your problem, and you'll use them from the beginning,
+ rather than refactoring your code halfway through. But this tutorial
+ intentionally has focused on writing the views "the hard way" until now, to
+ focus on core concepts.
+
+ You should know basic math before you start using a calculator.
+
+First, open the polls/urls.py URLconf. It looks like this, according to the
+tutorial so far::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('mysite.polls.views',
+ (r'^$', 'index'),
+ (r'^(?P<poll_id>\d+)/$', 'detail'),
+ (r'^(?P<poll_id>\d+)/results/$', 'results'),
+ (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
+ )
+
+Change it like so::
+
+ from django.conf.urls.defaults import *
+ from mysite.polls.models import Poll
+
+ info_dict = {
+ 'queryset': Poll.objects.all(),
+ }
+
+ urlpatterns = patterns('',
+ (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
+ (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
+ (r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html')),
+ (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
+ )
+
+We're using two generic views here: ``object_list`` and ``object_detail``.
+Respectively, those two views abstract the concepts of "display a list of
+objects" and "display a detail page for a particular type of object."
+
+ * Each generic view needs to know what data it will be acting upon. This
+ data is provided in a dictionary. The ``queryset`` key in this dictionary
+ points to the list of objects to be manipulated by the generic view.
+
+ * The ``object_detail`` generic view expects the ID value captured
+ from the URL to be called ``"object_id"``, so we've changed ``poll_id`` to
+ ``object_id`` for the generic views.
+
+By default, the ``object_detail`` generic view uses a template called
+``<app name>/<model name>_detail.html``. In our case, it'll use the template
+``"polls/poll_detail.html"``. Thus, rename your ``polls/detail.html`` template to
+``polls/poll_detail.html``, and change the ``render_to_response()`` line in
+``vote()``.
+
+Similarly, the ``object_list`` generic view uses a template called
+``<app name>/<model name>_list.html``. Thus, rename ``polls/index.html`` to
+``polls/poll_list.html``.
+
+Because we have more than one entry in the URLconf that uses ``object_detail``
+for the polls app, we manually specify a template name for the results view:
+``template_name='polls/results.html'``. Otherwise, both views would use the same
+template. Note that we use ``dict()`` to return an altered dictionary in place.
+
+.. note:: ``all()`` is lazy
+
+ It might look a little frightening to see ``Poll.objects.all()`` being used
+ in a detail view which only needs one ``Poll`` object, but don't worry;
+ ``Poll.objects.all()`` is actually a special object called a ``QuerySet``,
+ which is "lazy" and doesn't hit your database until it absolutely has to. By
+ the time the database query happens, the ``object_detail`` generic view will
+ have narrowed its scope down to a single object, so the eventual query will
+ only select one row from the database.
+
+ If you'd like to know more about how that works, The Django database API
+ documentation `explains the lazy nature of QuerySet objects`_.
+
+.. _explains the lazy nature of QuerySet objects: ../db_api/#querysets-are-lazy
+
+In previous parts of the tutorial, the templates have been provided with a context
+that contains the ``poll`` and ``latest_poll_list`` context variables. However,
+the generic views provide the variables ``object`` and ``object_list`` as context.
+Therefore, you need to change your templates to match the new context variables.
+Go through your templates, and modify any reference to ``latest_poll_list`` to
+``object_list``, and change any reference to ``poll`` to ``object``.
+
+You can now delete the ``index()``, ``detail()`` and ``results()`` views
+from ``polls/views.py``. We don't need them anymore -- they have been replaced
+by generic views.
+
+The ``vote()`` view is still required. However, it must be modified to match
+the new templates and context variables. Change the template call from
+``polls/detail.html`` to ``polls/poll_detail.html``, and pass ``object`` in the
+context instead of ``poll``.
+
+Run the server, and use your new polling app based on generic views.
+
+For full details on generic views, see the `generic views documentation`_.
+
+.. _generic views documentation: ../generic_views/
+
+Coming soon
+===========
+
+The tutorial ends here for the time being. But check back soon for the next
+installments:
+
+ * Advanced form processing
+ * Using the RSS framework
+ * Using the cache framework
+ * Using the comments framework
+ * Advanced admin features: Permissions
+ * Advanced admin features: Custom JavaScript
+
+.. _Tutorial 3: ../tutorial3/
diff --git a/google_appengine/lib/django/docs/url_dispatch.txt b/google_appengine/lib/django/docs/url_dispatch.txt
new file mode 100644
index 0000000..85c87de
--- /dev/null
+++ b/google_appengine/lib/django/docs/url_dispatch.txt
@@ -0,0 +1,481 @@
+==============
+URL dispatcher
+==============
+
+A clean, elegant URL scheme is an important detail in a high-quality Web
+application. Django lets you design URLs however you want, with no framework
+limitations.
+
+There's no ``.php`` or ``.cgi`` required, and certainly none of that
+``0,2097,1-1-1928,00`` nonsense.
+
+See `Cool URIs don't change`_, by World Wide Web creator Tim Berners-Lee, for
+excellent arguments on why URLs should be clean and usable.
+
+.. _Cool URIs don't change: http://www.w3.org/Provider/Style/URI
+
+Overview
+========
+
+To design URLs for an app, you create a Python module informally called a
+**URLconf** (URL configuration). This module is pure Python code and
+is a simple mapping between URL patterns (as simple regular expressions) to
+Python callback functions (your views).
+
+This mapping can be as short or as long as needed. It can reference other
+mappings. And, because it's pure Python code, it can be constructed
+dynamically.
+
+How Django processes a request
+==============================
+
+When a user requests a page from your Django-powered site, this is the
+algorithm the system follows to determine which Python code to execute:
+
+ 1. Django looks at the ``ROOT_URLCONF`` setting in your `settings file`_.
+ This should be a string representing the full Python import path to your
+ URLconf. For example: ``"mydjangoapps.urls"``.
+ 2. Django loads that Python module and looks for the variable
+ ``urlpatterns``. This should be a Python list, in the format returned by
+ the function ``django.conf.urls.defaults.patterns()``.
+ 3. Django runs through each URL pattern, in order, and stops at the first
+ one that matches the requested URL.
+ 4. Once one of the regexes matches, Django imports and calls the given
+ view, which is a simple Python function. The view gets passed a
+ `request object`_ as its first argument and any values captured in the
+ regex as remaining arguments.
+
+.. _settings file: ../settings/
+.. _request object: ../request_response/#httprequest-objects
+
+Example
+=======
+
+Here's a sample URLconf::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^articles/2003/$', 'news.views.special_case_2003'),
+ (r'^articles/(\d{4})/$', 'news.views.year_archive'),
+ (r'^articles/(\d{4})/(\d{2})/$', 'news.views.month_archive'),
+ (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'news.views.article_detail'),
+ )
+
+Notes:
+
+ * ``from django.conf.urls.defaults import *`` makes the ``patterns()``
+ function available.
+
+ * To capture a value from the URL, just put parenthesis around it.
+
+ * There's no need to add a leading slash, because every URL has that. For
+ example, it's ``^articles``, not ``^/articles``.
+
+ * The ``'r'`` in front of each regular expression string is optional but
+ recommended. It tells Python that a string is "raw" -- that nothing in
+ the string should be escaped. See `Dive Into Python's explanation`_.
+
+Example requests:
+
+ * A request to ``/articles/2005/03/`` would match the third entry in the
+ list. Django would call the function
+ ``news.views.month_archive(request, '2005', '03')``.
+
+ * ``/articles/2005/3/`` would not match any URL patterns, because the
+ third entry in the list requires two digits for the month.
+
+ * ``/articles/2003/`` would match the first pattern in the list, not the
+ second one, because the patterns are tested in order, and the first one
+ is the first test to pass. Feel free to exploit the ordering to insert
+ special cases like this.
+
+ * ``/articles/2003`` would not match any of these patterns, because each
+ pattern requires that the URL end with a slash.
+
+ * ``/articles/2003/03/3/`` would match the final pattern. Django would call
+ the function ``news.views.article_detail(request, '2003', '03', '3')``.
+
+.. _Dive Into Python's explanation: http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
+
+Named groups
+============
+
+The above example used simple, *non-named* regular-expression groups (via
+parenthesis) to capture bits of the URL and pass them as *positional* arguments
+to a view. In more advanced usage, it's possible to use *named*
+regular-expression groups to capture URL bits and pass them as *keyword*
+arguments to a view.
+
+In Python regular expressions, the syntax for named regular-expression groups
+is ``(?P<name>pattern)``, where ``name`` is the name of the group and
+``pattern`` is some pattern to match.
+
+Here's the above example URLconf, rewritten to use named groups::
+
+ urlpatterns = patterns('',
+ (r'^articles/2003/$', 'news.views.special_case_2003'),
+ (r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
+ (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'news.views.month_archive'),
+ (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'news.views.article_detail'),
+ )
+
+This accomplishes exactly the same thing as the previous example, with one
+subtle difference: The captured values are passed to view functions as keyword
+arguments rather than positional arguments. For example:
+
+ * A request to ``/articles/2005/03/`` would call the function
+ ``news.views.month_archive(request, year='2005', month='03')``, instead
+ of ``news.views.month_archive(request, '2005', '03')``.
+
+ * A request to ``/articles/2003/03/3/`` would call the function
+ ``news.views.article_detail(request, year='2003', month='03', day='3')``.
+
+In practice, this means your URLconfs are slightly more explicit and less prone
+to argument-order bugs -- and you can reorder the arguments in your views'
+function definitions. Of course, these benefits come at the cost of brevity;
+some developers find the named-group syntax ugly and too verbose.
+
+The matching/grouping algorithm
+-------------------------------
+
+Here's the algorithm the URLconf parser follows, with respect to named groups
+vs. non-named groups in a regular expression:
+
+If there are any named arguments, it will use those, ignoring non-named arguments.
+Otherwise, it will pass all non-named arguments as positional arguments.
+
+In both cases, it will pass any extra keyword arguments as keyword arguments.
+See "Passing extra options to view functions" below.
+
+What the URLconf searches against
+=================================
+
+The URLconf searches against the requested URL, as a normal Python string. This
+does not include GET or POST parameters, or the domain name.
+
+For example, in a request to ``http://www.example.com/myapp/``, the URLconf
+will look for ``/myapp/``.
+
+In a request to ``http://www.example.com/myapp/?page=3``, the URLconf will look
+for ``/myapp/``.
+
+The URLconf doesn't look at the request method. In other words, all request
+methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same
+function for the same URL.
+
+Syntax of the urlpatterns variable
+==================================
+
+``urlpatterns`` should be a Python list, in the format returned by the function
+``django.conf.urls.defaults.patterns()``. Always use ``patterns()`` to create
+the ``urlpatterns`` variable.
+
+Convention is to use ``from django.conf.urls.defaults import *`` at the top of
+your URLconf. This gives your module access to these objects:
+
+patterns
+--------
+
+A function that takes a prefix, and an arbitrary number of URL patterns, and
+returns a list of URL patterns in the format Django needs.
+
+The first argument to ``patterns()`` is a string ``prefix``. See
+"The view prefix" below.
+
+The remaining arguments should be tuples in this format::
+
+ (regular expression, Python callback function [, optional dictionary])
+
+...where ``optional dictionary`` is optional. (See
+_`Passing extra options to view functions` below.)
+
+handler404
+----------
+
+A string representing the full Python import path to the view that should be
+called if none of the URL patterns match.
+
+By default, this is ``'django.views.defaults.page_not_found'``. That default
+value should suffice.
+
+handler500
+----------
+
+A string representing the full Python import path to the view that should be
+called in case of server errors. Server errors happen when you have runtime
+errors in view code.
+
+By default, this is ``'django.views.defaults.server_error'``. That default
+value should suffice.
+
+include
+-------
+
+A function that takes a full Python import path to another URLconf that should
+be "included" in this place. See _`Including other URLconfs` below.
+
+Notes on capturing text in URLs
+===============================
+
+Each captured argument is sent to the view as a plain Python string, regardless
+of what sort of match the regular expression makes. For example, in this
+URLconf line::
+
+ (r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
+
+...the ``year`` argument to ``news.views.year_archive()`` will be a string, not
+an integer, even though the ``\d{4}`` will only match integer strings.
+
+A convenient trick is to specify default parameters for your views' arguments.
+Here's an example URLconf and view::
+
+ # URLconf
+ urlpatterns = patterns('',
+ (r'^blog/$', 'blog.views.page'),
+ (r'^blog/page(?P<num>\d+)/$', 'blog.views.page'),
+ )
+
+ # View (in blog/views.py)
+ def page(request, num="1"):
+ # Output the appropriate page of blog entries, according to num.
+
+In the above example, both URL patterns point to the same view --
+``blog.views.page`` -- but the first pattern doesn't capture anything from the
+URL. If the first pattern matches, the ``page()`` function will use its
+default argument for ``num``, ``"1"``. If the second pattern matches,
+``page()`` will use whatever ``num`` value was captured by the regex.
+
+Performance
+===========
+
+Each regular expression in a ``urlpatterns`` is compiled the first time it's
+accessed. This makes the system blazingly fast.
+
+The view prefix
+===============
+
+You can specify a common prefix in your ``patterns()`` call, to cut down on
+code duplication.
+
+Here's the example URLconf from the `Django overview`_::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^articles/(\d{4})/$', 'mysite.news.views.year_archive'),
+ (r'^articles/(\d{4})/(\d{2})/$', 'mysite.news.views.month_archive'),
+ (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'mysite.news.views.article_detail'),
+ )
+
+In this example, each view has a common prefix -- ``'mysite.news.views'``.
+Instead of typing that out for each entry in ``urlpatterns``, you can use the
+first argument to the ``patterns()`` function to specify a prefix to apply to
+each view function.
+
+With this in mind, the above example can be written more concisely as::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('mysite.news.views',
+ (r'^articles/(\d{4})/$', 'year_archive'),
+ (r'^articles/(\d{4})/(\d{2})/$', 'month_archive'),
+ (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'article_detail'),
+ )
+
+Note that you don't put a trailing dot (``"."``) in the prefix. Django puts
+that in automatically.
+
+.. _Django overview: ../overview/
+
+Multiple view prefixes
+----------------------
+
+In practice, you'll probably end up mixing and matching views to the point
+where the views in your ``urlpatterns`` won't have a common prefix. However,
+you can still take advantage of the view prefix shortcut to remove duplication.
+Just add multiple ``patterns()`` objects together, like this:
+
+Old::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^/?$', 'django.views.generic.date_based.archive_index'),
+ (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'django.views.generic.date_based.archive_month'),
+ (r'^tag/(?P<tag>\w+)/$', 'weblog.views.tag'),
+ )
+
+New::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('django.views.generic.date_based',
+ (r'^/?$', 'archive_index'),
+ (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$','archive_month'),
+ )
+
+ urlpatterns += patterns('weblog.views',
+ (r'^tag/(?P<tag>\w+)/$', 'tag'),
+ )
+
+Including other URLconfs
+========================
+
+At any point, your ``urlpatterns`` can "include" other URLconf modules. This
+essentially "roots" a set of URLs below other ones.
+
+For example, here's the URLconf for the `Django website`_ itself. It includes a
+number of other URLconfs::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^weblog/', include('django_website.apps.blog.urls.blog')),
+ (r'^documentation/', include('django_website.apps.docs.urls.docs')),
+ (r'^comments/', include('django.contrib.comments.urls.comments')),
+ )
+
+Note that the regular expressions in this example don't have a ``$``
+(end-of-string match character) but do include a trailing slash. Whenever
+Django encounters ``include()``, it chops off whatever part of the URL matched
+up to that point and sends the remaining string to the included URLconf for
+further processing.
+
+.. _`Django website`: http://www.djangoproject.com/
+
+Captured parameters
+-------------------
+
+An included URLconf receives any captured parameters from parent URLconfs, so
+the following example is valid::
+
+ # In settings/urls/main.py
+ urlpatterns = patterns('',
+ (r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
+ )
+
+ # In foo/urls/blog.py
+ urlpatterns = patterns('foo.views',
+ (r'^$', 'blog.index'),
+ (r'^archive/$', 'blog.archive'),
+ )
+
+In the above example, the captured ``"username"`` variable is passed to the
+included URLconf, as expected.
+
+Passing extra options to view functions
+=======================================
+
+URLconfs have a hook that lets you pass extra arguments to your view functions,
+as a Python dictionary.
+
+Any URLconf tuple can have an optional third element, which should be a
+dictionary of extra keyword arguments to pass to the view function.
+
+For example::
+
+ urlpatterns = patterns('blog.views',
+ (r'^/blog/(?P<year>\d{4})/$', 'year_archive', {'foo': 'bar'}),
+ )
+
+In this example, for a request to ``/blog/2005/``, Django will call the
+``blog.views.year_archive()`` view, passing it these keyword arguments::
+
+ year='2005', foo='bar'
+
+This technique is used in `generic views`_ and in the `syndication framework`_
+to pass metadata and options to views.
+
+.. _generic views: ../generic_views/
+.. _syndication framework: ../syndication/
+
+.. admonition:: Dealing with conflicts
+
+ It's possible to have a URL pattern which captures named keyword arguments,
+ and also passes arguments with the same names in its dictionary of extra
+ arguments. When this happens, the arguments in the dictionary will be used
+ instead of the arguments captured in the URL.
+
+Passing extra options to ``include()``
+--------------------------------------
+
+Similarly, you can pass extra options to ``include()``. When you pass extra
+options to ``include()``, *each* line in the included URLconf will be passed
+the extra options.
+
+For example, these two URLconf sets are functionally identical:
+
+Set one::
+
+ # main.py
+ urlpatterns = patterns('',
+ (r'^blog/', include('inner'), {'blogid': 3}),
+ )
+
+ # inner.py
+ urlpatterns = patterns('',
+ (r'^archive/$', 'mysite.views.archive'),
+ (r'^about/$', 'mysite.views.about'),
+ )
+
+Set two::
+
+ # main.py
+ urlpatterns = patterns('',
+ (r'^blog/', include('inner')),
+ )
+
+ # inner.py
+ urlpatterns = patterns('',
+ (r'^archive/$', 'mysite.views.archive', {'blogid': 3}),
+ (r'^about/$', 'mysite.views.about', {'blogid': 3}),
+ )
+
+Note that extra options will *always* be passed to *every* line in the included
+URLconf, regardless of whether the line's view actually accepts those options
+as valid. For this reason, this technique is only useful if you're certain that
+every view in the the included URLconf accepts the extra options you're passing.
+
+Passing callable objects instead of strings
+===========================================
+
+Some developers find it more natural to pass the actual Python function object
+rather than a string containing the path to its module. This alternative is
+supported -- you can pass any callable object as the view.
+
+For example, given this URLconf in "string" notation::
+
+ urlpatterns = patterns('',
+ (r'^archive/$', 'mysite.views.archive'),
+ (r'^about/$', 'mysite.views.about'),
+ (r'^contact/$', 'mysite.views.contact'),
+ )
+
+You can accomplish the same thing by passing objects rather than strings. Just
+be sure to import the objects::
+
+ from mysite.views import archive, about, contact
+
+ urlpatterns = patterns('',
+ (r'^archive/$', archive),
+ (r'^about/$', about),
+ (r'^contact/$', contact),
+ )
+
+The following example is functionally identical. It's just a bit more compact
+because it imports the module that contains the views, rather than importing
+each view individually::
+
+ from mysite import views
+
+ urlpatterns = patterns('',
+ (r'^archive/$', views.archive),
+ (r'^about/$', views.about),
+ (r'^contact/$', views.contact),
+ )
+
+The style you use is up to you.
+
+Note that if you use this technique -- passing objects rather than strings --
+the view prefix (as explained in "The view prefix" above) will have no effect.
diff --git a/google_appengine/lib/django/scripts/rpm-install.sh b/google_appengine/lib/django/scripts/rpm-install.sh
new file mode 100644
index 0000000..07a087c
--- /dev/null
+++ b/google_appengine/lib/django/scripts/rpm-install.sh
@@ -0,0 +1,19 @@
+#! /bin/sh
+#
+# this file is *inserted* into the install section of the generated
+# spec file
+#
+
+# this is, what dist.py normally does
+python setup.py install --root=${RPM_BUILD_ROOT} --record="INSTALLED_FILES"
+
+for i in `cat INSTALLED_FILES`; do
+ if [ -f ${RPM_BUILD_ROOT}/$i ]; then
+ echo $i >>FILES
+ fi
+ if [ -d ${RPM_BUILD_ROOT}/$i ]; then
+ echo %dir $i >>DIRS
+ fi
+done
+
+cat DIRS FILES >INSTALLED_FILES
diff --git a/google_appengine/lib/django/setup.cfg b/google_appengine/lib/django/setup.cfg
new file mode 100644
index 0000000..ce9779a
--- /dev/null
+++ b/google_appengine/lib/django/setup.cfg
@@ -0,0 +1,4 @@
+[bdist_rpm]
+doc_files = docs/*.txt
+install-script = scripts/rpm-install.sh
+
diff --git a/google_appengine/lib/django/setup.py b/google_appengine/lib/django/setup.py
new file mode 100755
index 0000000..3ced472
--- /dev/null
+++ b/google_appengine/lib/django/setup.py
@@ -0,0 +1,46 @@
+from distutils.core import setup
+from distutils.command.install import INSTALL_SCHEMES
+import os
+import sys
+
+# Tell distutils to put the data_files in platform-specific installation
+# locations. See here for an explanation:
+# http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb
+for scheme in INSTALL_SCHEMES.values():
+ scheme['data'] = scheme['purelib']
+
+# Compile the list of packages available, because distutils doesn't have
+# an easy way to do this.
+packages, data_files = [], []
+root_dir = os.path.dirname(__file__)
+len_root_dir = len(root_dir)
+django_dir = os.path.join(root_dir, 'django')
+
+for dirpath, dirnames, filenames in os.walk(django_dir):
+ # Ignore dirnames that start with '.'
+ for i, dirname in enumerate(dirnames):
+ if dirname.startswith('.'): del dirnames[i]
+ if '__init__.py' in filenames:
+ package = dirpath[len_root_dir:].lstrip('/').replace('/', '.')
+ packages.append(package)
+ else:
+ data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]])
+
+# Small hack for working with bdist_wininst.
+# See http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html
+if len(sys.argv) > 1 and sys.argv[1] == 'bdist_wininst':
+ for file_info in data_files:
+ file_info[0] = '/PURELIB/%s' % file_info[0]
+
+setup(
+ name = "Django",
+ version = "0.96.4",
+ url = 'http://www.djangoproject.com/',
+ author = 'Django Software Foundation',
+ author_email = 'foundation@djangoproject.com',
+ download_url = 'http://media.djangoproject.com/releases/0.96/Django-0.96.4.tar.gz',
+ description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.',
+ packages = packages,
+ data_files = data_files,
+ scripts = ['django/bin/django-admin.py'],
+)
diff --git a/google_appengine/lib/webob/LICENSE b/google_appengine/lib/webob/LICENSE
new file mode 100644
index 0000000..2e4ecaa
--- /dev/null
+++ b/google_appengine/lib/webob/LICENSE
@@ -0,0 +1,20 @@
+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.
diff --git a/google_appengine/lib/webob/PKG-INFO b/google_appengine/lib/webob/PKG-INFO
new file mode 100644
index 0000000..25364af
--- /dev/null
+++ b/google_appengine/lib/webob/PKG-INFO
@@ -0,0 +1,23 @@
+Metadata-Version: 1.0
+Name: WebOb
+Version: 0.9
+Summary: WSGI request and response object
+Home-page: http://pythonpaste.org/webob/
+Author: Ian Bicking
+Author-email: ianb@colorstudy.com
+License: MIT
+Description: WebOb provides wrappers around the WSGI request environment, and an
+ object to help create WSGI responses.
+
+ The objects map much of the specified behavior of HTTP, including
+ header parsing and accessors for other standard parts of the
+ environment.
+
+Keywords: wsgi request web http
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Framework :: Paste
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
diff --git a/google_appengine/lib/webob/WebOb.egg-info/PKG-INFO b/google_appengine/lib/webob/WebOb.egg-info/PKG-INFO
new file mode 100644
index 0000000..25364af
--- /dev/null
+++ b/google_appengine/lib/webob/WebOb.egg-info/PKG-INFO
@@ -0,0 +1,23 @@
+Metadata-Version: 1.0
+Name: WebOb
+Version: 0.9
+Summary: WSGI request and response object
+Home-page: http://pythonpaste.org/webob/
+Author: Ian Bicking
+Author-email: ianb@colorstudy.com
+License: MIT
+Description: WebOb provides wrappers around the WSGI request environment, and an
+ object to help create WSGI responses.
+
+ The objects map much of the specified behavior of HTTP, including
+ header parsing and accessors for other standard parts of the
+ environment.
+
+Keywords: wsgi request web http
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Framework :: Paste
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
diff --git a/google_appengine/lib/webob/WebOb.egg-info/SOURCES.txt b/google_appengine/lib/webob/WebOb.egg-info/SOURCES.txt
new file mode 100644
index 0000000..76fb8e7
--- /dev/null
+++ b/google_appengine/lib/webob/WebOb.egg-info/SOURCES.txt
@@ -0,0 +1,41 @@
+setup.cfg
+setup.py
+test
+WebOb.egg-info/PKG-INFO
+WebOb.egg-info/SOURCES.txt
+WebOb.egg-info/dependency_links.txt
+WebOb.egg-info/top_level.txt
+WebOb.egg-info/zip-safe
+docs/comment-example.txt
+docs/differences.txt
+docs/file-example.txt
+docs/index.txt
+docs/license.txt
+docs/news.txt
+docs/reference.txt
+docs/test-file.txt
+docs/wiki-example.txt
+docs/comment-example-code/example.py
+docs/wiki-example-code/example.py
+tests/__init__.py
+tests/conftest.py
+tests/test_request.py
+tests/test_request.txt
+tests/test_response.py
+tests/test_response.txt
+webob/__init__.py
+webob/acceptparse.py
+webob/byterange.py
+webob/cachecontrol.py
+webob/datastruct.py
+webob/etag.py
+webob/exc.py
+webob/headerdict.py
+webob/multidict.py
+webob/statusreasons.py
+webob/updatedict.py
+webob/util/__init__.py
+webob/util/dictmixin.py
+webob/util/reversed.py
+webob/util/safegzip.py
+webob/util/stringtemplate.py \ No newline at end of file
diff --git a/google_appengine/lib/webob/WebOb.egg-info/dependency_links.txt b/google_appengine/lib/webob/WebOb.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/google_appengine/lib/webob/WebOb.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/google_appengine/lib/webob/WebOb.egg-info/top_level.txt b/google_appengine/lib/webob/WebOb.egg-info/top_level.txt
new file mode 100644
index 0000000..1c2028e
--- /dev/null
+++ b/google_appengine/lib/webob/WebOb.egg-info/top_level.txt
@@ -0,0 +1 @@
+webob
diff --git a/google_appengine/lib/webob/WebOb.egg-info/zip-safe b/google_appengine/lib/webob/WebOb.egg-info/zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/google_appengine/lib/webob/WebOb.egg-info/zip-safe
@@ -0,0 +1 @@
+
diff --git a/google_appengine/lib/webob/docs/comment-example-code/example.py b/google_appengine/lib/webob/docs/comment-example-code/example.py
new file mode 100755
index 0000000..781acd3
--- /dev/null
+++ b/google_appengine/lib/webob/docs/comment-example-code/example.py
@@ -0,0 +1,150 @@
+import os
+import urllib
+import time
+import re
+from cPickle import load, dump
+from webob import Request, Response, html_escape
+from webob import exc
+
+class Commenter(object):
+
+ def __init__(self, app, storage_dir):
+ self.app = app
+ self.storage_dir = storage_dir
+ if not os.path.exists(storage_dir):
+ os.makedirs(storage_dir)
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ if req.path_info_peek() == '.comments':
+ return self.process_comment(req)(environ, start_response)
+ # This is the base path of *this* middleware:
+ base_url = req.application_url
+ resp = req.get_response(self.app)
+ if resp.content_type != 'text/html' or resp.status_int != 200:
+ # Not an HTML response, we don't want to
+ # do anything to it
+ return resp(environ, start_response)
+ # Make sure the content isn't gzipped:
+ resp.decode_content()
+ comments = self.get_data(req.url)
+ body = resp.body
+ body = self.add_to_end(body, self.format_comments(comments))
+ body = self.add_to_end(body, self.submit_form(base_url, req))
+ resp.body = body
+ return resp(environ, start_response)
+
+ def get_data(self, url):
+ # Double-quoting makes the filename safe
+ filename = self.url_filename(url)
+ if not os.path.exists(filename):
+ return []
+ else:
+ f = open(filename, 'rb')
+ data = load(f)
+ f.close()
+ return data
+
+ def save_data(self, url, data):
+ filename = self.url_filename(url)
+ f = open(filename, 'wb')
+ dump(data, f)
+ f.close()
+
+ def url_filename(self, url):
+ return os.path.join(self.storage_dir, urllib.quote(url, ''))
+
+ _end_body_re = re.compile(r'</body.*?>', re.I|re.S)
+
+ def add_to_end(self, html, extra_html):
+ """
+ Adds extra_html to the end of the html page (before </body>)
+ """
+ match = self._end_body_re.search(html)
+ if not match:
+ return html + extra_html
+ else:
+ return html[:match.start()] + extra_html + html[match.start():]
+
+ def format_comments(self, comments):
+ if not comments:
+ return ''
+ text = []
+ text.append('<hr>')
+ text.append('<h2><a name="comment-area"></a>Comments (%s):</h2>' % len(comments))
+ for comment in comments:
+ text.append('<h3><a href="%s">%s</a> at %s:</h3>' % (
+ html_escape(comment['homepage']), html_escape(comment['name']),
+ time.strftime('%c', comment['time'])))
+ # Susceptible to XSS attacks!:
+ text.append(comment['comments'])
+ return ''.join(text)
+
+ def submit_form(self, base_path, req):
+ return '''<h2>Leave a comment:</h2>
+ <form action="%s/.comments" method="POST">
+ <input type="hidden" name="url" value="%s">
+ <table width="100%%">
+ <tr><td>Name:</td>
+ <td><input type="text" name="name" style="width: 100%%"></td></tr>
+ <tr><td>URL:</td>
+ <td><input type="text" name="homepage" style="width: 100%%"></td></tr>
+ </table>
+ Comments:<br>
+ <textarea name="comments" rows=10 style="width: 100%%"></textarea><br>
+ <input type="submit" value="Submit comment">
+ </form>
+ ''' % (base_path, html_escape(req.url))
+
+ def process_comment(self, req):
+ try:
+ url = req.params['url']
+ name = req.params['name']
+ homepage = req.params['homepage']
+ comments = req.params['comments']
+ except KeyError, e:
+ resp = exc.HTTPBadRequest('Missing parameter: %s' % e)
+ return resp
+ data = self.get_data(url)
+ data.append(dict(
+ name=name,
+ homepage=homepage,
+ comments=comments,
+ time=time.gmtime()))
+ self.save_data(url, data)
+ resp = exc.HTTPSeeOther(location=url+'#comment-area')
+ return resp
+
+if __name__ == '__main__':
+ import optparse
+ parser = optparse.OptionParser(
+ usage='%prog --port=PORT BASE_DIRECTORY'
+ )
+ parser.add_option(
+ '-p', '--port',
+ default='8080',
+ dest='port',
+ type='int',
+ help='Port to serve on (default 8080)')
+ parser.add_option(
+ '--comment-data',
+ default='./comments',
+ dest='comment_data',
+ help='Place to put comment data into (default ./comments/)')
+ options, args = parser.parse_args()
+ if not args:
+ parser.error('You must give a BASE_DIRECTORY')
+ base_dir = args[0]
+ from paste.urlparser import StaticURLParser
+ app = StaticURLParser(base_dir)
+ app = Commenter(app, options.comment_data)
+ from wsgiref.simple_server import make_server
+ httpd = make_server('localhost', options.port, app)
+ print 'Serving on http://localhost:%s' % options.port
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print '^C'
+
+
+
diff --git a/google_appengine/lib/webob/docs/comment-example.txt b/google_appengine/lib/webob/docs/comment-example.txt
new file mode 100644
index 0000000..2ea47ca
--- /dev/null
+++ b/google_appengine/lib/webob/docs/comment-example.txt
@@ -0,0 +1,414 @@
+Comment Example
+===============
+
+.. contents::
+
+Introduction
+------------
+
+This is an example of how to write WSGI middleware with WebOb. The
+specific example adds a simple comment form to HTML web pages; any
+page served through the middleware that is HTML gets a comment form
+added to it, and shows any existing comments.
+
+Code
+----
+
+The finished code for this is available in
+`docs/comment-example-code/example.py
+<http://svn.pythonpaste.org/Paste/WebOb/trunk/docs/comment-example-code/example.py>`_
+-- you can run that file as a script to try it out.
+
+Instantiating Middleware
+------------------------
+
+Middleware of any complexity at all is usually best created as a
+class with its configuration as arguments to that class.
+
+Every middleware needs an application (``app``) that it wraps. This
+middleware also needs a location to store the comments; we'll put them
+all in a single directory.
+
+.. code-block::
+
+ import os
+
+ class Commenter(object):
+ def __init__(self, app, storage_dir):
+ self.app = app
+ self.storage_dir = storage_dir
+ if not os.path.exists(storage_dir):
+ os.makedirs(storage_dir)
+
+When you use this middleware, you'll use it like:
+
+.. code-block::
+
+ app = ... make the application ...
+ app = Commenter(app, storage_dir='./comments')
+
+For our application we'll use a simple static file server that is
+included with `Paste <http://pythonpaste.org>`_ (use ``easy_install
+Paste`` to install this). The setup is all at the bottom of
+``example.py``, and looks like this:
+
+.. code-block::
+
+ if __name__ == '__main__':
+ import optparse
+ parser = optparse.OptionParser(
+ usage='%prog --port=PORT BASE_DIRECTORY'
+ )
+ parser.add_option(
+ '-p', '--port',
+ default='8080',
+ dest='port',
+ type='int',
+ help='Port to serve on (default 8080)')
+ parser.add_option(
+ '--comment-data',
+ default='./comments',
+ dest='comment_data',
+ help='Place to put comment data into (default ./comments/)')
+ options, args = parser.parse_args()
+ if not args:
+ parser.error('You must give a BASE_DIRECTORY')
+ base_dir = args[0]
+ from paste.urlparser import StaticURLParser
+ app = StaticURLParser(base_dir)
+ app = Commenter(app, options.comment_data)
+ from wsgiref.simple_server import make_server
+ httpd = make_server('localhost', options.port, app)
+ print 'Serving on http://localhost:%s' % options.port
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print '^C'
+
+I won't explain it here, but basically it takes some options, creates
+an application that serves static files
+(``StaticURLParser(base_dir)``), wraps it with ``Commenter(app,
+options.comment_data)`` then serves that.
+
+The Middleware
+--------------
+
+While we've created the class structure for the middleware, it doesn't
+actually do anything. Here's a kind of minimal version of the
+middleware (using WebOb):
+
+.. code-block::
+
+ from webob import Request
+
+ class Commenter(object):
+
+ def __init__(self, app, storage_dir):
+ self.app = app
+ self.storage_dir = storage_dir
+ if not os.path.exists(storage_dir):
+ os.makedirs(storage_dir)
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ resp = req.get_response(self.app)
+ return resp(environ, start_response)
+
+This doesn't modify the response it any way. You could write it like
+this without WebOb:
+
+.. code-block::
+
+ class Commenter(object):
+ ...
+ def __call__(self, environ, start_response):
+ return self.app(environ, start_response)
+
+But it won't be as convenient later. First, lets create a little bit
+of infrastructure for our middleware. We need to save and load
+per-url data (the comments themselves). We'll keep them in pickles,
+where each url has a pickle named after the url (but double-quoted, so
+``http://localhost:8080/index.html`` becomes
+``http%3A%2F%2Flocalhost%3A8080%2Findex.html``).
+
+.. code-block::
+
+ from cPickle import load, dump
+
+ class Commenter(object):
+ ...
+
+ def get_data(self, url):
+ filename = self.url_filename(url)
+ if not os.path.exists(filename):
+ return []
+ else:
+ f = open(filename, 'rb')
+ data = load(f)
+ f.close()
+ return data
+
+ def save_data(self, url, data):
+ filename = self.url_filename(url)
+ f = open(filename, 'wb')
+ dump(data, f)
+ f.close()
+
+ def url_filename(self, url):
+ # Double-quoting makes the filename safe
+ return os.path.join(self.storage_dir, urllib.quote(url, ''))
+
+You can get the full request URL with ``req.url``, so to get the
+comment data with these methods you do ``data =
+self.get_data(req.url)``.
+
+Now we'll update the ``__call__`` method to filter *some* responses,
+and get the comment data for those. We don't want to change responses
+that were error responses (anything but ``200``), nor do we want to
+filter responses that aren't HTML. So we get:
+
+.. code-block::
+
+ class Commenter(object):
+ ...
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ resp = req.get_response(self.app)
+ if resp.content_type != 'text/html' or resp.status_int != 200:
+ return resp(environ, start_response)
+ data = self.get_data(req.url)
+ ... do stuff with data, update resp ...
+ return resp(environ, start_response)
+
+So far we're punting on actually adding the comments to the page. We
+also haven't defined what ``data`` will hold. Let's say it's a list
+of dictionaries, where each dictionary looks like ``{'name': 'John
+Doe', 'homepage': 'http://blog.johndoe.com', 'comments': 'Great
+site!'}``.
+
+We'll also need a simple method to add stuff to the page. We'll use a
+regular expression to find the end of the page and put text in:
+
+.. code-block::
+
+ import re
+
+ class Commenter(object):
+ ...
+
+ _end_body_re = re.compile(r'</body.*?>', re.I|re.S)
+
+ def add_to_end(self, html, extra_html):
+ """
+ Adds extra_html to the end of the html page (before </body>)
+ """
+ match = self._end_body_re.search(html)
+ if not match:
+ return html + extra_html
+ else:
+ return html[:match.start()] + extra_html + html[match.start():]
+
+And then we'll use it like:
+
+.. code-block::
+
+ data = self.get_data(req.url)
+ body = resp.body
+ body = self.add_to_end(body, self.format_comments(data))
+ resp.body = body
+ return resp(environ, start_response)
+
+We get the body, update it, and put it back in the response. This
+also updates ``Content-Length``. Then we define:
+
+.. code-block::
+
+ from webob import html_escape
+
+ class Commenter(object):
+ ...
+
+ def format_comments(self, comments):
+ if not comments:
+ return ''
+ text = []
+ text.append('<hr>')
+ text.append('<h2><a name="comment-area"></a>Comments (%s):</h2>' % len(comments))
+ for comment in comments:
+ text.append('<h3><a href="%s">%s</a> at %s:</h3>' % (
+ html_escape(comment['homepage']), html_escape(comment['name']),
+ time.strftime('%c', comment['time'])))
+ # Susceptible to XSS attacks!:
+ text.append(comment['comments'])
+ return ''.join(text)
+
+We put in a header (with an anchor we'll use later), and a section for
+each comment. Note that ``html_escape`` is the same as ``cgi.escape``
+and just turns ``&`` into ``&amp;``, etc.
+
+Because we put in some text without quoting it is susceptible to a
+`Cross-Site Scripting
+<http://en.wikipedia.org/wiki/Cross-site_scripting>`_ attack. Fixing
+that is beyond the scope of this tutorial; you could quote it or clean
+it with something like `lxml.html.clean
+<http://codespeak.net/lxml/lxmlhtml.html#cleaning-up-html>`_.
+
+Accepting Comments
+------------------
+
+All of those pieces *display* comments, but still no one can actually
+make comments. To handle this we'll take a little piece of the URL
+space for our own, everything under ``/.comments``, so when someone
+POSTs there it will add a comment.
+
+When the request comes in there are two parts to the path:
+``SCRIPT_NAME`` and ``PATH_INFO``. Everything in ``SCRIPT_NAME`` has
+already been parsed, and everything in ``PATH_INFO`` has yet to be
+parsed. That means that the URL *without* ``PATH_INFO`` is the path
+to the middleware; we can intercept anything else below
+``SCRIPT_NAME`` but nothing above it. The name for the URL without
+``PATH_INFO`` is ``req.application_url``. We have to capture it early
+to make sure it doesn't change (since the WSGI application we are
+wrapping may update ``SCRIPT_NAME`` and ``PATH_INFO``).
+
+So here's what this all looks like:
+
+.. code-block::
+
+ class Commenter(object):
+ ...
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ if req.path_info_peek() == '.comments':
+ return self.process_comment(req)(environ, start_response)
+ # This is the base path of *this* middleware:
+ base_url = req.application_url
+ resp = req.get_response(self.app)
+ if resp.content_type != 'text/html' or resp.status_int != 200:
+ # Not an HTML response, we don't want to
+ # do anything to it
+ return resp(environ, start_response)
+ # Make sure the content isn't gzipped:
+ resp.decode_content()
+ comments = self.get_data(req.url)
+ body = resp.body
+ body = self.add_to_end(body, self.format_comments(comments))
+ body = self.add_to_end(body, self.submit_form(base_url, req))
+ resp.body = body
+ return resp(environ, start_response)
+
+``base_url`` is the path where the middleware is located (if you run
+the example server, it will be ``http://localhost:PORT/``). We use
+``req.path_info_peek()`` to look at the next segment of the URL --
+what comes after base_url. If it is ``.comments`` then we handle it
+internally and don't pass the request on.
+
+We also put in a little guard, ``resp.decode_content()`` in case the
+application returns a gzipped response.
+
+Then we get the data, add the comments, add the *form* to make new
+comments, and return the result.
+
+submit_form
+~~~~~~~~~~~
+
+Here's what the form looks like:
+
+.. code-block::
+
+ class Commenter(object):
+ ...
+
+ def submit_form(self, base_path, req):
+ return '''<h2>Leave a comment:</h2>
+ <form action="%s/.comments" method="POST">
+ <input type="hidden" name="url" value="%s">
+ <table width="100%%">
+ <tr><td>Name:</td>
+ <td><input type="text" name="name" style="width: 100%%"></td></tr>
+ <tr><td>URL:</td>
+ <td><input type="text" name="homepage" style="width: 100%%"></td></tr>
+ </table>
+ Comments:<br>
+ <textarea name="comments" rows=10 style="width: 100%%"></textarea><br>
+ <input type="submit" value="Submit comment">
+ </form>
+ ''' % (base_path, html_escape(req.url))
+
+Nothing too exciting. It submits a form with the keys ``url`` (the
+URL being commented on), ``name``, ``homepage``, and ``comments``.
+
+process_comment
+~~~~~~~~~~~~~~~
+
+If you look at the method call, what we do is call the method then
+treat the result as a WSGI application:
+
+.. code-block::
+
+ return self.process_comment(req)(environ, start_response)
+
+You could write this as:
+
+.. code-block::
+
+ response = self.process_comment(req)
+ return response(environ, start_response)
+
+A common pattern in WSGI middleware that *doesn't* use WebOb is to
+just do:
+
+.. code-block::
+
+ return self.process_comment(environ, start_response)
+
+But the WebOb style makes it easier to modify the response if you want
+to; modifying a traditional WSGI response/application output requires
+changing your logic flow considerably.
+
+Here's the actual processing code:
+
+.. code-block::
+
+ from webob import exc
+ from webob import Response
+
+ class Commenter(object):
+ ...
+
+ def process_comment(self, req):
+ try:
+ url = req.params['url']
+ name = req.params['name']
+ homepage = req.params['homepage']
+ comments = req.params['comments']
+ except KeyError, e:
+ resp = exc.HTTPBadRequest('Missing parameter: %s' % e)
+ return resp
+ data = self.get_data(url)
+ data.append(dict(
+ name=name,
+ homepage=homepage,
+ comments=comments,
+ time=time.gmtime()))
+ self.save_data(url, data)
+ resp = exc.HTTPSeeOther(location=url+'#comment-area')
+ return resp
+
+We either give a Bad Request response (if the form submission is
+somehow malformed), or a redirect back to the original page.
+
+The classes in ``webob.exc`` (like ``HTTPBadRequest`` and
+``HTTPSeeOther``) are Response subclasses that can be used to quickly
+create responses for these non-200 cases where the response body
+usually doesn't matter much.
+
+Conclusion
+----------
+
+This shows how to make response modifying middleware, which is
+probably the most difficult kind of middleware to write with WSGI --
+modifying the request is quite simple in comparison, as you simply
+update ``environ``.
diff --git a/google_appengine/lib/webob/docs/differences.txt b/google_appengine/lib/webob/docs/differences.txt
new file mode 100644
index 0000000..db7df6a
--- /dev/null
+++ b/google_appengine/lib/webob/docs/differences.txt
@@ -0,0 +1,590 @@
+Differences Between WebOb and Other Systems
++++++++++++++++++++++++++++++++++++++++++++
+
+This document points out some of the API differences between the
+Request and Response object, and the objects in other systems.
+
+.. contents::
+
+paste.wsgiwrappers and Pylons
+=============================
+
+The Pylons ``request`` and ``response`` object are based on
+``paste.wsgiwrappers.WSGIRequest`` and ``WSGIResponse``
+
+There is no concept of ``defaults`` in WebOb. In Paste/Pylons these
+serve as threadlocal settings that control certain policies on the
+request and response object. In WebOb you should make your own
+subclasses to control policy (though in many ways simply being
+explicit elsewhere removes the need for this policy).
+
+Request
+-------
+
+``body``:
+ This is a file-like object in WSGIRequest. In WebOb it is a
+ string (to match Response.body) and the file-like object is
+ available through ``req.body_file``
+
+``languages()``:
+ This is available through ``req.accept_language``, particularly
+ ``req.accept_language.best_matches(fallback_language)``
+
+``match_accept(mimetypes)``:
+ This is available through ``req.accept.first_match(mimetypes)``;
+ or if you trust the client's quality ratings, you can use
+ ``req.accept.best_match(mimetypes)``
+
+``errors``:
+ This controls how unicode decode errors are handled; it is now
+ named ``unicode_errors``
+
+There are also many extra methods and attributes on WebOb Request
+objects.
+
+Response
+--------
+
+default ``content_type``:
+ The base Response object has no default content_type or charset.
+ You can set ``default_content_type`` in a subclass.
+
+``determine_charset()``:
+ Is now available as ``res.charset``
+
+``has_header(header)``:
+ Should be done with ``header in res.headers``
+
+``get_content()`` and ``wsgi_response()``:
+ These are gone; you should use ``res.body`` or ``res(environ,
+ start_response)``
+
+``write(content)``:
+ Available in ``res.body_file.write(content)``.
+
+``flush()`` and ``tell()``:
+ Not available.
+
+There are also many extra methods and attributes on WebOb Response
+objects.
+
+Django
+======
+
+This is a quick summary from reading `the Django documentation
+<http://www.djangoproject.com/documentation/request_response/>`_.
+
+Request
+-------
+
+``encoding``:
+ Is ``req.charset``
+
+``REQUEST``:
+ Is ``req.params``
+
+``FILES``:
+ File uploads are ``cgi.FieldStorage`` objects directly in
+ ``res.POST``
+
+``META``:
+ Is ``req.environ``
+
+``user``:
+ No equivalent (too connected to application model for WebOb).
+ There is ``req.remote_user``, which is only ever a string.
+
+``session``:
+ No equivalent
+
+``raw_post_data``:
+ Available with ``req.body``
+
+``__getitem__(key)``:
+ You have to use ``req.params``
+
+``is_secure()``:
+ No equivalent; you could use ``req.scheme == 'https'``.
+
+QueryDict
+---------
+
+QueryDict is the way Django represents the multi-key dictionary-like
+objects that are request variables (query string and POST body
+variables). The equivalent in WebOb is MultiDict.
+
+Mutability:
+ WebOb dictionaries are sometimes mutable (req.GET is,
+ req.params is not)
+
+Ordering:
+ I believe Django does not order the keys fully; MultiDict is a
+ full ordering. Methods that iterate over the parameters iterate
+ over keys in their order in the original request.
+
+``keys()``, ``items()``, ``values()`` (plus ``iter*``):
+ These return all values in MultiDict, but only the last value for
+ a QueryDict. That is, given ``a=1&a=2`` with MultiDict
+ ``d.items()`` returns ``[('a', '1'), ('a', '2')]``, but QueryDict
+ returns ``[('a', '1')]``
+
+``getlist(key)``:
+ Available as ``d.getall(key)``
+
+``setlist(key)``:
+ No direct equivalent
+
+``appendlist(key, value)``:
+ Available as ``d.add(key, value)``
+
+``setlistdefault(key, default_list)``:
+ No direct equivalent
+
+``lists()``:
+ Is ``d.dict_of_lists()``
+
+The MultiDict object has a ``d.getone(key)`` method, that raises
+KeyError if there is not exactly one key. There is a method
+``d.mixed()`` which returns a version where values are lists *if*
+there are multiple values for a list. This is similar to how many
+cgi-based request forms are represented.
+
+Response
+--------
+
+Constructor:
+ Totally different. The WebOb Response object should probably be
+ subclassed for direct application use; in WebOb it does not
+ *prefer* HTML or anything normal web application conventions
+
+dictionary-like:
+ The Django response object is somewhat dictionary-like, setting
+ headers. The equivalent dictionary-like object is
+ ``res.headers``. In WebOb this is a MultiDict.
+
+``has_header(header)``:
+ Use ``header in res.headers``
+
+``write(content)``:
+ As ``res.body_file.write(content)``
+
+``flush()``, ``tell()``:
+ Not available
+
+``content``:
+ Use ``res.body`` for the ``str`` value, ``res.unicode_body`` for
+ the ``unicode`` value
+
+Response Subclasses
+-------------------
+
+These are generally like ``webob.exc`` objects.
+``HttpResponseNotModified`` is ``HTTPNotModified``; this naming
+translation generally works.
+
+CherryPy/TurboGears
+===================
+
+The `CherryPy request object
+<http://www.cherrypy.org/wiki/RequestObject>`_ is also used by
+TurboGears 1.x.
+
+Request
+-------
+
+``app``:
+ No equivalent
+
+``base``:
+ ``req.application_url``
+
+``close()``:
+ No equivalent
+
+``closed``:
+ No equivalent
+
+``config``:
+ No equivalent
+
+``cookie``:
+ A ``SimpleCookie`` object in CherryPy; a dictionary in WebOb
+ (``SimpleCookie`` can represent cookie parameters, but cookie
+ parameters are only sent with responses not requests)
+
+``dispatch``:
+ No equivalent (this is the object dispatcher in CherryPy).
+
+``error_page``, ``error_response``, ``handle_error``:
+ No equivalent
+
+``get_resource()``:
+ Similar to ``req.get_response(app)``
+
+``handler``:
+ No equivalent
+
+``headers``, ``header_list``:
+ The WSGI environment represents headers as a dictionary, available
+ through ``req.headers`` (no list form is available in the request).
+
+``hooks``:
+ No equivalent
+
+``local``:
+ No equivalent
+
+``methods_with_bodies``:
+ This represents methods where CherryPy will automatically try to
+ read the request body. WebOb lazily reads POST requests with the
+ correct content type, and no other bodies.
+
+``namespaces``:
+ No equivalent
+
+``protocol``:
+ As ``req.environ['SERVER_PROTOCOL']``
+
+``query_string``:
+ As ``req.query_string``
+
+``remote``:
+ ``remote.ip`` is like ``req.remote_addr``. ``remote.port`` is not
+ available. ``remote.name`` is in
+ ``req.environ.get('REMOTE_HOST')``
+
+``request_line``:
+ No equivalent
+
+``respond()``:
+ A method that is somewhat similar to ``req.get_response()``.
+
+``rfile``:
+ ``req.body_file``
+
+``run``:
+ No equivalent
+
+``server_protocol``:
+ As ``req.environ['SERVER_PROTOCOL']``
+
+``show_tracebacks``:
+ No equivalent
+
+``throw_errors``:
+ No equivalent
+
+``throws``:
+ No equivalent
+
+``toolmaps``:
+ No equivalent
+
+``wsgi_environ``:
+ As ``req.environ``
+
+Response
+--------
+
+From information `from the wiki
+<http://www.cherrypy.org/wiki/ResponseObject>`_.
+
+``body``:
+ This is an iterable in CherryPy, a string in WebOb;
+ ``res.app_iter`` gives an iterable in WebOb.
+
+``check_timeout``:
+ No equivalent
+
+``collapse_body()``:
+ This turns a stream/iterator body into a single string. Accessing
+ ``res.body`` will do this automatically.
+
+``cookie``:
+ Accessible through ``res.set_cookie(...)``, ``res.delete_cookie``,
+ ``res.unset_cookie()``
+
+``finalize()``:
+ No equivalent
+
+``header_list``:
+ In ``res.headerlist``
+
+``stream``:
+ This can make CherryPy stream the response body out directory.
+ There is direct no equivalent; you can use a dynamically generated
+ iterator to do something similar.
+
+``time``:
+ No equivalent
+
+``timed_out``:
+ No equivalent
+
+Yaro
+====
+
+`Yaro <http://lukearno.com/projects/yaro/>`_ is a small wrapper around
+the WSGI environment, much like WebOb in scope.
+
+The WebOb objects have many more methods and attributes. The Yaro
+Response object is a much smaller subset of WebOb's Response.
+
+Request
+-------
+
+``query``:
+ As ``req.GET``
+
+``form``:
+ As ``req.POST``
+
+``cookie``:
+ A ``SimpleCookie`` object in Yaro; a dictionary in WebOb
+ (``SimpleCookie`` can represent cookie parameters, but cookie
+ parameters are only sent with responses not requests)
+
+``uri``:
+ Returns a URI object, no equivalent (only string URIs available).
+
+``redirect``:
+ Not available (response-related). ``webob.exc.HTTPFound()`` can
+ be useful here.
+
+``forward(yaroapp)``, ``wsgi_forward(wsgiapp)``:
+ Available with ``req.get_response(app)`` and
+ ``req.call_application(app)``. In both cases it is a WSGI
+ application in WebOb, there is no special kind of communication;
+ ``req.call_application()`` just returns a ``webob.Response`` object.
+
+``res``:
+ The request object in WebOb *may* have a ``req.response``
+ attribute.
+
+Werkzeug
+========
+
+Probably not that many people know about this library, which is a
+offshoot of `Pocoo <http://pocoo.org>`_, and used to go by another
+name (Columbrid?) This library is based around WSGI, similar to Paste
+and Yaro.
+
+This is take from the `wrapper documentation
+<http://werkzeug.pocoo.org/documentation/wrappers>`_.
+
+Request
+-------
+
+path:
+ As ``req.path_info``
+args:
+ As ``req.GET``
+form:
+ As ``req.POST``
+values:
+ As ``req.params``
+files:
+ In ``req.POST`` (as FieldStorage objects)
+data:
+ In ``req.body_file``
+
+Response
+--------
+
+response:
+ In ``res.body`` (settable as ``res.body`` or ``res.app_iter``)
+status:
+ In ``res.status_int``
+mimetype:
+ In ``res.content_type``
+write(data):
+ With ``res.body_file.write(data)``
+
+Zope 3
+======
+
+From the Zope 3 interfaces for the `Request
+<http://apidoc.zope.org/++apidoc++/Interface/zope.publisher.interfaces.browser.IBrowserRequest/index.html>`_
+and `Response
+<http://apidoc.zope.org/++apidoc++/Interface/zope.publisher.interfaces.http.IHTTPResponse/index.html>`_.
+
+Request
+-------
+
+``locale``, ``setupLocale()``:
+ This is not fully calculated, but information is available in
+ ``req.accept_languages``.
+
+``principal``, ``setPrincipal(principal)``:
+ ``req.remote_user`` gives the username, but there is no standard
+ place for a user *object*.
+
+``publication``, ``setPublication()``,
+ These are associated with the object publishing system in Zope.
+ This kind of publishing system is outside the scope of WebOb.
+
+``traverse(object)``, ``getTraversalStack()``, ``setTraversalStack()``:
+ These all relate to traversal, which is part of the publishing
+ system.
+
+``processInputs()``, ``setPathSuffix(steps)``:
+ Also associated with traversal and preparing the request.
+
+``environment``:
+ In ``req.environ``
+
+``bodyStream``:
+ In ``req.body_file``
+
+``interaction``:
+ This is the security context for the request; all the possible
+ participants or principals in the request. There's no
+ equivalent.
+
+``annotations``:
+ Extra information associated with the request. This would
+ generally go in custom keys of ``req.environ``, or if you set
+ attributes those attributes are stored in
+ ``req.environ['webob.adhoc_attrs']``.
+
+``debug``:
+ There is no standard debug flag for WebOb.
+
+``__getitem__(key)``, ``get(key)``, etc:
+ These treat the request like a dictionary, which WebOb does not
+ do. They seem to take values from the environment, not
+ parameters. Also on the Zope request object is ``items()``,
+ ``__contains__(key)``, ``__iter__()``, ``keys()``, ``__len__()``,
+ ``values()``.
+
+``getPositionalArguments()``:
+ I'm not sure what the equivalent would be, as there are no
+ positional arguments during instantiation (it doesn't fit into
+ WSGI). Maybe ``wsgiorg.urlvars``?
+
+``retry()``, ``supportsRetry()``:
+ Creates a new request that can be used to retry a request.
+ Similar to ``req.copy()``.
+
+``close()``, ``hold(obj)``:
+ This closes resources associated with the request, including any
+ "held" objects. There's nothing similar.
+
+Response
+--------
+
+``authUser``:
+ Not sure what this is or does.
+
+``reset()``:
+ No direct equivalent; you'd have to do ``res.headers = [];
+ res.body = ''; res.status = 200``
+
+``setCookie(name, value, **kw)``:
+ Is ``res.set_cookie(...)``.
+
+``getCookie(name)``:
+ No equivalent. Hm.
+
+``expireCookie(name)``:
+ Is ``res.delete_cookie(name)``.
+
+``appendToCookie(name, value)``:
+ This appends the value to any existing cookie (separating values
+ with a colon). WebOb does not do this.
+
+``setStatus(status)``:
+ Availble by setting ``res.status`` (can be set to an integer or a
+ string of "code reason").
+
+``getHeader(name, default=None)``:
+ Is ``res.headers.get(name)``.
+
+``getStatus()``:
+ Is ``res.status_int`` (or ``res.status`` to include reason)
+
+``addHeader(name, value)``:
+ Is ``res.headers.add(name, value)`` (in Zope and WebOb, this does
+ not clobber any previous value).
+
+``getHeaders()``:
+ Is ``res.headerlist``.
+
+``setHeader(name, value)``:
+ Is ``res.headers[name] = value``.
+
+``getStatusString()``:
+ Is ``res.status``.
+
+``consumeBody()``:
+ This consumes any non-string body to turn the body into a single
+ string. Any access to ``res.body`` will do this (e.g., when you
+ have set the ``res.app_iter``).
+
+``internalError()``:
+ This is available with ``webob.exc.HTTP*()``.
+
+``handleException(exc_info)``:
+ This is provided with a tool like ``paste.exceptions``.
+
+``consumeBodyIter()``:
+ This returns the iterable for the body, even if the body was a
+ string. Anytime you access ``res.app_iter`` you will get an
+ iterable. ``res.body`` and ``res.app_iter`` can be interchanged
+ and accessed as many times as you want, unlike the Zope
+ equivalents.
+
+``setResult(result)``:
+ You can achieve the same thing through ``res.body = result``, or
+ ``res.app_iter = result``. ``res.body`` accepts None, a unicode
+ string (*if* you have set a charset) or a normal string.
+ ``res.app_iter`` only accepts None and an interable. You can't
+ update all of a response with one call.
+
+ Like in Zope, WebOb updates Content-Length. Unlike Zope, it does
+ not automatically calculate a charset.
+
+
+mod_python
+==========
+
+Some key attributes from the `mod_python
+<http://modpython.org/live/current/doc-html/pyapi-mprequest-mem.html>`_
+request object.
+
+Request
+-------
+
+``req.uri``:
+ In ``req.path``.
+
+``req.user``:
+ In ``req.remote_user``.
+
+``req.get_remote_host()``:
+ In ``req.environ['REMOTE_ADDR']`` or ``req.remote_addr``.
+
+``req.headers_in.get('referer')``:
+ In ``req.headers.get('referer')`` or ``req.referer`` (same pattern
+ for other request headers, presumably).
+
+Response
+--------
+
+``util.redirect`` or ``req.status = apache.HTTP_MOVED_TEMPORARILY``:
+
+.. code-block::
+
+ from webob.exc import HTTPMovedTemporarily()
+ exc = HTTPMovedTemporarily(location=url)
+ return exc(environ, start_response)
+
+``req.content_type = "application/x-csv"`` and
+``req.headers_out.add('Content-Disposition', 'attachment;filename=somefile.csv'):
+
+.. code-block::
+
+ res = req.ResponseClass()
+ res.content_type = 'application/x-csv'
+ res.headers.add('Content-Disposition', 'attachment;filename=somefile.csv')
+ return res(environ, start_response)
diff --git a/google_appengine/lib/webob/docs/file-example.txt b/google_appengine/lib/webob/docs/file-example.txt
new file mode 100644
index 0000000..4d7906f
--- /dev/null
+++ b/google_appengine/lib/webob/docs/file-example.txt
@@ -0,0 +1,217 @@
+WebOb File-Serving Example
+==========================
+
+This document shows how you can make a static-file-serving application
+using WebOb. We'll quickly build this up from minimal functionality
+to a high-quality file serving application.
+
+.. comment:
+
+ >>> import webob, os
+ >>> base_dir = os.path.dirname(os.path.dirname(webob.__file__))
+ >>> doc_dir = os.path.join(base_dir, 'docs')
+ >>> from dtopt import ELLIPSIS
+
+First we'll setup a really simple shim around our application, which
+we can use as we improve our application:
+
+.. code-block::
+
+ >>> from webob import Request, Response
+ >>> import os
+ >>> class FileApp(object):
+ ... def __init__(self, filename):
+ ... self.filename = filename
+ ... def __call__(self, environ, start_response):
+ ... res = make_response(self.filename)
+ ... return res(environ, start_response)
+ >>> import mimetypes
+ >>> def get_mimetype(filename):
+ ... type, encoding = mimetypes.guess_type(filename)
+ ... # We'll ignore encoding, even though we shouldn't really
+ ... return type or 'application/octet-stream'
+
+Now we can make different definitions of ``make_response``. The
+simplest version:
+
+.. code-block::
+
+ >>> def make_response(filename):
+ ... res = Response(content_type=get_mimetype(filename))
+ ... res.body = open(filename, 'rb').read()
+ ... return res
+
+Let's give it a go. We'll test it out with a file ``test-file.txt``
+in the WebOb doc directory:
+
+.. code-block::
+
+ >>> fn = os.path.join(doc_dir, 'test-file.txt')
+ >>> open(fn).read()
+ 'This is a test. Hello test people!\n'
+ >>> app = FileApp(fn)
+ >>> req = Request.blank('/')
+ >>> print req.get_response(app)
+ 200 OK
+ content-type: text/plain
+ Content-Length: 36
+ <BLANKLINE>
+ This is a test. Hello test people!
+ <BLANKLINE>
+
+Well, that worked. But it's not a very fancy object. First, it reads
+everything into memory, and that's bad. We'll create an iterator instead:
+
+.. code-block::
+
+ >>> class FileIterable(object):
+ ... def __init__(self, filename):
+ ... self.filename = filename
+ ... def __iter__(self):
+ ... return FileIterator(self.filename)
+ >>> class FileIterator(object):
+ ... chunk_size = 4096
+ ... def __init__(self, filename):
+ ... self.filename = filename
+ ... self.fileobj = open(self.filename, 'rb')
+ ... def __iter__(self):
+ ... return self
+ ... def next(self):
+ ... chunk = self.fileobj.read(self.chunk_size)
+ ... if not chunk:
+ ... raise StopIteration
+ ... return chunk
+ >>> def make_response(filename):
+ ... res = Response(content_type=get_mimetype(filename))
+ ... res.app_iter = FileIterable(filename)
+ ... res.content_length = os.path.getsize(filename)
+ ... return res
+
+And testing:
+
+.. code-block::
+
+ >>> req = Request.blank('/')
+ >>> print req.get_response(app)
+ 200 OK
+ content-type: text/plain
+ Content-Length: 36
+ <BLANKLINE>
+ This is a test. Hello test people!
+ <BLANKLINE>
+
+Well, that doesn't *look* different, but lets *imagine* that it's
+different because we know we changed some code. Now to add some basic
+metadata to the response:
+
+.. code-block::
+
+ >>> def make_response(filename):
+ ... res = Response(content_type=get_mimetype(filename),
+ ... conditional_response=True)
+ ... res.app_iter = FileIterable(filename)
+ ... res.content_length = os.path.getsize(filename)
+ ... res.last_modified = os.path.getmtime(filename)
+ ... res.etag = '%s-%s-%s' % (os.path.getmtime(filename),
+ ... os.path.getsize(filename), hash(filename))
+ ... return res
+
+Now, with ``conditional_response`` on, and with ``last_modified`` and
+``etag`` set, we can do conditional requests:
+
+.. code-block::
+
+ >>> req = Request.blank('/')
+ >>> res = req.get_response(app)
+ >>> print res
+ 200 OK
+ content-type: text/plain
+ Content-Length: 36
+ Last-Modified: ... GMT
+ ETag: ...-...
+ <BLANKLINE>
+ This is a test. Hello test people!
+ <BLANKLINE>
+ >>> req2 = Request.blank('/')
+ >>> req2.if_none_match = res.etag
+ >>> req2.get_response(app)
+ <Response ... 304 Not Modified>
+ >>> req3 = Request.blank('/')
+ >>> req3.if_modified_since = res.last_modified
+ >>> req3.get_response(app)
+ <Response ... 304 Not Modified>
+
+We can even do Range requests, but it will currently involve iterating
+through the file unnecessarily. When there's a range request (and you
+set ``conditional_response=True``) the application will satisfy that
+request. But with an arbitrary iterator the only way to do that is to
+run through the beginning of the iterator until you get to the chunk
+that the client asked for. We can do better because we can use
+``fileobj.seek(pos)`` to move around the file much more efficiently.
+
+So we'll add an extra method, ``app_iter_range``, that ``Response``
+looks for:
+
+.. code-block::
+
+ >>> class FileIterable(object):
+ ... def __init__(self, filename, start=None, stop=None):
+ ... self.filename = filename
+ ... self.start = start
+ ... self.stop = stop
+ ... def __iter__(self):
+ ... return FileIterator(self.filename, self.start, self.stop)
+ ... def app_iter_range(self, start, stop):
+ ... return self.__class__(self.filename, start, stop)
+ >>> class FileIterator(object):
+ ... chunk_size = 4096
+ ... def __init__(self, filename, start, stop):
+ ... self.filename = filename
+ ... self.fileobj = open(self.filename, 'rb')
+ ... if start:
+ ... self.fileobj.seek(start)
+ ... if stop is not None:
+ ... self.length = stop - start
+ ... else:
+ ... self.length = None
+ ... def __iter__(self):
+ ... return self
+ ... def next(self):
+ ... if self.length is not None and self.length <= 0:
+ ... raise StopIteration
+ ... chunk = self.fileobj.read(self.chunk_size)
+ ... if not chunk:
+ ... raise StopIteration
+ ... if self.length is not None:
+ ... self.length -= len(chunk)
+ ... if self.length < 0:
+ ... # Chop off the extra:
+ ... chunk = chunk[:self.length]
+ ... return chunk
+
+Now we'll test it out:
+
+.. code-block::
+
+ >>> req = Request.blank('/')
+ >>> res = req.get_response(app)
+ >>> req2 = Request.blank('/')
+ >>> # Re-fetch the first 5 bytes:
+ >>> req2.range = (0, 5)
+ >>> res2 = req2.get_response(app)
+ >>> res2
+ <Response ... 206 Partial Content>
+ >>> # Let's check it's our custom class:
+ >>> res2.app_iter
+ <FileIterable object at ...>
+ >>> res2.body
+ 'This '
+ >>> # Now, conditional range support:
+ >>> req3 = Request.blank('/')
+ >>> req3.if_range = res.etag
+ >>> req3.range = (0, 5)
+ >>> req3.get_response(app)
+ <Response ... 206 Partial Content>
+ >>> req3.if_range = 'invalid-etag'
+ >>> req3.get_response(app)
+ <Response ... 200 OK>
diff --git a/google_appengine/lib/webob/docs/index.txt b/google_appengine/lib/webob/docs/index.txt
new file mode 100644
index 0000000..99ec9c9
--- /dev/null
+++ b/google_appengine/lib/webob/docs/index.txt
@@ -0,0 +1,328 @@
+WebOb
++++++
+
+Other documents:
+
+* `Reference <reference.html>`_
+
+* Extracted documentation for the `request
+ <class-webob.Request.html>`_ and `response
+ <class-webob.Response.html>`_.
+
+* `Differences between the WebOb API and other framework/libraries
+ <differences.html>`_
+
+* `File-serving example <file-example.html>`_
+
+* `Comment middleware example <comment-example.html>`_
+
+.. contents::
+
+.. comment:
+
+ >>> from dtopt import ELLIPSIS
+
+
+Status & License
+================
+
+WebOb is an extraction and refinement of pieces from `Paste
+<http://pythonpaste.org/>`_. It is under active development.
+Discussion should happen on the `Paste mailing lists
+<http://pythonpaste.org/community/>`_, and bugs can go on the `Paste
+trac instance <http://trac.pythonpaste.org/>`_.
+
+WebOb is released under an `MIT-style license <license.html>`_.
+
+WebOb is in an svn repository at
+`http://svn.pythonpaste.org/Paste/WebOb/trunk
+<http://svn.pythonpaste.org/Paste/WebOb/trunk#egg=WebOb-dev>`_. You
+can check it out with::
+
+ $ svn co http://svn.pythonpaste.org/Paste/WebOb/trunk WebOb
+
+Introduction
+============
+
+WebOb provides objects for HTTP requests and responses. Specifically
+it does this by wrapping the `WSGI <http://wsgi.org>`_ request
+environment and response status/headers/app_iter(body).
+
+The request and response objects provide many conveniences for parsing
+HTTP request and forming HTTP responses. Both objects are read/write:
+as a result, WebOb is also a nice way to create HTTP requests and
+parse HTTP responses; however, we won't cover that use case in this
+document. The `reference documentation <reference.html>`_ shows many
+examples of creating requests.
+
+Request
+=======
+
+The request object is a wrapper around the `WSGI environ dictionary
+<http://www.python.org/dev/peps/pep-0333/#environ-variables>`_. This
+dictionary contains keys for each header, keys that describe the
+request (including the path and query string), a file-like object for
+the request body, and a variety of custom keys. You can always access
+the environ with ``req.environ``.
+
+Some of the most important/interesting attributes of a request
+object:
+
+``req.method``:
+ The request method, e.g., ``'GET'``, ``'POST'``
+
+``req.GET``:
+ A `dictionary-like object`_ with all the variables in the query
+ string.
+
+``req.POST``:
+ A `dictionary-like object`_ with all the variables in the request
+ body. This only has variables if the request was a ``POST`` and
+ it is a form submission.
+
+``req.params``:
+ A `dictionary-like object`_ with a combination of everything in
+ ``req.GET`` and ``req.POST``.
+
+``req.body``:
+ The contents of the body of the request. This contains the entire
+ request body as a string. This is useful when the request is a
+ ``POST`` that is *not* a form submission, or a request like a
+ ``PUT``. You can also get ``req.body_file`` for a file-like
+ object.
+
+``req.cookies``:
+ A simple dictionary of all the cookies.
+
+``req.headers``:
+ A dictionary of all the headers. This is dictionary is case-insensitive.
+
+.. _`dictionary-like object`: #multidict
+
+Also, for standard HTTP request headers there are usually attributes,
+for instance: ``req.accept_language``, ``req.content_length``,
+``req.user_agent``, as an example. These properties expose the
+*parsed* form of each header, for whatever parsing makes sense. For
+instance, ``req.if_modified_since`` returns a `datetime
+<http://python.org/doc/current/lib/datetime-datetime.html>`_ object
+(or None if the header is was not provided). Details are in the
+`Request reference <class-webob.Request.html>`_.
+
+URLs
+----
+
+In addition to these attributes, there are several ways to get the URL
+of the request. I'll show various values for an example URL
+``http://localhost/app-root/doc?article_id=10``, where the application
+is mounted at ``http://localhost/app-root``.
+
+``req.url``:
+ The full request URL, with query string, e.g.,
+ ``'http://localhost/app-root/doc?article_id=10'``
+
+``req.application_url``:
+ The URL of the application (just the SCRIPT_NAME portion of the
+ path, not PATH_INFO). E.g., ``'http://localhost/app-root'``
+
+``req.host_url``:
+ The URL with the host, e.g., ``'http://localhost'``
+
+``req.relative_url(url, to_application=False)``:
+ Gives a URL, relative to the current URL. If ``to_application``
+ is True, then resolves it relative to ``req.application_url``.
+
+Methods
+-------
+
+There are `several methods <class-webob.Request.html#__init__>`_ but
+only a few you'll use often:
+
+``Request.blank(base_url)``:
+ Creates a new request with blank information, based at the given
+ URL. This can be useful for subrequests and artificial requests.
+ You can also use ``req.copy()`` to copy an existing request, or
+ for subrequests ``req.copy_get()`` which copies the request but
+ always turns it into a GET (which is safer to shrae for
+ subrequests).
+
+``req.get_response(wsgi_application)``:
+ This method calls the given WSGI application with this request,
+ and returns a `Response`_ object. You can also use this for
+ subrequests or testing.
+
+Unicode
+-------
+
+Many of the properties in the request object will return unicode
+values if the request encoding/charset is provided. The client *can*
+indicate the charset with something like ``Content-Type:
+application/x-www-form-urlencoded; charset=utf8``, but browsers seldom
+set this. You can set the charset with ``req.charset = 'utf8'``, or
+during instantiation with ``Request(environ, charset='utf8'). If you
+subclass ``Request`` you can also set ``charset`` as a class-level
+attribute.
+
+If it is set, then ``req.POST``, ``req.GET``, ``req.params``, and
+``req.cookies`` will contain unicode strings. Each has a
+corresponding ``req.str_*`` (like ``req.str_POST``) that is always
+``str`` and never unicode.
+
+Response
+========
+
+The response object looks a lot like the request object, though with
+some differences. The request object wraps a single ``environ``
+object; the response object has three fundamental parts (based on
+WSGI):
+
+``response.status``:
+ The response code plus message, like ``'200 OK'``. To set the
+ code without the reason, use ``response.status_int = 200``.
+
+``response.headerlist``:
+ A list of all the headers, like ``[('Content-Type',
+ 'text/html')]``. There's a case-insensitive `dictionary-like
+ object`_ in ``response.headers`` that also allows you to access
+ these same headers.
+
+``response.app_iter``:
+ An iterable (such as a list or generator) that will produce the
+ content of the response. This is also accessible as
+ ``response.body`` (a string), ``response.unicode_body`` (a
+ unicode object, informed by ``response.charset``), and
+ ``response.body_file`` (a file-like object; writing to it appends
+ to ``app_iter``).
+
+Everything else in the object derives from this underlying state.
+Here's the highlights:
+
+``response.content_type``:
+ The content type *not* including the ``charset`` parameter.
+ Typical use: ``response.content_type = 'text/html'``. You can
+ subclass ``Response`` and add a class-level attribute
+ ``default_content_type`` to set this automatically on
+ instantiation.
+
+``response.charset``:
+ The ``charset`` parameter of the content-type, it also informs
+ encoding in ``response.unicode_body``.
+ ``response.content_type_params`` is a dictionary of all the
+ parameters.
+
+``response.request``:
+ This optional attribute can point to the request object associated
+ with this response object.
+
+``response.set_cookie(key, value, max_age=None, path='/', domain=None, secure=None, httponly=False, version=None, comment=None)``:
+ Set a cookie. The keyword arguments control the various cookie
+ parameters.
+
+``response.delete_cookie(key, path='/', domain=None)``:
+ Delete a cookie from the client. This sets ``max_age`` to 0 and
+ the cookie value to ``''``.
+
+``response.cache_expires(seconds=0)``:
+ This makes this response cachable for the given number of seconds,
+ or if ``seconds`` is 0 then the response is uncacheable (this also
+ sets the ``Expires`` header).
+
+``response(environ, start_response)``: The response object is a WSGI
+ application. As an application, it acts according to how you
+ creat it. It *can* do conditional responses if you pass
+ ``conditional_response=True`` when instantiating (or set that
+ attribute later). It can also do HEAD and Range requests.
+
+Headers
+-------
+
+Like the request, most HTTP response headers are available as
+properties. These are parsed, so you can do things like
+``response.last_modified = os.path.getmtime(filename)``.
+
+The details are available in the `extracted Response documentation
+<class-webob.Response.html>`_.
+
+Instantiating the Response
+--------------------------
+
+Of course most of the time you just want to *make* a response.
+Generally any attribute of the response can be passed in as a keyword
+argument to the class; e.g.:
+
+.. code-block::
+
+ response = Response(body='hello world!', content_type='text/plain')
+
+The status defaults to ``'200 OK'``. The content_type does not
+default to anything, though if you subclass ``Response`` and set
+``default_content_type`` you can override this behavior.
+
+Exceptions
+==========
+
+To facilitate error responses like 404 Not Found, the module
+``webob.exc`` contains classes for each kind of error response. These
+include boring but appropriate error bodies.
+
+Each class is named ``webob.exc.HTTP*``, where ``*`` is the reason for
+the error. For instance, ``webob.exc.HTTPNotFound``. It subclasses
+``Response``, so you can manipulate the instances in the same way. A
+typical example is:
+
+.. code-block::
+
+ response = HTTPNotFound('There is no such resource')
+ # or:
+ response = HTTPMovedPermanently(location=new_url)
+
+These are not exceptions unless you are using Python 2.5+, because
+they are new-style classes which are not allowed as exceptions until
+Python 2.5. To get an exception object use ``response.exception``.
+You can use this like:
+
+.. code-block::
+
+ try:
+ ... stuff ...
+ raise HTTPNotFound('No such resource').exception
+ except HTTPException, e:
+ return e(environ, start_response)
+
+The exceptions are still WSGI applications, but you cannot set
+attributes like ``content_type``, ``charset``, etc. on these exception
+objects.
+
+Multidict
+=========
+
+Several parts of WebOb use a "multidict"; this is a dictionary where a
+key can have multiple values. The quintessential example is a query
+string like ``?pref=red&pref=blue``; the ``pref`` variable has two
+values: ``red`` and ``blue``.
+
+In a multidict, when you do ``request.GET['pref']`` you'll get back
+only ``'blue'`` (the last value of ``pref``). Sometimes returning a
+string, and sometimes returning a list, is the cause of frequent
+exceptions. If you want *all* the values back, use
+``request.GET.getall('pref')``. If you want to be sure there is *one
+and only one* value, use ``request.GET.getone('pref')``, which will
+raise an exception if there is zero or more than one value for
+``pref``.
+
+When you use operations like ``request.GET.items()`` you'll get back
+something like ``[('pref', 'red'), ('pref', 'blue')]``. All the
+key/value pairs will show up. Similarly ``request.GET.keys()``
+returns ``['pref', 'pref']``. Multidict is a view on a list of
+tuples; all the keys are ordered, and all the values are ordered.
+
+Example
+=======
+
+I haven't figured out the example I want to use here. The
+`file-serving example`_ shows how to do more advanced HTTP techniques,
+while the `comment middleware example`_ shows middleware. For
+applications it's more reasonable to use WebOb in the context of a
+larger framework. `Pylons <http://pylonshq.com>`_ uses WebOb
+optionally in 0.9.7+.
+
+
diff --git a/google_appengine/lib/webob/docs/license.txt b/google_appengine/lib/webob/docs/license.txt
new file mode 100644
index 0000000..2e4ecaa
--- /dev/null
+++ b/google_appengine/lib/webob/docs/license.txt
@@ -0,0 +1,20 @@
+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.
diff --git a/google_appengine/lib/webob/docs/news.txt b/google_appengine/lib/webob/docs/news.txt
new file mode 100644
index 0000000..41f5f92
--- /dev/null
+++ b/google_appengine/lib/webob/docs/news.txt
@@ -0,0 +1,123 @@
+News
+====
+
+.. contents::
+
+0.9
+---
+
+* Added ``req.urlarg``, which represents positional arguments in
+ ``environ['wsgiorg.routing_args']``.
+
+* For Python 2.4, added attribute get/set proxies on exception objects
+ from, for example, ``webob.exc.HTTPNotFound().exception``, so that
+ they act more like normal response objects (despite not being
+ new-style classes or ``webob.Response`` objects). In Python 2.5 the
+ exceptions are ``webob.Response`` objects.
+
+Backward Incompatible Changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* The ``Response`` constructor has changed: it is now ``Response([body],
+ [status], ...)`` (before it was ``Response([status], [body], ...)``).
+ Body may be str or unicode.
+
+* The ``Response`` class defaults to ``text/html`` for the
+ Content-Type, and ``utf8`` for the charset (charset is only set on
+ ``text/*`` and ``application/*+xml`` responses).
+
+Bugfixes and Small Changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* Use ``BaseCookie`` instead of ``SimpleCookie`` for parsing cookies.
+
+* Added ``resp.write(text)`` method, which is equivalent to
+ ``resp.body += text`` or ``resp.unicode_body += text``, depending on
+ the type of ``text``.
+
+* The ``decode_param_names`` argument (used like
+ ``Request(decode_param_names=True)``) was being ignored.
+
+* Unicode decoding of file uploads and file upload filenames were
+ causing errors when decoding non-file-upload fields (both fixes from
+ Ryan Barrett).
+
+0.8.5
+-----
+
+* Added response methods ``resp.encode_content()`` and
+ ``resp.decode_content()`` to gzip or ungzip content.
+
+* ``Response(status=404)`` now works (before you would have to use
+ ``status="404 Not Found"``).
+
+* Bugfix (typo) with reusing POST body.
+
+* Added ``226 IM Used`` response status.
+
+* Backport of ``string.Template`` included for Python 2.3
+ compatibility.
+
+0.8.4
+-----
+
+* ``__setattr__`` would keep ``Request`` subclasses from having
+ properly settable environ proxies (like ``req.path_info``).
+
+0.8.3
+-----
+
+* ``request.POST`` was giving FieldStorage objects for *every*
+ attribute, not just file uploads. This is fixed now.
+
+
+* Added request attributes ``req.server_name`` and ``req.server_port``
+ for the environ keys ``SERVER_NAME`` and ``SERVER_PORT``.
+
+* Avoid exceptions in ``req.content_length``, even if
+ ``environ['CONTENT_LENGTH']`` is somehow invalid.
+
+0.8.2
+-----
+
+* Python 2.3 compatibility: backport of ``reversed(seq)``
+
+* Made separate ``.exception`` attribute on ``webob.exc`` objects,
+ since new-style classes can't be raised as exceptions.
+
+* Deprecate ``req.postvars`` and ``req.queryvars``, instead using the
+ sole names ``req.GET`` and ``req.POST`` (also ``req.str_GET`` and
+ ``req.str_POST``). The old names give a warning; will give an error
+ in next release, and be completely gone in the following release.
+
+* ``req.user_agent`` is now just a simple string (parsing the
+ User-Agent header was just too volatile, and required too much
+ knowledge about current browsers). Similarly,
+ ``req.referer_search_query()`` is gone.
+
+* Added parameters ``version`` and ``comment`` to
+ ``Response.set_cookie()``, per William Dode's suggestion.
+
+* Was accidentally consuming file uploads, instead of putting the
+ ``FieldStorage`` object directly in the parameters.
+
+0.8.1
+-----
+
+* Added ``res.set_cookie(..., httponly=True)`` to set the ``HttpOnly``
+ attribute on the cookie, which keeps Javascript from reading the
+ cookie.
+
+* Added some WebDAV-related responses to ``webob.exc``
+
+* Set default ``Last-Modified`` when using ``response.cache_expire()``
+ (fixes issue with Opera)
+
+* Generally fix ``.cache_control``
+
+0.8
+---
+
+First release. Nothing is new, or everything is new, depending on how
+you think about it.
+
diff --git a/google_appengine/lib/webob/docs/reference.txt b/google_appengine/lib/webob/docs/reference.txt
new file mode 100644
index 0000000..9cebd85
--- /dev/null
+++ b/google_appengine/lib/webob/docs/reference.txt
@@ -0,0 +1,1001 @@
+WebOb Reference
++++++++++++++++
+
+.. contents::
+
+.. comment:
+
+ >>> from dtopt import ELLIPSIS
+
+Introduction
+============
+
+This document covers all the details of the Request and Response
+objects. It is written to be testable with `doctest
+<http://python.org/doc/current/lib/module-doctest.html>`_ -- this
+effects the flavor of the documentation, perhaps to its detriment.
+But it also means you can feel confident that the documentation is
+correct.
+
+This is a somewhat different approach to reference documentation
+compared to the extracted documentation for the `request
+<class-webob.Request.html>`_ and `response
+<class-webob.Response.html>`_.
+
+Request
+=======
+
+The primary object in WebOb is ``webob.Request``, a wrapper around a
+`WSGI environment <http://www.python.org/dev/peps/pep-0333/>`_.
+
+The basic way you create a request object is simple enough:
+
+.. code-block::
+
+ >>> from webob import Request
+ >>> environ = {}
+ >>> req = Request(environ)
+
+The request object *wraps* the environment; it has very little
+internal state of its own. Instead attributes you access read and
+write to the environment dictionary.
+
+You don't have to understand the details of WSGI to use this library;
+this library handles those details for you. You also don't have to
+use this exclusively of other libraries. If those other libraries
+also keep their state in the environment, multiple wrappers can
+coexist. Examples of libraries that can coexist include
+`paste.wsgiwrappers.Request
+<http://pythonpaste.org/class-paste.wsgiwrappers.WSGIRequest.html>`_
+(used by Pylons) and `yaro.Request
+<http://lukearno.com/projects/yaro/>`_.
+
+The WSGI environment has a number of required variables. To make it
+easier to test and play around with, the ``Request`` class has a
+constructor that will fill in a minimal environment:
+
+.. code-block::
+
+ >>> req = Request.blank('/article?id=1')
+ >>> from pprint import pprint
+ >>> pprint(req.environ)
+ {'HTTP_HOST': 'localhost:80',
+ 'PATH_INFO': '/article',
+ 'QUERY_STRING': 'id=1',
+ 'REQUEST_METHOD': 'GET',
+ 'SCRIPT_NAME': '',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'SERVER_PROTOCOL': 'HTTP/1.0',
+ 'wsgi.errors': <open file '<stderr>', mode 'w' at ...>,
+ 'wsgi.input': <cStringIO.StringI object at ...>,
+ 'wsgi.multiprocess': False,
+ 'wsgi.multithread': False,
+ 'wsgi.run_once': False,
+ 'wsgi.url_scheme': 'http',
+ 'wsgi.version': (1, 0)}
+
+Request Body
+------------
+
+``req.body`` is a file-like object that gives the body of the request
+(e.g., a POST form, the body of a PUT, etc). It's kind of boring to
+start, but you can set it to a string and that will be turned into a
+file-like object. You can read the entire body with
+``req.body``.
+
+.. code-block::
+
+ >>> req.body_file
+ <cStringIO.StringI object at ...>
+ >>> req.body
+ ''
+ >>> req.body = 'test'
+ >>> req.body_file
+ <cStringIO.StringI object at ...>
+ >>> req.body
+ 'test'
+
+Method & URL
+------------
+
+All the normal parts of a request are also accessible through the
+request object:
+
+.. code-block::
+
+ >>> req.method
+ 'GET'
+ >>> req.scheme
+ 'http'
+ >>> req.script_name # The base of the URL
+ ''
+ >>> req.script_name = '/blog' # make it more interesting
+ >>> req.path_info # The yet-to-be-consumed part of the URL
+ '/article'
+ >>> req.content_type # Content-Type of the request body
+ ''
+ >>> print req.remote_user # The authenticated user (there is none set)
+ None
+ >>> print req.remote_addr # The remote IP
+ None
+ >>> req.host
+ 'localhost:80'
+ >>> req.host_url
+ 'http://localhost'
+ >>> req.application_url
+ 'http://localhost/blog'
+ >>> req.path_url
+ 'http://localhost/blog/article'
+ >>> req.url
+ 'http://localhost/blog/article?id=1'
+ >>> req.path
+ '/blog/article'
+ >>> req.path_qs
+ '/blog/article?id=1'
+ >>> req.query_string
+ 'id=1'
+
+You can make new URLs:
+
+.. code-block::
+
+ >>> req.relative_url('archive')
+ 'http://localhost/blog/archive'
+
+For parsing the URLs, it is often useful to deal with just the next
+path segment on PATH_INFO:
+
+.. code-block::
+
+ >>> req.path_info_peek() # Doesn't change request
+ 'article'
+ >>> req.path_info_pop() # Does change request!
+ 'article'
+ >>> req.script_name
+ '/blog/article'
+ >>> req.path_info
+ ''
+
+Headers
+-------
+
+All request headers are available through a dictionary-like object
+``req.headers``. Keys are case-insensitive.
+
+.. code-block::
+
+ >>> req.headers['content-type'] = 'application/x-www-urlencoded'
+ >>> req.headers
+ {'Content-Length': '4', 'Content-Type': 'application/x-www-urlencoded', 'Host': 'localhost:80'}
+ >>> req.environ['CONTENT_TYPE']
+ 'application/x-www-urlencoded'
+
+Query & POST variables
+----------------------
+
+Requests can have variables in one of two locations: the query string
+(``?id=1``), or in the body of the request (generally a POST form).
+Note that even POST requests can have a query string, so both kinds of
+variables can exist at the same time. Also, a variable can show up
+more than once, as in ``?check=a&check=b``.
+
+For these variables WebOb uses a `MultiDict
+<class-webob.multidict.MultiDict.html>`_, which is basically a
+dictionary wrapper on a list of key/value pairs. It looks like a
+single-valued dictionary, but you can access all the values of a key
+with ``.getall(key)`` (which always returns a list, possibly an empty
+list). You also get all key/value pairs when using ``.items()`` and
+all values with ``.values()``.
+
+Some examples:
+
+.. code-block::
+
+ >>> req = Request.blank('/test?check=a&check=b&name=Bob')
+ >>> req.GET
+ MultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob')])
+ >>> req.GET['check']
+ 'b'
+ >>> req.GET.getall('check')
+ ['a', 'b']
+ >>> req.GET.items()
+ [('check', 'a'), ('check', 'b'), ('name', 'Bob')]
+
+We'll have to create a request body and change the method to get
+POST. Until we do that, the variables are boring:
+
+.. code-block::
+
+ >>> req.POST
+ <NoVars: Not a POST request>
+ >>> req.POST.items() # NoVars can be read like a dict, but not written
+ []
+ >>> req.method = 'POST'
+ >>> req.body = 'name=Joe&email=joe@example.com'
+ >>> req.POST
+ MultiDict([('name', 'Joe'), ('email', 'joe@example.com')])
+ >>> req.POST['name']
+ 'Joe'
+
+Often you won't care where the variables come from. (Even if you care
+about the method, the location of the variables might not be
+important.) There is a dictionary called ``req.params`` that
+contains variables from both sources:
+
+.. code-block::
+
+ >>> req.params
+ NestedMultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob'), ('name', 'Joe'), ('email', 'joe@example.com')])
+ >>> req.params['name']
+ 'Bob'
+ >>> req.params.getall('name')
+ ['Bob', 'Joe']
+ >>> for name, value in req.params.items():
+ ... print '%s: %r' % (name, value)
+ check: 'a'
+ check: 'b'
+ name: 'Bob'
+ name: 'Joe'
+ email: 'joe@example.com'
+
+Unicode Variables
+~~~~~~~~~~~~~~~~~
+
+Submissions are non-unicode (``str``) strings, unless some character
+set is indicated. A client can indicate the character set with
+``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but
+very few clients actually do this (sometimes XMLHttpRequest requests
+will do this, as JSON is always UTF8 even when a page is served with a
+different character set). You can force a charset, which will effect
+all the variables:
+
+.. code-block::
+
+ >>> req.charset = 'utf8'
+ >>> req.GET
+ UnicodeMultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')])
+
+If you always want ``str`` values, you can use ``req.str_GET``
+and ``str_POST``.
+
+Cookies
+-------
+
+Cookies are presented in a simple dictionary. Like other variables,
+they will be decoded into Unicode strings if you set the charset.
+
+.. code-block::
+
+ >>> req.headers['Cookie'] = 'test=value'
+ >>> req.cookies
+ UnicodeMultiDict([(u'test', u'value')])
+ >>> req.charset = None
+ >>> req.cookies
+ {'test': 'value'}
+
+Modifying the request
+---------------------
+
+The headers are all modifiable, as are other environmental variables
+(like ``req.remote_user``, which maps to
+``request.environ['REMOTE_USER']``).
+
+If you want to copy the request you can use ``req.copy()``; this
+copies the ``environ`` dictionary, and the request body from
+``environ['wsgi.input']``.
+
+The method ``req.remove_conditional_headers(remove_encoding=True)``
+can be used to remove headers that might result in a ``304 Not
+Modified`` response. If you are writing some intermediary it can be
+useful to avoid these headers. Also if ``remove_encoding`` is true
+(the default) then any ``Accept-Encoding`` header will be removed,
+which can result in gzipped responses.
+
+Header Getters
+--------------
+
+In addition to ``req.headers``, there are attributes for most of the
+request headers defined by the HTTP 1.1 specification. These
+attributes often return parsed forms of the headers.
+
+Accept-* headers
+~~~~~~~~~~~~~~~~
+
+There are several request headers that tell the server what the client
+accepts. These are ``accept`` (the Content-Type that is accepted),
+``accept_charset`` (the charset accepted), ``accept_encoding``
+(the Content-Encoding, like gzip, that is accepted), and
+``accept_language`` (generally the preferred language of the client).
+
+The objects returned support containment to test for acceptability.
+E.g.:
+
+.. code-block::
+
+ >>> 'text/html' in req.accept
+ True
+
+Because no header means anything is potentially acceptable, this is
+returning True. We can set it to see more interesting behavior (the
+example means that ``text/html`` is okay, but
+``application/xhtml+xml`` is preferred):
+
+.. code-block::
+
+ >>> req.accept = 'text/html;q=0.5, application/xhtml+xml;q=1'
+ >>> req.accept
+ <MIMEAccept at ... Accept: text/html;q=0.5, application/xhtml+xml>
+ >>> 'text/html' in req.accept
+ True
+
+There's three methods for different strategies of finding a match.
+First, when you trust the server's preference over the client (a good
+idea for Accept):
+
+.. code-block::
+
+ >>> req.accept.first_match(['text/html', 'application/xhtml+xml'])
+ 'text/html'
+
+Because ``text/html`` is at least *somewhat* acceptible, it is
+returned, even if the client says it prefers
+``application/xhtml+xml``. If we trust the client more:
+
+.. code-block::
+
+ >>> req.accept.best_match(['text/html', 'application/xhtml+xml'])
+ 'application/xhtml+xml'
+
+If we just want to know everything the client prefers, in the order it
+is preferred:
+
+.. code-block::
+
+ >>> req.accept.best_matches()
+ ['application/xhtml+xml', 'text/html']
+
+For languages you'll often have a "fallback" language. E.g., if there's
+nothing better then use ``en-US`` (and if ``en-US`` is okay, ignore
+any less preferrable languages):
+
+.. code-block::
+
+ >>> req.accept_language = 'es, pt-BR'
+ >>> req.accept_language.best_matches('en-US')
+ ['es', 'pt-BR', 'en-US']
+ >>> req.accept_language.best_matches('es')
+ ['es']
+
+Conditional Requests
+~~~~~~~~~~~~~~~~~~~~
+
+There a number of ways to make a conditional request. A conditional
+request is made when the client has a document, but it is not sure if
+the document is up to date. If it is not, it wants a new version. If
+the document is up to date then it doesn't want to waste the
+bandwidth, and expects a ``304 Not Modified`` response.
+
+ETags are generally the best technique for these kinds of requests;
+this is an opaque string that indicates the identity of the object.
+For instance, it's common to use the mtime (last modified) of the file,
+plus the number of bytes, and maybe a hash of the filename (if there's
+a possibility that the same URL could point to two different
+server-side filenames based on other variables). To test if a 304
+response is appropriate, you can use:
+
+.. code-block::
+
+ >>> server_token = 'opaque-token'
+ >>> server_token in req.if_none_match # You shouldn't return 304
+ False
+ >>> req.if_none_match = server_token
+ >>> req.if_none_match
+ <ETag opaque-token>
+ >>> server_token in req.if_none_match # You *should* return 304
+ True
+
+For date-based comparisons If-Modified-Since is used:
+
+.. code-block::
+
+ >>> from webob import UTC
+ >>> from datetime import datetime
+ >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
+ >>> req.headers['If-Modified-Since']
+ 'Sun, 01 Jan 2006 12:00:00 GMT'
+ >>> server_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
+ >>> req.if_modified_since and req.if_modified_since >= server_modified
+ True
+
+For range requests there are two important headers, If-Range (which is
+form of conditional request) and Range (which requests a range). If
+the If-Range header fails to match then the full response (not a
+range) should be returned:
+
+.. code-block::
+
+ >>> req.if_range
+ <Empty If-Range>
+ >>> req.if_range.match(etag='some-etag', last_modified=datetime(2005, 1, 1, 12, 0))
+ True
+ >>> req.if_range = 'opaque-etag'
+ >>> req.if_range.match(etag='other-etag')
+ False
+ >>> req.if_range.match(etag='opaque-etag')
+ True
+
+You can also pass in a response object with:
+
+.. code-block::
+
+ >>> from webob import Response
+ >>> res = Response(etag='opaque-etag')
+ >>> req.if_range.match_response(res)
+ True
+
+To get the range information:
+
+ >>> req.range = 'bytes=0-100'
+ >>> req.range
+ <Range ranges=(0, 99)>
+ >>> cr = req.range.content_range(length=1000)
+ >>> cr.start, cr.stop, cr.length
+ (0, 99, 1000)
+
+Note that the range headers use *inclusive* ranges (the last byte
+indexed is included), where Python always uses a range where the last
+index is excluded from the range. The ``.stop`` index is in the
+Python form.
+
+Another kind of conditional request is a request (typically PUT) that
+includes If-Match or If-Unmodified-Since. In this case you are saying
+"here is an update to a resource, but don't apply it if someone else
+has done something since I last got the resource". If-Match means "do
+this if the current ETag matches the ETag I'm giving".
+If-Unmodified-Since means "do this if the resource has remained
+unchanged".
+
+.. code-block::
+
+ >>> server_token in req.if_match # No If-Match means everything is ok
+ True
+ >>> req.if_match = server_token
+ >>> server_token in req.if_match # Still OK
+ True
+ >>> req.if_match = 'other-token'
+ >>> # Not OK, should return 412 Precondition Failed:
+ >>> server_token in req.if_match
+ False
+
+For more on this kind of conditional request, see `Detecting the Lost
+Update Problem Using Unreserved Checkout
+<http://www.w3.org/1999/04/Editing/>`_.
+
+Calling WSGI Applications
+-------------------------
+
+The request object can be used to make handy subrequests or test
+requests against WSGI applications. If you want to make subrequests,
+you should copy the request (with ``req.copy()``) before sending it to
+multiple applications, since applications might modify the request
+when they are run.
+
+There's two forms of the subrequest. The more primitive form is
+this:
+
+.. code-block::
+
+ >>> req = Request.blank('/')
+ >>> def wsgi_app(environ, start_response):
+ ... start_response('200 OK', [('Content-type', 'text/plain')])
+ ... return ['Hi!']
+ >>> req.call_application(wsgi_app)
+ ('200 OK', [('Content-type', 'text/plain')], ['Hi!'])
+
+Note it returns ``(status_string, header_list, app_iter)``. If
+``app_iter.close()`` exists, it is your responsibility to call it.
+
+A handier response can be had with:
+
+.. code-block::
+
+ >>> res = req.get_response(wsgi_app)
+ >>> res
+ <Response ... 200 OK>
+ >>> res.status
+ '200 OK'
+ >>> res.headers
+ HeaderDict([('Content-type', 'text/plain')])
+ >>> res.body
+ 'Hi!'
+
+You can learn more about this response object in the Response_ section.
+
+Thread-local Request Wrappers
+-----------------------------
+
+You can also give the ``Request`` object a function to get the
+environment. This can be used to make a single global request object
+dynamic. An example:
+
+.. code-block::
+
+ >>> import threading
+ >>> import webob
+ >>> environments = threading.local()
+ >>> def get_environ():
+ ... return environments.environ
+ >>> def set_thread_environ(environ):
+ ... environments.environ = environ
+ >>> request = webob.Request(environ_getter=get_environ)
+ >>> set_thread_environ({'SCRIPT_NAME': '/test'})
+ >>> request.script_name
+ '/test'
+
+Ad-Hoc Attributes
+-----------------
+
+You can assign attributes to your request objects. They will all go
+in ``environ['webob.adhoc_attrs']`` (a dictionary).
+
+.. code-block::
+
+ >>> req = Request.blank('/')
+ >>> req.some_attr = 'blah blah blah'
+ >>> new_req = Request(req.environ)
+ >>> new_req.some_attr
+ 'blah blah blah'
+ >>> req.environ['webob.adhoc_attrs']
+ {'some_attr': 'blah blah blah'}
+
+Response
+========
+
+The ``webob.Response`` object contains everything necessary to make a
+WSGI response. Instances of it are in fact WSGI applications, but it
+can also represent the result of calling a WSGI application (as noted
+in `Calling WSGI Applications`_). It can also be a way of
+accumulating a response in your WSGI application.
+
+A WSGI response is made up of a status (like ``200 OK``), a list of
+headers, and a body (or iterator that will produce a body).
+
+Core Attributes
+---------------
+
+The core attributes are unsurprising:
+
+.. code-block::
+
+ >>> from webob import Response
+ >>> res = Response()
+ >>> res.status
+ '200 OK'
+ >>> res.headerlist
+ [('Content-Length', '0')]
+ >>> res.body
+ ''
+
+You can set any of these attributes, e.g.:
+
+.. code-block::
+
+ >>> res.status = 404
+ >>> res.status
+ '404 Not Found'
+ >>> res.status_int
+ 404
+ >>> res.headerlist = [('Content-type', 'text/html')]
+ >>> res.body = 'test'
+ >>> print res
+ 404 Not Found
+ Content-type: text/html
+ Content-Length: 4
+ <BLANKLINE>
+ test
+ >>> res.body = u"test"
+ Traceback (most recent call last):
+ ...
+ TypeError: You cannot set Response.body to a unicode object (use Response.unicode_body)
+ >>> res.unicode_body = u"test"
+ Traceback (most recent call last):
+ ...
+ AttributeError: You cannot access Response.unicode_body unless charset is set
+ >>> res.charset = 'utf8'
+ >>> res.unicode_body = u"test"
+ >>> res.body
+ 'test'
+
+You can set any attribute with the constructor, like
+``Response(charset='utf8')``
+
+Headers
+-------
+
+In addition to ``res.headerlist``, there is dictionary-like view on
+the list in ``res.headers``:
+
+.. code-block::
+
+ >>> res.headers
+ HeaderDict([('content-type', 'text/html; charset=utf8'), ('Content-Length', '4')])
+
+This is case-insensitive. It can support multiple values for a key,
+though only if you use ``res.headers.add(key, value)`` or read them
+with ``res.headers.getall(key)``.
+
+Body & app_iter
+---------------
+
+The ``res.body`` attribute represents the entire body of the request
+as a single string (not unicode, though you can set it to unicode if
+you have a charset defined). There is also a ``res.app_iter``
+attribute that reprsents the body as an iterator. WSGI applications
+return these ``app_iter`` iterators instead of strings, and sometimes
+it can be problematic to load the entire iterator at once (for
+instance, if it returns the contents of a very large file). Generally
+it is not a problem, and often the iterator is something simple like a
+one-item list containing a string with the entire body.
+
+If you set the body then Content-Length will also be set, and an
+``res.app_iter`` will be created for you. If you set ``res.app_iter``
+then Content-Length will be cleared, but it won't be set for you.
+
+There is also a file-like object you can access, which will update the
+app_iter in-place (turning the app_iter into a list if necessary):
+
+.. code-block::
+
+ >>> res = Response(content_type='text/plain')
+ >>> f = res.body_file
+ >>> f.write('hey')
+ >>> f.write(u'test')
+ Traceback (most recent call last):
+ . . .
+ TypeError: You can only write unicode to Response.body_file if charset has been set
+ >>> f.encoding
+ >>> res.charset = 'utf8'
+ >>> f.encoding
+ 'utf8'
+ >>> f.write(u'test')
+ >>> res.app_iter
+ ['hey', 'test']
+ >>> res.body
+ 'heytest'
+
+Header Getters
+--------------
+
+Like Request, HTTP response headers are also available as individual
+properties. These represent parsed forms of the headers.
+
+Content-Type is a special case, as the type and the charset are
+handled through two separate properties:
+
+.. code-block::
+
+ >>> res = Response()
+ >>> res.content_type = 'text/html'
+ >>> res.charset = 'utf8'
+ >>> res.content_type
+ 'text/html'
+ >>> res.headers['content-type']
+ 'text/html; charset=utf8'
+ >>> res.content_type = 'application/atom+xml'
+ >>> res.content_type_params
+ {'charset': 'utf8'}
+ >>> res.content_type_params = {'type': 'entry', 'charset': 'utf8'}
+ >>> res.headers['content-type']
+ 'application/atom+xml; charset=utf8; type=entry'
+
+Other headers:
+
+.. code-block::
+
+ >>> # Used with a redirect:
+ >>> res.location = 'http://localhost/foo'
+
+ >>> # Indicates that the server accepts Range requests:
+ >>> res.accept_ranges = 'bytes'
+
+ >>> # Used by caching proxies to tell the client how old the
+ >>> # response is:
+ >>> res.age = 120
+
+ >>> # Show what methods the client can do; typically used in
+ >>> # a 405 Method Not Allowed response:
+ >>> res.allow = ['GET', 'PUT']
+
+ >>> # Set the cache-control header:
+ >>> res.cache_control.max_age = 360
+ >>> res.cache_control.no_transform = True
+
+ >>> # Used if you had gzipped the body:
+ >>> res.content_encoding = 'gzip'
+
+ >>> # What language(s) are in the content:
+ >>> res.content_language = ['en']
+
+ >>> # Seldom used header that tells the client where the content
+ >>> # is from:
+ >>> res.content_location = 'http://localhost/foo'
+
+ >>> # Seldom used header that gives a hash of the body:
+ >>> res.content_md5 = 'big-hash'
+
+ >>> # Means we are serving bytes 0-500 inclusive, out of 1000 bytes total:
+ >>> # you can also use the range setter shown earlier
+ >>> res.content_range = (0, 499, 1000)
+
+ >>> # The length of the content; set automatically if you set
+ >>> # res.body:
+ >>> res.content_length = 4
+
+ >>> # Used to indicate the current date as the server understands
+ >>> # it:
+ >>> res.date = datetime.now()
+
+ >>> # The etag:
+ >>> res.etag = 'opaque-token'
+ >>> # You can generate it from the body too:
+ >>> res.md5_etag()
+ >>> res.etag
+ '1B2M2Y8AsgTpgAmY7PhCfg'
+
+ >>> # When this page should expire from a cache (Cache-Control
+ >>> # often works better):
+ >>> import time
+ >>> res.expires = time.time() + 60*60 # 1 hour
+
+ >>> # When this was last modified, of course:
+ >>> res.last_modified = datetime(2007, 1, 1, 12, 0, tzinfo=UTC)
+
+ >>> # Used with 503 Service Unavailable to hint the client when to
+ >>> # try again:
+ >>> res.retry_after = 160
+
+ >>> # Indicate the server software:
+ >>> res.server = 'WebOb/1.0'
+
+ >>> # Give a list of headers that the cache should vary on:
+ >>> res.vary = ['Cookie']
+
+Note in each case you can general set the header to a string to avoid
+any parsing, and set it to None to remove the header (or do something
+like ``del res.vary``).
+
+In the case of date-related headers you can set the value to a
+``datetime`` instance (ideally with a UTC timezone), a time tuple, an
+integer timestamp, or a properly-formatted string.
+
+After setting all these headers, here's the result:
+
+.. code-block::
+
+ >>> for name, value in res.headerlist:
+ ... print '%s: %s' % (name, value)
+ content-type: application/atom+xml; charset=utf8; type=entry
+ location: http://localhost/foo
+ Accept-Ranges: bytes
+ Age: 120
+ Allow: GET, PUT
+ Cache-Control: max-age=360, no-transform
+ Content-Encoding: gzip
+ Content-Language: en
+ Content-Location: http://localhost/foo
+ Content-MD5: big-hash
+ Content-Range: bytes 0-500/1000
+ Content-Length: 4
+ Date: ... GMT
+ ETag: ...
+ Expires: ... GMT
+ Last-Modified: Mon, 01 Jan 2007 12:00:00 GMT
+ Retry-After: 160
+ Server: WebOb/1.0
+ Vary: Cookie
+
+You can also set Cache-Control related attributes with
+``req.cache_expires(seconds, **attrs)``, like:
+
+.. code-block::
+
+ >>> res = Response()
+ >>> res.cache_expires(10)
+ >>> res.headers['Cache-Control']
+ 'max-age=10'
+ >>> res.cache_expires(0)
+ >>> res.headers['Cache-Control']
+ 'max-age=0, must-revalidate, no-cache, no-store'
+ >>> res.headers['Expires']
+ '... GMT'
+
+You can also use the `timedelta
+<http://python.org/doc/current/lib/datetime-timedelta.html>`_
+constants defined, e.g.:
+
+.. code-block::
+
+ >>> from webob import *
+ >>> res = Response()
+ >>> res.cache_expires(2*day+4*hour)
+ >>> res.headers['Cache-Control']
+ 'max-age=187200'
+
+Cookies
+-------
+
+Cookies (and the Set-Cookie header) are handled with a couple
+methods. Most importantly:
+
+.. code-block::
+
+ >>> res.set_cookie('key', 'value', max_age=360, path='/',
+ ... domain='example.org', secure=True)
+ >>> res.headers['Set-Cookie']
+ 'key=value; Domain=example.org; Max-Age=360; Path=/; secure;'
+ >>> # To delete a cookie previously set in the client:
+ >>> res.delete_cookie('bad_cookie')
+ >>> res.headers['Set-Cookie']
+ 'bad_cookie=; Max-Age=0; Path=/;'
+
+The only other real method of note (note that this does *not* delete
+the cookie from clients, only from the response object):
+
+.. code-block::
+
+ >>> res.unset_cookie('key')
+ >>> res.unset_cookie('bad_cookie')
+ >>> print res.headers.get('Set-Cookie')
+ None
+
+Binding a Request
+-----------------
+
+You can bind a request (or request WSGI environ) to the response
+object. This is available through ``res.request`` or
+``res.environ``. This is currently only used in setting
+``res.location``, to make the location absolute if necessary.
+
+Response as a WSGI application
+------------------------------
+
+A response is a WSGI application, in that you can do:
+
+.. code-block::
+
+ >>> req = Request.blank('/')
+ >>> status, headers, app_iter = req.call_application(res)
+
+A possible pattern for your application might be:
+
+.. code-block::
+
+ >>> def my_app(environ, start_response):
+ ... req = Request(environ)
+ ... res = Response()
+ ... res.content_type = 'text/plain'
+ ... parts = []
+ ... for name, value in sorted(req.environ.items()):
+ ... parts.append('%s: %r' % (name, value))
+ ... res.body = '\n'.join(parts)
+ ... return res(environ, start_response)
+ >>> req = Request.blank('/')
+ >>> res = req.get_response(my_app)
+ >>> print res
+ 200 OK
+ content-type: text/plain
+ Content-Length: 394
+ <BLANKLINE>
+ HTTP_HOST: 'localhost:80'
+ PATH_INFO: '/'
+ QUERY_STRING: ''
+ REQUEST_METHOD: 'GET'
+ SCRIPT_NAME: ''
+ SERVER_NAME: 'localhost'
+ SERVER_PORT: '80'
+ SERVER_PROTOCOL: 'HTTP/1.0'
+ wsgi.errors: <open file '<stderr>', mode 'w' at ...>
+ wsgi.input: <cStringIO.StringI object at ...>
+ wsgi.multiprocess: False
+ wsgi.multithread: False
+ wsgi.run_once: False
+ wsgi.url_scheme: 'http'
+ wsgi.version: (1, 0)
+
+Exceptions
+==========
+
+In addition to Request and Response objects, there are a set of Python
+exceptions for different HTTP responses (3xx, 4xx, 5xx codes).
+
+These provide a simple way to provide these non-200 response. A very
+simple body is provided.
+
+.. code-block::
+
+ >>> from webob.exc import *
+ >>> exc = HTTPTemporaryRedirect(location='foo')
+ >>> req = Request.blank('/path/to/something')
+ >>> print str(req.get_response(exc)).strip()
+ 307 Temporary Redirect
+ content-type: text/html
+ location: http://localhost/path/to/foo
+ Content-Length: 126
+ <BLANKLINE>
+ 307 Temporary Redirect
+ <BLANKLINE>
+ The resource has been moved to http://localhost/path/to/foo; you should be redirected automatically.
+
+Note that only if there's an ``Accept: text/html`` header in the
+request will an HTML response be given:
+
+.. code-block::
+
+ >>> req.accept += 'text/html'
+ >>> print str(req.get_response(exc)).strip()
+ 307 Temporary Redirect
+ content-type: text/html
+ location: http://localhost/path/to/foo
+ Content-Length: 270
+ <BLANKLINE>
+ <html>
+ <head>
+ <title>307 Temporary Redirect</title>
+ </head>
+ <body>
+ <h1>307 Temporary Redirect</h1>
+ The resource has been moved to <a href="http://localhost/path/to/foo">http://localhost/path/to/foo</a>;
+ you should be redirected automatically.
+ <BLANKLINE>
+ <BLANKLINE>
+ </body>
+ </html>
+
+
+This is taken from `paste.httpexceptions
+<http://pythonpaste.org/module-paste.httpexceptions.html>`_, and if
+you have Paste installed then these exceptions will be subclasses of
+the Paste exceptions.
+
+Note that on Python 2.4 and before, new-style classes could not be
+used as exceptions. ``Response`` objects must be new-style classes,
+so this causes a bit of a conflict. The base class
+``webob.exc.HTTPException`` *is* an exception, so you can catch that,
+and it *is* a WSGI application. But you may not be able to use
+Response methods. You can always get ``obj.exception`` to get an
+exception that you can raise, and ``obj.wsgi_response`` to get the
+``Response`` object that you can use.
+
+Conditional WSGI Application
+----------------------------
+
+The Response object can handle your conditional responses for you,
+checking If-None-Match, If-Modified-Since, and Range/If-Range.
+
+To enable this you must create the response like
+``Response(conditional_request=True)``, or make a subclass like:
+
+.. code-block::
+
+ >>> class AppResponse(Response):
+ ... default_content_type = 'text/html'
+ ... default_conditional_response = True
+ >>> res = AppResponse(body='0123456789',
+ ... last_modified=datetime(2005, 1, 1, 12, 0, tzinfo=UTC))
+ >>> req = Request.blank('/')
+ >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
+ >>> req.get_response(res)
+ <Response ... 304 Not Modified>
+ >>> del req.if_modified_since
+ >>> res.etag = 'opaque-tag'
+ >>> req.if_none_match = 'opaque-tag'
+ >>> req.get_response(res)
+ <Response ... 304 Not Modified>
+ >>> del req.if_none_match
+ >>> req.range = (1, 5)
+ >>> result = req.get_response(res)
+ >>> result.headers['content-range']
+ 'bytes 1-6/10'
+ >>> result.body
+ '1234'
diff --git a/google_appengine/lib/webob/docs/test-file.txt b/google_appengine/lib/webob/docs/test-file.txt
new file mode 100644
index 0000000..2b448e0
--- /dev/null
+++ b/google_appengine/lib/webob/docs/test-file.txt
@@ -0,0 +1 @@
+This is a test. Hello test people!
diff --git a/google_appengine/lib/webob/docs/wiki-example-code/example.py b/google_appengine/lib/webob/docs/wiki-example-code/example.py
new file mode 100755
index 0000000..c76ffa7
--- /dev/null
+++ b/google_appengine/lib/webob/docs/wiki-example-code/example.py
@@ -0,0 +1,200 @@
+import os
+import re
+from webob import Request, Response
+from webob import exc
+from tempita import HTMLTemplate
+
+VIEW_TEMPLATE = HTMLTemplate("""\
+<html>
+ <head>
+ <title>{{page.title}}</title>
+ </head>
+ <body>
+<h1>{{page.title}}</h1>
+{{if message}}
+<div style="background-color: #99f">{{message}}</div>
+{{endif}}
+
+<div>{{page.content|html}}</div>
+
+<hr>
+<a href="{{req.url}}?action=edit">Edit</a>
+ </body>
+</html>
+""")
+
+EDIT_TEMPLATE = HTMLTemplate("""\
+<html>
+ <head>
+ <title>Edit: {{page.title}}</title>
+ </head>
+ <body>
+{{if page.exists}}
+<h1>Edit: {{page.title}}</h1>
+{{else}}
+<h1>Create: {{page.title}}</h1>
+{{endif}}
+
+<form action="{{req.path_url}}" method="POST">
+ <input type="hidden" name="mtime" value="{{page.mtime}}">
+ Title: <input type="text" name="title" style="width: 70%" value="{{page.title}}"><br>
+ Content: <input type="submit" value="Save">
+ <a href="{{req.path_url}}">Cancel</a>
+ <br>
+ <textarea name="content" style="width: 100%; height: 75%" rows="40">{{page.content}}</textarea>
+ <br>
+ <input type="submit" value="Save">
+ <a href="{{req.path_url}}">Cancel</a>
+</form>
+</body></html>
+""")
+
+class WikiApp(object):
+
+ view_template = VIEW_TEMPLATE
+ edit_template = EDIT_TEMPLATE
+
+ def __init__(self, storage_dir):
+ self.storage_dir = os.path.abspath(os.path.normpath(storage_dir))
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ action = req.params.get('action', 'view')
+ page = self.get_page(req.path_info)
+ try:
+ try:
+ meth = getattr(self, 'action_%s_%s' % (action, req.method))
+ except AttributeError:
+ raise exc.HTTPBadRequest('No such action %r' % action).exception
+ resp = meth(req, page)
+ except exc.HTTPException, e:
+ resp = e
+ return resp(environ, start_response)
+
+ def get_page(self, path):
+ path = path.lstrip('/')
+ if not path:
+ path = 'index'
+ path = os.path.join(self.storage_dir)
+ path = os.path.normpath(path)
+ if path.endswith('/'):
+ path += 'index'
+ if not path.startswith(self.storage_dir):
+ raise exc.HTTPBadRequest("Bad path").exception
+ path += '.html'
+ return Page(path)
+
+ def action_view_GET(self, req, page):
+ if not page.exists:
+ return exc.HTTPTemporaryRedirect(
+ location=req.url + '?action=edit')
+ if req.cookies.get('message'):
+ message = req.cookies['message']
+ else:
+ message = None
+ text = self.view_template.substitute(
+ page=page, req=req, message=message)
+ resp = Response(text)
+ if message:
+ resp.delete_cookie('message')
+ else:
+ resp.last_modified = page.mtime
+ resp.conditional_response = True
+ return resp
+
+ def action_view_POST(self, req, page):
+ submit_mtime = int(req.params.get('mtime') or '0') or None
+ if page.mtime != submit_mtime:
+ return exc.HTTPPreconditionFailed(
+ "The page has been updated since you started editing it")
+ page.set(
+ title=req.params['title'],
+ content=req.params['content'])
+ resp = exc.HTTPSeeOther(
+ location=req.path_url)
+ resp.set_cookie('message', 'Page updated')
+ return resp
+
+ def action_edit_GET(self, req, page):
+ text = self.edit_template.substitute(
+ page=page, req=req)
+ return Response(text)
+
+class Page(object):
+ def __init__(self, filename):
+ self.filename = filename
+
+ @property
+ def exists(self):
+ return os.path.exists(self.filename)
+
+ @property
+ def title(self):
+ if not self.exists:
+ # we need to guess the title
+ basename = os.path.splitext(os.path.basename(self.filename))[0]
+ basename = re.sub(r'[_-]', ' ', basename)
+ return basename.capitalize()
+ content = self.full_content
+ match = re.search(r'<title>(.*?)</title>', content, re.I|re.S)
+ return match.group(1)
+
+ @property
+ def full_content(self):
+ f = open(self.filename, 'rb')
+ try:
+ return f.read()
+ finally:
+ f.close()
+
+ @property
+ def content(self):
+ if not self.exists:
+ return ''
+ content = self.full_content
+ match = re.search(r'<body[^>]*>(.*?)</body>', content, re.I|re.S)
+ return match.group(1)
+
+ @property
+ def mtime(self):
+ if not self.exists:
+ return None
+ else:
+ return os.stat(self.filename).st_mtime
+
+ def set(self, title, content):
+ dir = os.path.dirname(self.filename)
+ if not os.path.exists(dir):
+ os.makedirs(dir)
+ new_content = """<html><head><title>%s</title></head><body>%s</body></html>""" % (
+ title, content)
+ f = open(self.filename, 'wb')
+ f.write(new_content)
+ f.close()
+
+if __name__ == '__main__':
+ import optparse
+ parser = optparse.OptionParser(
+ usage='%prog --port=PORT'
+ )
+ parser.add_option(
+ '-p', '--port',
+ default='8080',
+ dest='port',
+ type='int',
+ help='Port to serve on (default 8080)')
+ parser.add_option(
+ '--wiki-data',
+ default='./wiki',
+ dest='wiki_data',
+ help='Place to put wiki data into (default ./wiki/)')
+ options, args = parser.parse_args()
+ print 'Writing wiki pages to %s' % options.wiki_data
+ app = WikiApp(options.wiki_data)
+ from wsgiref.simple_server import make_server
+ httpd = make_server('localhost', options.port, app)
+ print 'Serving on http://localhost:%s' % options.port
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print '^C'
diff --git a/google_appengine/lib/webob/docs/wiki-example.txt b/google_appengine/lib/webob/docs/wiki-example.txt
new file mode 100644
index 0000000..6abd35d
--- /dev/null
+++ b/google_appengine/lib/webob/docs/wiki-example.txt
@@ -0,0 +1,693 @@
+Wiki Example
+============
+
+:author: Ian Bicking <ianb@colorstudy.com>
+
+.. contents::
+
+Introduction
+------------
+
+This is an example of how to write a WSGI application using WebOb.
+WebOb isn't itself intended to write applications -- it is not a web
+framework on its own -- but it is *possible* to write applications
+using just WebOb.
+
+The `file serving example <file-example.html>`_ is a better example of
+advanced HTTP usage. The `comment middleware example
+<comment-example.html>`_ is a better example of using middleware.
+This example provides some completeness by showing an
+application-focused end point.
+
+This example implements a very simple wiki.
+
+Code
+----
+
+The finished code for this is available in
+`docs/wiki-example-code/example.py
+<http://svn.pythonpaste.org/Paste/WebOb/trunk/docs/wiki-example-code/example.py>`_
+-- you can run that file as a script to try it out.
+
+Creating an Application
+-----------------------
+
+A common pattern for creating small WSGI applications is to have a
+class which is instantiated with the configuration. For our
+application we'll be storing the pages under a directory.
+
+.. code-block::
+
+ class WikiApp(object):
+
+ def __init__(self, storage_dir):
+ self.storage_dir = os.path.abspath(os.path.normpath(storage_dir))
+
+WSGI applications are callables like ``wsgi_app(environ,
+start_response)``. *Instances* of `WikiApp` are WSGI applications, so
+we'll implement a ``__call__`` method:
+
+.. code-block::
+
+ class WikiApp(object):
+ ...
+ def __call__(self, environ, start_response):
+ # what we'll fill in
+
+To make the script runnable we'll create a simple command-line
+interface:
+
+.. code-block::
+
+ if __name__ == '__main__':
+ import optparse
+ parser = optparse.OptionParser(
+ usage='%prog --port=PORT'
+ )
+ parser.add_option(
+ '-p', '--port',
+ default='8080',
+ dest='port',
+ type='int',
+ help='Port to serve on (default 8080)')
+ parser.add_option(
+ '--wiki-data',
+ default='./wiki',
+ dest='wiki_data',
+ help='Place to put wiki data into (default ./wiki/)')
+ options, args = parser.parse_args()
+ print 'Writing wiki pages to %s' % options.wiki_data
+ app = WikiApp(options.wiki_data)
+ from wsgiref.simple_server import make_server
+ httpd = make_server('localhost', options.port, app)
+ print 'Serving on http://localhost:%s' % options.port
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print '^C'
+
+There's not much to talk about in this code block. The application is
+instantiated and served with the built-in module
+`wsgiref.simple_server
+<http://www.python.org/doc/current/lib/module-wsgiref.simple_server.html>`_.
+
+The WSGI Application
+--------------------
+
+Of course all the interesting stuff is in that ``__call__`` method.
+WebOb lets you ignore some of the details of WSGI, like what
+``start_response`` really is. ``environ`` is a CGI-like dictionary,
+but ``webob.Request`` gives an object interface to it.
+``webob.Response`` represents a response, and is itself a WSGI
+application. Here's kind of the hello world of WSGI applications
+using these objects:
+
+.. code-block::
+
+ from webob import Request, Response
+
+ class WikiApp(object):
+ ...
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ resp = Response(
+ 'Hello %s!' % req.params.get('name', 'World'))
+ return resp(environ, start_response)
+
+``req.params.get('name', 'World')`` gets any query string parameter
+(like ``?name=Bob``), or if it's a POST form request it will look for
+a form parameter ``name``. We instantiate the response with the body
+of the response. You could also give keyword arguments like
+``content_type='text/plain'`` (``text/html`` is the default content
+type and ``200 OK`` is the default status).
+
+For the wiki application we'll support a couple different kinds of
+screens, and we'll make our ``__call__`` method dispatch to different
+methods depending on the request. We'll support an ``action``
+parameter like ``?action=edit``, and also dispatch on the method (GET,
+POST, etc, in ``req.method``). We'll pass in the request and expect a
+response object back.
+
+Also, WebOb has a series of exceptions in ``webob.exc``, like
+``webob.exc.HTTPNotFound``, ``webob.exc.HTTPTemporaryRedirect``, etc.
+We'll also let the method raise one of these exceptions and turn it
+into a response.
+
+One last thing we'll do in our ``__call__`` method is create our
+``Page`` object, which represents a wiki page.
+
+All this together makes:
+
+.. code-block::
+
+ from webob import Request, Response
+ from webob import exc
+
+ class WikiApp(object):
+ ...
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ action = req.params.get('action', 'view')
+ # Here's where we get the Page domain object:
+ page = self.get_page(req.path_info)
+ try:
+ try:
+ # The method name is action_{action_param}_{request_method}:
+ meth = getattr(self, 'action_%s_%s' % (action, req.method))
+ except AttributeError:
+ # If the method wasn't found there must be
+ # something wrong with the request:
+ raise exc.HTTPBadRequest('No such action %r' % action).exception
+ resp = meth(req, page)
+ except exc.HTTPException, e:
+ # The exception object itself is a WSGI application/response:
+ resp = e
+ return resp(environ, start_response)
+
+The Domain Object
+-----------------
+
+The ``Page`` domain object isn't really related to the web, but it is
+important to implementing this. Each ``Page`` is just a file on the
+filesystem. Our ``get_page`` method figures out the filename given
+the path (the path is in ``req.path_info``, which is all the path
+after the base path). The ``Page`` class handles getting and setting
+the title and content.
+
+Here's the method to figure out the filename:
+
+.. code-block::
+
+ import os
+
+ class WikiApp(object):
+ ...
+
+ def get_page(self, path):
+ path = path.lstrip('/')
+ if not path:
+ # The path was '/', the home page
+ path = 'index'
+ path = os.path.join(self.storage_dir)
+ path = os.path.normpath(path)
+ if path.endswith('/'):
+ path += 'index'
+ if not path.startswith(self.storage_dir):
+ raise exc.HTTPBadRequest("Bad path").exception
+ path += '.html'
+ return Page(path)
+
+Mostly this is just the kind of careful path construction you have to
+do when mapping a URL to a filename. While the server *may* normalize
+the path (so that a path like ``/../../`` can't be requested), you can
+never really be sure. By using ``os.path.normpath`` we eliminate
+these, and then we make absolutely sure that the resulting path is
+under our ``self.storage_dir`` with ``if not
+path.startswith(self.storage_dir): raise exc.HTTPBadRequest("Bad
+path").exception``.
+
+.. note::
+
+ ``exc.HTTPBadRequest("Bad path")`` is a ``webob.Response`` object.
+ This is a new-style class, so you can't raise it in Python 2.4 or
+ under (only old-style classes work). The attribute ``.exception``
+ can actually be raised. The exception object is *also* a WSGI
+ application, though it doesn't have attributes like
+ ``.content_type``, etc.
+
+Here's the actual domain object:
+
+.. code-block::
+
+ class Page(object):
+ def __init__(self, filename):
+ self.filename = filename
+
+ @property
+ def exists(self):
+ return os.path.exists(self.filename)
+
+ @property
+ def title(self):
+ if not self.exists:
+ # we need to guess the title
+ basename = os.path.splitext(os.path.basename(self.filename))[0]
+ basename = re.sub(r'[_-]', ' ', basename)
+ return basename.capitalize()
+ content = self.full_content
+ match = re.search(r'<title>(.*?)</title>', content, re.I|re.S)
+ return match.group(1)
+
+ @property
+ def full_content(self):
+ f = open(self.filename, 'rb')
+ try:
+ return f.read()
+ finally:
+ f.close()
+
+ @property
+ def content(self):
+ if not self.exists:
+ return ''
+ content = self.full_content
+ match = re.search(r'<body[^>]*>(.*?)</body>', content, re.I|re.S)
+ return match.group(1)
+
+ @property
+ def mtime(self):
+ if not self.exists:
+ return None
+ else:
+ return os.stat(self.filename).st_mtime
+
+ def set(self, title, content):
+ dir = os.path.dirname(self.filename)
+ if not os.path.exists(dir):
+ os.makedirs(dir)
+ new_content = """<html><head><title>%s</title></head><body>%s</body></html>""" % (
+ title, content)
+ f = open(self.filename, 'wb')
+ f.write(new_content)
+ f.close()
+
+Basically it provides a ``.title`` attribute, a ``.content``
+attribute, the ``.mtime`` (last modified time), and the page can exist
+or not (giving appropriate guesses for title and content when the page
+does not exist). It encodes these on the filesystem as a simple HTML
+page that is parsed by some regular expressions.
+
+None of this really applies much to the web or WebOb, so I'll leave it
+to you to figure out the details of this.
+
+URLs, PATH_INFO, and SCRIPT_NAME
+--------------------------------
+
+This is an aside for the tutorial, but an important concept. In WSGI,
+and accordingly with WebOb, the URL is split up into several pieces.
+Some of these are obvious and some not.
+
+An example::
+
+ http://example.com:8080/wiki/article/12?version=10
+
+There are several components here:
+
+* req.scheme: ``http``
+* req.host: ``example.com:8080``
+* req.server_name: ``example.com``
+* req.server_port: 8080
+* req.script_name: ``/wiki``
+* req.path_info: ``/article/12``
+* req.query_string: ``version=10``
+
+One non-obvious part is ``req.script_name`` and ``req.path_info``.
+These correspond to the CGI environmental variables ``SCRIPT_NAME``
+and ``PATH_INFO``. ``req.script_name`` points to the *application*.
+You might have several applications in your site at different paths:
+one at ``/wiki``, one at ``/blog``, one at ``/``. Each application
+doesn't necessarily know about the others, but it has to construct its
+URLs properly -- so any internal links to the wiki application should
+start with ``/wiki``.
+
+Just as there are pieces to the URL, there are several properties in
+WebOb to construct URLs based on these:
+
+* req.host_url: ``http://example.com:8080``
+* req.application_url: ``http://example.com:8080/wiki``
+* req.path_url: ``http://example.com:8080/wiki/article/12``
+* req.path: ``/wiki/article/12``
+* req.path_qs: ``/wiki/article/12?version=10``
+* req.url: ``http://example.com:8080/wiki/article/12?version10``
+
+You can also create URLs with
+``req.relative_url('some/other/page')``. In this example that would
+resolve to ``http://example.com:8080/wiki/article/some/other/page``.
+You can also create a relative URL to the application URL
+(SCRIPT_NAME) like ``req.relative_url('some/other/page', True)`` which
+would be ``http://example.com:8080/wiki/some/other/page``.
+
+Back to the Application
+-----------------------
+
+We have a dispatching function with ``__call__`` and we have a domain
+object with ``Page``, but we aren't actually doing anything.
+
+The dispatching goes to ``action_ACTION_METHOD``, where ACTION
+defaults to ``view``. So a simple page view will be
+``action_view_GET``. Let's implement that:
+
+.. code-block::
+
+ class WikiApp(object):
+ ...
+
+ def action_view_GET(self, req, page):
+ if not page.exists:
+ return exc.HTTPTemporaryRedirect(
+ location=req.url + '?action=edit')
+ text = self.view_template.substitute(
+ page=page, req=req)
+ resp = Response(text)
+ resp.last_modified = page.mtime
+ resp.conditional_response = True
+ return resp
+
+The first thing we do is redirect the user to the edit screen if the
+page doesn't exist. ``exc.HTTPTemporaryRedirect`` is a response that
+gives a ``307 Temporary Redirect`` response with the given location.
+
+Otherwise we fill in a template. The template language we're going to
+use in this example is `Tempita <http://pythonpaste.org/tempita/>`_, a
+very simple template language with a similar interface to
+`string.Template <>`_.
+
+The template actually looks like this:
+
+.. code-block::
+
+ from tempita import HTMLTemplate
+
+ VIEW_TEMPLATE = HTMLTemplate("""\
+ <html>
+ <head>
+ <title>{{page.title}}</title>
+ </head>
+ <body>
+ <h1>{{page.title}}</h1>
+
+ <div>{{page.content|html}}</div>
+
+ <hr>
+ <a href="{{req.url}}?action=edit">Edit</a>
+ </body>
+ </html>
+ """)
+
+ class WikiApp(object):
+ view_template = VIEW_TEMPLATE
+ ...
+
+As you can see it's a simple template using the title and the body,
+and a link to the edit screen. We copy the template object into a
+class method (``view_template = VIEW_TEMPLATE``) so that potentially a
+subclass could override these templates.
+
+``tempita.HTMLTemplate`` is a template that does automatic HTML
+escaping. Our wiki will just be written in plain HTML, so we disable
+escaping of the content with ``{{page.content|html}}``.
+
+So let's look at the ``action_view_GET`` method again:
+
+.. code-block::
+
+ def action_view_GET(self, req, page):
+ if not page.exists:
+ return exc.HTTPTemporaryRedirect(
+ location=req.url + '?action=edit')
+ text = self.view_template.substitute(
+ page=page, req=req)
+ resp = Response(text)
+ resp.last_modified = page.mtime
+ resp.conditional_response = True
+ return resp
+
+The template should be pretty obvious now. We create a response with
+``Response(text)``, which already has a default Content-Type of
+``text/html``.
+
+To allow conditional responses we set ``resp.last_modified``. You can
+set this attribute to a date, None (effectively removing the header),
+a time tuple (like produced by ``time.localtime()``), or as in this
+case to an integer timestamp. If you get the value back it will
+always be a `datetime <>`_ object (or None). With this header we can
+process requests with If-Modified-Since headers, and return ``304 Not
+Modified`` if appropriate. It won't actually do that unless you set
+``resp.conditional_response`` to True.
+
+.. note::
+
+ If you subclass ``webob.Response`` you can set the class attribute
+ ``default_conditional_response = True`` and this setting will be
+ on by default. You can also set other defaults, like the
+ ``default_charset`` (``"utf8"``), or ``default_content_type``
+ (``"text/html"``).
+
+The Edit Screen
+---------------
+
+The edit screen will be implemented in the method
+``action_edit_GET``. There's a template and a very simple method:
+
+.. code-block::
+
+ EDIT_TEMPLATE = HTMLTemplate("""\
+ <html>
+ <head>
+ <title>Edit: {{page.title}}</title>
+ </head>
+ <body>
+ {{if page.exists}}
+ <h1>Edit: {{page.title}}</h1>
+ {{else}}
+ <h1>Create: {{page.title}}</h1>
+ {{endif}}
+
+ <form action="{{req.path_url}}" method="POST">
+ <input type="hidden" name="mtime" value="{{page.mtime}}">
+ Title: <input type="text" name="title" style="width: 70%" value="{{page.title}}"><br>
+ Content: <input type="submit" value="Save">
+ <a href="{{req.path_url}}">Cancel</a>
+ <br>
+ <textarea name="content" style="width: 100%; height: 75%" rows="40">{{page.content}}</textarea>
+ <br>
+ <input type="submit" value="Save">
+ <a href="{{req.path_url}}">Cancel</a>
+ </form>
+ </body></html>
+ """)
+
+ class WikiApp(object):
+ ...
+
+ edit_template = EDIT_TEMPLATE
+
+ def action_edit_GET(self, req, page):
+ text = self.edit_template.substitute(
+ page=page, req=req)
+ return Response(text)
+
+As you can see, all the action here is in the template.
+
+In ``<form action="{{req.path_url}}" method="POST">`` we submit to
+``req.path_url``; that's everything *but* ``?action=edit``. So we are
+POSTing right over the view page. This has the nice side effect of
+automatically invalidating any caches of the original page. It also
+is vaguely `RESTful <>`_.
+
+We save the last modified time in a hidden ``mtime`` field. This way
+we can detect concurrent updates. If start editing the page who's
+mtime is 100000, and someone else edits and saves a revision changing
+the mtime to 100010, we can use this hidden field to detect that
+conflict. Actually resolving the conflict is a little tricky and
+outside the scope of this particular tutorial, we'll just note the
+conflict to the user in an error.
+
+From there we just have a very straight-forward HTML form. Note that
+we don't quote the values because that is done automatically by
+``HTMLTemplate``; if you are using something like ``string.Template``
+or a templating language that doesn't do automatic quoting, you have
+to be careful to quote all the field values.
+
+We don't have any error conditions in our application, but if there
+were error conditions we might have to re-display this form with the
+input values the user already gave. In that case we'd do something
+like::
+
+ <input type="text" name="title"
+ value="{{req.params.get('title', page.title)}}">
+
+This way we use the value in the request (``req.params`` is both the
+query string parameters and any variables in a POST response), but if
+there is no value (e.g., first request) then we use the page values.
+
+Processing the Form
+-------------------
+
+The form submits to ``action_view_POST`` (``view`` is the default
+action). So we have to implement that method:
+
+.. code-block::
+
+ class WikiApp(object):
+ ...
+
+ def action_view_POST(self, req, page):
+ submit_mtime = int(req.params.get('mtime') or '0') or None
+ if page.mtime != submit_mtime:
+ return exc.HTTPPreconditionFailed(
+ "The page has been updated since you started editing it")
+ page.set(
+ title=req.params['title'],
+ content=req.params['content'])
+ resp = exc.HTTPSeeOther(
+ location=req.path_url)
+ return resp
+
+The first thing we do is check the mtime value. It can be an empty
+string (when there's no mtime, like when you are creating a page) or
+an integer. ``int(req.params.get('time') or '0') or None`` basically
+makes sure we don't pass ``""`` to ``int()`` (which is an error) then
+turns 0 into None (``0 or None`` will evaluate to None in Python --
+``false_value or other_value`` in Python resolves to ``other_value``).
+If it fails we just give a not-very-helpful error message, using ``412
+Precondition Failed`` (typically preconditions are HTTP headers like
+``If-Unmodified-Since``, but we can't really get the browser to send
+requests like that, so we use the hidden field instead).
+
+.. note::
+
+ Error statuses in HTTP are often under-used because people think
+ they need to either return an error (useful for machines) or an
+ error message or interface (useful for humans). In fact you can
+ do both: you can give any human readable error message with your
+ error response.
+
+ One problem is that Internet Explorer will replace error messages
+ with its own incredibly unhelpful error messages. However, it
+ will only do this if the error message is short. If it's fairly
+ large (4Kb is large enough) it will show the error message it was
+ given. You can load your error with a big HTML comment to
+ accomplish this, like ``"<!-- %s -->" % ('x'*4000)``.
+
+ You can change the status of any response with ``resp.status_int =
+ 412``, or you can change the body of an ``exc.HTTPSomething`` with
+ ``resp.body = new_body``. The primary advantage of using the
+ classes in ``webob.exc`` is giving the response a clear name and a
+ boilerplate error message.
+
+After we check the mtime we get the form parameters from
+``req.params`` and issue a redirect back to the original view page.
+``303 See Other`` is a good response to give after accepting a POST
+form submission, as it gets rid of the POST (no warning messages for the
+user if they try to go back).
+
+In this example we've used ``req.params`` for all the form values. If
+we wanted to be specific about where we get the values from, they
+could come from ``req.GET`` (the query string, a misnomer since the
+query string is present even in POST requests) or ``req.POST`` (a POST
+form body). While sometimes it's nice to distinguish between these
+two locations, for the most part it doesn't matter. If you want to
+check the request method (e.g., make sure you can't change a page with
+a GET request) there's no reason to do it by accessing these
+method-specific getters. It's better to just handle the method
+specifically. We do it here by including the request method in our
+dispatcher (dispatching to ``action_view_GET`` or
+``action_view_POST``).
+
+
+Cookies
+-------
+
+One last little improvement we can do is show the user a message when
+they update the page, so it's not quite so mysteriously just another
+page view.
+
+A simple way to do this is to set a cookie after the save, then
+display it in the page view. To set it on save, we add a little to
+``action_view_POST``:
+
+.. code-block::
+
+ def action_view_POST(self, req, page):
+ ...
+ resp = exc.HTTPSeeOther(
+ location=req.path_url)
+ resp.set_cookie('message', 'Page updated')
+ return resp
+
+And then in ``action_view_GET``:
+
+.. code-block::
+
+
+ VIEW_TEMPLATE = HTMLTemplate("""\
+ ...
+ {{if message}}
+ <div style="background-color: #99f">{{message}}</div>
+ {{endif}}
+ ...""")
+
+ class WikiApp(object):
+ ...
+
+ def action_view_GET(self, req, page):
+ ...
+ if req.cookies.get('message'):
+ message = req.cookies['message']
+ else:
+ message = None
+ text = self.view_template.substitute(
+ page=page, req=req, message=message)
+ resp = Response(text)
+ if message:
+ resp.delete_cookie('message')
+ else:
+ resp.last_modified = page.mtime
+ resp.conditional_response = True
+ return resp
+
+``req.cookies`` is just a dictionary, and we also delete the cookie if
+it is present (so the message doesn't keep getting set). The
+conditional response stuff only applies when there isn't any
+message, as messages are private. Another alternative would be to
+display the message with Javascript, like::
+
+ <script type="text/javascript">
+ function readCookie(name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for (var i=0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+ }
+ return null;
+ }
+
+ function createCookie(name, value, days) {
+ if (days) {
+ var date = new Date();
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ var expires = "; expires="+date.toGMTString();
+ } else {
+ var expires = "";
+ }
+ document.cookie = name+"="+value+expires+"; path=/";
+ }
+
+ function eraseCookie(name) {
+ createCookie(name, "", -1);
+ }
+
+ function showMessage() {
+ var message = readCookie('message');
+ if (message) {
+ var el = document.getElementById('message');
+ el.innerHTML = message;
+ el.style.display = '';
+ eraseCookie('message');
+ }
+ }
+ </script>
+
+Then put ``<div id="messaage" style="display: none"></div>`` in the
+page somewhere. This has the advantage of being very cacheable and
+simple on the server side.
+
+Conclusion
+----------
+
+We're done, hurrah!
diff --git a/google_appengine/lib/webob/setup.cfg b/google_appengine/lib/webob/setup.cfg
new file mode 100644
index 0000000..25aa790
--- /dev/null
+++ b/google_appengine/lib/webob/setup.cfg
@@ -0,0 +1,48 @@
+[pudge]
+highlighter = pygments
+title = WebOb
+dest = docs/html
+docs = docs/index.txt docs/license.txt docs/differences.txt docs/file-example.txt
+ docs/news.txt docs/reference.txt docs/comment-example.txt
+ docs/wiki-example.txt
+settings = no_about=true
+ link1=/ paste
+ link2=/news.html news
+ link3=http://pypi.python.org/pypi/WebOb/ download
+ extra_credits=Hosting courtesy of <a href="http://www.tummy.com">Tummy.com</a>
+modules = webob
+ webob.acceptparse
+ webob.byterange
+ webob.cachecontrol
+ webob.datastruct
+ webob.etag
+ webob.exc
+ webob.headerdict
+ webob.multidict
+ webob.statusreasons
+ webob.updatedict
+ webob.util
+ webob.util.safegzip
+doc_base = docs/
+theme = pythonpaste.org
+mailing_list_url = /community/mailing-list.html
+organization = Python Paste
+organization_url = http://pythonpaste.org/
+trac_url = http://trac.pythonpaste.org/
+
+[global]
+command_packages = buildutils.pudge_command, buildutils.publish_command
+
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
+[publish]
+doc-dest = scp://ianb@webwareforpython.org/home/paste/htdocs/webob/
+make-dirs = 1
+doc-dir = docs/html
+
+[aliases]
+distribute = register sdist bdist_egg upload pudge publish
+
diff --git a/google_appengine/lib/webob/setup.py b/google_appengine/lib/webob/setup.py
new file mode 100755
index 0000000..4be4a90
--- /dev/null
+++ b/google_appengine/lib/webob/setup.py
@@ -0,0 +1,33 @@
+from setuptools import setup, find_packages
+import sys, os
+
+version = '0.9'
+
+setup(name='WebOb',
+ version=version,
+ description="WSGI request and response object",
+ long_description="""\
+WebOb provides wrappers around the WSGI request environment, and an
+object to help create WSGI responses.
+
+The objects map much of the specified behavior of HTTP, including
+header parsing and accessors for other standard parts of the
+environment.
+""",
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Framework :: Paste",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Topic :: Internet :: WWW/HTTP :: WSGI",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
+ keywords='wsgi request web http',
+ author='Ian Bicking',
+ author_email='ianb@colorstudy.com',
+ url='http://pythonpaste.org/webob/',
+ license='MIT',
+ packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+ include_package_data=True,
+ zip_safe=True,
+ )
diff --git a/google_appengine/lib/webob/test b/google_appengine/lib/webob/test
new file mode 100755
index 0000000..5e12388
--- /dev/null
+++ b/google_appengine/lib/webob/test
@@ -0,0 +1,8 @@
+#!/bin/sh
+NOSE_WITH_DOCTEST=t
+export NOSE_WITH_DOCTEST
+NOSE_DOCTEST_EXTENSION=txt
+export NOSE_DOCTEST_EXTENSION
+#NOSE_DETAILED_ERRORS=t
+#export NOSE_DETAILED_ERRORS
+nosetests $*
diff --git a/google_appengine/lib/webob/tests/__init__.py b/google_appengine/lib/webob/tests/__init__.py
new file mode 100755
index 0000000..792d600
--- /dev/null
+++ b/google_appengine/lib/webob/tests/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/google_appengine/lib/webob/tests/conftest.py b/google_appengine/lib/webob/tests/conftest.py
new file mode 100755
index 0000000..cdb3a9e
--- /dev/null
+++ b/google_appengine/lib/webob/tests/conftest.py
@@ -0,0 +1,4 @@
+import os, sys
+sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
+import pkg_resources
+pkg_resources.require('WebOb')
diff --git a/google_appengine/lib/webob/tests/test_request.py b/google_appengine/lib/webob/tests/test_request.py
new file mode 100755
index 0000000..8435209
--- /dev/null
+++ b/google_appengine/lib/webob/tests/test_request.py
@@ -0,0 +1,94 @@
+from paste.fixture import *
+from webob import Request
+from py.test import raises
+
+def simpleapp(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ request = Request(environ)
+ request.remote_user = 'bob'
+ return [
+ 'Hello world!\n',
+ 'The get is %r' % request.GET,
+ ' and Val is %s\n' % request.GET.get('name'),
+ 'The languages are: %s\n' % request.accept_language.best_matches('en-US'),
+ 'The accepttypes is: %s\n' % request.accept.best_match(['text/html', 'application/xml']),
+ 'post is %r\n' % request.POST,
+ 'params is %r\n' % request.params,
+ 'cookies is %r\n' % request.cookies,
+ 'body: %r\n' % request.body,
+ 'method: %s\n' % request.method,
+ 'remote_user: %r\n' % request.environ['REMOTE_USER'],
+ 'host_url: %r; application_url: %r; path_url: %r; url: %r\n' % (request.host_url, request.application_url, request.path_url, request.url),
+ 'urlvars: %r\n' % request.urlvars,
+ 'urlargs: %r\n' % (request.urlargs, ),
+ 'is_xhr: %r\n' % request.is_xhr,
+ 'if_modified_since: %r\n' % request.if_modified_since,
+ 'user_agent: %r\n' % request.user_agent,
+ 'if_none_match: %r\n' % request.if_none_match,
+ ]
+
+def test_gets():
+ app = TestApp(simpleapp)
+ res = app.get('/')
+ print res
+ assert 'Hello' in res
+ assert "get is MultiDict([])" in res
+ assert "post is <NoVars: Not a POST request>" in res
+
+ res = app.get('/?name=george')
+ res.mustcontain("get is MultiDict([('name', 'george')])")
+ res.mustcontain("Val is george")
+
+def test_language_parsing():
+ app = TestApp(simpleapp)
+ res = app.get('/')
+ assert "The languages are: ['en-US']" in res
+
+ res = app.get('/', headers={'Accept-Language':'da, en-gb;q=0.8, en;q=0.7'})
+ assert "languages are: ['da', 'en-gb', 'en', 'en-US']" in res
+
+ res = app.get('/', headers={'Accept-Language':'en-gb;q=0.8, da, en;q=0.7'})
+ assert "languages are: ['da', 'en-gb', 'en', 'en-US']" in res
+
+def test_mime_parsing():
+ app = TestApp(simpleapp)
+ res = app.get('/', headers={'Accept':'text/html'})
+ assert "accepttypes is: text/html" in res
+
+ res = app.get('/', headers={'Accept':'application/xml'})
+ assert "accepttypes is: application/xml" in res
+
+ res = app.get('/', headers={'Accept':'application/xml,*/*'})
+ assert "accepttypes is: application/xml" in res
+
+def test_headers():
+ app = TestApp(simpleapp)
+ headers = {
+ 'If-Modified-Since': 'Sat, 29 Oct 1994 19:43:31 GMT',
+ 'Cookie': 'var1=value1',
+ 'User-Agent': 'Mozilla 4.0 (compatible; MSIE)',
+ 'If-None-Match': '"etag001", "etag002"',
+ 'X-Requested-With': 'XMLHttpRequest',
+ }
+ res = app.get('/?foo=bar&baz', headers=headers)
+ res.mustcontain(
+ 'if_modified_since: datetime.datetime(1994, 10, 29, 19, 43, 31, tzinfo=UTC)',
+ "user_agent: 'Mozilla",
+ 'is_xhr: True',
+ "cookies is {'var1': 'value1'}",
+ "params is NestedMultiDict([('foo', 'bar'), ('baz', '')])",
+ "if_none_match: <ETag etag001 or etag002>",
+ )
+
+def test_bad_cookie():
+ req = Request.blank('/')
+ req.headers['Cookie'] = '070-it-:><?0'
+ assert req.cookies == {}
+ req.headers['Cookie'] = 'foo=bar'
+ assert req.cookies == {'foo': 'bar'}
+ req.headers['Cookie'] = '...'
+ assert req.cookies == {}
+ req.headers['Cookie'] = '=foo'
+ assert req.cookies == {}
diff --git a/google_appengine/lib/webob/tests/test_request.txt b/google_appengine/lib/webob/tests/test_request.txt
new file mode 100644
index 0000000..e286e62
--- /dev/null
+++ b/google_appengine/lib/webob/tests/test_request.txt
@@ -0,0 +1,317 @@
+This demonstrates how the Request object works, and tests it.
+
+You can instantiate a request using ``Request.blank()``, to create a
+fresh environment dictionary with all the basic keys such a dictionary
+should have.
+
+ >>> from dtopt import ELLIPSIS
+ >>> from webob import Request, UTC
+ >>> req = Request.blank('/')
+ >>> req # doctest: +ELLIPSIS
+ <Request at ... GET http://localhost/>
+ >>> print repr(str(req))
+ 'GET /\r\nHost: localhost:80\r\n\r\n'
+ >>> req.environ # doctest: +ELLIPSIS
+ {...}
+ >>> req.body_file # doctest: +ELLIPSIS
+ <cStringIO.StringI object at ...>
+ >>> req.scheme
+ 'http'
+ >>> req.method
+ 'GET'
+ >>> req.script_name
+ ''
+ >>> req.path_info
+ '/'
+ >>> req.content_type
+ ''
+ >>> print req.remote_user
+ None
+ >>> req.host_url
+ 'http://localhost'
+ >>> req.script_name = '/foo'
+ >>> req.path_info = '/bar/'
+ >>> req.environ['QUERY_STRING'] = 'a=b'
+ >>> req.application_url
+ 'http://localhost/foo'
+ >>> req.path_url
+ 'http://localhost/foo/bar/'
+ >>> req.url
+ 'http://localhost/foo/bar/?a=b'
+ >>> req.relative_url('baz')
+ 'http://localhost/foo/bar/baz'
+ >>> req.relative_url('baz', to_application=True)
+ 'http://localhost/foo/baz'
+ >>> req.relative_url('http://example.org')
+ 'http://example.org'
+ >>> req.path_info_peek()
+ 'bar'
+ >>> req.path_info_pop()
+ 'bar'
+ >>> req.script_name, req.path_info
+ ('/foo/bar', '/')
+ >>> print req.environ.get('wsgiorg.routing_args')
+ None
+ >>> req.urlvars
+ {}
+ >>> req.environ['wsgiorg.routing_args']
+ ((), {})
+ >>> req.urlvars = dict(x='y')
+ >>> req.environ['wsgiorg.routing_args']
+ ((), {'x': 'y'})
+ >>> req.urlargs
+ ()
+ >>> req.urlargs = (1, 2, 3)
+ >>> req.environ['wsgiorg.routing_args']
+ ((1, 2, 3), {'x': 'y'})
+ >>> del req.urlvars
+ >>> req.environ['wsgiorg.routing_args']
+ ((1, 2, 3), {})
+ >>> req.urlvars = {'test': 'value'}
+ >>> del req.urlargs
+ >>> req.environ['wsgiorg.routing_args']
+ ((), {'test': 'value'})
+ >>> req.is_xhr
+ False
+ >>> req.environ['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ >>> req.is_xhr
+ True
+ >>> req.host
+ 'localhost:80'
+
+There are also variables to access the variables and body:
+
+ >>> from cStringIO import StringIO
+ >>> body = 'var1=value1&var2=value2&rep=1&rep=2'
+ >>> req = Request.blank('/')
+ >>> req.method = 'POST'
+ >>> req.body_file = StringIO(body)
+ >>> req.environ['CONTENT_LENGTH'] = str(len(body))
+ >>> vars = req.str_POST
+ >>> vars
+ MultiDict([('var1', 'value1'), ('var2', 'value2'), ('rep', '1'), ('rep', '2')])
+ >>> vars is req.str_POST
+ True
+ >>> req.POST
+ MultiDict([('var1', 'value1'), ('var2', 'value2'), ('rep', '1'), ('rep', '2')])
+ >>> req.charset = 'utf8'
+ >>> req.POST
+ UnicodeMultiDict([(u'var1', u'value1'), (u'var2', u'value2'), (u'rep', u'1'), (u'rep', u'2')])
+
+Note that the variables are there for GET requests and non-form POST
+requests, but they are empty and read-only:
+
+ >>> req = Request.blank('/')
+ >>> req.str_POST
+ <NoVars: Not a POST request>
+ >>> req.str_POST.items()
+ []
+ >>> req.str_POST['x'] = 'y'
+ Traceback (most recent call last):
+ ...
+ KeyError: 'Cannot add variables: Not a POST request'
+ >>> req.method = 'POST'
+ >>> req.str_POST
+ MultiDict([])
+ >>> req.content_type = 'text/xml'
+ >>> req.body_file = StringIO('<xml></xml>')
+ >>> req.str_POST
+ <NoVars: Not an HTML form submission (Content-Type: text/xml)>
+ >>> req.body
+ '<xml></xml>'
+
+You can also get access to the query string variables, of course:
+
+ >>> req = Request.blank('/?a=b&d=e&d=f')
+ >>> req.GET
+ MultiDict([('a', 'b'), ('d', 'e'), ('d', 'f')])
+ >>> req.GET['d']
+ 'f'
+ >>> req.GET.getall('d')
+ ['e', 'f']
+ >>> req.method = 'POST'
+ >>> req.body = 'x=y&d=g'
+ >>> req.body_file # doctest: +ELLIPSIS
+ <cStringIO.StringI object at ...>
+ >>> req.environ['CONTENT_LENGTH']
+ '7'
+ >>> req.params
+ NestedMultiDict([('a', 'b'), ('d', 'e'), ('d', 'f'), ('x', 'y'), ('d', 'g')])
+ >>> req.params['d']
+ 'f'
+ >>> req.params.getall('d')
+ ['e', 'f', 'g']
+
+Cookie are viewed as a dictionary (*view only*):
+
+ >>> req = Request.blank('/')
+ >>> req.environ['HTTP_COOKIE'] = 'var1=value1; var2=value2'
+ >>> req.str_cookies
+ {'var1': 'value1', 'var2': 'value2'}
+ >>> req.cookies
+ {'var1': 'value1', 'var2': 'value2'}
+ >>> req.charset = 'utf8'
+ >>> req.cookies
+ UnicodeMultiDict([(u'var1', u'value1'), (u'var2', u'value2')])
+
+Sometimes conditional headers are problematic. You can remove them:
+
+ >>> from datetime import datetime
+ >>> req = Request.blank('/')
+ >>> req.if_match = 'some-etag'
+ >>> req.if_modified_since = datetime(2005, 1, 1, 12, 0)
+ >>> req.environ['HTTP_ACCEPT_ENCODING'] = 'gzip'
+ >>> print req.headers
+ {'Host': 'localhost:80', 'If-Match': 'some-etag', 'Accept-Encoding': 'gzip', 'If-Modified-Since': 'Sat, 01 Jan 2005 12:00:00 GMT'}
+ >>> req.remove_conditional_headers()
+ >>> print req.headers
+ {'Host': 'localhost:80'}
+
+Some headers are handled specifically (more should be added):
+
+ >>> req = Request.blank('/')
+ >>> req.if_none_match = 'xxx'
+ >>> 'xxx' in req.if_none_match
+ True
+ >>> 'yyy' in req.if_none_match
+ False
+ >>> req.if_modified_since = datetime(2005, 1, 1, 12, 0)
+ >>> req.if_modified_since < datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
+ True
+ >>> req.user_agent
+ ''
+ >>> req.user_agent = 'MSIE-Win'
+ >>> req.user_agent
+ 'MSIE-Win'
+
+Accept-* headers are parsed into read-only objects that support
+containment tests, and some useful methods. Note that parameters on
+mime types are not supported.
+
+ >>> req = Request.blank('/')
+ >>> req.environ['HTTP_ACCEPT'] = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
+ >>> req.accept # doctest: +ELLIPSIS
+ <MIMEAccept at ... Accept: text/*;q=0.3, text/html;q=0.7, text/html, text/html;q=0.4, */*;q=0.5>
+ >>> for item, quality in req.accept._parsed:
+ ... print '%s: %0.1f' % (item, quality)
+ text/*: 0.3
+ text/html: 0.7
+ text/html: 1.0
+ text/html: 0.4
+ */*: 0.5
+ >>> '%0.1f' % req.accept.quality('text/html')
+ '0.3'
+ >>> req.accept.first_match(['text/plain', 'text/html', 'image/png'])
+ 'text/plain'
+ >>> 'image/png' in req.accept
+ True
+ >>> req.environ['HTTP_ACCEPT'] = "text/html, application/xml; q=0.7, text/*; q=0.5, */*; q=0.1"
+ >>> req.accept # doctest: +ELLIPSIS
+ <MIMEAccept at ... Accept: text/html, application/xml;q=0.7, text/*;q=0.5, */*;q=0.1>
+ >>> req.accept.best_match(['text/plain', 'application/xml'])
+ 'application/xml'
+ >>> req.accept.first_match(['application/xml', 'text/html'])
+ 'application/xml'
+ >>> req.accept = "text/html, application/xml, text/*; q=0.5"
+ >>> 'image/png' in req.accept
+ False
+ >>> 'text/plain' in req.accept
+ True
+ >>> req.accept_charset = 'utf8'
+ >>> 'UTF8' in req.accept_charset
+ True
+ >>> 'gzip' in req.accept_encoding
+ False
+ >>> req.accept_encoding = 'gzip'
+ >>> 'GZIP' in req.accept_encoding
+ True
+ >>> req.accept_language = {'en-US': 0.5, 'es': 0.7}
+ >>> str(req.accept_language)
+ 'es;q=0.7, en-US;q=0.5'
+ >>> req.headers['Accept-Language']
+ 'es;q=0.7, en-US;q=0.5'
+ >>> req.accept_language.best_matches('en-GB')
+ ['es', 'en-US', 'en-GB']
+ >>> req.accept_language.best_matches('es')
+ ['es']
+ >>> req.accept_language.best_matches('ES')
+ ['es']
+
+The If-Range header is a combination of a possible conditional date or
+etag match::
+
+ >>> req = Request.blank('/')
+ >>> req.if_range = 'asdf'
+ >>> req.if_range
+ <IfRange etag=asdf, date=*>
+ >>> from webob import Response
+ >>> res = Response()
+ >>> res.etag = 'asdf'
+ >>> req.if_range.match_response(res)
+ True
+ >>> res.etag = None
+ >>> req.if_range.match_response(res)
+ False
+ >>> res.last_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
+ >>> req.if_range = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
+ >>> req.if_range
+ <IfRange etag=*, date=Sun, 01 Jan 2006 12:00:00 GMT>
+ >>> req.if_range.match_response(res)
+ True
+ >>> res.last_modified = datetime(2007, 1, 1, 12, 0, tzinfo=UTC)
+ >>> req.if_range.match_response(res)
+ False
+ >>> req = Request.blank('/')
+ >>> req.if_range
+ <Empty If-Range>
+ >>> req.if_range.match_response(res)
+ True
+
+Ranges work like so::
+
+ >>> req = Request.blank('/')
+ >>> req.range = (0, 100)
+ >>> req.range
+ <Range ranges=(0, 100)>
+ >>> str(req.range)
+ 'bytes=0-101'
+
+You can use them with responses::
+
+ >>> res = Response()
+ >>> res.content_range = req.range.content_range(1000)
+ >>> res.content_range
+ <ContentRange bytes 0-101/1000>
+ >>> str(res.content_range)
+ 'bytes 0-101/1000'
+ >>> start, end, length = res.content_range
+ >>> start, end, length
+ (0, 100, 1000)
+
+A quick test of caching the request body:
+
+ >>> from cStringIO import StringIO
+ >>> length = Request.request_body_tempfile_limit+10
+ >>> data = StringIO('x'*length)
+ >>> req = Request.blank('/')
+ >>> req.content_length = length
+ >>> req.body_file = data
+ >>> req.body_file
+ <cStringIO.StringI object at ...>
+ >>> len(req.body)
+ 10250
+ >>> req.body_file
+ <open file '<fdopen>', mode 'w+b' at ...>
+
+Some query tests:
+
+ >>> req = Request.blank('/')
+ >>> req.GET.get('unknown')
+ >>> req.GET.get('unknown', '?')
+ '?'
+ >>> req.POST.get('unknown')
+ >>> req.POST.get('unknown', '?')
+ '?'
+ >>> req.params.get('unknown')
+ >>> req.params.get('unknown', '?')
+ '?'
diff --git a/google_appengine/lib/webob/tests/test_response.py b/google_appengine/lib/webob/tests/test_response.py
new file mode 100755
index 0000000..5a9f657
--- /dev/null
+++ b/google_appengine/lib/webob/tests/test_response.py
@@ -0,0 +1,37 @@
+from webob import *
+
+def simple_app(environ, start_response):
+ start_response('200 OK', [
+ ('Content-Type', 'text/html; charset=utf8'),
+ ])
+ return ['OK']
+
+def test_response():
+ req = Request.blank('/')
+ res = req.get_response(simple_app)
+ assert res.status == '200 OK'
+ assert res.status_int == 200
+ assert res.body == "OK"
+ assert res.charset == 'utf8'
+ assert res.content_type == 'text/html'
+ res.status = 404
+ assert res.status == '404 Not Found'
+ assert res.status_int == 404
+ res.body = 'Not OK'
+ assert ''.join(res.app_iter) == 'Not OK'
+ res.charset = 'iso8859-1'
+ assert res.headers['content-type'] == 'text/html; charset=iso8859-1'
+ res.content_type = 'text/xml'
+ assert res.headers['content-type'] == 'text/xml; charset=iso8859-1'
+ res.headers = {'content-type': 'text/html'}
+ assert res.headers['content-type'] == 'text/html'
+ assert res.headerlist == [('content-type', 'text/html')]
+ res.set_cookie('x', 'y')
+ assert res.headers['set-cookie'].strip(';') == 'x=y; Path=/'
+ res = Response('a body', '200 OK', content_type='text/html')
+ res.encode_content()
+ assert res.content_encoding == 'gzip'
+ assert res.body == '\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xffKTH\xcaO\xa9\x04\x00\xf6\x86GI\x06\x00\x00\x00'
+ res.decode_content()
+ assert res.content_encoding is None
+ assert res.body == 'a body'
diff --git a/google_appengine/lib/webob/tests/test_response.txt b/google_appengine/lib/webob/tests/test_response.txt
new file mode 100644
index 0000000..270e139
--- /dev/null
+++ b/google_appengine/lib/webob/tests/test_response.txt
@@ -0,0 +1,254 @@
+This demonstrates how the Response object works, and tests it at the
+same time.
+
+ >>> from dtopt import ELLIPSIS
+ >>> from webob import Response, UTC
+ >>> from datetime import datetime
+ >>> res = Response('Test', status='200 OK')
+
+This is a minimal response object. We can do things like get and set
+the body:
+
+ >>> res.body
+ 'Test'
+ >>> res.body = 'Another test'
+ >>> res.body
+ 'Another test'
+ >>> res.body = 'Another'
+ >>> res.write(' test')
+ >>> res.app_iter
+ ['Another test']
+ >>> res.content_length
+ 12
+ >>> res.headers['content-length']
+ '12'
+
+Content-Length is only applied when setting the body to a string; you
+have to set it manually otherwise. There are also getters and setters
+for the various pieces:
+
+ >>> res.app_iter = ['test']
+ >>> print res.content_length
+ None
+ >>> res.content_length = 4
+ >>> res.status
+ '200 OK'
+ >>> res.status_int
+ 200
+ >>> res.headers
+ HeaderDict([('content-type', 'text/html; charset=utf8'), ('Content-Length', '4')])
+ >>> res.headerlist
+ [('content-type', 'text/html; charset=utf8'), ('Content-Length', '4')]
+
+Content-type and charset are handled separately as properties, though
+they are both in the ``res.headers['content-type']`` header:
+
+ >>> res.content_type
+ 'text/html'
+ >>> res.content_type = 'text/html'
+ >>> res.content_type
+ 'text/html'
+ >>> res.charset
+ 'utf8'
+ >>> res.charset = 'iso-8859-1'
+ >>> res.charset
+ 'iso-8859-1'
+ >>> res.content_type
+ 'text/html'
+ >>> res.headers['content-type']
+ 'text/html; charset=iso-8859-1'
+
+Cookie handling is done through methods:
+
+ >>> res.set_cookie('test', 'value')
+ >>> res.headers['set-cookie']
+ 'test=value; Path=/'
+ >>> res.set_cookie('test2', 'value2', max_age=10000)
+ >>> res.headers['set-cookie'] # We only see the last header
+ 'test2=value2; Max-Age=10000; Path=/'
+ >>> res.headers.getall('set-cookie')
+ ['test=value; Path=/', 'test2=value2; Max-Age=10000; Path=/']
+ >>> res.unset_cookie('test')
+ >>> res.headers.getall('set-cookie')
+ ['test2=value2; Max-Age=10000; Path=/']
+
+Most headers are available in a parsed getter/setter form through
+properties:
+
+ >>> res.age = 10
+ >>> res.age, res.headers['age']
+ (10, '10')
+ >>> res.allow = ['GET', 'PUT']
+ >>> res.allow, res.headers['allow']
+ (['GET', 'PUT'], 'GET, PUT')
+ >>> res.cache_control
+ <CacheControl ''>
+ >>> print res.cache_control.max_age
+ None
+ >>> res.cache_control.properties['max-age'] = None
+ >>> print res.cache_control.max_age
+ -1
+ >>> res.cache_control.max_age = 10
+ >>> res.cache_control
+ <CacheControl 'max-age=10'>
+ >>> res.headers['cache-control']
+ 'max-age=10'
+ >>> res.cache_control.max_stale = 10
+ Traceback (most recent call last):
+ ...
+ AttributeError: The property max-stale only applies to request Cache-Control
+ >>> res.cache_control = {}
+ >>> res.cache_control
+ <CacheControl ''>
+ >>> res.content_encoding = 'gzip'
+ >>> (res.content_encoding, res.headers['content-encoding'])
+ ('gzip', 'gzip')
+ >>> res.content_language = 'en'
+ >>> (res.content_language, res.headers['content-language'])
+ (['en'], 'en')
+ >>> res.content_location = 'http://localhost:8080'
+ >>> res.headers['content-location']
+ 'http://localhost:8080'
+ >>> res.content_range = (0, 100, 1000)
+ >>> (res.content_range, res.headers['content-range'])
+ (<ContentRange bytes 0-101/1000>, 'bytes 0-101/1000')
+ >>> res.date = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
+ >>> (res.date, res.headers['date'])
+ (datetime.datetime(2005, 1, 1, 12, 0, tzinfo=UTC), 'Sat, 01 Jan 2005 12:00:00 GMT')
+ >>> print res.etag
+ None
+ >>> res.etag = 'foo'
+ >>> (res.etag, res.headers['etag'])
+ ('foo', 'foo')
+ >>> res.expires = res.date
+ >>> res.retry_after = 120 # two minutes
+ >>> res.retry_after
+ datetime.datetime(...)
+ >>> res.server = 'Python/foo'
+ >>> res.headers['server']
+ 'Python/foo'
+ >>> res.vary = ['Cookie']
+ >>> (res.vary, res.headers['vary'])
+ (['Cookie'], 'Cookie')
+
+The location header will try to absolutify itself if you have a
+request object attached.
+
+ >>> res.location = '/test.html'
+ >>> from webob import Request
+ >>> res.request = Request.blank('/')
+ >>> res.location
+ 'http://localhost/test.html'
+ >>> res.request = None
+ >>> res.location
+ '/test.html'
+ >>> res.request = Request.blank('/')
+ >>> res.location = '/test2.html'
+ >>> res.request = None
+ >>> res.location
+ 'http://localhost/test2.html'
+
+There's some conditional response handling too (you have to turn on
+conditioanl_response)::
+
+ >>> res = Response(conditional_response=True)
+ >>> req = Request.blank('/')
+ >>> res.etag = 'tag'
+ >>> req.if_none_match = 'tag'
+ >>> req.get_response(res)
+ <Response ... 304 Not Modified>
+ >>> res.etag = 'other-tag'
+ >>> req.get_response(res)
+ <Response ... 200 OK>
+ >>> del req.if_none_match
+ >>> req.if_modified_since = datetime(2005, 1, 1, 12, 1, tzinfo=UTC)
+ >>> res.last_modified = datetime(2005, 1, 1, 12, 1, tzinfo=UTC)
+ >>> req.get_response(res)
+ <Response ... 304 Not Modified>
+ >>> res.last_modified = datetime(2006, 1, 1, 12, 1, tzinfo=UTC)
+ >>> req.get_response(res)
+ <Response ... 200 OK>
+ >>> res.last_modified = None
+ >>> req.get_response(res)
+ <Response ... 200 OK>
+
+Also range response::
+
+ >>> res = Response(conditional_response=True)
+ >>> req = Request.blank('/')
+ >>> res.body = '0123456789'
+ >>> req.range = (1, 5)
+ >>> result = req.get_response(res)
+ >>> result.body
+ '1234'
+ >>> result.content_range
+ <ContentRange bytes 1-6/10>
+ >>> tuple(result.content_range)
+ (1, 5, 10)
+ >>> # Now an invalid range:
+ >>> req.range = (0, 20)
+ >>> str(req.range)
+ 'bytes=0-21'
+ >>> result = req.get_response(res)
+ >>> result.body
+ '0123456789'
+ >>> print result.content_range
+ None
+
+That was easier; we'll try it with a iterator for the body::
+
+ >>> res = Response(conditional_response=True)
+ >>> res.app_iter = ['01234', '567', '89']
+ >>> req = Request.blank('/')
+ >>> req.range = (1, 5)
+ >>> result = req.get_response(res)
+ >>> # Because we don't know the length of the app_iter, this
+ >>> # doesn't work:
+ >>> result.body
+ '0123456789'
+ >>> print result.content_range
+ None
+ >>> req.range = (5, None)
+ >>> result = req.get_response(res)
+ >>> result.body
+ '56789'
+ >>> result.content_range
+ <ContentRange bytes 5-*/10>
+ >>> # If we set Content-Length then we can use it with an app_iter
+ >>> res.content_length = 10
+ >>> req.range = (1, 5)
+ >>> result = req.get_response(res)
+ >>> result.body
+ '1234'
+ >>> result.content_range
+ <ContentRange bytes 1-6/10>
+ >>> # And trying If-modified-since
+ >>> res.etag = 'foobar'
+ >>> req.if_range = 'foobar'
+ >>> req.if_range
+ <IfRange etag=foobar, date=*>
+ >>> result = req.get_response(res)
+ >>> result.content_range
+ <ContentRange bytes 1-6/10>
+ >>> req.if_range = 'blah'
+ >>> result = req.get_response(res)
+ >>> result.content_range
+ >>> req.if_range = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
+ >>> res.last_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
+ >>> result = req.get_response(res)
+ >>> result.content_range
+ <ContentRange bytes 1-6/10>
+ >>> res.last_modified = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
+ >>> result = req.get_response(res)
+ >>> result.content_range
+
+Some tests of exceptions::
+
+ >>> from webob import exc
+ >>> res = exc.HTTPNotFound('Not found!')
+ >>> res.exception.content_type = 'text/plain'
+ >>> res.content_type
+ 'text/plain'
+ >>> res = exc.HTTPNotModified()
+ >>> res.headers
+ HeaderDict([])
diff --git a/google_appengine/lib/webob/webob/__init__.py b/google_appengine/lib/webob/webob/__init__.py
new file mode 100755
index 0000000..bbb69c6
--- /dev/null
+++ b/google_appengine/lib/webob/webob/__init__.py
@@ -0,0 +1,2252 @@
+from cStringIO import StringIO
+import sys
+import cgi
+import urllib
+import urlparse
+import re
+import textwrap
+from Cookie import BaseCookie
+from rfc822 import parsedate_tz, mktime_tz, formatdate
+from datetime import datetime, date, timedelta, tzinfo
+import time
+import calendar
+import tempfile
+import warnings
+from webob.datastruct import EnvironHeaders
+from webob.multidict import MultiDict, UnicodeMultiDict, NestedMultiDict, NoVars
+from webob.etag import AnyETag, NoETag, ETagMatcher, IfRange, NoIfRange
+from webob.headerdict import HeaderDict
+from webob.statusreasons import status_reasons
+from webob.cachecontrol import CacheControl, serialize_cache_control
+from webob.acceptparse import Accept, MIMEAccept, NilAccept, MIMENilAccept, NoAccept
+from webob.byterange import Range, ContentRange
+
+_CHARSET_RE = re.compile(r';\s*charset=([^;]*)', re.I)
+_SCHEME_RE = re.compile(r'^[a-z]+:', re.I)
+_PARAM_RE = re.compile(r'([a-z0-9]+)=(?:"([^"]*)"|([a-z0-9_.-]*))', re.I)
+_OK_PARAM_RE = re.compile(r'^[a-z0-9_.-]+$', re.I)
+
+__all__ = ['Request', 'Response', 'UTC', 'day', 'week', 'hour', 'minute', 'second', 'month', 'year', 'html_escape']
+
+class _UTC(tzinfo):
+ def dst(self, dt):
+ return timedelta(0)
+ def utcoffset(self, dt):
+ return timedelta(0)
+ def tzname(self, dt):
+ return 'UTC'
+ def __repr__(self):
+ return 'UTC'
+
+UTC = _UTC()
+
+def html_escape(s):
+ """HTML-escape a string or object
+
+ This converts any non-string objects passed into it to strings
+ (actually, using ``unicode()``). All values returned are
+ non-unicode strings (using ``&#num;`` entities for all non-ASCII
+ characters).
+
+ None is treated specially, and returns the empty string.
+ """
+ if s is None:
+ return ''
+ if not isinstance(s, basestring):
+ if hasattr(s, '__unicode__'):
+ s = unicode(s)
+ else:
+ s = str(s)
+ s = cgi.escape(s, True)
+ if isinstance(s, unicode):
+ s = s.encode('ascii', 'xmlcharrefreplace')
+ return s
+
+def timedelta_to_seconds(td):
+ """
+ Converts a timedelta instance to seconds.
+ """
+ return td.seconds + (td.days*24*60*60)
+
+day = timedelta(days=1)
+week = timedelta(weeks=1)
+hour = timedelta(hours=1)
+minute = timedelta(minutes=1)
+second = timedelta(seconds=1)
+# Estimate, I know; good enough for expirations
+month = timedelta(days=30)
+year = timedelta(days=365)
+
+class _NoDefault:
+ def __repr__(self):
+ return '(No Default)'
+NoDefault = _NoDefault()
+
+class environ_getter(object):
+ """For delegating an attribute to a key in self.environ."""
+
+ def __init__(self, key, default='', default_factory=None,
+ settable=True, deletable=True, doc=None,
+ rfc_section=None):
+ self.key = key
+ self.default = default
+ self.default_factory = default_factory
+ self.settable = settable
+ self.deletable = deletable
+ docstring = "Gets"
+ if self.settable:
+ docstring += " and sets"
+ if self.deletable:
+ docstring += " and deletes"
+ docstring += " the %r key from the environment." % self.key
+ docstring += _rfc_reference(self.key, rfc_section)
+ if doc:
+ docstring += '\n\n' + textwrap.dedent(doc)
+ self.__doc__ = docstring
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ if self.key not in obj.environ:
+ if self.default_factory:
+ val = obj.environ[self.key] = self.default_factory()
+ return val
+ else:
+ return self.default
+ return obj.environ[self.key]
+
+ def __set__(self, obj, value):
+ if not self.settable:
+ raise AttributeError("Read-only attribute (key %r)" % self.key)
+ if value is None:
+ if self.key in obj.environ:
+ del obj.environ[self.key]
+ else:
+ obj.environ[self.key] = value
+
+ def __delete__(self, obj):
+ if not self.deletable:
+ raise AttributeError("You cannot delete the key %r" % self.key)
+ del obj.environ[self.key]
+
+ def __repr__(self):
+ return '<Proxy for WSGI environ %r key>' % self.key
+
+class header_getter(object):
+ """For delegating an attribute to a header in self.headers"""
+
+ def __init__(self, header, default=None,
+ settable=True, deletable=True, doc=None, rfc_section=None):
+ self.header = header
+ self.default = default
+ self.settable = settable
+ self.deletable = deletable
+ docstring = "Gets"
+ if self.settable:
+ docstring += " and sets"
+ if self.deletable:
+ docstring += " and deletes"
+ docstring += " they header %s from the headers" % self.header
+ docstring += _rfc_reference(self.header, rfc_section)
+ if doc:
+ docstring += '\n\n' + textwrap.dedent(doc)
+ self.__doc__ = docstring
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ if self.header not in obj.headers:
+ return self.default
+ else:
+ return obj.headers[self.header]
+
+ def __set__(self, obj, value):
+ if not self.settable:
+ raise AttributeError("Read-only attribute (header %s)" % self.header)
+ if value is None:
+ if self.header in obj.headers:
+ del obj.headers[self.header]
+ else:
+ obj.headers[self.header] = value
+
+ def __delete__(self, obj):
+ if not self.deletable:
+ raise AttributeError("You cannot delete the header %s" % self.header)
+ del obj.headers[self.header]
+
+ def __repr__(self):
+ return '<Proxy for header %s>' % self.header
+
+class converter(object):
+ """
+ Wraps a decorator, and applies conversion for that decorator
+ """
+ def __init__(self, decorator, getter_converter, setter_converter, convert_name=None, doc=None, converter_args=()):
+ self.decorator = decorator
+ self.getter_converter = getter_converter
+ self.setter_converter = setter_converter
+ self.convert_name = convert_name
+ self.converter_args = converter_args
+ docstring = decorator.__doc__ or ''
+ docstring += " Converts it as a "
+ if convert_name:
+ docstring += convert_name + '.'
+ else:
+ docstring += "%r and %r." % (getter_converter, setter_converter)
+ if doc:
+ docstring += '\n\n' + textwrap.dedent(doc)
+ self.__doc__ = docstring
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ value = self.decorator.__get__(obj, type)
+ return self.getter_converter(value, *self.converter_args)
+
+ def __set__(self, obj, value):
+ value = self.setter_converter(value, *self.converter_args)
+ self.decorator.__set__(obj, value)
+
+ def __delete__(self, obj):
+ self.decorator.__delete__(obj)
+
+ def __repr__(self):
+ if self.convert_name:
+ name = ' %s' % self.convert_name
+ else:
+ name = ''
+ return '<Converted %r%s>' % (self.decorator, name)
+
+def _rfc_reference(header, section):
+ if not section:
+ return ''
+ major_section = section.split('.')[0]
+ link = 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec%s.html#sec%s' % (
+ major_section, section)
+ if header.startswith('HTTP_'):
+ header = header[5:].title().replace('_', '-')
+ return " For more information on %s see `section %s <%s>`_." % (
+ header, section, link)
+
+class deprecated_property(object):
+ """
+ Wraps a decorator, with a deprecation warning or error
+ """
+ def __init__(self, decorator, attr, message, warning=True):
+ self.decorator = decorator
+ self.attr = attr
+ self.message = message
+ self.warning = warning
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ self.warn()
+ return self.decorator.__get__(obj, type)
+
+ def __set__(self, obj, value):
+ self.warn()
+ self.decorator.__set__(obj, value)
+
+ def __delete__(self, obj):
+ self.warn()
+ self.decorator.__delete__(obj)
+
+ def __repr__(self):
+ return '<Deprecated attribute %s: %r>' % (
+ self.attr,
+ self.decorator)
+
+ def warn(self):
+ if not self.warning:
+ raise DeprecationWarning(
+ 'The attribute %s is deprecated: %s' % (self.attr, self.message))
+ else:
+ warnings.warn(
+ 'The attribute %s is deprecated: %s' % (self.attr, self.message),
+ DeprecationWarning,
+ stacklevel=3)
+
+def _parse_date(value):
+ if not value:
+ return None
+ t = parsedate_tz(value)
+ if t is None:
+ # Could not parse
+ return None
+ t = mktime_tz(t)
+ return datetime.fromtimestamp(t, UTC)
+
+def _serialize_date(dt):
+ if dt is None:
+ return None
+ if isinstance(dt, unicode):
+ dt = dt.encode('ascii')
+ if isinstance(dt, str):
+ return dt
+ if isinstance(dt, timedelta):
+ dt = datetime.now() + dt
+ if isinstance(dt, (datetime, date)):
+ dt = dt.timetuple()
+ if isinstance(dt, (tuple, time.struct_time)):
+ dt = calendar.timegm(dt)
+ if not isinstance(dt, (float, int)):
+ raise ValueError(
+ "You must pass in a datetime, date, time tuple, or integer object, not %r" % dt)
+ return formatdate(dt)
+
+def _parse_date_delta(value):
+ """
+ like _parse_date, but also handle delta seconds
+ """
+ if not value:
+ return None
+ try:
+ value = int(value)
+ except ValueError:
+ pass
+ else:
+ delta = timedelta(seconds=value)
+ return datetime.now() + delta
+ return _parse_date(value)
+
+def _serialize_date_delta(value):
+ if not value and value != 0:
+ return None
+ if isinstance(value, (float, int)):
+ return str(int(value))
+ return _serialize_date(value)
+
+def _parse_etag(value, default=True):
+ if value is None:
+ value = ''
+ value = value.strip()
+ if not value:
+ if default:
+ return AnyETag
+ else:
+ return NoETag
+ if value == '*':
+ return AnyETag
+ else:
+ return ETagMatcher.parse(value)
+
+def _serialize_etag(value, default=True):
+ if value is None:
+ return None
+ if value is AnyETag:
+ if default:
+ return None
+ else:
+ return '*'
+ return str(value)
+
+def _parse_if_range(value):
+ if not value:
+ return NoIfRange
+ else:
+ return IfRange.parse(value)
+
+def _serialize_if_range(value):
+ if value is None:
+ return value
+ if isinstance(value, (datetime, date)):
+ return _serialize_date(value)
+ if not isinstance(value, str):
+ value = str(value)
+ return value or None
+
+def _parse_range(value):
+ if not value:
+ return None
+ # Might return None too:
+ return Range.parse(value)
+
+def _serialize_range(value):
+ if isinstance(value, (list, tuple)):
+ if len(value) != 2:
+ raise ValueError(
+ "If setting .range to a list or tuple, it must be of length 2 (not %r)"
+ % value)
+ value = Range([value])
+ if value is None:
+ return None
+ value = str(value)
+ return value or None
+
+def _parse_int(value):
+ if value is None or value == '':
+ return None
+ return int(value)
+
+def _parse_int_safe(value):
+ if value is None or value == '':
+ return None
+ try:
+ return int(value)
+ except ValueError:
+ return None
+
+def _serialize_int(value):
+ if value is None:
+ return None
+ return str(value)
+
+def _parse_content_range(value):
+ if not value or not value.strip():
+ return None
+ # May still return None
+ return ContentRange.parse(value)
+
+def _serialize_content_range(value):
+ if value is None:
+ return None
+ if isinstance(value, (tuple, list)):
+ if len(value) not in (2, 3):
+ raise ValueError(
+ "When setting content_range to a list/tuple, it must "
+ "be length 2 or 3 (not %r)" % value)
+ if len(value) == 2:
+ begin, end = value
+ length = None
+ else:
+ begin, end, length = value
+ value = ContentRange(begin, end, length)
+ value = str(value).strip()
+ if not value:
+ return None
+ return value
+
+def _parse_list(value):
+ if value is None:
+ return None
+ value = value.strip()
+ if not value:
+ return None
+ return [v.strip() for v in value.split(',')
+ if v.strip()]
+
+def _serialize_list(value):
+ if not value:
+ return None
+ if isinstance(value, unicode):
+ value = str(value)
+ if isinstance(value, str):
+ return value
+ return ', '.join(map(str, value))
+
+def _parse_accept(value, header_name, AcceptClass, NilClass):
+ if not value:
+ return NilClass(header_name)
+ return AcceptClass(header_name, value)
+
+def _serialize_accept(value, header_name, AcceptClass, NilClass):
+ if not value or isinstance(value, NilClass):
+ return None
+ if isinstance(value, (list, tuple, dict)):
+ value = NilClass(header_name) + value
+ value = str(value).strip()
+ if not value:
+ return None
+ return value
+
+class Request(object):
+
+ ## Options:
+ charset = None
+ unicode_errors = 'strict'
+ decode_param_names = False
+ ## The limit after which request bodies should be stored on disk
+ ## if they are read in (under this, and the request body is stored
+ ## in memory):
+ request_body_tempfile_limit = 10*1024
+
+ def __init__(self, environ=None, environ_getter=None, charset=NoDefault, unicode_errors=NoDefault,
+ decode_param_names=NoDefault):
+ if environ is None and environ_getter is None:
+ raise TypeError(
+ "You must provide one of environ or environ_getter")
+ if environ is not None and environ_getter is not None:
+ raise TypeError(
+ "You can only provide one of the environ and environ_getter arguments")
+ if environ is None:
+ self._environ_getter = environ_getter
+ else:
+ if not isinstance(environ, dict):
+ raise TypeError(
+ "Bad type for environ: %s" % type(environ))
+ self._environ = environ
+ if charset is not NoDefault:
+ self.__dict__['charset'] = charset
+ if unicode_errors is not NoDefault:
+ self.__dict__['unicode_errors'] = unicode_errors
+ if decode_param_names is not NoDefault:
+ self.__dict__['decode_param_names'] = decode_param_names
+
+ def __setattr__(self, attr, value, DEFAULT=[]):
+ ## FIXME: I don't know why I need this guard (though experimentation says I do)
+ if getattr(self.__class__, attr, DEFAULT) is not DEFAULT or attr.startswith('_'):
+ object.__setattr__(self, attr, value)
+ else:
+ self.environ.setdefault('webob.adhoc_attrs', {})[attr] = value
+
+ def __getattr__(self, attr):
+ ## FIXME: I don't know why I need this guard (though experimentation says I do)
+ if attr in self.__class__.__dict__:
+ return object.__getattribute__(self, attr)
+ try:
+ return self.environ['webob.adhoc_attrs'][attr]
+ except KeyError:
+ raise AttributeError(attr)
+
+ def __delattr__(self, attr):
+ ## FIXME: I don't know why I need this guard (though experimentation says I do)
+ if attr in self.__class__.__dict__:
+ return object.__delattr__(self, attr)
+ try:
+ del self.environ['webob.adhoc_attrs'][attr]
+ except KeyError:
+ raise AttributeError(attr)
+
+ def environ(self):
+ """
+ The WSGI environment dictionary for this request
+ """
+ return self._environ_getter()
+ environ = property(environ, doc=environ.__doc__)
+
+ def _environ_getter(self):
+ return self._environ
+
+ def _body_file__get(self):
+ """
+ Access the body of the request (wsgi.input) as a file-like
+ object.
+
+ If you set this value, CONTENT_LENGTH will also be updated
+ (either set to -1, 0 if you delete the attribute, or if you
+ set the attribute to a string then the length of the string).
+ """
+ return self.environ['wsgi.input']
+ def _body_file__set(self, value):
+ if isinstance(value, str):
+ length = len(value)
+ value = StringIO(value)
+ else:
+ length = -1
+ self.environ['wsgi.input'] = value
+ self.environ['CONTENT_LENGTH'] = str(length)
+ def _body_file__del(self):
+ self.environ['wsgi.input'] = StringIO('')
+ self.environ['CONTENT_LENGTH'] = '0'
+ body_file = property(_body_file__get, _body_file__set, _body_file__del, doc=_body_file__get.__doc__)
+
+ scheme = environ_getter('wsgi.url_scheme')
+ method = environ_getter('REQUEST_METHOD')
+ script_name = environ_getter('SCRIPT_NAME')
+ path_info = environ_getter('PATH_INFO')
+ ## FIXME: should I strip out parameters?:
+ content_type = environ_getter('CONTENT_TYPE', rfc_section='14.17')
+ content_length = converter(
+ environ_getter('CONTENT_LENGTH', rfc_section='14.13'),
+ _parse_int_safe, _serialize_int, 'int')
+ remote_user = environ_getter('REMOTE_USER', default=None)
+ remote_addr = environ_getter('REMOTE_ADDR', default=None)
+ query_string = environ_getter('QUERY_STRING')
+ server_name = environ_getter('SERVER_NAME')
+ server_port = converter(
+ environ_getter('SERVER_PORT'),
+ _parse_int, _serialize_int, 'int')
+
+ _headers = None
+
+ def _headers__get(self):
+ """
+ All the request headers as a case-insensitive dictionary-like
+ object.
+ """
+ if self._headers is None:
+ self._headers = EnvironHeaders(self.environ)
+ return self._headers
+
+ def _headers__set(self, value):
+ self.headers.clear()
+ self.headers.update(value)
+
+ headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
+
+ def host_url(self):
+ """
+ The URL through the host (no path)
+ """
+ e = self.environ
+ url = e['wsgi.url_scheme'] + '://'
+ if e.get('HTTP_HOST'):
+ host = e['HTTP_HOST']
+ if ':' in host:
+ host, port = host.split(':', 1)
+ else:
+
+ port = None
+ else:
+ host = e['SERVER_NAME']
+ port = e['SERVER_PORT']
+ if self.environ['wsgi.url_scheme'] == 'https':
+ if port == '443':
+ port = None
+ elif self.environ['wsgi.url_scheme'] == 'http':
+ if port == '80':
+ port = None
+ url += host
+ if port:
+ url += ':%s' % port
+ return url
+ host_url = property(host_url, doc=host_url.__doc__)
+
+ def application_url(self):
+ """
+ The URL including SCRIPT_NAME (no PATH_INFO or query string)
+ """
+ return self.host_url + urllib.quote(self.environ.get('SCRIPT_NAME', ''))
+ application_url = property(application_url, doc=application_url.__doc__)
+
+ def path_url(self):
+ """
+ The URL including SCRIPT_NAME and PATH_INFO, but not QUERY_STRING
+ """
+ return self.application_url + urllib.quote(self.environ.get('PATH_INFO', ''))
+ path_url = property(path_url, doc=path_url.__doc__)
+
+ def path(self):
+ """
+ The path of the request, without host or query string
+ """
+ return urllib.quote(self.script_name) + urllib.quote(self.path_info)
+ path = property(path, doc=path.__doc__)
+
+ def path_qs(self):
+ """
+ The path of the request, without host but with query string
+ """
+ path = self.path
+ qs = self.environ.get('QUERY_STRING')
+ if qs:
+ path += '?' + qs
+ return path
+ path_qs = property(path_qs, doc=path_qs.__doc__)
+
+ def url(self):
+ """
+ The full request URL, including QUERY_STRING
+ """
+ url = self.path_url
+ if self.environ.get('QUERY_STRING'):
+ url += '?' + self.environ['QUERY_STRING']
+ return url
+ url = property(url, doc=url.__doc__)
+
+ def relative_url(self, other_url, to_application=False):
+ """
+ Resolve other_url relative to the request URL.
+
+ If ``to_application`` is True, then resolve it relative to the
+ URL with only SCRIPT_NAME
+ """
+ if to_application:
+ url = self.application_url
+ if not url.endswith('/'):
+ url += '/'
+ else:
+ url = self.path_url
+ return urlparse.urljoin(url, other_url)
+
+ def path_info_pop(self):
+ """
+ 'Pops' off the next segment of PATH_INFO, pushing it onto
+ SCRIPT_NAME, and returning the popped segment. Returns None if
+ there is nothing left on PATH_INFO.
+
+ Does not return ``''`` when there's an empty segment (like
+ ``/path//path``); these segments are just ignored.
+ """
+ path = self.path_info
+ if not path:
+ return None
+ while path.startswith('/'):
+ self.script_name += '/'
+ path = path[1:]
+ if '/' not in path:
+ self.script_name += path
+ self.path_info = ''
+ return path
+ else:
+ segment, path = path.split('/', 1)
+ self.path_info = '/' + path
+ self.script_name += segment
+ return segment
+
+ def path_info_peek(self):
+ """
+ Returns the next segment on PATH_INFO, or None if there is no
+ next segment. Doesn't modify the environment.
+ """
+ path = self.path_info
+ if not path:
+ return None
+ path = path.lstrip('/')
+ return path.split('/', 1)[0]
+
+ def _urlvars__get(self):
+ """
+ Return any *named* variables matched in the URL.
+
+ Takes values from ``environ['wsgiorg.routing_args']``.
+ Systems like ``routes`` set this value.
+ """
+ if 'paste.urlvars' in self.environ:
+ return self.environ['paste.urlvars']
+ elif 'wsgiorg.routing_args' in self.environ:
+ return self.environ['wsgiorg.routing_args'][1]
+ else:
+ result = {}
+ self.environ['wsgiorg.routing_args'] = ((), result)
+ return result
+
+ def _urlvars__set(self, value):
+ environ = self.environ
+ if 'wsgiorg.routing_args' in environ:
+ environ['wsgiorg.routing_args'] = (environ['wsgiorg.routing_args'][0], value)
+ if 'paste.urlvars' in environ:
+ del environ['paste.urlvars']
+ elif 'paste.urlvars' in environ:
+ environ['paste.urlvars'] = value
+ else:
+ environ['wsgiorg.routing_args'] = ((), value)
+
+ def _urlvars__del(self):
+ if 'paste.urlvars' in self.environ:
+ del self.environ['paste.urlvars']
+ if 'wsgiorg.routing_args' in self.environ:
+ if not self.environ['wsgiorg.routing_args'][0]:
+ del self.environ['wsgiorg.routing_args']
+ else:
+ self.environ['wsgiorg.routing_args'] = (self.environ['wsgiorg.routing_args'][0], {})
+
+ urlvars = property(_urlvars__get, _urlvars__set, _urlvars__del, doc=_urlvars__get.__doc__)
+
+ def _urlargs__get(self):
+ """
+ Return any *positional* variables matched in the URL.
+
+ Takes values from ``environ['wsgiorg.routing_args']``.
+ Systems like ``routes`` set this value.
+ """
+ if 'wsgiorg.routing_args' in self.environ:
+ return self.environ['wsgiorg.routing_args'][0]
+ else:
+ # Since you can't update this value in-place, we don't need
+ # to set the key in the environment
+ return ()
+
+ def _urlargs__set(self, value):
+ environ = self.environ
+ if 'paste.urlvars' in environ:
+ # Some overlap between this and wsgiorg.routing_args; we need
+ # wsgiorg.routing_args to make this work
+ routing_args = (value, environ.pop('paste.urlvars'))
+ elif 'wsgiorg.routing_args' in environ:
+ routing_args = (value, environ['wsgiorg.routing_args'][1])
+ else:
+ routing_args = (value, {})
+ environ['wsgiorg.routing_args'] = routing_args
+
+ def _urlargs__del(self):
+ if 'wsgiorg.routing_args' in self.environ:
+ if not self.environ['wsgiorg.routing_args'][1]:
+ del self.environ['wsgiorg.routing_args']
+ else:
+ self.environ['wsgiorg.routing_args'] = ((), self.environ['wsgiorg.routing_args'][1])
+
+ urlargs = property(_urlargs__get, _urlargs__set, _urlargs__del, _urlargs__get.__doc__)
+
+ def is_xhr(self):
+ """Returns a boolean if X-Requested-With is present and ``XMLHttpRequest``
+
+ Note: this isn't set by every XMLHttpRequest request, it is
+ only set if you are using a Javascript library that sets it
+ (or you set the header yourself manually). Currently
+ Prototype and jQuery are known to set this header."""
+ return self.environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest'
+ is_xhr = property(is_xhr, doc=is_xhr.__doc__)
+
+ def _host__get(self):
+ """Host name provided in HTTP_HOST, with fall-back to SERVER_NAME"""
+ if 'HTTP_HOST' in self.environ:
+ return self.environ['HTTP_HOST']
+ else:
+ return '%(SERVER_NAME)s:%(SERVER_PORT)s' % self.environ
+ def _host__set(self, value):
+ self.environ['HTTP_HOST'] = value
+ def _host__del(self):
+ if 'HTTP_HOST' in self.environ:
+ del self.environ['HTTP_HOST']
+ host = property(_host__get, _host__set, _host__del, doc=_host__get.__doc__)
+
+ def _body__get(self):
+ """
+ Return the content of the request body.
+ """
+ try:
+ length = int(self.environ.get('CONTENT_LENGTH', '0'))
+ except ValueError:
+ return ''
+ c = self.body_file.read(length)
+ tempfile_limit = self.request_body_tempfile_limit
+ if tempfile_limit and len(c) > tempfile_limit:
+ fileobj = tempfile.TemporaryFile()
+ fileobj.write(c)
+ fileobj.seek(0)
+ else:
+ fileobj = StringIO(c)
+ # We don't want/need to lose CONTENT_LENGTH here (as setting
+ # self.body_file would do):
+ self.environ['wsgi.input'] = fileobj
+ return c
+
+ def _body__set(self, value):
+ if value is None:
+ del self.body
+ return
+ if not isinstance(value, str):
+ raise TypeError(
+ "You can only set Request.body to a str (not %r)" % type(value))
+ body_file = StringIO(value)
+ self.body_file = body_file
+ self.environ['CONTENT_LENGTH'] = str(len(value))
+
+ def _body__del(self, value):
+ del self.body_file
+
+ body = property(_body__get, _body__set, _body__del, doc=_body__get.__doc__)
+
+ def str_POST(self):
+ """
+ Return a MultiDict containing all the variables from a POST
+ form request. Does *not* return anything for non-POST
+ requests or for non-form requests (returns empty dict-like
+ object in that case).
+ """
+ env = self.environ
+ if self.method != 'POST':
+ return NoVars('Not a POST request')
+ if 'webob._parsed_post_vars' in env:
+ vars, body_file = env['webob._parsed_post_vars']
+ if body_file is self.body_file:
+ return vars
+ # Paste compatibility:
+ if 'paste.parsed_formvars' in env:
+ # from paste.request.parse_formvars
+ vars, body_file = env['paste.parsed_formvars']
+ if body_file is self.body_file:
+ # FIXME: is it okay that this isn't *our* MultiDict?
+ return vars
+ content_type = self.content_type
+ if ';' in content_type:
+ content_type = content_type.split(';', 1)[0]
+ if content_type not in ('', 'application/x-www-form-urlencoded',
+ 'multipart/form-data'):
+ # Not an HTML form submission
+ return NoVars('Not an HTML form submission (Content-Type: %s)'
+ % content_type)
+ if 'CONTENT_LENGTH' not in env:
+ # FieldStorage assumes a default CONTENT_LENGTH of -1, but a
+ # default of 0 is better:
+ env['CONTENT_TYPE'] = '0'
+ fs_environ = env.copy()
+ fs_environ['QUERY_STRING'] = ''
+ fs = cgi.FieldStorage(fp=self.body_file,
+ environ=fs_environ,
+ keep_blank_values=True)
+ vars = MultiDict.from_fieldstorage(fs)
+ FakeCGIBody.update_environ(env, vars)
+ env['webob._parsed_post_vars'] = (vars, self.body_file)
+ return vars
+
+ str_POST = property(str_POST, doc=str_POST.__doc__)
+
+ str_postvars = deprecated_property(str_POST, 'str_postvars',
+ 'use str_POST instead')
+
+ def POST(self):
+ """
+ Like ``.str_POST``, but may decode values and keys
+ """
+ vars = self.str_POST
+ if self.charset:
+ vars = UnicodeMultiDict(vars, encoding=self.charset,
+ errors=self.unicode_errors,
+ decode_keys=self.decode_param_names)
+ return vars
+
+ POST = property(POST, doc=POST.__doc__)
+
+ postvars = deprecated_property(POST, 'postvars',
+ 'use POST instead')
+
+ def str_GET(self):
+ """
+ Return a MultiDict containing all the variables from the
+ QUERY_STRING.
+ """
+ env = self.environ
+ source = env.get('QUERY_STRING', '')
+ if 'webob._parsed_query_vars' in env:
+ vars, qs = env['webob._parsed_query_vars']
+ if qs == source:
+ return vars
+ if not source:
+ vars = MultiDict()
+ else:
+ vars = MultiDict(cgi.parse_qsl(
+ source, keep_blank_values=True,
+ strict_parsing=False))
+ env['webob._parsed_query_vars'] = (vars, source)
+ return vars
+
+ str_GET = property(str_GET, doc=str_GET.__doc__)
+
+ str_queryvars = deprecated_property(str_GET, 'str_queryvars',
+ 'use str_GET instead')
+
+
+ def GET(self):
+ """
+ Like ``.str_GET``, but may decode values and keys
+ """
+ vars = self.str_GET
+ if self.charset:
+ vars = UnicodeMultiDict(vars, encoding=self.charset,
+ errors=self.unicode_errors,
+ decode_keys=self.decode_param_names)
+ return vars
+
+ GET = property(GET, doc=GET.__doc__)
+
+ queryvars = deprecated_property(GET, 'queryvars',
+ 'use GET instead')
+
+ def str_params(self):
+ """
+ A dictionary-like object containing both the parameters from
+ the query string and request body.
+ """
+ return NestedMultiDict(self.str_GET, self.str_POST)
+
+ str_params = property(str_params, doc=str_params.__doc__)
+
+ def params(self):
+ """
+ Like ``.str_params``, but may decode values and keys
+ """
+ params = self.str_params
+ if self.charset:
+ params = UnicodeMultiDict(params, encoding=self.charset,
+ errors=self.unicode_errors,
+ decode_keys=self.decode_param_names)
+ return params
+
+ params = property(params, doc=params.__doc__)
+
+ def str_cookies(self):
+ """
+ Return a *plain* dictionary of cookies as found in the request.
+ """
+ env = self.environ
+ source = env.get('HTTP_COOKIE', '')
+ if 'webob._parsed_cookies' in env:
+ vars, var_source = env['webob._parsed_cookies']
+ if var_source == source:
+ return vars
+ vars = {}
+ if source:
+ cookies = BaseCookie()
+ cookies.load(source)
+ for name in cookies:
+ vars[name] = cookies[name].value
+ env['webob._parsed_cookies'] = (vars, source)
+ return vars
+
+ str_cookies = property(str_cookies, doc=str_cookies.__doc__)
+
+ def cookies(self):
+ """
+ Like ``.str_cookies``, but may decode values and keys
+ """
+ vars = self.str_cookies
+ if self.charset:
+ vars = UnicodeMultiDict(vars, encoding=self.charset,
+ errors=self.unicode_errors,
+ decode_keys=self.decode_param_names)
+ return vars
+
+ cookies = property(cookies, doc=cookies.__doc__)
+
+ def copy(self):
+ """
+ Copy the request and environment object.
+
+ This only does a shallow copy, except of wsgi.input
+ """
+ env = self.environ.copy()
+ data = self.body
+ tempfile_limit = self.request_body_tempfile_limit
+ if tempfile_limit and len(data) > tempfile_limit:
+ fileobj = tempfile.TemporaryFile()
+ fileobj.write(data)
+ fileobj.seek(0)
+ else:
+ fileobj = StringIO(data)
+ env['wsgi.input'] = fileobj
+ return self.__class__(env)
+
+ def copy_get(self):
+ """
+ Copies the request and environment object, but turning this request
+ into a GET along the way. If this was a POST request (or any other verb)
+ then it becomes GET, and the request body is thrown away.
+ """
+ env = self.environ.copy()
+ env['wsgi.input'] = StringIO('')
+ env['CONTENT_LENGTH'] = '0'
+ if 'CONTENT_TYPE' in env:
+ del env['CONTENT_TYPE']
+ env['REQUEST_METHOD'] = 'GET'
+ return self.__class__(env)
+
+ def remove_conditional_headers(self, remove_encoding=True):
+ """
+ Remove headers that make the request conditional.
+
+ These headers can cause the response to be 304 Not Modified,
+ which in some cases you may not want to be possible.
+
+ This does not remove headers like If-Match, which are used for
+ conflict detection.
+ """
+ for key in ['HTTP_IF_MATCH', 'HTTP_IF_MODIFIED_SINCE',
+ 'HTTP_IF_RANGE', 'HTTP_RANGE']:
+ if key in self.environ:
+ del self.environ[key]
+ if remove_encoding:
+ if 'HTTP_ACCEPT_ENCODING' in self.environ:
+ del self.environ['HTTP_ACCEPT_ENCODING']
+
+ accept = converter(
+ environ_getter('HTTP_ACCEPT', rfc_section='14.1'),
+ _parse_accept, _serialize_accept, 'MIME Accept',
+ converter_args=('Accept', MIMEAccept, MIMENilAccept))
+
+ accept_charset = converter(
+ environ_getter('HTTP_ACCEPT_CHARSET', rfc_section='14.2'),
+ _parse_accept, _serialize_accept, 'accept header',
+ converter_args=('Accept-Charset', Accept, NilAccept))
+
+ accept_encoding = converter(
+ environ_getter('HTTP_ACCEPT_ENCODING', rfc_section='14.3'),
+ _parse_accept, _serialize_accept, 'accept header',
+ converter_args=('Accept-Encoding', Accept, NoAccept))
+
+ accept_language = converter(
+ environ_getter('HTTP_ACCEPT_LANGUAGE', rfc_section='14.4'),
+ _parse_accept, _serialize_accept, 'accept header',
+ converter_args=('Accept-Language', Accept, NilAccept))
+
+ ## FIXME: 14.8 Authorization
+ ## http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.8
+
+ def _cache_control__get(self):
+ """
+ Get/set/modify the Cache-Control header (section `14.9
+ <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9>`_)
+ """
+ env = self.environ
+ value = env.get('HTTP_CACHE_CONTROL', '')
+ cache_header, cache_obj = env.get('webob._cache_control', (None, None))
+ if cache_obj is not None and cache_header == value:
+ return cache_obj
+ cache_obj = CacheControl.parse(value, type='request')
+ env['webob._cache_control'] = (value, cache_obj)
+ return cache_obj
+
+ def _cache_control__set(self, value):
+ env = self.environ
+ if not value:
+ value = ""
+ if isinstance(value, dict):
+ value = CacheControl(value, type='request')
+ elif isinstance(value, CacheControl):
+ str_value = str(value)
+ env['HTTP_CACHE_CONTROL'] = str_value
+ env['webob._cache_control'] = (str_value, value)
+ else:
+ env['HTTP_CACHE_CONTROL'] = str(value)
+ if 'webob._cache_control' in env:
+ del env['webob._cache_control']
+
+ def _cache_control__del(self, value):
+ env = self.environ
+ if 'HTTP_CACHE_CONTROL' in env:
+ del env['HTTP_CACHE_CONTROL']
+ if 'webob._cache_control' in env:
+ del env['webob._cache_control']
+
+ cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__)
+
+ date = converter(
+ environ_getter('HTTP_DATE', rfc_section='14.8'),
+ _parse_date, _serialize_date, 'HTTP date')
+
+ if_match = converter(
+ environ_getter('HTTP_IF_MATCH', rfc_section='14.24'),
+ _parse_etag, _serialize_etag, 'ETag', converter_args=(True,))
+
+ if_modified_since = converter(
+ environ_getter('HTTP_IF_MODIFIED_SINCE', rfc_section='14.25'),
+ _parse_date, _serialize_date, 'HTTP date')
+
+ if_none_match = converter(
+ environ_getter('HTTP_IF_NONE_MATCH', rfc_section='14.26'),
+ _parse_etag, _serialize_etag, 'ETag', converter_args=(False,))
+
+ if_range = converter(
+ environ_getter('HTTP_IF_RANGE', rfc_section='14.27'),
+ _parse_if_range, _serialize_if_range, 'IfRange object')
+
+ if_unmodified_since = converter(
+ environ_getter('HTTP_IF_UNMODIFIED_SINCE', rfc_section='14.28'),
+ _parse_date, _serialize_date, 'HTTP date')
+
+ max_forwards = converter(
+ environ_getter('HTTP_MAX_FORWARDS', rfc_section='14.31'),
+ _parse_int, _serialize_int, 'int')
+
+ pragma = environ_getter('HTTP_PRAGMA', rfc_section='14.32')
+
+ range = converter(
+ environ_getter('HTTP_RANGE', rfc_section='14.35'),
+ _parse_range, _serialize_range, 'Range object')
+
+ referer = environ_getter('HTTP_REFERER', rfc_section='14.36')
+ referrer = referer
+
+ user_agent = environ_getter('HTTP_USER_AGENT', rfc_section='14.43')
+
+ def __repr__(self):
+ msg = '<%s at %x %s %s>' % (
+ self.__class__.__name__,
+ abs(id(self)), self.method, self.url)
+ return msg
+
+ def __str__(self):
+ url = self.url
+ host = self.host_url
+ assert url.startswith(host)
+ url = url[len(host):]
+ if 'Host' not in self.headers:
+ self.headers['Host'] = self.host
+ parts = ['%s %s' % (self.method, url)]
+ for name, value in sorted(self.headers.items()):
+ parts.append('%s: %s' % (name, value))
+ parts.append('')
+ parts.append(self.body)
+ return '\r\n'.join(parts)
+
+ def call_application(self, application, catch_exc_info=False):
+ """
+ Call the given WSGI application, returning ``(status_string,
+ headerlist, app_iter)``
+
+ Be sure to call ``app_iter.close()`` if it's there.
+
+ If catch_exc_info is true, then returns ``(status_string,
+ headerlist, app_iter, exc_info)``, where the fourth item may
+ be None, but won't be if there was an exception. If you don't
+ do this and there was an exception, the exception will be
+ raised directly.
+ """
+ captured = []
+ output = []
+ def start_response(status, headers, exc_info=None):
+ if exc_info is not None and not catch_exc_info:
+ raise exc_info[0], exc_info[1], exc_info[2]
+ captured[:] = [status, headers, exc_info]
+ return output.append
+ app_iter = application(self.environ, start_response)
+ if (not captured
+ or output):
+ try:
+ output.extend(app_iter)
+ finally:
+ if hasattr(app_iter, 'close'):
+ app_iter.close()
+ app_iter = output
+ if catch_exc_info:
+ return (captured[0], captured[1], app_iter, captured[2])
+ else:
+ return (captured[0], captured[1], app_iter)
+
+ # Will be filled in later:
+ ResponseClass = None
+
+ def get_response(self, application, catch_exc_info=False):
+ """
+ Like ``.call_application(application)``, except returns a
+ response object with ``.status``, ``.headers``, and ``.body``
+ attributes.
+
+ This will use ``self.ResponseClass`` to figure out the class
+ of the response object to return.
+ """
+ if catch_exc_info:
+ status, headers, app_iter, exc_info = self.call_application(
+ application, catch_exc_info=True)
+ del exc_info
+ else:
+ status, headers, app_iter = self.call_application(
+ application, catch_exc_info=False)
+ return self.ResponseClass(
+ status=status, headerlist=headers, app_iter=app_iter,
+ request=self)
+
+ #@classmethod
+ def blank(cls, path, environ=None, base_url=None, headers=None):
+ """
+ Create a blank request environ (and Request wrapper) with the
+ given path (path should be urlencoded), and any keys from
+ environ.
+
+ The path will become path_info, with any query string split
+ off and used.
+
+ All necessary keys will be added to the environ, but the
+ values you pass in will take precedence. If you pass in
+ base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will
+ be filled in from that value.
+ """
+ if _SCHEME_RE.search(path):
+ scheme, netloc, path, qs, fragment = urlparse.urlsplit(path)
+ if fragment:
+ raise TypeError(
+ "Path cannot contain a fragment (%r)" % fragment)
+ if qs:
+ path += '?' + qs
+ if ':' not in netloc:
+ if scheme == 'http':
+ netloc += ':80'
+ elif scheme == 'https':
+ netloc += ':443'
+ else:
+ raise TypeError("Unknown scheme: %r" % scheme)
+ else:
+ scheme = 'http'
+ netloc = 'localhost:80'
+ if path and '?' in path:
+ path_info, query_string = path.split('?', 1)
+ path_info = urllib.unquote(path_info)
+ else:
+ path_info = urllib.unquote(path)
+ query_string = ''
+ env = {
+ 'REQUEST_METHOD': 'GET',
+ 'SCRIPT_NAME': '',
+ 'PATH_INFO': path_info or '',
+ 'QUERY_STRING': query_string,
+ 'SERVER_NAME': netloc.split(':')[0],
+ 'SERVER_PORT': netloc.split(':')[1],
+ 'HTTP_HOST': netloc,
+ 'SERVER_PROTOCOL': 'HTTP/1.0',
+ 'wsgi.version': (1, 0),
+ 'wsgi.url_scheme': scheme,
+ 'wsgi.input': StringIO(''),
+ 'wsgi.errors': sys.stderr,
+ 'wsgi.multithread': False,
+ 'wsgi.multiprocess': False,
+ 'wsgi.run_once': False,
+ }
+ if base_url:
+ scheme, netloc, path, query, fragment = urlparse.urlsplit(base_url)
+ if query or fragment:
+ raise ValueError(
+ "base_url (%r) cannot have a query or fragment"
+ % base_url)
+ if scheme:
+ env['wsgi.url_scheme'] = scheme
+ if netloc:
+ if ':' not in netloc:
+ if scheme == 'http':
+ netloc += ':80'
+ elif scheme == 'https':
+ netloc += ':443'
+ else:
+ raise ValueError(
+ "Unknown scheme: %r" % scheme)
+ host, port = netloc.split(':', 1)
+ env['SERVER_PORT'] = port
+ env['SERVER_NAME'] = host
+ env['HTTP_HOST'] = netloc
+ if path:
+ env['SCRIPT_NAME'] = urllib.unquote(path)
+ if environ:
+ env.update(environ)
+ obj = cls(env)
+ if headers is not None:
+ obj.headers.update(headers)
+ return obj
+
+ blank = classmethod(blank)
+
+class Response(object):
+
+ """
+ Represents a WSGI response
+ """
+
+ default_content_type = 'text/html'
+ default_charset = 'utf8'
+ default_conditional_response = False
+
+ def __init__(self, body=None, status='200 OK', headerlist=None, app_iter=None,
+ request=None, content_type=None, conditional_response=NoDefault,
+ **kw):
+ if app_iter is None:
+ if body is None:
+ body = ''
+ elif body is not None:
+ raise TypeError(
+ "You may only give one of the body and app_iter arguments")
+ self.status = status
+ if headerlist is None:
+ self._headerlist = []
+ else:
+ self._headerlist = headerlist
+ self._headers = None
+ if request is not None:
+ if hasattr(request, 'environ'):
+ self._environ = request.environ
+ self._request = request
+ else:
+ self._environ = request
+ self._request = None
+ else:
+ self._environ = self._request = None
+ if content_type is not None:
+ self.content_type = content_type
+ elif self.default_content_type is not None and headerlist is None:
+ self.content_type = self.default_content_type
+ if conditional_response is NoDefault:
+ self.conditional_response = self.default_conditional_response
+ else:
+ self.conditional_response = conditional_response
+ if 'charset' in kw:
+ # We set this early, so something like unicode_body works later
+ value = kw.pop('charset')
+ if value:
+ self.charset = value
+ elif self.default_charset and not self.charset and headerlist is None:
+ ct = self.content_type
+ if ct and (ct.startswith('text/') or ct.startswith('application/xml')
+ or (ct.startswith('application/') and ct.endswith('+xml'))):
+ self.charset = self.default_charset
+ if app_iter is not None:
+ self._app_iter = app_iter
+ self._body = None
+ else:
+ if isinstance(body, unicode):
+ self.unicode_body = body
+ else:
+ self.body = body
+ self._app_iter = None
+ for name, value in kw.items():
+ if not hasattr(self.__class__, name):
+ # Not a basic attribute
+ raise TypeError(
+ "Unexpected keyword: %s=%r in %r" % (name, value))
+ setattr(self, name, value)
+
+ def __repr__(self):
+ return '<%s %x %s>' % (
+ self.__class__.__name__,
+ abs(id(self)),
+ self.status)
+
+ def __str__(self):
+ return (self.status + '\n'
+ + '\n'.join(['%s: %s' % (name, value)
+ for name, value in self.headerlist])
+ + '\n\n'
+ + self.body)
+
+ def _status__get(self):
+ """
+ The status string
+ """
+ return self._status
+
+ def _status__set(self, value):
+ if isinstance(value, int):
+ value = str(value)
+ if not isinstance(value, str):
+ raise TypeError(
+ "You must set status to a string or integer (not %s)"
+ % type(value))
+ if ' ' not in value:
+ # Need to add a reason:
+ code = int(value)
+ reason = status_reasons[code]
+ value += ' ' + reason
+ self._status = value
+
+ status = property(_status__get, _status__set, doc=_status__get.__doc__)
+
+ def _status_int__get(self):
+ """
+ The status as an integer
+ """
+ return int(self.status.split()[0])
+ def _status_int__set(self, value):
+ self.status = value
+ status_int = property(_status_int__get, _status_int__set, doc=_status_int__get.__doc__)
+
+ def _headerlist__get(self):
+ """
+ The list of response headers
+ """
+ return self._headerlist
+
+ def _headerlist__set(self, value):
+ self._headers = None
+ if not isinstance(value, list):
+ if hasattr(value, 'items'):
+ value = value.items()
+ value = list(value)
+ self._headerlist = value
+
+ def _headerlist__del(self):
+ self.headerlist = []
+
+ headerlist = property(_headerlist__get, _headerlist__set, _headerlist__del, doc=_headerlist__get.__doc__)
+
+ def _charset__get(self):
+ """
+ Get/set the charset (in the Content-Type)
+ """
+ header = self.headers.get('content-type')
+ if not header:
+ return None
+ match = _CHARSET_RE.search(header)
+ if match:
+ return match.group(1)
+ return None
+
+ def _charset__set(self, charset):
+ if charset is None:
+ del self.charset
+ return
+ try:
+ header = self.headers.pop('content-type')
+ except KeyError:
+ raise AttributeError(
+ "You cannot set the charset when no content-type is defined")
+ match = _CHARSET_RE.search(header)
+ if match:
+ header = header[:match.start()] + header[match.end():]
+ header += '; charset=%s' % charset
+ self.headers['content-type'] = header
+
+ def _charset__del(self):
+ try:
+ header = self.headers.pop('content-type')
+ except KeyError:
+ # Don't need to remove anything
+ return
+ match = _CHARSET_RE.search(header)
+ if match:
+ header = header[:match.start()] + header[match.end():]
+ self.headers['content-type'] = header
+
+ charset = property(_charset__get, _charset__set, _charset__del, doc=_charset__get.__doc__)
+
+ def _content_type__get(self):
+ """
+ Get/set the Content-Type header (or None), *without* the
+ charset or any parameters.
+
+ If you include parameters (or ``;`` at all) when setting the
+ content_type, any existing parameters will be deleted;
+ otherwise they will be preserved.
+ """
+ header = self.headers.get('content-type')
+ if not header:
+ return None
+ return header.split(';', 1)[0]
+
+ def _content_type__set(self, value):
+ if ';' not in value:
+ header = self.headers.get('content-type', '')
+ if ';' in header:
+ params = header.split(';', 1)[1]
+ value += ';' + params
+ self.headers['content-type'] = value
+
+ def _content_type__del(self):
+ try:
+ del self.headers['content-type']
+ except KeyError:
+ pass
+
+ content_type = property(_content_type__get, _content_type__set,
+ _content_type__del, doc=_content_type__get.__doc__)
+
+ def _content_type_params__get(self):
+ """
+ Returns a dictionary of all the parameters in the content type.
+ """
+ params = self.headers.get('content-type', '')
+ if ';' not in params:
+ return {}
+ params = params.split(';', 1)[1]
+ result = {}
+ for match in _PARAM_RE.finditer(params):
+ result[match.group(1)] = match.group(2) or match.group(3) or ''
+ return result
+
+ def _content_type_params__set(self, value_dict):
+ if not value_dict:
+ del self.content_type_params
+ return
+ params = []
+ for k, v in sorted(value_dict.items()):
+ if not _OK_PARAM_RE.search(v):
+ ## FIXME: I'm not sure what to do with "'s in the parameter value
+ ## I think it might be simply illegal
+ v = '"%s"' % v.replace('"', '\\"')
+ params.append('; %s=%s' % (k, v))
+ ct = self.headers.pop('content-type', '').split(';', 1)[0]
+ ct += ''.join(params)
+ self.headers['content-type'] = ct
+
+ def _content_type_params__del(self, value):
+ self.headers['content-type'] = self.headers.get('content-type', '').split(';', 1)[0]
+
+ content_type_params = property(_content_type_params__get, _content_type_params__set, _content_type_params__del, doc=_content_type_params__get.__doc__)
+
+ def _headers__get(self):
+ """
+ The headers in a dictionary-like object
+ """
+ if self._headers is None:
+ self._headers = HeaderDict.view_list(self.headerlist)
+ return self._headers
+
+ def _headers__set(self, value):
+ if hasattr(value, 'items'):
+ value = value.items()
+ self.headerlist = value
+ self._headers = None
+
+ headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
+
+ def _body__get(self):
+ """
+ The body of the response, as a ``str``. This will read in the
+ entire app_iter if necessary.
+ """
+ if self._body is None:
+ if self._app_iter is None:
+ raise AttributeError(
+ "No body has been set")
+ try:
+ self._body = ''.join(self._app_iter)
+ finally:
+ if hasattr(self._app_iter, 'close'):
+ self._app_iter.close()
+ self._app_iter = None
+ self.content_length = len(self._body)
+ return self._body
+
+ def _body__set(self, value):
+ if isinstance(value, unicode):
+ raise TypeError(
+ "You cannot set Response.body to a unicode object (use Response.unicode_body)")
+ if not isinstance(value, str):
+ raise TypeError(
+ "You can only set the body to a str (not %s)"
+ % type(value))
+ self._body = value
+ self.content_length = len(value)
+ self._app_iter = None
+
+ def _body__del(self):
+ self._body = None
+ self.content_length = None
+ self._app_iter = None
+
+ body = property(_body__get, _body__set, _body__del, doc=_body__get.__doc__)
+
+ def _body_file__get(self):
+ """
+ Returns a file-like object that can be used to write to the
+ body. If you passed in a list app_iter, that app_iter will be
+ modified by writes.
+ """
+ return ResponseBodyFile(self)
+
+ def _body_file__del(self):
+ del self.body
+
+ body_file = property(_body_file__get, fdel=_body_file__del, doc=_body_file__get.__doc__)
+
+ def write(self, text):
+ if isinstance(text, unicode):
+ self.unicode_body += text
+ else:
+ self.body += text
+
+ def _unicode_body__get(self):
+ """
+ Get/set the unicode value of the body (using the charset of the Content-Type)
+ """
+ if not self.charset:
+ raise AttributeError(
+ "You cannot access Response.unicode_body unless charset is set")
+ body = self.body
+ return body.decode(self.charset)
+
+ def _unicode_body__set(self, value):
+ if not self.charset:
+ raise AttributeError(
+ "You cannot access Response.unicode_body unless charset is set")
+ if not isinstance(value, unicode):
+ raise TypeError(
+ "You can only set Response.unicode_body to a unicode string (not %s)" % type(value))
+ self.body = value.encode(self.charset)
+
+ def _unicode_body__del(self):
+ del self.body
+
+ unicode_body = property(_unicode_body__get, _unicode_body__set, _unicode_body__del, doc=_unicode_body__get.__doc__)
+
+ def _app_iter__get(self):
+ """
+ Returns the app_iter of the response.
+
+ If body was set, this will create an app_iter from that body
+ (a single-item list)
+ """
+ if self._app_iter is None:
+ if self._body is None:
+ raise AttributeError(
+ "No body or app_iter has been set")
+ return [self._body]
+ else:
+ return self._app_iter
+
+ def _app_iter__set(self, value):
+ if self._body is not None:
+ # Undo the automatically-set content-length
+ self.content_length = None
+ self._app_iter = value
+ self._body = None
+
+ def _app_iter__del(self):
+ self.content_length = None
+ self._app_iter = self._body = None
+
+ app_iter = property(_app_iter__get, _app_iter__set, _app_iter__del, doc=_app_iter__get.__doc__)
+
+ def set_cookie(self, key, value='', max_age=None,
+ path='/', domain=None, secure=None, httponly=False,
+ version=None, comment=None):
+ """
+ Set (add) a cookie for the response
+ """
+ cookies = BaseCookie()
+ cookies[key] = value
+ for var_name, var_value in [
+ ('max_age', max_age),
+ ('path', path),
+ ('domain', domain),
+ ('secure', secure),
+ ('HttpOnly', httponly),
+ ('version', version),
+ ('comment', comment),
+ ]:
+ if var_value is not None and var_value is not False:
+ cookies[key][var_name.replace('_', '-')] = str(var_value)
+ header_value = cookies[key].output(header='').lstrip()
+ self.headerlist.append(('Set-Cookie', header_value))
+
+ def delete_cookie(self, key, path='/', domain=None):
+ """
+ Delete a cookie from the client. Note that path and domain must match
+ how the cookie was originally set.
+
+ This sets the cookie to the empty string, and max_age=0 so
+ that it should expire immediately.
+ """
+ self.set_cookie(key, '', path=path, domain=domain,
+ max_age=0)
+
+ def unset_cookie(self, key):
+ """
+ Unset a cookie with the given name (remove it from the
+ response). If there are multiple cookies (e.g., two cookies
+ with the same name and different paths or domains), all such
+ cookies will be deleted.
+ """
+ existing = self.headers.getall('Set-Cookie')
+ if not existing:
+ raise KeyError(
+ "No cookies at all have been set")
+ del self.headers['Set-Cookie']
+ found = False
+ for header in existing:
+ cookies = BaseCookie()
+ cookies.load(header)
+ if key in cookies:
+ found = True
+ del cookies[key]
+ header = cookies.output(header='').lstrip()
+ if header:
+ self.headers.add('Set-Cookie', header)
+ if not found:
+ raise KeyError(
+ "No cookie has been set with the name %r" % key)
+
+ def _location__get(self):
+ """
+ Retrieve the Location header of the response, or None if there
+ is no header. If the header is not absolute and this response
+ is associated with a request, make the header absolute.
+
+ For more information see `section 14.30
+ <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30>`_.
+ """
+ if 'location' not in self.headers:
+ return None
+ location = self.headers['location']
+ if _SCHEME_RE.search(location):
+ # Absolute
+ return location
+ if self.request is not None:
+ base_uri = self.request.url
+ location = urlparse.urljoin(base_uri, location)
+ return location
+
+ def _location__set(self, value):
+ if not _SCHEME_RE.search(value):
+ # Not absolute, see if we can make it absolute
+ if self.request is not None:
+ value = urlparse.urljoin(self.request.url, value)
+ self.headers['location'] = value
+
+ def _location__del(self):
+ if 'location' in self.headers:
+ del self.headers['location']
+
+ location = property(_location__get, _location__set, _location__del, doc=_location__get.__doc__)
+
+ accept_ranges = header_getter('Accept-Ranges', rfc_section='14.5')
+
+ age = converter(
+ header_getter('Age', rfc_section='14.6'),
+ _parse_int_safe, _serialize_int, 'int')
+
+ allow = converter(
+ header_getter('Allow', rfc_section='14.7'),
+ _parse_list, _serialize_list, 'list')
+
+ _cache_control_obj = None
+
+ def _cache_control__get(self):
+ """
+ Get/set/modify the Cache-Control header (section `14.9
+ <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9>`_)
+ """
+ value = self.headers.get('cache-control', '')
+ if self._cache_control_obj is None:
+ self._cache_control_obj = CacheControl.parse(value, updates_to=self._update_cache_control, type='response')
+ self._cache_control_obj.header_value = value
+ if self._cache_control_obj.header_value != value:
+ new_obj = CacheControl.parse(value, type='response')
+ self._cache_control_obj.properties.clear()
+ self._cache_control_obj.properties.update(new_obj.properties)
+ self._cache_control_obj.header_value = value
+ return self._cache_control_obj
+
+ def _cache_control__set(self, value):
+ # This actually becomes a copy
+ if not value:
+ value = ""
+ if isinstance(value, dict):
+ value = CacheControl(value, 'response')
+ if isinstance(value, unicode):
+ value = str(value)
+ if isinstance(value, str):
+ if self._cache_control_obj is None:
+ self.headers['Cache-Control'] = value
+ return
+ value = CacheControl.parse(value, 'response')
+ cache = self.cache_control
+ cache.properties.clear()
+ cache.properties.update(value.properties)
+
+ def _cache_control__del(self):
+ self.cache_control = {}
+
+ def _update_cache_control(self, prop_dict):
+ value = serialize_cache_control(prop_dict)
+ if not value:
+ if 'Cache-Control' in self.headers:
+ del self.headers['Cache-Control']
+ else:
+ self.headers['Cache-Control'] = value
+
+ cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__)
+
+ def cache_expires(self, seconds=0, **kw):
+ """
+ Set expiration on this request. This sets the response to
+ expire in the given seconds, and any other attributes are used
+ for cache_control (e.g., private=True, etc).
+ """
+ cache_control = self.cache_control
+ if isinstance(seconds, timedelta):
+ seconds = timedelta_to_seconds(seconds)
+ if not seconds:
+ # To really expire something, you have to force a
+ # bunch of these cache control attributes, and IE may
+ # not pay attention to those still so we also set
+ # Expires.
+ cache_control.no_store = True
+ cache_control.no_cache = True
+ cache_control.must_revalidate = True
+ cache_control.max_age = 0
+ cache_control.post_check = 0
+ cache_control.pre_check = 0
+ self.expires = datetime.utcnow()
+ if 'last-modified' not in self.headers:
+ self.last_modified = datetime.utcnow()
+ self.pragma = 'no-cache'
+ else:
+ cache_control.max_age = seconds
+ self.expires = datetime.utcnow() + timedelta(seconds=seconds)
+ for name, value in kw.items():
+ setattr(cache_control, name, value)
+
+ content_encoding = header_getter('Content-Encoding', rfc_section='14.11')
+
+ def encode_content(self, encoding='gzip'):
+ """
+ Encode the content with the given encoding (only gzip and
+ identity are supported).
+ """
+ if encoding == 'identity':
+ return
+ if encoding != 'gzip':
+ raise ValueError(
+ "Unknown encoding: %r" % encoding)
+ if self.content_encoding:
+ if self.content_encoding == encoding:
+ return
+ self.decode_content()
+ from webob.util.safegzip import GzipFile
+ f = StringIO()
+ gzip_f = GzipFile(filename='', mode='w', fileobj=f)
+ gzip_f.write(self.body)
+ gzip_f.close()
+ new_body = f.getvalue()
+ f.close()
+ self.content_encoding = 'gzip'
+ self.body = new_body
+
+ def decode_content(self):
+ content_encoding = self.content_encoding
+ if not content_encoding or content_encoding == 'identity':
+ return
+ if content_encoding != 'gzip':
+ raise ValueError(
+ "I don't know how to decode the content %s" % content_encoding)
+ from webob.util.safegzip import GzipFile
+ f = StringIO(self.body)
+ gzip_f = GzipFile(filename='', mode='r', fileobj=f)
+ new_body = gzip_f.read()
+ gzip_f.close()
+ f.close()
+ self.content_encoding = None
+ self.body = new_body
+
+ content_language = converter(
+ header_getter('Content-Language', rfc_section='14.12'),
+ _parse_list, _serialize_list, 'list')
+
+ content_location = header_getter(
+ 'Content-Location', rfc_section='14.14')
+
+ content_md5 = header_getter(
+ 'Content-MD5', rfc_section='14.14')
+
+ content_range = converter(
+ header_getter('Content-Range', rfc_section='14.16'),
+ _parse_content_range, _serialize_content_range, 'ContentRange object')
+
+ content_length = converter(
+ header_getter('Content-Length', rfc_section='14.17'),
+ _parse_int, _serialize_int, 'int')
+
+ date = converter(
+ header_getter('Date', rfc_section='14.18'),
+ _parse_date, _serialize_date, 'HTTP date')
+
+ etag = header_getter('ETag', rfc_section='14.19')
+
+ def md5_etag(self, body=None):
+ """
+ Generate an etag for the response object using an MD5 hash of
+ the body (the body parameter, or ``self.body`` if not given)
+
+ Sets ``self.etag``
+ """
+ if body is None:
+ body = self.body
+ import md5
+ h = md5.new(body)
+ self.etag = h.digest().encode('base64').replace('\n', '').strip('=')
+
+ expires = converter(
+ header_getter('Expires', rfc_section='14.21'),
+ _parse_date, _serialize_date, 'HTTP date')
+
+ last_modified = converter(
+ header_getter('Last-Modified', rfc_section='14.29'),
+ _parse_date, _serialize_date, 'HTTP date')
+
+ pragma = header_getter('Pragma', rfc_section='14.32')
+
+ retry_after = converter(
+ header_getter('Retry-After', rfc_section='14.37'),
+ _parse_date_delta, _serialize_date_delta, 'HTTP date or delta seconds')
+
+ server = header_getter('Server', rfc_section='14.38')
+
+ ## FIXME: I realize response.vary += 'something' won't work. It should.
+ ## Maybe for all listy headers.
+ vary = converter(
+ header_getter('Vary', rfc_section='14.44'),
+ _parse_list, _serialize_list, 'list')
+
+ ## FIXME: 14.47 WWW-Authenticate
+ ## http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.47
+
+
+ def _request__get(self):
+ """
+ Return the request associated with this response if any.
+ """
+ if self._request is None and self._environ is not None:
+ self._request = self.RequestClass(self._environ)
+ return self._request
+
+ def _request__set(self, value):
+ if value is None:
+ del self.request
+ return
+ if isinstance(value, dict):
+ self._environ = value
+ self._request = None
+ else:
+ self._request = value
+ self._environ = value.environ
+
+ def _request__del(self):
+ self._request = self._environ = None
+
+ request = property(_request__get, _request__set, _request__del, doc=_request__get.__doc__)
+
+ def _environ__get(self):
+ """
+ Get/set the request environ associated with this response, if
+ any.
+ """
+ return self._environ
+
+ def _environ__set(self, value):
+ if value is None:
+ del self.environ
+ self._environ = value
+ self._request = None
+
+ def _environ__del(self):
+ self._request = self._environ = None
+
+ environ = property(_environ__get, _environ__set, _environ__del, doc=_environ__get.__doc__)
+
+ def __call__(self, environ, start_response):
+ """
+ WSGI application interface
+ """
+ if self.conditional_response:
+ return self.conditional_response_app(environ, start_response)
+ start_response(self.status, self.headerlist)
+ if environ['REQUEST_METHOD'] == 'HEAD':
+ # Special case here...
+ return []
+ return self.app_iter
+
+ _safe_methods = ('GET', 'HEAD')
+
+ def conditional_response_app(self, environ, start_response):
+ """
+ Like the normal __call__ interface, but checks conditional headers:
+
+ * If-Modified-Since (304 Not Modified; only on GET, HEAD)
+ * If-None-Match (304 Not Modified; only on GET, HEAD)
+ * Range (406 Partial Content; only on GET, HEAD)
+ """
+ req = self.RequestClass(environ)
+ status304 = False
+ if req.method in self._safe_methods:
+ if req.if_modified_since and self.last_modified and self.last_modified <= req.if_modified_since:
+ status304 = True
+ if req.if_none_match and self.etag:
+ ## FIXME: should a weak match be okay?
+ if self.etag in req.if_none_match:
+ status304 = True
+ else:
+ # Even if If-Modified-Since matched, if ETag doesn't then reject it
+ status304 = False
+ if status304:
+ start_response('304 Not Modified', self.headerlist)
+ return []
+ if req.method == 'HEAD':
+ start_response(self.status, self.headerlist)
+ return []
+ if (req.range and req.if_range.match_response(self)
+ and self.content_range is None
+ and req.method == 'GET'
+ and self.status_int == 200):
+ content_range = req.range.content_range(self.content_length)
+ if content_range is not None:
+ app_iter = self.app_iter_range(content_range.start, content_range.stop)
+ if app_iter is not None:
+ headers = list(self.headerlist)
+ headers.append(('Content-Range', str(content_range)))
+ start_response('206 Partial Content', headers)
+ return app_iter
+ start_response(self.status, self.headerlist)
+ return self.app_iter
+
+ def app_iter_range(self, start, stop):
+ """
+ Return a new app_iter built from the response app_iter, that
+ serves up only the given ``start:stop`` range.
+ """
+ if self._app_iter is None:
+ return [self.body[start:stop]]
+ app_iter = self.app_iter
+ if hasattr(app_iter, 'app_iter_range'):
+ return app_iter.app_iter_range(start, stop)
+ return AppIterRange(app_iter, start, stop)
+
+
+Request.ResponseClass = Response
+Response.RequestClass = Request
+
+def _cgi_FieldStorage__repr__patch(self):
+ """ monkey patch for FieldStorage.__repr__
+
+ Unbelievely, the default __repr__ on FieldStorage reads
+ the entire file content instead of being sane about it.
+ This is a simple replacement that doesn't do that
+ """
+ if self.file:
+ return "FieldStorage(%r, %r)" % (
+ self.name, self.filename)
+ return "FieldStorage(%r, %r, %r)" % (
+ self.name, self.filename, self.value)
+
+cgi.FieldStorage.__repr__ = _cgi_FieldStorage__repr__patch
+
+class FakeCGIBody(object):
+
+ def __init__(self, vars):
+ self.vars = vars
+ self._body = None
+ self.position = 0
+
+ def read(self, size=-1):
+ body = self._get_body()
+ if size == -1:
+ v = body[self.position:]
+ self.position = len(body)
+ return v
+ else:
+ v = body[self.position:self.position+size]
+ self.position = min(len(body), self.position+size)
+ return v
+
+ def _get_body(self):
+ if self._body is None:
+ self._body = urllib.urlencode(self.vars.items())
+ return self._body
+
+ def readline(self, size=None):
+ # We ignore size, but allow it to be hinted
+ rest = self._get_body()[self.position:]
+ next = rest.find('\r\n')
+ if next == -1:
+ return self.read()
+ self.position += next+2
+ return rest[:next+2]
+
+ def readlines(self, hint=None):
+ # Again, allow hint but ignore
+ body = self._get_body()
+ rest = body[self.position:]
+ self.position = len(body)
+ result = []
+ while 1:
+ next = rest.find('\r\n')
+ if next == -1:
+ result.append(rest)
+ break
+ result.append(rest[:next+2])
+ rest = rest[next+2:]
+ return result
+
+ def __iter__(self):
+ return iter(self.readlines())
+
+ def __repr__(self):
+ inner = repr(self.vars)
+ if len(inner) > 20:
+ inner = inner[:15] + '...' + inner[-5:]
+ return '<%s at %x viewing %s>' % (
+ self.__class__.__name__,
+ abs(id(self)), inner)
+
+ #@classmethod
+ def update_environ(cls, environ, vars):
+ obj = cls(vars)
+ environ['CONTENT_LENGTH'] = '-1'
+ environ['wsgi.input'] = obj
+
+ update_environ = classmethod(update_environ)
+
+class ResponseBodyFile(object):
+
+ def __init__(self, response):
+ self.response = response
+
+ def __repr__(self):
+ return '<body_file for %r>' % (
+ self.response)
+
+ def close(self):
+ raise NotImplementedError(
+ "Response bodies cannot be closed")
+
+ def flush(self):
+ pass
+
+ def write(self, s):
+ if isinstance(s, unicode):
+ if self.response.charset is not None:
+ s = s.encode(self.response.charset)
+ else:
+ raise TypeError(
+ "You can only write unicode to Response.body_file "
+ "if charset has been set")
+ if not isinstance(s, str):
+ raise TypeError(
+ "You can only write str to a Response.body_file, not %s"
+ % type(s))
+ if not isinstance(self.response._app_iter, list):
+ body = self.response.body
+ if body:
+ self.response.app_iter = [body]
+ else:
+ self.response.app_iter = []
+ self.response.app_iter.append(s)
+
+ def writelines(self, seq):
+ for item in seq:
+ self.write(item)
+
+ closed = False
+
+ def encoding(self):
+ """
+ The encoding of the file (inherited from response.charset)
+ """
+ return self.response.charset
+
+ encoding = property(encoding, doc=encoding.__doc__)
+
+ mode = 'wb'
+
+class AppIterRange(object):
+ """
+ Wraps an app_iter, returning just a range of bytes
+ """
+
+ def __init__(self, app_iter, start, stop):
+ assert start >= 0, "Bad start: %r" % start
+ assert stop is None or (stop >= 0 and stop >= start), (
+ "Bad stop: %r" % stop)
+ self.app_iter = app_iter
+ self.app_iterator = iter(app_iter)
+ self.start = start
+ if stop is None:
+ self.length = -1
+ else:
+ self.length = stop - start
+ if start:
+ self._served = None
+ else:
+ self._served = 0
+ if hasattr(app_iter, 'close'):
+ self.close = app_iter.close
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if self._served is None:
+ # Haven't served anything; need to skip some leading bytes
+ skipped = 0
+ start = self.start
+ while 1:
+ chunk = self.app_iterator.next()
+ skipped += len(chunk)
+ extra = skipped - start
+ if extra == 0:
+ self._served = 0
+ break
+ elif extra > 0:
+ self._served = extra
+ return chunk[-extra:]
+ length = self.length
+ if length is None:
+ # Spent
+ raise StopIteration
+ chunk = self.app_iterator.next()
+ if length == -1:
+ return chunk
+ if self._served + len(chunk) > length:
+ extra = self._served + len(chunk) - length
+ self.length = None
+ return chunk[:-extra]
+ self._served += len(chunk)
+ return chunk
+
diff --git a/google_appengine/lib/webob/webob/__init__.pyc b/google_appengine/lib/webob/webob/__init__.pyc
new file mode 100644
index 0000000..04d60a6
--- /dev/null
+++ b/google_appengine/lib/webob/webob/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/acceptparse.py b/google_appengine/lib/webob/webob/acceptparse.py
new file mode 100755
index 0000000..b9cb20c
--- /dev/null
+++ b/google_appengine/lib/webob/webob/acceptparse.py
@@ -0,0 +1,293 @@
+"""
+Parses a variety of ``Accept-*`` headers.
+
+These headers generally take the form of::
+
+ value1; q=0.5, value2; q=0
+
+Where the ``q`` parameter is optional. In theory other parameters
+exists, but this ignores them.
+"""
+
+import re
+
+part_re = re.compile(
+ r',\s*([^\s;,\n]+)(?:[^,]*?;\s*q=([0-9.]*))?')
+
+def parse_accept(value):
+ """
+ Parses an ``Accept-*`` style header.
+
+ A list of ``[(value, quality), ...]`` is returned. ``quality``
+ will be 1 if it was not given.
+ """
+ result = []
+ for match in part_re.finditer(','+value):
+ name = match.group(1)
+ if name == 'q':
+ continue
+ quality = match.group(2) or ''
+ if not quality:
+ quality = 1
+ else:
+ try:
+ quality = max(min(float(quality), 1), 0)
+ except ValueError:
+ quality = 1
+ result.append((name, quality))
+ return result
+
+class Accept(object):
+ """
+ Represents a generic ``Accept-*`` style header.
+
+ This object should not be modified. To add items you can use
+ ``accept_obj + 'accept_thing'`` to get a new object
+ """
+
+ def __init__(self, header_name, header_value):
+ self.header_name = header_name
+ self.header_value = header_value
+ self._parsed = parse_accept(header_value)
+
+ def __repr__(self):
+ return '<%s at %x %s: %s>' % (
+ self.__class__.__name__,
+ abs(id(self)),
+ self.header_name, str(self))
+
+ def __str__(self):
+ result = []
+ for match, quality in self._parsed:
+ if quality != 1:
+ match = '%s;q=%0.1f' % (match, quality)
+ result.append(match)
+ return ', '.join(result)
+
+ # FIXME: should subtraction be allowed?
+ def __add__(self, other, reversed=False):
+ if isinstance(other, Accept):
+ other = other.header_value
+ if hasattr(other, 'items'):
+ other = sorted(other.items(), key=lambda item: -item[1])
+ if isinstance(other, (list, tuple)):
+ result = []
+ for item in other:
+ if isinstance(item, (list, tuple)):
+ name, quality = item
+ result.append('%s; q=%s' % (name, quality))
+ else:
+ result.append(item)
+ other = ', '.join(result)
+ other = str(other)
+ my_value = self.header_value
+ if reversed:
+ other, my_value = my_value, other
+ if not other:
+ new_value = my_value
+ elif not my_value:
+ new_value = other
+ else:
+ new_value = my_value + ', ' + other
+ return self.__class__(self.header_name, new_value)
+
+ def __radd__(self, other):
+ return self.__add__(other, True)
+
+ def __contains__(self, match):
+ """
+ Returns true if the given object is listed in the accepted
+ types.
+ """
+ for item, quality in self._parsed:
+ if self._match(item, match):
+ return True
+
+ def quality(self, match):
+ """
+ Return the quality of the given match. Returns None if there
+ is no match (not 0).
+ """
+ for item, quality in self._parsed:
+ if self._match(item, match):
+ return quality
+ return None
+
+ def first_match(self, matches):
+ """
+ Returns the first match in the sequences of matches that is
+ allowed. Ignores quality. Returns the first item if nothing
+ else matches; or if you include None at the end of the match
+ list then that will be returned.
+ """
+ if not matches:
+ raise ValueError(
+ "You must pass in a non-empty list")
+ for match in matches:
+ for item, quality in self._parsed:
+ if self._match(item, match):
+ return match
+ if match is None:
+ return None
+ return matches[0]
+
+ def best_match(self, matches, default_match=None):
+ """
+ Returns the best match in the sequence of matches.
+
+ The sequence can be a simple sequence, or you can have
+ ``(match, server_quality)`` items in the sequence. If you
+ have these tuples then the client quality is multiplied by the
+ server_quality to get a total.
+
+ default_match (default None) is returned if there is no intersection.
+ """
+ best_quality = -1
+ best_match = default_match
+ for match_item in matches:
+ if isinstance(match_item, (tuple, list)):
+ match, server_quality = match_item
+ else:
+ match = match_item
+ server_quality = 1
+ for item, quality in self._parsed:
+ possible_quality = server_quality * quality
+ if possible_quality < best_quality:
+ continue
+ if self._match(item, match):
+ best_quality = possible_quality
+ best_match = match
+ return best_match
+
+ def best_matches(self, fallback=None):
+ """
+ Return all the matches in order of quality, with fallback (if
+ given) at the end.
+ """
+ items = [
+ i for i, q in sorted(self._parsed, key=lambda iq: -iq[1])]
+ if fallback:
+ for index, item in enumerate(items):
+ if self._match(item, fallback):
+ items[index+1:] = []
+ break
+ else:
+ items.append(fallback)
+ return items
+
+ def _match(self, item, match):
+ return item.lower() == match.lower() or item == '*'
+
+class NilAccept(object):
+
+ """
+ Represents an Accept header with no value.
+ """
+
+ MasterClass = Accept
+
+ def __init__(self, header_name):
+ self.header_name = header_name
+
+ def __repr__(self):
+ return '<%s for %s: %s>' % (
+ self.__class__.__name__, self.header_name, self.MasterClass)
+
+ def __str__(self):
+ return ''
+
+ def __add__(self, item):
+ if isinstance(item, self.MasterClass):
+ return item
+ else:
+ return self.MasterClass(self.header_name, '') + item
+
+ def __radd__(self, item):
+ if isinstance(item, self.MasterClass):
+ return item
+ else:
+ return item + self.MasterClass(self.header_name, '')
+
+ def __contains__(self, item):
+ return True
+
+ def quality(self, match, default_quality=1):
+ return 0
+
+ def first_match(self, matches):
+ return matches[0]
+
+ def best_match(self, matches, default_match=None):
+ best_quality = -1
+ best_match = default_match
+ for match_item in matches:
+ if isinstance(match_item, (list, tuple)):
+ match, quality = match_item
+ else:
+ match = match_item
+ quality = 1
+ if quality > best_quality:
+ best_match = match
+ best_quality = quality
+ return best_match
+
+ def best_matches(self, fallback=None):
+ if fallback:
+ return [fallback]
+ else:
+ return []
+
+class NoAccept(NilAccept):
+
+ def __contains__(self, item):
+ return False
+
+class MIMEAccept(Accept):
+
+ """
+ Represents the ``Accept`` header, which is a list of mimetypes.
+
+ This class knows about mime wildcards, like ``image/*``
+ """
+
+ def _match(self, item, match):
+ item = item.lower()
+ if item == '*':
+ item = '*/*'
+ match = match.lower()
+ if match == '*':
+ match = '*/*'
+ if '/' not in item:
+ # Bad, but we ignore
+ return False
+ if '/' not in match:
+ raise ValueError(
+ "MIME matches must include / (bad: %r)" % match)
+ item_major, item_minor = item.split('/', 1)
+ match_major, match_minor = match.split('/', 1)
+ if match_major == '*' and match_minor != '*':
+ raise ValueError(
+ "A MIME type of %r doesn't make sense" % match)
+ if item_major == '*' and item_minor != '*':
+ # Bad, but we ignore
+ return False
+ if ((item_major == '*' and item_minor == '*')
+ or (match_major == '*' and match_minor == '*')):
+ return True
+ if (item_major == match_major
+ and ((item_minor == '*' or match_minor == '*')
+ or item_minor == match_minor)):
+ return True
+ return False
+
+ def accept_html(self):
+ """
+ Returns true if any HTML-like type is accepted
+ """
+ return ('text/html' in self
+ or 'application/xhtml+xml' in self
+ or 'application/xml' in self
+ or 'text/xml' in self)
+
+class MIMENilAccept(NilAccept):
+ MasterClass = MIMEAccept
diff --git a/google_appengine/lib/webob/webob/acceptparse.pyc b/google_appengine/lib/webob/webob/acceptparse.pyc
new file mode 100644
index 0000000..fdfde8e
--- /dev/null
+++ b/google_appengine/lib/webob/webob/acceptparse.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/byterange.py b/google_appengine/lib/webob/webob/byterange.py
new file mode 100755
index 0000000..24641c9
--- /dev/null
+++ b/google_appengine/lib/webob/webob/byterange.py
@@ -0,0 +1,295 @@
+class Range(object):
+
+ """
+ Represents the Range header.
+
+ This only represents ``bytes`` ranges, which are the only kind
+ specified in HTTP. This can represent multiple sets of ranges,
+ but no place else is this multi-range facility supported.
+ """
+
+ def __init__(self, ranges):
+ for begin, end in ranges:
+ assert end is None or end >= 0, "Bad ranges: %r" % ranges
+ self.ranges = ranges
+
+ def satisfiable(self, length):
+ """
+ Returns true if this range can be satisfied by the resource
+ with the given byte length.
+ """
+ for begin, end in self.ranges:
+ if end is not None and end >= length:
+ return False
+ return True
+
+ def range_for_length(self, length):
+ """
+ *If* there is only one range, and *if* it is satisfiable by
+ the given length, then return a (begin, end) non-inclusive range
+ of bytes to serve. Otherwise return None
+
+ If length is None (unknown length), then the resulting range
+ may be (begin, None), meaning it should be served from that
+ point. If it's a range with a fixed endpoint we won't know if
+ it is satisfiable, so this will return None.
+ """
+ if len(self.ranges) != 1:
+ return None
+ begin, end = self.ranges[0]
+ if length is None:
+ # Unknown; only works with ranges with no end-point
+ if end is None:
+ return (begin, end)
+ return None
+ if end >= length:
+ # Overshoots the end
+ return None
+ return (begin, end)
+
+ def content_range(self, length):
+ """
+ Works like range_for_length; returns None or a ContentRange object
+
+ You can use it like::
+
+ response.content_range = req.range.content_range(response.content_length)
+
+ Though it's still up to you to actually serve that content range!
+ """
+ range = self.range_for_length(length)
+ if range is None:
+ return None
+ return ContentRange(range[0], range[1], length)
+
+ def __str__(self):
+ return self.serialize_bytes('bytes', self.python_ranges_to_bytes(self.ranges))
+
+ def __repr__(self):
+ return '<%s ranges=%s>' % (
+ self.__class__.__name__,
+ ', '.join(map(repr, self.ranges)))
+
+ #@classmethod
+ def parse(cls, header):
+ """
+ Parse the header; may return None if header is invalid
+ """
+ bytes = cls.parse_bytes(header)
+ if bytes is None:
+ return None
+ units, ranges = bytes
+ if units.lower() != 'bytes':
+ return None
+ ranges = cls.bytes_to_python_ranges(ranges)
+ if ranges is None:
+ return None
+ return cls(ranges)
+ parse = classmethod(parse)
+
+ #@staticmethod
+ def parse_bytes(header):
+ """
+ Parse a Range header into (bytes, list_of_ranges). Note that the
+ ranges are *inclusive* (like in HTTP, not like in Python
+ typically).
+
+ Will return None if the header is invalid
+ """
+ if not header:
+ raise TypeError(
+ "The header must not be empty")
+ ranges = []
+ last_end = 0
+ try:
+ (units, range) = header.split("=", 1)
+ units = units.strip().lower()
+ for item in range.split(","):
+ if '-' not in item:
+ raise ValueError()
+ if item.startswith('-'):
+ # This is a range asking for a trailing chunk
+ if last_end < 0:
+ raise ValueError('too many end ranges')
+ begin = int(item)
+ end = None
+ last_end = -1
+ else:
+ (begin, end) = item.split("-", 1)
+ begin = int(begin)
+ if begin < last_end or last_end < 0:
+ print begin, last_end
+ raise ValueError('begin<last_end, or last_end<0')
+ if not end.strip():
+ end = None
+ else:
+ end = int(end)
+ if end is not None and begin > end:
+ raise ValueError('begin>end')
+ last_end = end
+ ranges.append((begin, end))
+ except ValueError, e:
+ # In this case where the Range header is malformed,
+ # section 14.16 says to treat the request as if the
+ # Range header was not present. How do I log this?
+ print e
+ return None
+ return (units, ranges)
+ parse_bytes = staticmethod(parse_bytes)
+
+ #@staticmethod
+ def serialize_bytes(units, ranges):
+ """
+ Takes the output of parse_bytes and turns it into a header
+ """
+ parts = []
+ for begin, end in ranges:
+ if end is None:
+ if begin >= 0:
+ parts.append('%s-' % begin)
+ else:
+ parts.append(str(begin))
+ else:
+ if begin < 0:
+ raise ValueError(
+ "(%r, %r) should have a non-negative first value" % (begin, end))
+ if end < 0:
+ raise ValueError(
+ "(%r, %r) should have a non-negative second value" % (begin, end))
+ parts.append('%s-%s' % (begin, end))
+ return '%s=%s' % (units, ','.join(parts))
+ serialize_bytes = staticmethod(serialize_bytes)
+
+ #@staticmethod
+ def bytes_to_python_ranges(ranges, length=None):
+ """
+ Converts the list-of-ranges from parse_bytes() to a Python-style
+ list of ranges (non-inclusive end points)
+
+ In the list of ranges, the last item can be None to indicate that
+ it should go to the end of the file, and the first item can be
+ negative to indicate that it should start from an offset from the
+ end. If you give a length then this will not occur (negative
+ numbers and offsets will be resolved).
+
+ If length is given, and any range is not value, then None is
+ returned.
+ """
+ result = []
+ for begin, end in ranges:
+ if begin < 0:
+ if length is None:
+ result.append((begin, None))
+ continue
+ else:
+ begin = length - begin
+ end = length
+ if begin is None:
+ begin = 0
+ if end is None and length is not None:
+ end = length
+ if length is not None and end is not None and end > length:
+ return None
+ if end is not None:
+ end -= 1
+ result.append((begin, end))
+ return result
+ bytes_to_python_ranges = staticmethod(bytes_to_python_ranges)
+
+ #@staticmethod
+ def python_ranges_to_bytes(ranges):
+ """
+ Converts a Python-style list of ranges to what serialize_bytes
+ expects.
+
+ This is the inverse of bytes_to_python_ranges
+ """
+ result = []
+ for begin, end in ranges:
+ if end is None:
+ result.append((begin, None))
+ else:
+ result.append((begin, end+1))
+ return result
+ python_ranges_to_bytes = staticmethod(python_ranges_to_bytes)
+
+class ContentRange(object):
+
+ """
+ Represents the Content-Range header
+
+ This header is ``start-stop/length``, where stop and length can be
+ ``*`` (represented as None in the attributes).
+ """
+
+ def __init__(self, start, stop, length):
+ assert start >= 0, "Bad start: %r" % start
+ assert stop is None or (stop >= 0 and stop >= start), (
+ "Bad stop: %r" % stop)
+ self.start = start
+ self.stop = stop
+ self.length = length
+
+ def __repr__(self):
+ return '<%s %s>' % (
+ self.__class__.__name__,
+ self)
+
+ def __str__(self):
+ if self.stop is None:
+ stop = '*'
+ else:
+ stop = self.stop + 1
+ if self.length is None:
+ length = '*'
+ else:
+ length = self.length
+ return 'bytes %s-%s/%s' % (self.start, stop, length)
+
+ def __iter__(self):
+ """
+ Mostly so you can unpack this, like:
+
+ start, stop, length = res.content_range
+ """
+ return iter([self.start, self.stop, self.length])
+
+ #@classmethod
+ def parse(cls, value):
+ """
+ Parse the header. May return None if it cannot parse.
+ """
+ if value is None:
+ return None
+ value = value.strip()
+ if not value.startswith('bytes '):
+ # Unparseable
+ return None
+ value = value[len('bytes '):].strip()
+ if '/' not in value:
+ # Invalid, no length given
+ return None
+ range, length = value.split('/', 1)
+ if '-' not in range:
+ # Invalid, no range
+ return None
+ start, end = range.split('-', 1)
+ try:
+ start = int(start)
+ if end == '*':
+ end = None
+ else:
+ end = int(end)
+ if length == '*':
+ length = None
+ else:
+ length = int(length)
+ except ValueError:
+ # Parse problem
+ return None
+ if end is None:
+ return cls(start, None, length)
+ else:
+ return cls(start, end-1, length)
+ parse = classmethod(parse)
+
diff --git a/google_appengine/lib/webob/webob/byterange.pyc b/google_appengine/lib/webob/webob/byterange.pyc
new file mode 100644
index 0000000..f779dd4
--- /dev/null
+++ b/google_appengine/lib/webob/webob/byterange.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/cachecontrol.py b/google_appengine/lib/webob/webob/cachecontrol.py
new file mode 100755
index 0000000..fc3a842
--- /dev/null
+++ b/google_appengine/lib/webob/webob/cachecontrol.py
@@ -0,0 +1,165 @@
+"""
+Represents the Cache-Control header
+"""
+
+import re
+from webob.updatedict import UpdateDict
+
+token_re = re.compile(
+ r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?')
+need_quote_re = re.compile(r'[^a-zA-Z0-9._-]')
+
+class exists_property(object):
+ """
+ Represents a property that either is listed in the Cache-Control
+ header, or is not listed (has no value)
+ """
+ def __init__(self, prop, type=None):
+ self.prop = prop
+ self.type = type
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ return self.prop in obj.properties
+ def __set__(self, obj, value):
+ if (self.type is not None
+ and self.type != obj.type):
+ raise AttributeError(
+ "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
+ if value:
+ obj.properties[self.prop] = None
+ else:
+ if self.prop in obj.properties:
+ del obj.properties[self.prop]
+ def __del__(self, obj):
+ self.__set__(obj, False)
+
+class value_property(object):
+ """
+ Represents a property that has a value in the Cache-Control header.
+
+ When no value is actually given, the value of self.none is returned.
+ """
+ def __init__(self, prop, default=None, none=None, type=None):
+ self.prop = prop
+ self.default = default
+ self.none = none
+ self.type = type
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ if self.prop in obj.properties:
+ value = obj.properties[self.prop]
+ if value is None:
+ return self.none
+ else:
+ return value
+ else:
+ return self.default
+ def __set__(self, obj, value):
+ if (self.type is not None
+ and self.type != obj.type):
+ raise AttributeError(
+ "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
+ if value == self.default:
+ if self.prop in obj.properties:
+ del obj.properties[self.prop]
+ elif value is True:
+ obj.properties[self.prop] = None # Empty value, but present
+ else:
+ obj.properties[self.prop] = value
+ def __del__(self, obj):
+ if self.prop in obj.properties:
+ del obj.properties[self.prop]
+
+class CacheControl(object):
+
+ """
+ Represents the Cache-Control header.
+
+ By giving a type of ``'request'`` or ``'response'`` you can
+ control what attributes are allowed (some Cache-Control values
+ only apply to requests or responses).
+ """
+
+ def __init__(self, properties, type):
+ self.properties = properties
+ self.type = type
+
+ #@classmethod
+ def parse(cls, header, updates_to=None, type=None):
+ """
+ Parse the header, returning a CacheControl object.
+
+ The object is bound to the request or response object
+ ``updates_to``, if that is given.
+ """
+ if updates_to:
+ props = UpdateDict()
+ props.updated = updates_to
+ else:
+ props = {}
+ for match in token_re.finditer(header):
+ name = match.group(1)
+ value = match.group(2) or match.group(3) or None
+ if value:
+ try:
+ value = int(value)
+ except ValueError:
+ pass
+ props[name] = value
+ obj = cls(props, type=type)
+ if updates_to:
+ props.updated_args = (obj,)
+ return obj
+
+ parse = classmethod(parse)
+
+ def __repr__(self):
+ return '<CacheControl %r>' % str(self)
+
+ # Request values:
+ # no-cache shared (below)
+ # no-store shared (below)
+ # max-age shared (below)
+ max_stale = value_property('max-stale', none='*', type='request')
+ min_fresh = value_property('min-fresh', type='request')
+ # no-transform shared (below)
+ only_if_cached = exists_property('only-if-cached', type='request')
+
+ # Response values:
+ public = exists_property('public', type='response')
+ private = value_property('private', none='*', type='response')
+ no_cache = value_property('no-cache', none='*')
+ no_store = exists_property('no-store')
+ no_transform = exists_property('no-transform')
+ must_revalidate = exists_property('must-revalidate', type='response')
+ proxy_revalidate = exists_property('proxy-revalidate', type='response')
+ max_age = value_property('max-age', none=-1)
+ s_maxage = value_property('s-maxage', type='response')
+ s_max_age = s_maxage
+
+ def __str__(self):
+ return serialize_cache_control(self.properties)
+
+ def copy(self):
+ """
+ Returns a copy of this object.
+ """
+ return self.__class__(self.properties.copy(), type=self.type)
+
+def serialize_cache_control(properties):
+ if isinstance(properties, CacheControl):
+ properties = properties.properties
+ parts = []
+ for name, value in sorted(properties.items()):
+ if value is None:
+ parts.append(name)
+ continue
+ value = str(value)
+ if need_quote_re.search(value):
+ value = '"%s"' % value
+ parts.append('%s=%s' % (name, value))
+ return ', '.join(parts)
+
diff --git a/google_appengine/lib/webob/webob/cachecontrol.pyc b/google_appengine/lib/webob/webob/cachecontrol.pyc
new file mode 100644
index 0000000..40e8729
--- /dev/null
+++ b/google_appengine/lib/webob/webob/cachecontrol.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/datastruct.py b/google_appengine/lib/webob/webob/datastruct.py
new file mode 100755
index 0000000..d59116b
--- /dev/null
+++ b/google_appengine/lib/webob/webob/datastruct.py
@@ -0,0 +1,58 @@
+"""
+Contains some data structures.
+"""
+
+from webob.util.dictmixin import DictMixin
+
+class EnvironHeaders(DictMixin):
+ """An object that represents the headers as present in a
+ WSGI environment.
+
+ This object is a wrapper (with no internal state) for a WSGI
+ request object, representing the CGI-style HTTP_* keys as a
+ dictionary. Because a CGI environment can only hold one value for
+ each key, this dictionary is single-valued (unlike outgoing
+ headers).
+ """
+
+ def __init__(self, environ):
+ self.environ = environ
+
+ def _trans_name(self, name):
+ key = 'HTTP_'+name.replace('-', '_').upper()
+ if key == 'HTTP_CONTENT_LENGTH':
+ key = 'CONTENT_LENGTH'
+ elif key == 'HTTP_CONTENT_TYPE':
+ key = 'CONTENT_TYPE'
+ return key
+
+ def _trans_key(self, key):
+ if key == 'CONTENT_TYPE':
+ return 'Content-Type'
+ elif key == 'CONTENT_LENGTH':
+ return 'Content-Length'
+ elif key.startswith('HTTP_'):
+ return key[5:].replace('_', '-').title()
+ else:
+ return None
+
+ def __getitem__(self, item):
+ return self.environ[self._trans_name(item)]
+
+ def __setitem__(self, item, value):
+ self.environ[self._trans_name(item)] = value
+
+ def __delitem__(self, item):
+ del self.environ[self._trans_name(item)]
+
+ def __iter__(self):
+ for key in self.environ:
+ name = self._trans_key(key)
+ if name is not None:
+ yield name
+
+ def keys(self):
+ return list(iter(self))
+
+ def __contains__(self, item):
+ return self._trans_name(item) in self.environ
diff --git a/google_appengine/lib/webob/webob/datastruct.pyc b/google_appengine/lib/webob/webob/datastruct.pyc
new file mode 100644
index 0000000..c7eb5b5
--- /dev/null
+++ b/google_appengine/lib/webob/webob/datastruct.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/etag.py b/google_appengine/lib/webob/webob/etag.py
new file mode 100755
index 0000000..a4e6c1e
--- /dev/null
+++ b/google_appengine/lib/webob/webob/etag.py
@@ -0,0 +1,214 @@
+"""
+Does parsing of ETag-related headers: If-None-Matches, If-Matches
+
+Also If-Range parsing
+"""
+
+import webob
+
+__all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'NoIfRange']
+
+class _AnyETag(object):
+ """
+ Represents an ETag of *, or a missing ETag when matching is 'safe'
+ """
+
+ def __repr__(self):
+ return '<ETag *>'
+
+ def __nonzero__(self):
+ return False
+
+ def __contains__(self, other):
+ return True
+
+ def weak_match(self, other):
+ return True
+
+ def __str__(self):
+ return '*'
+
+AnyETag = _AnyETag()
+
+class _NoETag(object):
+ """
+ Represents a missing ETag when matching is unsafe
+ """
+
+ def __repr__(self):
+ return '<No ETag>'
+
+ def __nonzero__(self):
+ return False
+
+ def __contains__(self, other):
+ return False
+
+ def weak_match(self, other):
+ return False
+
+ def __str__(self):
+ return ''
+
+NoETag = _NoETag()
+
+class ETagMatcher(object):
+
+ """
+ Represents an ETag request. Supports containment to see if an
+ ETag matches. You can also use
+ ``etag_matcher.weak_contains(etag)`` to allow weak ETags to match
+ (allowable for conditional GET requests, but not ranges or other
+ methods).
+ """
+
+ def __init__(self, etags, weak_etags=()):
+ self.etags = etags
+ self.weak_etags = weak_etags
+
+ def __contains__(self, other):
+ return other in self.etags
+
+ def weak_match(self, other):
+ if other.lower().startswith('w/'):
+ other = other[2:]
+ return other in self.etags or other in self.weak_etags
+
+ def __repr__(self):
+ return '<ETag %s>' % (
+ ' or '.join(self.etags))
+
+ def parse(cls, value):
+ """
+ Parse this from a header value
+ """
+ results = []
+ weak_results = []
+ while value:
+ if value.lower().startswith('w/'):
+ # Next item is weak
+ weak = True
+ value = value[2:]
+ else:
+ weak = False
+ if value.startswith('"'):
+ try:
+ etag, rest = value[1:].split('"', 1)
+ except ValueError:
+ etag = value.strip(' ",')
+ rest = ''
+ else:
+ rest = rest.strip(', ')
+ else:
+ if ',' in value:
+ etag, rest = value.split(',', 1)
+ rest = rest.strip()
+ else:
+ etag = value
+ rest = ''
+ if etag == '*':
+ return AnyETag
+ if etag:
+ if weak:
+ weak_results.append(etag)
+ else:
+ results.append(etag)
+ value = rest
+ return cls(results, weak_results)
+ parse = classmethod(parse)
+
+ def __str__(self):
+ # FIXME: should I quote these?
+ items = list(self.etags)
+ for weak in self.weak_etags:
+ items.append('W/%s' % weak)
+ return ', '.join(items)
+
+class IfRange(object):
+ """
+ Parses and represents the If-Range header, which can be
+ an ETag *or* a date
+ """
+ def __init__(self, etag=None, date=None):
+ self.etag = etag
+ self.date = date
+
+ def __repr__(self):
+ if self.etag is None:
+ etag = '*'
+ else:
+ etag = str(self.etag)
+ if self.date is None:
+ date = '*'
+ else:
+ date = webob._serialize_date(self.date)
+ return '<%s etag=%s, date=%s>' % (
+ self.__class__.__name__,
+ etag, date)
+
+ def __str__(self):
+ if self.etag is not None:
+ return str(self.etag)
+ elif self.date:
+ return webob._serialize_date(self.date)
+ else:
+ return ''
+
+ def match(self, etag=None, last_modified=None):
+ """
+ Return True if the If-Range header matches the given etag or last_modified
+ """
+ if self.date is not None:
+ if last_modified is None:
+ # Conditional with nothing to base the condition won't work
+ return False
+ return last_modified <= self.date
+ elif self.etag is not None:
+ if not etag:
+ return False
+ return etag in self.etag
+ return True
+
+ def match_response(self, response):
+ """
+ Return True if this matches the given ``webob.Response`` instance.
+ """
+ return self.match(etag=response.etag, last_modified=response.last_modified)
+
+ #@classmethod
+ def parse(cls, value):
+ """
+ Parse this from a header value.
+ """
+ date = etag = None
+ if not value:
+ etag = NoETag()
+ elif value and value.endswith(' GMT'):
+ # Must be a date
+ date = webob._parse_date(value)
+ else:
+ etag = ETagMatcher.parse(value)
+ return cls(etag=etag, date=date)
+ parse = classmethod(parse)
+
+class _NoIfRange(object):
+ """
+ Represents a missing If-Range header
+ """
+
+ def __repr__(self):
+ return '<Empty If-Range>'
+
+ def __str__(self):
+ return ''
+
+ def __nonzero__(self):
+ return False
+
+ def match(self, etag=None, last_modified=None):
+ return True
+
+ def match_response(self, response):
+ return True
+
+NoIfRange = _NoIfRange()
diff --git a/google_appengine/lib/webob/webob/etag.pyc b/google_appengine/lib/webob/webob/etag.pyc
new file mode 100644
index 0000000..62dbd5f
--- /dev/null
+++ b/google_appengine/lib/webob/webob/etag.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/exc.py b/google_appengine/lib/webob/webob/exc.py
new file mode 100755
index 0000000..37f7e47
--- /dev/null
+++ b/google_appengine/lib/webob/webob/exc.py
@@ -0,0 +1,657 @@
+"""
+HTTP Exception
+
+This module processes Python exceptions that relate to HTTP exceptions
+by defining a set of exceptions, all subclasses of HTTPException.
+Each exception, in addition to being a Python exception that can be
+raised and caught, is also a WSGI application and ``webob.Response``
+object.
+
+This module defines exceptions according to RFC 2068 [1]_ : codes with
+100-300 are not really errors; 400's are client errors, and 500's are
+server errors. According to the WSGI specification [2]_ , the application
+can call ``start_response`` more then once only under two conditions:
+(a) the response has not yet been sent, or (b) if the second and
+subsequent invocations of ``start_response`` have a valid ``exc_info``
+argument obtained from ``sys.exc_info()``. The WSGI specification then
+requires the server or gateway to handle the case where content has been
+sent and then an exception was encountered.
+
+Exception
+ HTTPException
+ HTTPOk
+ * 200 - HTTPOk
+ * 201 - HTTPCreated
+ * 202 - HTTPAccepted
+ * 203 - HTTPNonAuthoritativeInformation
+ * 204 - HTTPNoContent
+ * 205 - HTTPResetContent
+ * 206 - HTTPPartialContent
+ HTTPRedirection
+ * 300 - HTTPMultipleChoices
+ * 301 - HTTPMovedPermanently
+ * 302 - HTTPFound
+ * 303 - HTTPSeeOther
+ * 304 - HTTPNotModified
+ * 305 - HTTPUseProxy
+ * 306 - Unused (not implemented, obviously)
+ * 307 - HTTPTemporaryRedirect
+ HTTPError
+ HTTPClientError
+ * 400 - HTTPBadRequest
+ * 401 - HTTPUnauthorized
+ * 402 - HTTPPaymentRequired
+ * 403 - HTTPForbidden
+ * 404 - HTTPNotFound
+ * 405 - HTTPMethodNotAllowed
+ * 406 - HTTPNotAcceptable
+ * 407 - HTTPProxyAuthenticationRequired
+ * 408 - HTTPRequestTimeout
+ * 409 - HTTPConfict
+ * 410 - HTTPGone
+ * 411 - HTTPLengthRequired
+ * 412 - HTTPPreconditionFailed
+ * 413 - HTTPRequestEntityTooLarge
+ * 414 - HTTPRequestURITooLong
+ * 415 - HTTPUnsupportedMediaType
+ * 416 - HTTPRequestRangeNotSatisfiable
+ * 417 - HTTPExpectationFailed
+ HTTPServerError
+ * 500 - HTTPInternalServerError
+ * 501 - HTTPNotImplemented
+ * 502 - HTTPBadGateway
+ * 503 - HTTPServiceUnavailable
+ * 504 - HTTPGatewayTimeout
+ * 505 - HTTPVersionNotSupported
+
+References:
+
+.. [1] http://www.python.org/peps/pep-0333.html#error-handling
+.. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5
+
+
+"""
+
+import re
+import urlparse
+import sys
+try:
+ from string import Template
+except ImportError:
+ from webob.util.stringtemplate import Template
+import types
+from webob import Response, Request, html_escape
+
+tag_re = re.compile(r'<.*?>', re.S)
+br_re = re.compile(r'<br.*?>', re.I|re.S)
+comment_re = re.compile(r'<!--|-->')
+
+def no_escape(value):
+ if value is None:
+ return ''
+ if not isinstance(value, basestring):
+ if hasattr(value, '__unicode__'):
+ value = unicode(value)
+ else:
+ value = str(value)
+ return value
+
+def strip_tags(value):
+ value = value.replace('\n', ' ')
+ value = value.replace('\r', '')
+ value = br_re.sub('\n', value)
+ value = comment_re.sub('', value)
+ value = tag_re.sub('', value)
+ return value
+
+class HTTPException(Exception):
+ """
+ Exception used on pre-Python-2.5, where new-style classes cannot be used as
+ an exception.
+ """
+
+ def __init__(self, message, wsgi_response):
+ Exception.__init__(self, message)
+ self.__dict__['wsgi_response'] = wsgi_response
+
+ def __call__(self, environ, start_response):
+ return self.wsgi_response(environ, start_response)
+
+ def exception(self):
+ return self
+
+ exception = property(exception)
+
+ if sys.version_info < (2, 5):
+ def __getattr__(self, attr):
+ if not attr.startswith('_'):
+ return getattr(self.wsgi_response, attr)
+ else:
+ raise AttributeError(attr)
+
+ def __setattr__(self, attr, value):
+ if attr.startswith('_') or attr in ('args',):
+ self.__dict__[attr] = value
+ else:
+ setattr(self.wsgi_response, attr, value)
+
+class WSGIHTTPException(Response, HTTPException):
+
+ ## You should set in subclasses:
+ # code = 200
+ # title = 'OK'
+ # explanation = 'why this happens'
+ # body_template_obj = Template('response template')
+ code = None
+ title = None
+ explanation = ''
+ body_template_obj = Template('''\
+${explanation}<br /><br />
+${detail}
+${html_comment}
+''')
+
+ plain_template_obj = Template('''\
+${status}
+
+${body}''')
+
+ html_template_obj = Template('''\
+<html>
+ <head>
+ <title>${status}</title>
+ </head>
+ <body>
+ <h1>${status}</h1>
+ ${body}
+ </body>
+</html>''')
+
+ ## Set this to True for responses that should have no request body
+ empty_body = False
+
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None):
+ Response.__init__(self,
+ status='%s %s' % (self.code, self.title),
+ content_type='text/html')
+ Exception.__init__(self, detail)
+ if headers:
+ self.headers.update(headers)
+ self.detail = detail
+ self.comment = comment
+ if body_template is not None:
+ self.body_template = body_template
+ self.body_template_obj = Template(body_template)
+ if self.empty_body:
+ del self.content_type
+ del self.content_length
+
+ def _make_body(self, environ, escape):
+ args = {
+ 'explanation': escape(self.explanation),
+ 'detail': escape(self.detail or ''),
+ 'comment': escape(self.comment or ''),
+ }
+ if self.comment:
+ args['html_comment'] = '<!-- %s -->' % escape(self.comment)
+ else:
+ args['html_comment'] = ''
+ body_tmpl = self.body_template_obj
+ if WSGIHTTPException.body_template_obj is not self.body_template_obj:
+ # Custom template; add headers to args
+ for k, v in environ.items():
+ args[k] = escape(v)
+ for k, v in self.headers.items():
+ args[k.lower()] = escape(v)
+ t_obj = self.body_template_obj
+ return t_obj.substitute(args)
+
+ def plain_body(self, environ):
+ body = self._make_body(environ, no_escape)
+ body = strip_tags(body)
+ return self.plain_template_obj.substitute(status=self.status,
+ title=self.title,
+ body=body)
+
+ def html_body(self, environ):
+ body = self._make_body(environ, html_escape)
+ return self.html_template_obj.substitute(status=self.status,
+ body=body)
+
+ def generate_response(self, environ, start_response):
+ if self.content_length is not None:
+ del self.content_length
+ headerlist = list(self.headerlist)
+ accept = environ.get('HTTP_ACCEPT', '')
+ if accept and 'html' in accept or '*/*' in accept:
+ body = self.html_body(environ)
+ if not self.content_type:
+ headerlist.append('text/html; charset=utf8')
+ else:
+ body = self.plain_body(environ)
+ if not self.content_type:
+ headerlist.append('text/plain; charset=utf8')
+ headerlist.append(('Content-Length', str(len(body))))
+ start_response(self.status, headerlist)
+ return [body]
+
+ def __call__(self, environ, start_response):
+ if environ['REQUEST_METHOD'] == 'HEAD':
+ start_response(self.status, self.headerlist)
+ return []
+ if not self.body and not self.empty_body:
+ return self.generate_response(environ, start_response)
+ return Response.__call__(self, environ, start_response)
+
+ def wsgi_response(self):
+ return self
+
+ wsgi_response = property(wsgi_response)
+
+ def exception(self):
+ if sys.version_info >= (2, 5):
+ return self
+ else:
+ return HTTPException(self.detail, self)
+
+ exception = property(exception)
+
+class HTTPError(WSGIHTTPException):
+ """
+ base class for status codes in the 400's and 500's
+
+ This is an exception which indicates that an error has occurred,
+ and that any work in progress should not be committed. These are
+ typically results in the 400's and 500's.
+ """
+
+class HTTPRedirection(WSGIHTTPException):
+ """
+ base class for 300's status code (redirections)
+
+ This is an abstract base class for 3xx redirection. It indicates
+ that further action needs to be taken by the user agent in order
+ to fulfill the request. It does not necessarly signal an error
+ condition.
+ """
+
+class HTTPOk(WSGIHTTPException):
+ """
+ Base class for the 200's status code (successful responses)
+ """
+ code = 200
+ title = 'OK'
+
+############################################################
+## 2xx success
+############################################################
+
+class HTTPCreated(HTTPOk):
+ code = 201
+ title = 'Created'
+
+class HTTPAccepted(HTTPOk):
+ code = 202
+ title = 'Accepted'
+ explanation = 'The request is accepted for processing.'
+
+class HTTPNonAuthoritativeInformation(HTTPOk):
+ code = 203
+ title = 'Non-Authoritative Information'
+
+class HTTPNoContent(HTTPOk):
+ code = 204
+ title = 'No Content'
+ empty_body = True
+
+class HTTPResetContent(HTTPOk):
+ code = 205
+ title = 'Reset Content'
+ empty_body = True
+
+class HTTPPartialContent(HTTPOk):
+ code = 206
+ title = 'Partial Content'
+
+## FIXME: add 207 Multi-Status (but it's complicated)
+
+############################################################
+## 3xx redirection
+############################################################
+
+class _HTTPMove(HTTPRedirection):
+ """
+ redirections which require a Location field
+
+ Since a 'Location' header is a required attribute of 301, 302, 303,
+ 305 and 307 (but not 304), this base class provides the mechanics to
+ make this easy.
+
+ You can provide a location keyword argument to set the location
+ immediately. You may also give ``add_slash=True`` if you want to
+ redirect to the same URL as the request, except with a ``/`` added
+ to the end.
+
+ Relative URLs in the location will be resolved to absolute.
+ """
+ explanation = 'The resource has been moved to'
+ body_template_obj = Template('''\
+${explanation} <a href="${location}">${location}</a>;
+you should be redirected automatically.
+${detail}
+${html_comment}''')
+
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None, location=None, add_slash=False):
+ super(_HTTPMove, self).__init__(
+ detail=detail, headers=headers, comment=comment,
+ body_template=body_template)
+ if location is not None:
+ self.location = location
+ if add_slash:
+ raise TypeError(
+ "You can only provide one of the arguments location and add_slash")
+ self.add_slash = add_slash
+
+ def __call__(self, environ, start_response):
+ req = Request(environ)
+ if self.add_slash:
+ url = req.path_url
+ url += '/'
+ if req.environ.get('QUERY_STRING'):
+ url += '?' + req.environ['QUERY_STRING']
+ self.location = url
+ self.location = urlparse.urljoin(req.path_url, self.location)
+ return super(_HTTPMove, self).__call__(
+ environ, start_response)
+
+class HTTPMultipleChoices(_HTTPMove):
+ code = 300
+ title = 'Multiple Choices'
+
+class HTTPMovedPermanently(_HTTPMove):
+ code = 301
+ title = 'Moved Permanently'
+
+class HTTPFound(_HTTPMove):
+ code = 302
+ title = 'Found'
+ explanation = 'The resource was found at'
+
+# This one is safe after a POST (the redirected location will be
+# retrieved with GET):
+class HTTPSeeOther(_HTTPMove):
+ code = 303
+ title = 'See Other'
+
+class HTTPNotModified(HTTPRedirection):
+ # FIXME: this should include a date or etag header
+ code = 304
+ title = 'Not Modified'
+ empty_body = True
+
+class HTTPUseProxy(_HTTPMove):
+ # Not a move, but looks a little like one
+ code = 305
+ title = 'Use Proxy'
+ explanation = (
+ 'The resource must be accessed through a proxy located at')
+
+class HTTPTemporaryRedirect(_HTTPMove):
+ code = 307
+ title = 'Temporary Redirect'
+
+############################################################
+## 4xx client error
+############################################################
+
+class HTTPClientError(HTTPError):
+ """
+ base class for the 400's, where the client is in error
+
+ This is an error condition in which the client is presumed to be
+ in-error. This is an expected problem, and thus is not considered
+ a bug. A server-side traceback is not warranted. Unless specialized,
+ this is a '400 Bad Request'
+ """
+ code = 400
+ title = 'Bad Request'
+ explanation = ('The server could not comply with the request since\r\n'
+ 'it is either malformed or otherwise incorrect.\r\n')
+
+class HTTPBadRequest(HTTPClientError):
+ pass
+
+class HTTPUnauthorized(HTTPClientError):
+ code = 401
+ title = 'Unauthorized'
+ explanation = (
+ 'This server could not verify that you are authorized to\r\n'
+ 'access the document you requested. Either you supplied the\r\n'
+ 'wrong credentials (e.g., bad password), or your browser\r\n'
+ 'does not understand how to supply the credentials required.\r\n')
+
+class HTTPPaymentRequired(HTTPClientError):
+ code = 402
+ title = 'Payment Required'
+ explanation = ('Access was denied for financial reasons.')
+
+class HTTPForbidden(HTTPClientError):
+ code = 403
+ title = 'Forbidden'
+ explanation = ('Access was denied to this resource.')
+
+class HTTPNotFound(HTTPClientError):
+ code = 404
+ title = 'Not Found'
+ explanation = ('The resource could not be found.')
+
+class HTTPMethodNotAllowed(HTTPClientError):
+ code = 405
+ title = 'Method Not Allowed'
+ # override template since we need an environment variable
+ body_template_obj = Template('''\
+The method ${REQUEST_METHOD} is not allowed for this resource. <br /><br />
+${detail}''')
+
+class HTTPNotAcceptable(HTTPClientError):
+ code = 406
+ title = 'Not Acceptable'
+ # override template since we need an environment variable
+ template = Template('''\
+The resource could not be generated that was acceptable to your browser
+(content of type ${HTTP_ACCEPT}. <br /><br />
+${detail}''')
+
+class HTTPProxyAuthenticationRequired(HTTPClientError):
+ code = 407
+ title = 'Proxy Authentication Required'
+ explanation = ('Authentication with a local proxy is needed.')
+
+class HTTPRequestTimeout(HTTPClientError):
+ code = 408
+ title = 'Request Timeout'
+ explanation = ('The server has waited too long for the request to '
+ 'be sent by the client.')
+
+class HTTPConflict(HTTPClientError):
+ code = 409
+ title = 'Conflict'
+ explanation = ('There was a conflict when trying to complete '
+ 'your request.')
+
+class HTTPGone(HTTPClientError):
+ code = 410
+ title = 'Gone'
+ explanation = ('This resource is no longer available. No forwarding '
+ 'address is given.')
+
+class HTTPLengthRequired(HTTPClientError):
+ code = 411
+ title = 'Length Required'
+ explanation = ('Content-Length header required.')
+
+class HTTPPreconditionFailed(HTTPClientError):
+ code = 412
+ title = 'Precondition Failed'
+ explanation = ('Request precondition failed.')
+
+class HTTPRequestEntityTooLarge(HTTPClientError):
+ code = 413
+ title = 'Request Entity Too Large'
+ explanation = ('The body of your request was too large for this server.')
+
+class HTTPRequestURITooLong(HTTPClientError):
+ code = 414
+ title = 'Request-URI Too Long'
+ explanation = ('The request URI was too long for this server.')
+
+class HTTPUnsupportedMediaType(HTTPClientError):
+ code = 415
+ title = 'Unsupported Media Type'
+ # override template since we need an environment variable
+ template_obj = Template('''\
+The request media type ${CONTENT_TYPE} is not supported by this server.
+<br /><br />
+${detail}''')
+
+class HTTPRequestRangeNotSatisfiable(HTTPClientError):
+ code = 416
+ title = 'Request Range Not Satisfiable'
+ explanation = ('The Range requested is not available.')
+
+class HTTPExpectationFailed(HTTPClientError):
+ code = 417
+ title = 'Expectation Failed'
+ explanation = ('Expectation failed.')
+
+class HTTPUnprocessableEntity(HTTPClientError):
+ ## Note: from WebDAV
+ code = 422
+ title = 'Unprocessable Entity'
+ explanation = 'Unable to process the contained instructions'
+
+class HTTPLocked(HTTPClientError):
+ ## Note: from WebDAV
+ code = 423
+ title = 'Locked'
+ explanation = ('The resource is locked')
+
+class HTTPFailedDependency(HTTPClientError):
+ ## Note: from WebDAV
+ code = 424
+ title = 'Failed Dependency'
+ explanation = ('The method could not be performed because the requested '
+ 'action dependended on another action and that action failed')
+
+############################################################
+## 5xx Server Error
+############################################################
+# Response status codes beginning with the digit "5" indicate cases in
+# which the server is aware that it has erred or is incapable of
+# performing the request. Except when responding to a HEAD request, the
+# server SHOULD include an entity containing an explanation of the error
+# situation, and whether it is a temporary or permanent condition. User
+# agents SHOULD display any included entity to the user. These response
+# codes are applicable to any request method.
+
+class HTTPServerError(HTTPError):
+ """
+ base class for the 500's, where the server is in-error
+
+ This is an error condition in which the server is presumed to be
+ in-error. This is usually unexpected, and thus requires a traceback;
+ ideally, opening a support ticket for the customer. Unless specialized,
+ this is a '500 Internal Server Error'
+ """
+ code = 500
+ title = 'Internal Server Error'
+ explanation = (
+ 'The server has either erred or is incapable of performing\r\n'
+ 'the requested operation.\r\n')
+
+class HTTPInternalServerError(HTTPServerError):
+ pass
+
+class HTTPNotImplemented(HTTPServerError):
+ code = 501
+ title = 'Not Implemented'
+ template = Template('''
+The request method ${REQUEST_METHOD} is not implemented for this server. <br /><br />
+${detail}''')
+
+class HTTPBadGateway(HTTPServerError):
+ code = 502
+ title = 'Bad Gateway'
+ explanation = ('Bad gateway.')
+
+class HTTPServiceUnavailable(HTTPServerError):
+ code = 503
+ title = 'Service Unavailable'
+ explanation = ('The server is currently unavailable. '
+ 'Please try again at a later time.')
+
+class HTTPGatewayTimeout(HTTPServerError):
+ code = 504
+ title = 'Gateway Timeout'
+ explanation = ('The gateway has timed out.')
+
+class HTTPVersionNotSupported(HTTPServerError):
+ code = 505
+ title = 'HTTP Version Not Supported'
+ explanation = ('The HTTP version is not supported.')
+
+class HTTPInsufficientStorage(HTTPServerError):
+ code = 507
+ title = 'Insufficient Storage'
+ explanation = ('There was not enough space to save the resource')
+
+class HTTPExceptionMiddleware(object):
+ """
+ Middleware that catches exceptions in the sub-application. This
+ does not catch exceptions in the app_iter; only during the initial
+ calling of the application.
+
+ This should be put *very close* to applications that might raise
+ these exceptions. This should not be applied globally; letting
+ *expected* exceptions raise through the WSGI stack is dangerous.
+ """
+
+ def __init__(self, application):
+ self.application = application
+ def __call__(self, environ, start_response):
+ try:
+ return self.application(environ, start_response)
+ except HTTPException, exc:
+ parent_exc_info = sys.exc_info()
+ def repl_start_response(status, headers, exc_info=None):
+ if exc_info is None:
+ exc_info = parent_exc_info
+ return start_response(status, headers, exc_info)
+ return exc(environ, repl_start_response)
+
+try:
+ from paste import httpexceptions
+except ImportError:
+ # Without Paste we don't need to do this fixup
+ pass
+else:
+ for name in dir(httpexceptions):
+ obj = globals().get(name)
+ if (obj and isinstance(obj, type) and issubclass(obj, HTTPException)
+ and obj is not HTTPException
+ and obj is not WSGIHTTPException):
+ obj.__bases__ = obj.__bases__ + (getattr(httpexceptions, name),)
+ del name, obj, httpexceptions
+
+__all__ = ['HTTPExceptionMiddleware', 'status_map']
+status_map={}
+for name, value in globals().items():
+ if (isinstance(value, (type, types.ClassType)) and issubclass(value, HTTPException)
+ and not name.startswith('_')):
+ __all__.append(name)
+ if getattr(value, 'code', None):
+ status_map[value.code]=value
+del name, value
+
diff --git a/google_appengine/lib/webob/webob/headerdict.py b/google_appengine/lib/webob/webob/headerdict.py
new file mode 100755
index 0000000..34b8378
--- /dev/null
+++ b/google_appengine/lib/webob/webob/headerdict.py
@@ -0,0 +1,110 @@
+"""
+Represents the response header list as a dictionary-like object.
+"""
+
+from webob.multidict import MultiDict
+try:
+ reversed
+except NameError:
+ from webob.util.reversed import reversed
+
+class HeaderDict(MultiDict):
+
+ """
+ Like a MultiDict, this wraps a list. Keys are normalized
+ for case and whitespace.
+ """
+
+ def normalize(self, key):
+ return str(key).lower().strip()
+
+ def __getitem__(self, key):
+ normalize = self.normalize
+ key = normalize(key)
+ for k, v in reversed(self._items):
+ if normalize(k) == key:
+ return v
+ raise KeyError(key)
+
+ def getall(self, key):
+ normalize = self.normalize
+ key = normalize(key)
+ result = []
+ for k, v in self._items:
+ if normalize(k) == key:
+ result.append(v)
+ return result
+
+ def mixed(self):
+ result = {}
+ multi = {}
+ normalize = self.normalize
+ for key, value in self.iteritems():
+ key = normalize(key)
+ if key in result:
+ if key in multi:
+ result[key].append(value)
+ else:
+ result[key] = [result[key], value]
+ multi[key] = None
+ else:
+ result[key] = value
+ return result
+
+ def dict_of_lists(self):
+ result = {}
+ normalize = self.normalize
+ for key, value in self.iteritems():
+ key = normalize(key)
+ if key in result:
+ result[key].append(value)
+ else:
+ result[key] = [value]
+ return result
+
+ def __delitem__(self, key):
+ normalize = self.normalize
+ key = normalize(key)
+ items = self._items
+ found = False
+ for i in range(len(items)-1, -1, -1):
+ if normalize(items[i][0]) == key:
+ del items[i]
+ found = True
+ if not found:
+ raise KeyError(key)
+
+ def __contains__(self, key):
+ normalize = self.normalize
+ key = normalize(key)
+ for k, v in self._items:
+ if normalize(k) == key:
+ return True
+ return False
+
+ has_key = __contains__
+
+ def setdefault(self, key, default=None):
+ normalize = self.normalize
+ c_key = normalize(key)
+ for k, v in self._items:
+ if normalize(k) == c_key:
+ return v
+ self._items.append((key, default))
+ return default
+
+ def pop(self, key, *args):
+ if len(args) > 1:
+ raise TypeError, "pop expected at most 2 arguments, got "\
+ + repr(1 + len(args))
+ key = self.normalize(key)
+ for i in range(len(self._items)):
+ if self.normalize(self._items[i][0]) == key:
+ v = self._items[i][1]
+ del self._items[i]
+ return v
+ if args:
+ return args[0]
+ else:
+ raise KeyError(key)
+
diff --git a/google_appengine/lib/webob/webob/headerdict.pyc b/google_appengine/lib/webob/webob/headerdict.pyc
new file mode 100644
index 0000000..f9bfc47
--- /dev/null
+++ b/google_appengine/lib/webob/webob/headerdict.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/multidict.py b/google_appengine/lib/webob/webob/multidict.py
new file mode 100755
index 0000000..e8fcb2d
--- /dev/null
+++ b/google_appengine/lib/webob/webob/multidict.py
@@ -0,0 +1,593 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+"""
+Gives a multi-value dictionary object (MultiDict) plus several wrappers
+"""
+import cgi
+import copy
+import sys
+from webob.util.dictmixin import DictMixin
+try:
+ reversed
+except NameError:
+ from webob.util.reversed import reversed
+
+__all__ = ['MultiDict', 'UnicodeMultiDict', 'NestedMultiDict', 'NoVars']
+
+class MultiDict(DictMixin):
+
+ """
+ An ordered dictionary that can have multiple values for each key.
+ Adds the methods getall, getone, mixed, and add to the normal
+ dictionary interface.
+ """
+
+ def __init__(self, *args, **kw):
+ if len(args) > 1:
+ raise TypeError(
+ "MultiDict can only be called with one positional argument")
+ if args:
+ if hasattr(args[0], 'iteritems'):
+ items = list(args[0].iteritems())
+ elif hasattr(args[0], 'items'):
+ items = args[0].items()
+ else:
+ items = list(args[0])
+ self._items = items
+ else:
+ self._items = []
+ self._items.extend(kw.iteritems())
+
+ #@classmethod
+ def view_list(cls, lst):
+ """
+ Create a dict that is a view on the given list
+ """
+ if not isinstance(lst, list):
+ raise TypeError(
+ "%s.view_list(obj) takes only actual list objects, not %r"
+ % (cls.__name__, lst))
+ obj = cls()
+ obj._items = lst
+ return obj
+
+ view_list = classmethod(view_list)
+
+ #@classmethod
+ def from_fieldstorage(cls, fs):
+ """
+ Create a dict from a cgi.FieldStorage instance
+ """
+ obj = cls()
+ if fs.list:
+ # fs.list can be None when there's nothing to parse
+ for field in fs.list:
+ if field.filename:
+ obj.add(field.name, field)
+ else:
+ obj.add(field.name, field.value)
+ return obj
+
+ from_fieldstorage = classmethod(from_fieldstorage)
+
+ def __getitem__(self, key):
+ for k, v in reversed(self._items):
+ if k == key:
+ return v
+ raise KeyError(key)
+
+ def __setitem__(self, key, value):
+ try:
+ del self[key]
+ except KeyError:
+ pass
+ self._items.append((key, value))
+
+ def add(self, key, value):
+ """
+ Add the key and value, not overwriting any previous value.
+ """
+ self._items.append((key, value))
+
+ def getall(self, key):
+ """
+ Return a list of all values matching the key (may be an empty list)
+ """
+ result = []
+ for k, v in self._items:
+ if key == k:
+ result.append(v)
+ return result
+
+ def getone(self, key):
+ """
+ Get one value matching the key, raising a KeyError if multiple
+ values were found.
+ """
+ v = self.getall(key)
+ if not v:
+ raise KeyError('Key not found: %r' % key)
+ if len(v) > 1:
+ raise KeyError('Multiple values match %r: %r' % (key, v))
+ return v[0]
+
+ def mixed(self):
+ """
+ Returns a dictionary where the values are either single
+ values, or a list of values when a key/value appears more than
+ once in this dictionary. This is similar to the kind of
+ dictionary often used to represent the variables in a web
+ request.
+ """
+ result = {}
+ multi = {}
+ for key, value in self.iteritems():
+ if key in result:
+ # We do this to not clobber any lists that are
+ # *actual* values in this dictionary:
+ if key in multi:
+ result[key].append(value)
+ else:
+ result[key] = [result[key], value]
+ multi[key] = None
+ else:
+ result[key] = value
+ return result
+
+ def dict_of_lists(self):
+ """
+ Returns a dictionary where each key is associated with a
+ list of values.
+ """
+ result = {}
+ for key, value in self.iteritems():
+ if key in result:
+ result[key].append(value)
+ else:
+ result[key] = [value]
+ return result
+
+ def __delitem__(self, key):
+ items = self._items
+ found = False
+ for i in range(len(items)-1, -1, -1):
+ if items[i][0] == key:
+ del items[i]
+ found = True
+ if not found:
+ raise KeyError(key)
+
+ def __contains__(self, key):
+ for k, v in self._items:
+ if k == key:
+ return True
+ return False
+
+ has_key = __contains__
+
+ def clear(self):
+ self._items = []
+
+ def copy(self):
+ return self.__class__(self)
+
+ def setdefault(self, key, default=None):
+ for k, v in self._items:
+ if key == k:
+ return v
+ self._items.append((key, default))
+ return default
+
+ def pop(self, key, *args):
+ if len(args) > 1:
+ raise TypeError, "pop expected at most 2 arguments, got "\
+ + repr(1 + len(args))
+ for i in range(len(self._items)):
+ if self._items[i][0] == key:
+ v = self._items[i][1]
+ del self._items[i]
+ return v
+ if args:
+ return args[0]
+ else:
+ raise KeyError(key)
+
+ def popitem(self):
+ return self._items.pop()
+
+ def update(self, other=None, **kwargs):
+ if other is None:
+ pass
+ elif hasattr(other, 'items'):
+ self._items.extend(other.items())
+ elif hasattr(other, 'keys'):
+ for k in other.keys():
+ self._items.append((k, other[k]))
+ else:
+ for k, v in other:
+ self._items.append((k, v))
+ if kwargs:
+ self.update(kwargs)
+
+ def __repr__(self):
+ items = ', '.join(['(%r, %r)' % v for v in self.iteritems()])
+ return '%s([%s])' % (self.__class__.__name__, items)
+
+ def __len__(self):
+ return len(self._items)
+
+ ##
+ ## All the iteration:
+ ##
+
+ def keys(self):
+ return [k for k, v in self._items]
+
+ def iterkeys(self):
+ for k, v in self._items:
+ yield k
+
+ __iter__ = iterkeys
+
+ def items(self):
+ return self._items[:]
+
+ def iteritems(self):
+ return iter(self._items)
+
+ def values(self):
+ return [v for k, v in self._items]
+
+ def itervalues(self):
+ for k, v in self._items:
+ yield v
+
+class UnicodeMultiDict(DictMixin):
+ """
+ A MultiDict wrapper that decodes returned values to unicode on the
+ fly. Decoding is not applied to assigned values.
+
+ The key/value contents are assumed to be ``str``/``strs`` or
+ ``str``/``FieldStorages`` (as is returned by the ``paste.request.parse_``
+ functions).
+
+ Can optionally also decode keys when the ``decode_keys`` argument is
+ True.
+
+ ``FieldStorage`` instances are cloned, and the clone's ``filename``
+ variable is decoded. Its ``name`` variable is decoded when ``decode_keys``
+ is enabled.
+
+ """
+ def __init__(self, multi=None, encoding=None, errors='strict',
+ decode_keys=False):
+ self.multi = multi
+ if encoding is None:
+ encoding = sys.getdefaultencoding()
+ self.encoding = encoding
+ self.errors = errors
+ self.decode_keys = decode_keys
+
+ def _decode_key(self, key):
+ if self.decode_keys:
+ try:
+ key = key.decode(self.encoding, self.errors)
+ except AttributeError:
+ pass
+ return key
+
+ def _decode_value(self, value):
+ """
+ Decode the specified value to unicode. Assumes value is a ``str`` or
+ `FieldStorage`` object.
+
+ ``FieldStorage`` objects are specially handled.
+ """
+ if isinstance(value, cgi.FieldStorage):
+ # decode FieldStorage's field name and filename
+ value = copy.copy(value)
+ if self.decode_keys:
+ value.name = value.name.decode(self.encoding, self.errors)
+ if value.filename:
+ value.filename = value.filename.decode(self.encoding,
+ self.errors)
+ else:
+ try:
+ value = value.decode(self.encoding, self.errors)
+ except AttributeError:
+ pass
+ return value
+
+ def __getitem__(self, key):
+ return self._decode_value(self.multi.__getitem__(key))
+
+ def __setitem__(self, key, value):
+ self.multi.__setitem__(key, value)
+
+ def add(self, key, value):
+ """
+ Add the key and value, not overwriting any previous value.
+ """
+ self.multi.add(key, value)
+
+ def getall(self, key):
+ """
+ Return a list of all values matching the key (may be an empty list)
+ """
+ return [self._decode_value(v) for v in self.multi.getall(key)]
+
+ def getone(self, key):
+ """
+ Get one value matching the key, raising a KeyError if multiple
+ values were found.
+ """
+ return self._decode_value(self.multi.getone(key))
+
+ def mixed(self):
+ """
+ Returns a dictionary where the values are either single
+ values, or a list of values when a key/value appears more than
+ once in this dictionary. This is similar to the kind of
+ dictionary often used to represent the variables in a web
+ request.
+ """
+ unicode_mixed = {}
+ for key, value in self.multi.mixed().iteritems():
+ if isinstance(value, list):
+ value = [self._decode_value(value) for value in value]
+ else:
+ value = self._decode_value(value)
+ unicode_mixed[self._decode_key(key)] = value
+ return unicode_mixed
+
+ def dict_of_lists(self):
+ """
+ Returns a dictionary where each key is associated with a
+ list of values.
+ """
+ unicode_dict = {}
+ for key, value in self.multi.dict_of_lists().iteritems():
+ value = [self._decode_value(value) for value in value]
+ unicode_dict[self._decode_key(key)] = value
+ return unicode_dict
+
+ def __delitem__(self, key):
+ self.multi.__delitem__(key)
+
+ def __contains__(self, key):
+ return self.multi.__contains__(key)
+
+ has_key = __contains__
+
+ def clear(self):
+ self.multi.clear()
+
+ def copy(self):
+ return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors)
+
+ def setdefault(self, key, default=None):
+ return self._decode_value(self.multi.setdefault(key, default))
+
+ def pop(self, key, *args):
+ return self._decode_value(self.multi.pop(key, *args))
+
+ def popitem(self):
+ k, v = self.multi.popitem()
+ return (self._decode_key(k), self._decode_value(v))
+
+ def __repr__(self):
+ items = ', '.join(['(%r, %r)' % v for v in self.items()])
+ return '%s([%s])' % (self.__class__.__name__, items)
+
+ def __len__(self):
+ return self.multi.__len__()
+
+ ##
+ ## All the iteration:
+ ##
+
+ def keys(self):
+ return [self._decode_key(k) for k in self.multi.iterkeys()]
+
+ def iterkeys(self):
+ for k in self.multi.iterkeys():
+ yield self._decode_key(k)
+
+ __iter__ = iterkeys
+
+ def items(self):
+ return [(self._decode_key(k), self._decode_value(v)) for \
+ k, v in self.multi.iteritems()]
+
+ def iteritems(self):
+ for k, v in self.multi.iteritems():
+ yield (self._decode_key(k), self._decode_value(v))
+
+ def values(self):
+ return [self._decode_value(v) for v in self.multi.itervalues()]
+
+ def itervalues(self):
+ for v in self.multi.itervalues():
+ yield self._decode_value(v)
+
+_dummy = object()
+
+class NestedMultiDict(MultiDict):
+ """
+ Wraps several MultiDict objects, treating it as one large MultiDict
+ """
+
+ def __init__(self, *dicts):
+ self.dicts = dicts
+
+ def __getitem__(self, key):
+ for d in self.dicts:
+ value = d.get(key, _dummy)
+ if value is not _dummy:
+ return value
+ raise KeyError(key)
+
+ def _readonly(self, *args, **kw):
+ raise KeyError("NestedMultiDict objects are read-only")
+ __setitem__ = _readonly
+ add = _readonly
+ __delitem__ = _readonly
+ clear = _readonly
+ setdefault = _readonly
+ pop = _readonly
+ popitem = _readonly
+ update = _readonly
+
+ def getall(self, key):
+ result = []
+ for d in self.dicts:
+ result.extend(d.getall(key))
+ return result
+
+ # Inherited:
+ # getone
+ # mixed
+ # dict_of_lists
+ # copy
+
+ def __contains__(self, key):
+ for d in self.dicts:
+ if key in d:
+ return True
+ return False
+
+ has_key = __contains__
+
+ def __len__(self):
+ v = 0
+ for d in self.dicts:
+ v += len(d)
+ return v
+
+ def __nonzero__(self):
+ for d in self.dicts:
+ if d:
+ return True
+ return False
+
+ def items(self):
+ return list(self.iteritems())
+
+ def iteritems(self):
+ for d in self.dicts:
+ for item in d.iteritems():
+ yield item
+
+ def values(self):
+ return list(self.itervalues())
+
+ def itervalues(self):
+ for d in self.dicts:
+ for value in d.itervalues():
+ yield value
+
+ def keys(self):
+ return list(self.iterkeys())
+
+ def __iter__(self):
+ for d in self.dicts:
+ for key in d:
+ yield key
+
+ iterkeys = __iter__
+
+class NoVars(object):
+ """
+ Represents no variables; used when no variables
+ are applicable.
+
+ This is read-only
+ """
+
+ def __init__(self, reason=None):
+ self.reason = reason or 'N/A'
+
+ def __getitem__(self, key):
+ raise KeyError("No key %r: %s" % (key, self.reason))
+
+ def __setitem__(self, *args, **kw):
+ raise KeyError("Cannot add variables: %s" % self.reason)
+
+ add = __setitem__
+ setdefault = __setitem__
+ update = __setitem__
+
+ def __delitem__(self, *args, **kw):
+ raise KeyError("No keys to delete: %s" % self.reason)
+ clear = __delitem__
+ pop = __delitem__
+ popitem = __delitem__
+
+ def get(self, key, default=None):
+ return default
+
+ def getall(self, key):
+ return []
+
+ def getone(self, key):
+ return self[key]
+
+ def mixed(self):
+ return {}
+ dict_of_lists = mixed
+
+ def __contains__(self, key):
+ return False
+ has_key = __contains__
+
+ def copy(self):
+ return self
+
+ def __repr__(self):
+ return '<%s: %s>' % (self.__class__.__name__,
+ self.reason)
+
+ def __len__(self):
+ return 0
+
+ def __cmp__(self, other):
+ return cmp({}, other)
+
+ def keys(self):
+ return []
+ def iterkeys(self):
+ return iter([])
+ __iter__ = iterkeys
+ items = keys
+ iteritems = iterkeys
+ values = keys
+ itervalues = iterkeys
+
+__test__ = {
+ 'general': """
+ >>> d = MultiDict(a=1, b=2)
+ >>> d['a']
+ 1
+ >>> d.getall('c')
+ []
+ >>> d.add('a', 2)
+ >>> d['a']
+ 2
+ >>> d.getall('a')
+ [1, 2]
+ >>> d['b'] = 4
+ >>> d.getall('b')
+ [4]
+ >>> d.keys()
+ ['a', 'a', 'b']
+ >>> d.items()
+ [('a', 1), ('a', 2), ('b', 4)]
+ >>> d.mixed()
+ {'a': [1, 2], 'b': 4}
+ >>> MultiDict([('a', 'b')], c=2)
+ MultiDict([('a', 'b'), ('c', 2)])
+ """}
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
diff --git a/google_appengine/lib/webob/webob/multidict.pyc b/google_appengine/lib/webob/webob/multidict.pyc
new file mode 100644
index 0000000..6c12d8e
--- /dev/null
+++ b/google_appengine/lib/webob/webob/multidict.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/statusreasons.py b/google_appengine/lib/webob/webob/statusreasons.py
new file mode 100755
index 0000000..888ec3e
--- /dev/null
+++ b/google_appengine/lib/webob/webob/statusreasons.py
@@ -0,0 +1,67 @@
+"""
+Gives ``status_reasons``, a dictionary of HTTP reasons for integer status codes
+"""
+
+__all__ = ['status_reasons']
+
+status_reasons = {
+ # Status Codes
+ # Informational
+ 100: 'Continue',
+ 101: 'Switching Protocols',
+ 102: 'Processing',
+
+ # Successful
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 207: 'Multi Status',
+ 226: 'IM Used',
+
+ # Redirection
+ 300: 'Multiple Choices',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 307: 'Temporary Redirect',
+
+ # Client Error
+ 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',
+ 422: 'Unprocessable Entity',
+ 423: 'Locked',
+ 424: 'Failed Dependency',
+ 426: 'Upgrade Required',
+
+ # Server Error
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported',
+ 507: 'Insufficient Storage',
+ 510: 'Not Extended',
+ }
diff --git a/google_appengine/lib/webob/webob/statusreasons.pyc b/google_appengine/lib/webob/webob/statusreasons.pyc
new file mode 100644
index 0000000..28f7e22
--- /dev/null
+++ b/google_appengine/lib/webob/webob/statusreasons.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/updatedict.py b/google_appengine/lib/webob/webob/updatedict.py
new file mode 100755
index 0000000..5f6b70b
--- /dev/null
+++ b/google_appengine/lib/webob/webob/updatedict.py
@@ -0,0 +1,41 @@
+"""
+Dict that has a callback on all updates
+"""
+
+class UpdateDict(dict):
+ updated = None
+ updated_args = None
+ def _updated(self):
+ """
+ Assign to new_dict.updated to track updates
+ """
+ updated = self.updated
+ if updated is not None:
+ args = self.updated_args
+ if args is None:
+ args = (self,)
+ updated(*args)
+ def __setitem__(self, key, item):
+ dict.__setitem__(self, key, item)
+ self._updated()
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+ self._updated()
+ def clear(self):
+ dict.clear(self)
+ self._updated()
+ def update(self, *args, **kw):
+ dict.update(self, *args, **kw)
+ self._updated()
+ def setdefault(self, key, failobj=None):
+ dict.setdefault(self, key, failobj)
+ self._updated()
+ def pop(self):
+ v = dict.pop(self)
+ self._updated()
+ return v
+ def popitem(self):
+ v = dict.popitem(self)
+ self._updated()
+ return v
+
diff --git a/google_appengine/lib/webob/webob/updatedict.pyc b/google_appengine/lib/webob/webob/updatedict.pyc
new file mode 100644
index 0000000..ec79ff9
--- /dev/null
+++ b/google_appengine/lib/webob/webob/updatedict.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/util/__init__.py b/google_appengine/lib/webob/webob/util/__init__.py
new file mode 100755
index 0000000..792d600
--- /dev/null
+++ b/google_appengine/lib/webob/webob/util/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/google_appengine/lib/webob/webob/util/__init__.pyc b/google_appengine/lib/webob/webob/util/__init__.pyc
new file mode 100644
index 0000000..9e53754
--- /dev/null
+++ b/google_appengine/lib/webob/webob/util/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/util/dictmixin.py b/google_appengine/lib/webob/webob/util/dictmixin.py
new file mode 100755
index 0000000..7698efc
--- /dev/null
+++ b/google_appengine/lib/webob/webob/util/dictmixin.py
@@ -0,0 +1,102 @@
+"""
+A backport of UserDict.DictMixin for pre-python-2.4
+"""
+__all__ = ['DictMixin']
+
+try:
+ from UserDict import DictMixin
+except ImportError:
+ class DictMixin:
+ # Mixin defining all dictionary methods for classes that already have
+ # a minimum dictionary interface including getitem, setitem, delitem,
+ # and keys. Without knowledge of the subclass constructor, the mixin
+ # does not define __init__() or copy(). In addition to the four base
+ # methods, progressively more efficiency comes with defining
+ # __contains__(), __iter__(), and iteritems().
+
+ # second level definitions support higher levels
+ def __iter__(self):
+ for k in self.keys():
+ yield k
+ def has_key(self, key):
+ try:
+ value = self[key]
+ except KeyError:
+ return False
+ return True
+ def __contains__(self, key):
+ return self.has_key(key)
+
+ # third level takes advantage of second level definitions
+ def iteritems(self):
+ for k in self:
+ yield (k, self[k])
+ def iterkeys(self):
+ return self.__iter__()
+
+ # fourth level uses definitions from lower levels
+ def itervalues(self):
+ for _, v in self.iteritems():
+ yield v
+ def values(self):
+ return [v for _, v in self.iteritems()]
+ def items(self):
+ return list(self.iteritems())
+ def clear(self):
+ for key in self.keys():
+ del self[key]
+ def setdefault(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ self[key] = default
+ return default
+ def pop(self, key, *args):
+ if len(args) > 1:
+ raise TypeError, "pop expected at most 2 arguments, got "\
+ + repr(1 + len(args))
+ try:
+ value = self[key]
+ except KeyError:
+ if args:
+ return args[0]
+ raise
+ del self[key]
+ return value
+ def popitem(self):
+ try:
+ k, v = self.iteritems().next()
+ except StopIteration:
+ raise KeyError, 'container is empty'
+ del self[k]
+ return (k, v)
+ def update(self, other=None, **kwargs):
+ # Make progressively weaker assumptions about "other"
+ if other is None:
+ pass
+ elif hasattr(other, 'iteritems'): # iteritems saves memory and lookups
+ for k, v in other.iteritems():
+ self[k] = v
+ elif hasattr(other, 'keys'):
+ for k in other.keys():
+ self[k] = other[k]
+ else:
+ for k, v in other:
+ self[k] = v
+ if kwargs:
+ self.update(kwargs)
+ def get(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ return default
+ def __repr__(self):
+ return repr(dict(self.iteritems()))
+ def __cmp__(self, other):
+ if other is None:
+ return 1
+ if isinstance(other, DictMixin):
+ other = dict(other.iteritems())
+ return cmp(dict(self.iteritems()), other)
+ def __len__(self):
+ return len(self.keys())
diff --git a/google_appengine/lib/webob/webob/util/dictmixin.pyc b/google_appengine/lib/webob/webob/util/dictmixin.pyc
new file mode 100644
index 0000000..4dd85f8
--- /dev/null
+++ b/google_appengine/lib/webob/webob/util/dictmixin.pyc
Binary files differ
diff --git a/google_appengine/lib/webob/webob/util/reversed.py b/google_appengine/lib/webob/webob/util/reversed.py
new file mode 100755
index 0000000..605cad3
--- /dev/null
+++ b/google_appengine/lib/webob/webob/util/reversed.py
@@ -0,0 +1,4 @@
+## Backport of reversed
+
+def reversed(seq):
+ return iter(list(seq)[::-1])
diff --git a/google_appengine/lib/webob/webob/util/safegzip.py b/google_appengine/lib/webob/webob/util/safegzip.py
new file mode 100755
index 0000000..8b16a5b
--- /dev/null
+++ b/google_appengine/lib/webob/webob/util/safegzip.py
@@ -0,0 +1,21 @@
+"""
+GZip that doesn't include the timestamp
+"""
+import gzip
+
+class GzipFile(gzip.GzipFile):
+
+ def _write_gzip_header(self):
+ self.fileobj.write('\037\213') # magic header
+ self.fileobj.write('\010') # compression method
+ fname = self.filename[:-3]
+ flags = 0
+ if fname:
+ flags = gzip.FNAME
+ self.fileobj.write(chr(flags))
+ ## This is what WebOb patches:
+ gzip.write32u(self.fileobj, long(0))
+ self.fileobj.write('\002')
+ self.fileobj.write('\377')
+ if fname:
+ self.fileobj.write(fname + '\000')
diff --git a/google_appengine/lib/webob/webob/util/stringtemplate.py b/google_appengine/lib/webob/webob/util/stringtemplate.py
new file mode 100755
index 0000000..ceb948e
--- /dev/null
+++ b/google_appengine/lib/webob/webob/util/stringtemplate.py
@@ -0,0 +1,128 @@
+"""
+Just string.Template, backported for use with Python 2.3
+"""
+####################################################################
+import re as _re
+
+class _multimap:
+ """Helper class for combining multiple mappings.
+
+ Used by .{safe_,}substitute() to combine the mapping and keyword
+ arguments.
+ """
+ def __init__(self, primary, secondary):
+ self._primary = primary
+ self._secondary = secondary
+
+ def __getitem__(self, key):
+ try:
+ return self._primary[key]
+ except KeyError:
+ return self._secondary[key]
+
+
+class _TemplateMetaclass(type):
+ pattern = r"""
+ %(delim)s(?:
+ (?P<escaped>%(delim)s) | # Escape sequence of two delimiters
+ (?P<named>%(id)s) | # delimiter and a Python identifier
+ {(?P<braced>%(id)s)} | # delimiter and a braced identifier
+ (?P<invalid>) # Other ill-formed delimiter exprs
+ )
+ """
+
+ def __init__(cls, name, bases, dct):
+ super(_TemplateMetaclass, cls).__init__(name, bases, dct)
+ if 'pattern' in dct:
+ pattern = cls.pattern
+ else:
+ pattern = _TemplateMetaclass.pattern % {
+ 'delim' : _re.escape(cls.delimiter),
+ 'id' : cls.idpattern,
+ }
+ cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
+
+
+class Template:
+ """A string class for supporting $-substitutions."""
+ __metaclass__ = _TemplateMetaclass
+
+ delimiter = '$'
+ idpattern = r'[_a-z][_a-z0-9]*'
+
+ def __init__(self, template):
+ self.template = template
+
+ # Search for $$, $identifier, ${identifier}, and any bare $'s
+
+ def _invalid(self, mo):
+ i = mo.start('invalid')
+ lines = self.template[:i].splitlines(True)
+ if not lines:
+ colno = 1
+ lineno = 1
+ else:
+ colno = i - len(''.join(lines[:-1]))
+ lineno = len(lines)
+ raise ValueError('Invalid placeholder in string: line %d, col %d' %
+ (lineno, colno))
+
+ def substitute(self, *args, **kws):
+ if len(args) > 1:
+ raise TypeError('Too many positional arguments')
+ if not args:
+ mapping = kws
+ elif kws:
+ mapping = _multimap(kws, args[0])
+ else:
+ mapping = args[0]
+ # Helper function for .sub()
+ def convert(mo):
+ # Check the most common path first.
+ named = mo.group('named') or mo.group('braced')
+ if named is not None:
+ val = mapping[named]
+ # We use this idiom instead of str() because the latter will
+ # fail if val is a Unicode containing non-ASCII characters.
+ return '%s' % val
+ if mo.group('escaped') is not None:
+ return self.delimiter
+ if mo.group('invalid') is not None:
+ self._invalid(mo)
+ raise ValueError('Unrecognized named group in pattern',
+ self.pattern)
+ return self.pattern.sub(convert, self.template)
+
+ def safe_substitute(self, *args, **kws):
+ if len(args) > 1:
+ raise TypeError('Too many positional arguments')
+ if not args:
+ mapping = kws
+ elif kws:
+ mapping = _multimap(kws, args[0])
+ else:
+ mapping = args[0]
+ # Helper function for .sub()
+ def convert(mo):
+ named = mo.group('named')
+ if named is not None:
+ try:
+ # We use this idiom instead of str() because the latter
+ # will fail if val is a Unicode containing non-ASCII
+ return '%s' % mapping[named]
+ except KeyError:
+ return self.delimiter + named
+ braced = mo.group('braced')
+ if braced is not None:
+ try:
+ return '%s' % mapping[braced]
+ except KeyError:
+ return self.delimiter + '{' + braced + '}'
+ if mo.group('escaped') is not None:
+ return self.delimiter
+ if mo.group('invalid') is not None:
+ return self.delimiter
+ raise ValueError('Unrecognized named group in pattern',
+ self.pattern)
+ return self.pattern.sub(convert, self.template)
+
diff --git a/google_appengine/lib/yaml/LICENSE b/google_appengine/lib/yaml/LICENSE
new file mode 100644
index 0000000..050ced2
--- /dev/null
+++ b/google_appengine/lib/yaml/LICENSE
@@ -0,0 +1,19 @@
+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.
diff --git a/google_appengine/lib/yaml/PKG-INFO b/google_appengine/lib/yaml/PKG-INFO
new file mode 100644
index 0000000..fb2dd02
--- /dev/null
+++ b/google_appengine/lib/yaml/PKG-INFO
@@ -0,0 +1,28 @@
+Metadata-Version: 1.0
+Name: PyYAML
+Version: 3.05
+Summary: YAML parser and emitter for Python
+Home-page: http://pyyaml.org/wiki/PyYAML
+Author: Kirill Simonov
+Author-email: xi@resolvent.net
+License: MIT
+Download-URL: http://pyyaml.org/download/pyyaml/PyYAML-3.05.tar.gz
+Description: YAML is a data serialization format designed for human readability and
+ interaction with scripting languages. PyYAML is a YAML parser and
+ emitter for Python.
+
+ PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
+ support, capable extension API, and sensible error messages. PyYAML
+ supports standard YAML tags and provides Python-specific tags that allow
+ to represent an arbitrary Python object.
+
+ PyYAML is applicable for a broad range of tasks from complex
+ configuration files to object serialization and persistance.
+Platform: Any
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Text Processing :: Markup
diff --git a/google_appengine/lib/yaml/README b/google_appengine/lib/yaml/README
new file mode 100644
index 0000000..9687f87
--- /dev/null
+++ b/google_appengine/lib/yaml/README
@@ -0,0 +1,24 @@
+PyYAML - The next generation YAML parser and emitter for Python.
+
+To install, type 'python setup.py install'.
+
+You may build faster LibYAML based parser and emitter with
+'python setup_with_libyaml.py install'.
+Then you may use the LibYAML based parser this way:
+ >>> yaml.load(stream, Loader=yaml.CLoader)
+ >>> yaml.dump(data, Dumper=yaml.CDumper)
+
+For more information, check the PyYAML homepage:
+'http://pyyaml.org/wiki/PyYAML'.
+
+Documentation (rough and incomplete though):
+'http://pyyaml.org/wiki/PyYAMLDocumentation'.
+
+Post your questions and opinions to the YAML-Core mailing list:
+'http://lists.sourceforge.net/lists/listinfo/yaml-core'.
+
+Submit bug reports and feature requests to the PyYAML bug tracker:
+'http://pyyaml.org/newticket?component=pyyaml'.
+
+PyYAML is written by Kirill Simonov <xi@resolvent.net>. It is released
+under the MIT license. See the file LICENSE for more details.
diff --git a/google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.cfg b/google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.cfg
new file mode 100644
index 0000000..69bb847
--- /dev/null
+++ b/google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.cfg
@@ -0,0 +1,115 @@
+%YAML 1.1
+---
+
+ascii:
+
+ header: "\e[0;1;30;40m"
+
+ footer: "\e[0m"
+
+ tokens:
+ stream-start:
+ stream-end:
+ directive: { start: "\e[35m", end: "\e[0;1;30;40m" }
+ document-start: { start: "\e[35m", end: "\e[0;1;30;40m" }
+ document-end: { start: "\e[35m", end: "\e[0;1;30;40m" }
+ block-sequence-start:
+ block-mapping-start:
+ block-end:
+ flow-sequence-start: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ flow-mapping-start: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ flow-sequence-end: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ flow-mapping-end: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ key: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ value: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ block-entry: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ flow-entry: { start: "\e[33m", end: "\e[0;1;30;40m" }
+ alias: { start: "\e[32m", end: "\e[0;1;30;40m" }
+ anchor: { start: "\e[32m", end: "\e[0;1;30;40m" }
+ tag: { start: "\e[32m", end: "\e[0;1;30;40m" }
+ scalar: { start: "\e[36m", end: "\e[0;1;30;40m" }
+
+ replaces:
+ - "\r\n": "\n"
+ - "\r": "\n"
+ - "\n": "\n"
+ - "\x85": "\n"
+ - "\u2028": "\n"
+ - "\u2029": "\n"
+
+html: &html
+
+ tokens:
+ stream-start:
+ stream-end:
+ directive: { start: <code class="directive_token">, end: </code> }
+ document-start: { start: <code class="document_start_token">, end: </code> }
+ document-end: { start: <code class="document_end_token">, end: </code> }
+ block-sequence-start:
+ block-mapping-start:
+ block-end:
+ flow-sequence-start: { start: <code class="delimiter_token">, end: </code> }
+ flow-mapping-start: { start: <code class="delimiter_token">, end: </code> }
+ flow-sequence-end: { start: <code class="delimiter_token">, end: </code> }
+ flow-mapping-end: { start: <code class="delimiter_token">, end: </code> }
+ key: { start: <code class="delimiter_token">, end: </code> }
+ value: { start: <code class="delimiter_token">, end: </code> }
+ block-entry: { start: <code class="delimiter_token">, end: </code> }
+ flow-entry: { start: <code class="delimiter_token">, end: </code> }
+ alias: { start: <code class="anchor_token">, end: </code> }
+ anchor: { start: <code class="anchor_token">, end: </code> }
+ tag: { start: <code class="tag_token">, end: </code> }
+ scalar: { start: <code class="scalar_token">, end: </code> }
+
+ events:
+ stream-start: { start: <pre class="yaml_stream"> }
+ stream-end: { end: </pre> }
+ document-start: { start: <span class="document"> }
+ document-end: { end: </span> }
+ sequence-start: { start: <span class="sequence"> }
+ sequence-end: { end: </span> }
+ mapping-start: { start: <span class="mapping"> }
+ mapping-end: { end: </span> }
+ scalar: { start: <span class="scalar">, end: </span> }
+
+ replaces:
+ - "\r\n": "\n"
+ - "\r": "\n"
+ - "\n": "\n"
+ - "\x85": "\n"
+ - "\u2028": "\n"
+ - "\u2029": "\n"
+ - "&": "&amp;"
+ - "<": "&lt;"
+ - ">": "&gt;"
+
+html-page:
+
+ header: |
+ <html>
+ <head>
+ <title>A YAML stream</title>
+ <style type="text/css">
+ .document { background: #FFF }
+ .sequence { background: #EEF }
+ .mapping { background: #EFE }
+ .scalar { background: #FEE }
+ .directive_token { color: #C0C }
+ .document_start_token { color: #C0C; font-weight: bold }
+ .document_end_token { color: #C0C; font-weight: bold }
+ .delimiter_token { color: #600; font-weight: bold }
+ .anchor_token { color: #090 }
+ .tag_token { color: #090 }
+ .scalar_token { color: #000 }
+ .yaml_stream { color: #999 }
+ </style>
+ <body>
+
+ footer: |
+ </body>
+ </html>
+
+ <<: *html
+
+
+# vim: ft=yaml
diff --git a/google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.py b/google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.py
new file mode 100755
index 0000000..d6f7bf4
--- /dev/null
+++ b/google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.py
@@ -0,0 +1,114 @@
+#!/usr/bin/python
+
+import yaml, codecs, sys, os.path, optparse
+
+class Style:
+
+ def __init__(self, header=None, footer=None,
+ tokens=None, events=None, replaces=None):
+ self.header = header
+ self.footer = footer
+ self.replaces = replaces
+ self.substitutions = {}
+ for domain, Class in [(tokens, 'Token'), (events, 'Event')]:
+ if not domain:
+ continue
+ for key in domain:
+ name = ''.join([part.capitalize() for part in key.split('-')])
+ cls = getattr(yaml, '%s%s' % (name, Class))
+ value = domain[key]
+ if not value:
+ continue
+ start = value.get('start')
+ end = value.get('end')
+ if start:
+ self.substitutions[cls, -1] = start
+ if end:
+ self.substitutions[cls, +1] = end
+
+ def __setstate__(self, state):
+ self.__init__(**state)
+
+yaml.add_path_resolver(u'tag:yaml.org,2002:python/object:__main__.Style',
+ [None], dict)
+yaml.add_path_resolver(u'tag:yaml.org,2002:pairs',
+ [None, u'replaces'], list)
+
+class YAMLHighlight:
+
+ def __init__(self, options):
+ config = yaml.load(file(options.config, 'rb').read())
+ self.style = config[options.style]
+ if options.input:
+ self.input = file(options.input, 'rb')
+ else:
+ self.input = sys.stdin
+ if options.output:
+ self.output = file(options.output, 'wb')
+ else:
+ self.output = sys.stdout
+
+ def highlight(self):
+ input = self.input.read()
+ if input.startswith(codecs.BOM_UTF16_LE):
+ input = unicode(input, 'utf-16-le')
+ elif input.startswith(codecs.BOM_UTF16_BE):
+ input = unicode(input, 'utf-16-be')
+ else:
+ input = unicode(input, 'utf-8')
+ substitutions = self.style.substitutions
+ tokens = yaml.scan(input)
+ events = yaml.parse(input)
+ markers = []
+ number = 0
+ for token in tokens:
+ number += 1
+ if token.start_mark.index != token.end_mark.index:
+ cls = token.__class__
+ if (cls, -1) in substitutions:
+ markers.append([token.start_mark.index, +2, number, substitutions[cls, -1]])
+ if (cls, +1) in substitutions:
+ markers.append([token.end_mark.index, -2, number, substitutions[cls, +1]])
+ number = 0
+ for event in events:
+ number += 1
+ cls = event.__class__
+ if (cls, -1) in substitutions:
+ markers.append([event.start_mark.index, +1, number, substitutions[cls, -1]])
+ if (cls, +1) in substitutions:
+ markers.append([event.end_mark.index, -1, number, substitutions[cls, +1]])
+ markers.sort()
+ markers.reverse()
+ chunks = []
+ position = len(input)
+ for index, weight1, weight2, substitution in markers:
+ if index < position:
+ chunk = input[index:position]
+ for substring, replacement in self.style.replaces:
+ chunk = chunk.replace(substring, replacement)
+ chunks.append(chunk)
+ position = index
+ chunks.append(substitution)
+ chunks.reverse()
+ result = u''.join(chunks)
+ if self.style.header:
+ self.output.write(self.style.header)
+ self.output.write(result.encode('utf-8'))
+ if self.style.footer:
+ self.output.write(self.style.footer)
+
+if __name__ == '__main__':
+ parser = optparse.OptionParser()
+ parser.add_option('-s', '--style', dest='style', default='ascii',
+ help="specify the highlighting style", metavar='STYLE')
+ parser.add_option('-c', '--config', dest='config',
+ default=os.path.join(os.path.dirname(sys.argv[0]), 'yaml_hl.cfg'),
+ help="set an alternative configuration file", metavar='CONFIG')
+ parser.add_option('-i', '--input', dest='input', default=None,
+ help="set the input file (default: stdin)", metavar='FILE')
+ parser.add_option('-o', '--output', dest='output', default=None,
+ help="set the output file (default: stdout)", metavar='FILE')
+ (options, args) = parser.parse_args()
+ hl = YAMLHighlight(options)
+ hl.highlight()
+
diff --git a/google_appengine/lib/yaml/ext/_yaml.c b/google_appengine/lib/yaml/ext/_yaml.c
new file mode 100644
index 0000000..29455a2
--- /dev/null
+++ b/google_appengine/lib/yaml/ext/_yaml.c
@@ -0,0 +1,9054 @@
+/* Generated by Pyrex 0.9.4.1 on Sun May 13 00:52:19 2007 */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+#include "structmember.h"
+#ifndef PY_LONG_LONG
+ #define PY_LONG_LONG LONG_LONG
+#endif
+#if PY_VERSION_HEX < 0x02050000
+ typedef int Py_ssize_t;
+ #define PY_SSIZE_T_MAX INT_MAX
+ #define PY_SSIZE_T_MIN INT_MIN
+ #define PyInt_FromSsize_t(z) PyInt_FromLong(z)
+ #define PyInt_AsSsize_t(o) PyInt_AsLong(o)
+#endif
+#ifdef __cplusplus
+#define __PYX_EXTERN_C extern "C"
+#else
+#define __PYX_EXTERN_C extern
+#endif
+__PYX_EXTERN_C double pow(double, double);
+#include "_yaml.h"
+
+
+typedef struct {PyObject **p; char *s;} __Pyx_InternTabEntry; /*proto*/
+typedef struct {PyObject **p; char *s; long n;} __Pyx_StringTabEntry; /*proto*/
+static PyObject *__Pyx_UnpackItem(PyObject *, Py_ssize_t); /*proto*/
+static int __Pyx_EndUnpack(PyObject *, Py_ssize_t); /*proto*/
+static int __Pyx_PrintItem(PyObject *); /*proto*/
+static int __Pyx_PrintNewline(void); /*proto*/
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb); /*proto*/
+static void __Pyx_ReRaise(void); /*proto*/
+static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list); /*proto*/
+static PyObject *__Pyx_GetExcValue(void); /*proto*/
+static int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed, char *name); /*proto*/
+static int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); /*proto*/
+static int __Pyx_GetStarArgs(PyObject **args, PyObject **kwds, char *kwd_list[], Py_ssize_t nargs, PyObject **args2, PyObject **kwds2); /*proto*/
+static void __Pyx_WriteUnraisable(char *name); /*proto*/
+static void __Pyx_AddTraceback(char *funcname); /*proto*/
+static PyTypeObject *__Pyx_ImportType(char *module_name, char *class_name, long size); /*proto*/
+static int __Pyx_SetVtable(PyObject *dict, void *vtable); /*proto*/
+static int __Pyx_GetVtable(PyObject *dict, void *vtabptr); /*proto*/
+static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name); /*proto*/
+
+static PyObject *__pyx_m;
+static PyObject *__pyx_b;
+static int __pyx_lineno;
+static char *__pyx_filename;
+static char **__pyx_f;
+
+/* Declarations from _yaml */
+
+
+struct __pyx_obj_5_yaml_Mark {
+ PyObject_HEAD
+ PyObject *name;
+ int index;
+ int line;
+ int column;
+ PyObject *buffer;
+ PyObject *pointer;
+};
+
+
+struct __pyx_obj_5_yaml_CParser {
+ PyObject_HEAD
+ struct __pyx_vtabstruct_5_yaml_CParser *__pyx_vtab;
+ yaml_parser_t parser;
+ yaml_event_t parsed_event;
+ PyObject *stream;
+ PyObject *stream_name;
+ PyObject *current_token;
+ PyObject *current_event;
+ PyObject *anchors;
+};
+
+struct __pyx_vtabstruct_5_yaml_CParser {
+ PyObject *((*_parser_error)(struct __pyx_obj_5_yaml_CParser *));
+ PyObject *((*_scan)(struct __pyx_obj_5_yaml_CParser *));
+ PyObject *((*_token_to_object)(struct __pyx_obj_5_yaml_CParser *,yaml_token_t (*)));
+ PyObject *((*_parse)(struct __pyx_obj_5_yaml_CParser *));
+ PyObject *((*_event_to_object)(struct __pyx_obj_5_yaml_CParser *,yaml_event_t (*)));
+ PyObject *((*_compose_document)(struct __pyx_obj_5_yaml_CParser *));
+ PyObject *((*_compose_node)(struct __pyx_obj_5_yaml_CParser *,PyObject *,PyObject *));
+ PyObject *((*_compose_scalar_node)(struct __pyx_obj_5_yaml_CParser *,PyObject *));
+ PyObject *((*_compose_sequence_node)(struct __pyx_obj_5_yaml_CParser *,PyObject *));
+ PyObject *((*_compose_mapping_node)(struct __pyx_obj_5_yaml_CParser *,PyObject *));
+ int ((*_parse_next_event)(struct __pyx_obj_5_yaml_CParser *));
+};
+static struct __pyx_vtabstruct_5_yaml_CParser *__pyx_vtabptr_5_yaml_CParser;
+
+
+struct __pyx_obj_5_yaml_CEmitter {
+ PyObject_HEAD
+ struct __pyx_vtabstruct_5_yaml_CEmitter *__pyx_vtab;
+ yaml_emitter_t emitter;
+ PyObject *stream;
+ yaml_encoding_t use_encoding;
+ int document_start_implicit;
+ int document_end_implicit;
+ PyObject *use_version;
+ PyObject *use_tags;
+ PyObject *serialized_nodes;
+ PyObject *anchors;
+ int last_alias_id;
+ int closed;
+};
+
+struct __pyx_vtabstruct_5_yaml_CEmitter {
+ PyObject *((*_emitter_error)(struct __pyx_obj_5_yaml_CEmitter *));
+ int ((*_object_to_event)(struct __pyx_obj_5_yaml_CEmitter *,PyObject *,yaml_event_t (*)));
+ int ((*_anchor_node)(struct __pyx_obj_5_yaml_CEmitter *,PyObject *));
+ int ((*_serialize_node)(struct __pyx_obj_5_yaml_CEmitter *,PyObject *,PyObject *,PyObject *));
+};
+static struct __pyx_vtabstruct_5_yaml_CEmitter *__pyx_vtabptr_5_yaml_CEmitter;
+
+static PyTypeObject *__pyx_ptype_5_yaml_Mark = 0;
+static PyTypeObject *__pyx_ptype_5_yaml_CParser = 0;
+static PyTypeObject *__pyx_ptype_5_yaml_CEmitter = 0;
+static PyObject *__pyx_k2;
+static PyObject *__pyx_k3;
+static PyObject *__pyx_k4;
+static PyObject *__pyx_k5;
+static PyObject *__pyx_k6;
+static PyObject *__pyx_k7;
+static PyObject *__pyx_k8;
+static PyObject *__pyx_k9;
+static PyObject *__pyx_k10;
+static PyObject *__pyx_k11;
+static int (__pyx_f_5_yaml_input_handler(void (*),char (*),int ,int (*))); /*proto*/
+static int (__pyx_f_5_yaml_output_handler(void (*),char (*),int )); /*proto*/
+
+/* Implementation of _yaml */
+
+
+static PyObject *__pyx_n_yaml;
+static PyObject *__pyx_n_get_version_string;
+static PyObject *__pyx_n_get_version;
+static PyObject *__pyx_n_YAMLError;
+static PyObject *__pyx_n_ReaderError;
+static PyObject *__pyx_n_ScannerError;
+static PyObject *__pyx_n_ParserError;
+static PyObject *__pyx_n_ComposerError;
+static PyObject *__pyx_n_ConstructorError;
+static PyObject *__pyx_n_EmitterError;
+static PyObject *__pyx_n_SerializerError;
+static PyObject *__pyx_n_RepresenterError;
+static PyObject *__pyx_n_StreamStartToken;
+static PyObject *__pyx_n_StreamEndToken;
+static PyObject *__pyx_n_DirectiveToken;
+static PyObject *__pyx_n_DocumentStartToken;
+static PyObject *__pyx_n_DocumentEndToken;
+static PyObject *__pyx_n_BlockSequenceStartToken;
+static PyObject *__pyx_n_BlockMappingStartToken;
+static PyObject *__pyx_n_BlockEndToken;
+static PyObject *__pyx_n_FlowSequenceStartToken;
+static PyObject *__pyx_n_FlowMappingStartToken;
+static PyObject *__pyx_n_FlowSequenceEndToken;
+static PyObject *__pyx_n_FlowMappingEndToken;
+static PyObject *__pyx_n_KeyToken;
+static PyObject *__pyx_n_ValueToken;
+static PyObject *__pyx_n_BlockEntryToken;
+static PyObject *__pyx_n_FlowEntryToken;
+static PyObject *__pyx_n_AliasToken;
+static PyObject *__pyx_n_AnchorToken;
+static PyObject *__pyx_n_TagToken;
+static PyObject *__pyx_n_ScalarToken;
+static PyObject *__pyx_n_StreamStartEvent;
+static PyObject *__pyx_n_StreamEndEvent;
+static PyObject *__pyx_n_DocumentStartEvent;
+static PyObject *__pyx_n_DocumentEndEvent;
+static PyObject *__pyx_n_AliasEvent;
+static PyObject *__pyx_n_ScalarEvent;
+static PyObject *__pyx_n_SequenceStartEvent;
+static PyObject *__pyx_n_SequenceEndEvent;
+static PyObject *__pyx_n_MappingStartEvent;
+static PyObject *__pyx_n_MappingEndEvent;
+static PyObject *__pyx_n_ScalarNode;
+static PyObject *__pyx_n_SequenceNode;
+static PyObject *__pyx_n_MappingNode;
+static PyObject *__pyx_n_error;
+static PyObject *__pyx_n_reader;
+static PyObject *__pyx_n_scanner;
+static PyObject *__pyx_n_parser;
+static PyObject *__pyx_n_composer;
+static PyObject *__pyx_n_constructor;
+static PyObject *__pyx_n_emitter;
+static PyObject *__pyx_n_serializer;
+static PyObject *__pyx_n_representer;
+static PyObject *__pyx_n_tokens;
+static PyObject *__pyx_n_events;
+static PyObject *__pyx_n_nodes;
+
+static PyObject *__pyx_f_5_yaml_get_version_string(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_get_version_string(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":5 */
+ __pyx_1 = PyString_FromString(yaml_get_version_string()); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 5; goto __pyx_L1;}
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("_yaml.get_version_string");
+ __pyx_r = 0;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_get_version(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_get_version(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ int __pyx_v_major;
+ int __pyx_v_minor;
+ int __pyx_v_patch;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":9 */
+ yaml_get_version((&__pyx_v_major),(&__pyx_v_minor),(&__pyx_v_patch));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":10 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_major); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 10; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_minor); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 10; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_patch); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 10; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 10; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_3);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.get_version");
+ __pyx_r = 0;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static int __pyx_f_5_yaml_4Mark___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_5_yaml_4Mark___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_name = 0;
+ int __pyx_v_index;
+ int __pyx_v_line;
+ int __pyx_v_column;
+ PyObject *__pyx_v_buffer = 0;
+ PyObject *__pyx_v_pointer = 0;
+ int __pyx_r;
+ static char *__pyx_argnames[] = {"name","index","line","column","buffer","pointer",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "OiiiOO", __pyx_argnames, &__pyx_v_name, &__pyx_v_index, &__pyx_v_line, &__pyx_v_column, &__pyx_v_buffer, &__pyx_v_pointer)) return -1;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_name);
+ Py_INCREF(__pyx_v_buffer);
+ Py_INCREF(__pyx_v_pointer);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":69 */
+ Py_INCREF(__pyx_v_name);
+ Py_DECREF(((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->name);
+ ((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->name = __pyx_v_name;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":70 */
+ ((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->index = __pyx_v_index;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":71 */
+ ((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->line = __pyx_v_line;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":72 */
+ ((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->column = __pyx_v_column;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":73 */
+ Py_INCREF(__pyx_v_buffer);
+ Py_DECREF(((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->buffer);
+ ((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->buffer = __pyx_v_buffer;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":74 */
+ Py_INCREF(__pyx_v_pointer);
+ Py_DECREF(((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->pointer);
+ ((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->pointer = __pyx_v_pointer;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("_yaml.Mark.__init__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_name);
+ Py_DECREF(__pyx_v_buffer);
+ Py_DECREF(__pyx_v_pointer);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_4Mark_get_snippet(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_4Mark_get_snippet(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":77 */
+ Py_INCREF(Py_None);
+ __pyx_r = Py_None;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("_yaml.Mark.get_snippet");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k12p;
+
+static char (__pyx_k12[]) = " in \"%s\", line %d, column %d";
+
+static PyObject *__pyx_f_5_yaml_4Mark___str__(PyObject *__pyx_v_self); /*proto*/
+static PyObject *__pyx_f_5_yaml_4Mark___str__(PyObject *__pyx_v_self) {
+ PyObject *__pyx_v_where;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_where = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":81 */
+ __pyx_1 = PyInt_FromLong((((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->line + 1)); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 81; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong((((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->column + 1)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 81; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(3); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 81; goto __pyx_L1;}
+ Py_INCREF(((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->name);
+ PyTuple_SET_ITEM(__pyx_3, 0, ((struct __pyx_obj_5_yaml_Mark *)__pyx_v_self)->name);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_3, 2, __pyx_2);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_1 = PyNumber_Remainder(__pyx_k12p, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 81; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_v_where);
+ __pyx_v_where = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":82 */
+ Py_INCREF(__pyx_v_where);
+ __pyx_r = __pyx_v_where;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.Mark.__str__");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_where);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_MemoryError;
+static PyObject *__pyx_n_hasattr;
+static PyObject *__pyx_n_read;
+static PyObject *__pyx_n_name;
+static PyObject *__pyx_n_AttributeError;
+static PyObject *__pyx_n_TypeError;
+
+static PyObject *__pyx_k14p;
+static PyObject *__pyx_k15p;
+static PyObject *__pyx_k16p;
+static PyObject *__pyx_k17p;
+
+static char (__pyx_k14[]) = "<file>";
+static char (__pyx_k15[]) = "<unicode string>";
+static char (__pyx_k16[]) = "<string>";
+static char (__pyx_k17[]) = "a string or stream input is required";
+
+static int __pyx_f_5_yaml_7CParser___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_5_yaml_7CParser___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_stream = 0;
+ int __pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ static char *__pyx_argnames[] = {"stream",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_stream)) return -1;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_stream);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":254 */
+ __pyx_1 = (yaml_parser_initialize((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parser)) == 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":255 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 255; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 255; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":256 */
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parsed_event.type = YAML_NO_EVENT;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":257 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_hasattr); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 257; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 257; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_stream);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_stream);
+ Py_INCREF(__pyx_n_read);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_n_read);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 257; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_1 = PyObject_IsTrue(__pyx_4); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 257; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":258 */
+ Py_INCREF(__pyx_v_stream);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream = __pyx_v_stream;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":259 */
+ /*try:*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":260 */
+ __pyx_2 = PyObject_GetAttr(__pyx_v_stream, __pyx_n_name); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 260; goto __pyx_L4;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name = __pyx_2;
+ __pyx_2 = 0;
+ }
+ goto __pyx_L5;
+ __pyx_L4:;
+ Py_XDECREF(__pyx_3); __pyx_3 = 0;
+ Py_XDECREF(__pyx_4); __pyx_4 = 0;
+ Py_XDECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":261 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_AttributeError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 261; goto __pyx_L1;}
+ __pyx_1 = PyErr_ExceptionMatches(__pyx_3);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (__pyx_1) {
+ __Pyx_AddTraceback("_yaml.__init__");
+ __pyx_4 = __Pyx_GetExcValue(); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 261; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":262 */
+ Py_INCREF(__pyx_k14p);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name = __pyx_k14p;
+ goto __pyx_L5;
+ }
+ goto __pyx_L1;
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":263 */
+ yaml_parser_set_input((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parser),__pyx_f_5_yaml_input_handler,((void (*))__pyx_v_self));
+ goto __pyx_L3;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":265 */
+ __pyx_1 = (PyUnicode_CheckExact(__pyx_v_stream) != 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":266 */
+ __pyx_2 = PyUnicode_AsUTF8String(__pyx_v_stream); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 266; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_stream);
+ __pyx_v_stream = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":267 */
+ Py_INCREF(__pyx_k15p);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name = __pyx_k15p;
+ goto __pyx_L6;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":269 */
+ Py_INCREF(__pyx_k16p);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream_name = __pyx_k16p;
+ }
+ __pyx_L6:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":270 */
+ __pyx_1 = (PyString_CheckExact(__pyx_v_stream) == 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":271 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 271; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 271; goto __pyx_L1;}
+ Py_INCREF(__pyx_k17p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k17p);
+ __pyx_2 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 271; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 271; goto __pyx_L1;}
+ goto __pyx_L7;
+ }
+ __pyx_L7:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":272 */
+ Py_INCREF(__pyx_v_stream);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->stream = __pyx_v_stream;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":273 */
+ yaml_parser_set_input_string((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parser),PyString_AS_STRING(__pyx_v_stream),PyString_GET_SIZE(__pyx_v_stream));
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":274 */
+ Py_INCREF(Py_None);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":275 */
+ Py_INCREF(Py_None);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":276 */
+ __pyx_3 = PyDict_New(); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 276; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->anchors);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->anchors = __pyx_3;
+ __pyx_3 = 0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser.__init__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_stream);
+ return __pyx_r;
+}
+
+static void __pyx_f_5_yaml_7CParser___dealloc__(PyObject *__pyx_v_self); /*proto*/
+static void __pyx_f_5_yaml_7CParser___dealloc__(PyObject *__pyx_v_self) {
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":279 */
+ yaml_parser_delete((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parser));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":280 */
+ yaml_event_delete((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parsed_event));
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("_yaml.CParser.__dealloc__");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+}
+
+static PyObject *__pyx_n_ValueError;
+
+static PyObject *__pyx_k18p;
+static PyObject *__pyx_k19p;
+
+static char (__pyx_k18[]) = "?";
+static char (__pyx_k19[]) = "no parser error";
+
+static PyObject *__pyx_f_5_yaml_7CParser__parser_error(struct __pyx_obj_5_yaml_CParser *__pyx_v_self) {
+ PyObject *__pyx_v_context_mark;
+ PyObject *__pyx_v_problem_mark;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ PyObject *__pyx_6 = 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_context_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_problem_mark = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":283 */
+ __pyx_1 = (__pyx_v_self->parser.error == YAML_MEMORY_ERROR);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":284 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 284; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 284; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_1 = (__pyx_v_self->parser.error == YAML_READER_ERROR);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":286 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_ReaderError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 286; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parser.problem_offset); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 286; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parser.problem_value); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 287; goto __pyx_L1;}
+ __pyx_5 = PyString_FromString(__pyx_v_self->parser.problem); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 287; goto __pyx_L1;}
+ __pyx_6 = PyTuple_New(5); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 286; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_6, 1, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_6, 2, __pyx_4);
+ Py_INCREF(__pyx_k18p);
+ PyTuple_SET_ITEM(__pyx_6, 3, __pyx_k18p);
+ PyTuple_SET_ITEM(__pyx_6, 4, __pyx_5);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_5 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_2, __pyx_6); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 286; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 286; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_1 = (__pyx_v_self->parser.error == YAML_SCANNER_ERROR);
+ if (!__pyx_1) {
+ __pyx_1 = (__pyx_v_self->parser.error == YAML_PARSER_ERROR);
+ }
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":290 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_context_mark);
+ __pyx_v_context_mark = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":291 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_problem_mark);
+ __pyx_v_problem_mark = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":292 */
+ __pyx_1 = (__pyx_v_self->parser.context != 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":293 */
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parser.context_mark.index); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 294; goto __pyx_L1;}
+ __pyx_5 = PyInt_FromLong(__pyx_v_self->parser.context_mark.line); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 295; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parser.context_mark.column); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 296; goto __pyx_L1;}
+ __pyx_6 = PyTuple_New(6); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 293; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_6, 1, __pyx_4);
+ PyTuple_SET_ITEM(__pyx_6, 2, __pyx_5);
+ PyTuple_SET_ITEM(__pyx_6, 3, __pyx_2);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_6, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_6, 5, Py_None);
+ __pyx_4 = 0;
+ __pyx_5 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_6); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 293; goto __pyx_L1;}
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ Py_DECREF(__pyx_v_context_mark);
+ __pyx_v_context_mark = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":297 */
+ __pyx_1 = (__pyx_v_self->parser.problem != 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":298 */
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parser.problem_mark.index); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 299; goto __pyx_L1;}
+ __pyx_5 = PyInt_FromLong(__pyx_v_self->parser.problem_mark.line); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 300; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parser.problem_mark.column); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 301; goto __pyx_L1;}
+ __pyx_6 = PyTuple_New(6); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 298; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_6, 1, __pyx_4);
+ PyTuple_SET_ITEM(__pyx_6, 2, __pyx_5);
+ PyTuple_SET_ITEM(__pyx_6, 3, __pyx_2);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_6, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_6, 5, Py_None);
+ __pyx_4 = 0;
+ __pyx_5 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_6); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 298; goto __pyx_L1;}
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ Py_DECREF(__pyx_v_problem_mark);
+ __pyx_v_problem_mark = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":302 */
+ __pyx_1 = (__pyx_v_self->parser.error == YAML_SCANNER_ERROR);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":303 */
+ __pyx_1 = (__pyx_v_self->parser.context != 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":304 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_ScannerError); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 304; goto __pyx_L1;}
+ __pyx_5 = PyString_FromString(__pyx_v_self->parser.context); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 304; goto __pyx_L1;}
+ __pyx_2 = PyString_FromString(__pyx_v_self->parser.problem); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 305; goto __pyx_L1;}
+ __pyx_6 = PyTuple_New(4); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 304; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_5);
+ Py_INCREF(__pyx_v_context_mark);
+ PyTuple_SET_ITEM(__pyx_6, 1, __pyx_v_context_mark);
+ PyTuple_SET_ITEM(__pyx_6, 2, __pyx_2);
+ Py_INCREF(__pyx_v_problem_mark);
+ PyTuple_SET_ITEM(__pyx_6, 3, __pyx_v_problem_mark);
+ __pyx_5 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_4, __pyx_6); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 304; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L6;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":307 */
+ __pyx_5 = __Pyx_GetName(__pyx_m, __pyx_n_ScannerError); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 307; goto __pyx_L1;}
+ __pyx_2 = PyString_FromString(__pyx_v_self->parser.problem); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 308; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(4); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 307; goto __pyx_L1;}
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 0, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 1, Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_2);
+ Py_INCREF(__pyx_v_problem_mark);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_v_problem_mark);
+ __pyx_2 = 0;
+ __pyx_6 = PyObject_CallObject(__pyx_5, __pyx_4); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 307; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_6;
+ __pyx_6 = 0;
+ goto __pyx_L0;
+ }
+ __pyx_L6:;
+ goto __pyx_L5;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":310 */
+ __pyx_1 = (__pyx_v_self->parser.context != 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":311 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_ParserError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 311; goto __pyx_L1;}
+ __pyx_2 = PyString_FromString(__pyx_v_self->parser.context); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 311; goto __pyx_L1;}
+ __pyx_5 = PyString_FromString(__pyx_v_self->parser.problem); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 312; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(4); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 311; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_2);
+ Py_INCREF(__pyx_v_context_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_context_mark);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_5);
+ Py_INCREF(__pyx_v_problem_mark);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_v_problem_mark);
+ __pyx_2 = 0;
+ __pyx_5 = 0;
+ __pyx_6 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 311; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_6;
+ __pyx_6 = 0;
+ goto __pyx_L0;
+ goto __pyx_L7;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":314 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_ParserError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 314; goto __pyx_L1;}
+ __pyx_5 = PyString_FromString(__pyx_v_self->parser.problem); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 315; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(4); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 314; goto __pyx_L1;}
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_3, 0, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_3, 1, Py_None);
+ PyTuple_SET_ITEM(__pyx_3, 2, __pyx_5);
+ Py_INCREF(__pyx_v_problem_mark);
+ PyTuple_SET_ITEM(__pyx_3, 3, __pyx_v_problem_mark);
+ __pyx_5 = 0;
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 314; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ }
+ __pyx_L7:;
+ }
+ __pyx_L5:;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":316 */
+ __pyx_6 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 316; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 316; goto __pyx_L1;}
+ Py_INCREF(__pyx_k19p);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_k19p);
+ __pyx_2 = PyObject_CallObject(__pyx_6, __pyx_5); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 316; goto __pyx_L1;}
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 316; goto __pyx_L1;}
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ Py_XDECREF(__pyx_6);
+ __Pyx_AddTraceback("_yaml.CParser._parser_error");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_context_mark);
+ Py_DECREF(__pyx_v_problem_mark);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_raw_scan(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_raw_scan(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ yaml_token_t __pyx_v_token;
+ int __pyx_v_done;
+ int __pyx_v_count;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":322 */
+ __pyx_v_count = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":323 */
+ __pyx_v_done = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":324 */
+ while (1) {
+ __pyx_L2:;
+ __pyx_1 = (__pyx_v_done == 0);
+ if (!__pyx_1) break;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":325 */
+ __pyx_1 = yaml_parser_scan((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parser),(&__pyx_v_token)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 325; goto __pyx_L1;}
+ __pyx_2 = (__pyx_1 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":326 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parser_error(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 326; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":327 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 327; goto __pyx_L1;}
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":328 */
+ __pyx_1 = (__pyx_v_token.type == YAML_NO_TOKEN);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":329 */
+ __pyx_v_done = 1;
+ goto __pyx_L5;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":331 */
+ __pyx_v_count = (__pyx_v_count + 1);
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":332 */
+ yaml_token_delete((&__pyx_v_token));
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":333 */
+ __pyx_3 = PyInt_FromLong(__pyx_v_count); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 333; goto __pyx_L1;}
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.CParser.raw_scan");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser__scan(struct __pyx_obj_5_yaml_CParser *__pyx_v_self) {
+ yaml_token_t __pyx_v_token;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_v_token_object;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+ __pyx_v_token_object = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":337 */
+ __pyx_1 = yaml_parser_scan((&__pyx_v_self->parser),(&__pyx_v_token)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 337; goto __pyx_L1;}
+ __pyx_2 = (__pyx_1 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":338 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parser_error(__pyx_v_self); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 338; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":339 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 339; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":340 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_token_to_object(__pyx_v_self,(&__pyx_v_token)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 340; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_token_object);
+ __pyx_v_token_object = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":341 */
+ yaml_token_delete((&__pyx_v_token));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":342 */
+ Py_INCREF(__pyx_v_token_object);
+ __pyx_r = __pyx_v_token_object;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.CParser._scan");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_token_object);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_YAML;
+static PyObject *__pyx_n_TAG;
+static PyObject *__pyx_n_False;
+static PyObject *__pyx_n_True;
+
+static PyObject *__pyx_k20p;
+static PyObject *__pyx_k21p;
+static PyObject *__pyx_k22p;
+static PyObject *__pyx_k30p;
+static PyObject *__pyx_k31p;
+static PyObject *__pyx_k32p;
+static PyObject *__pyx_k33p;
+static PyObject *__pyx_k34p;
+static PyObject *__pyx_k35p;
+
+static char (__pyx_k20[]) = "utf-8";
+static char (__pyx_k21[]) = "utf-16-le";
+static char (__pyx_k22[]) = "utf-16-be";
+static char (__pyx_k25[]) = "strict";
+static char (__pyx_k26[]) = "strict";
+static char (__pyx_k27[]) = "strict";
+static char (__pyx_k28[]) = "strict";
+static char (__pyx_k29[]) = "strict";
+static char (__pyx_k30[]) = "";
+static char (__pyx_k31[]) = "\'";
+static char (__pyx_k32[]) = "\"";
+static char (__pyx_k33[]) = "|";
+static char (__pyx_k34[]) = ">";
+static char (__pyx_k35[]) = "unknown token type";
+
+static PyObject *__pyx_f_5_yaml_7CParser__token_to_object(struct __pyx_obj_5_yaml_CParser *__pyx_v_self,yaml_token_t (*__pyx_v_token)) {
+ PyObject *__pyx_v_start_mark;
+ PyObject *__pyx_v_end_mark;
+ PyObject *__pyx_v_encoding;
+ PyObject *__pyx_v_value;
+ PyObject *__pyx_v_handle;
+ PyObject *__pyx_v_suffix;
+ PyObject *__pyx_v_plain;
+ PyObject *__pyx_v_style;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ int __pyx_5;
+ int __pyx_6;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_start_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_end_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_encoding = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+ __pyx_v_handle = Py_None; Py_INCREF(Py_None);
+ __pyx_v_suffix = Py_None; Py_INCREF(Py_None);
+ __pyx_v_plain = Py_None; Py_INCREF(Py_None);
+ __pyx_v_style = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":345 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_token->start_mark.index); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 346; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_token->start_mark.line); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 347; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_token->start_mark.column); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 348; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(6); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 345; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_3);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 5, Py_None);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_1 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 345; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_start_mark);
+ __pyx_v_start_mark = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":350 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_token->end_mark.index); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 351; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_token->end_mark.line); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 352; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_token->end_mark.column); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 353; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(6); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 350; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_4);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 5, Py_None);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_2 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 350; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_v_end_mark);
+ __pyx_v_end_mark = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":355 */
+ __pyx_5 = (__pyx_v_token->type == YAML_NO_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":356 */
+ Py_INCREF(Py_None);
+ __pyx_r = Py_None;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_STREAM_START_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":358 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":359 */
+ __pyx_5 = (__pyx_v_token->data.stream_start.encoding == YAML_UTF8_ENCODING);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":360 */
+ Py_INCREF(__pyx_k20p);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = __pyx_k20p;
+ goto __pyx_L3;
+ }
+ __pyx_5 = (__pyx_v_token->data.stream_start.encoding == YAML_UTF16LE_ENCODING);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":362 */
+ Py_INCREF(__pyx_k21p);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = __pyx_k21p;
+ goto __pyx_L3;
+ }
+ __pyx_5 = (__pyx_v_token->data.stream_start.encoding == YAML_UTF16BE_ENCODING);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":364 */
+ Py_INCREF(__pyx_k22p);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = __pyx_k22p;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":365 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_StreamStartToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 365; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 365; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_encoding);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_v_encoding);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 365; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_STREAM_END_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":367 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_StreamEndToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 367; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 367; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_end_mark);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 367; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_VERSION_DIRECTIVE_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":369 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_DirectiveToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 369; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_token->data.version_directive.major); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 370; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_token->data.version_directive.minor); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 371; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 370; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_3);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_2 = PyTuple_New(4); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 369; goto __pyx_L1;}
+ Py_INCREF(__pyx_n_YAML);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_n_YAML);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_4);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 2, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 3, __pyx_v_end_mark);
+ __pyx_4 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 369; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_TAG_DIRECTIVE_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":374 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_DirectiveToken); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 374; goto __pyx_L1;}
+ __pyx_1 = PyString_FromString(__pyx_v_token->data.tag_directive.handle); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 375; goto __pyx_L1;}
+ __pyx_2 = PyString_FromString(__pyx_v_token->data.tag_directive.prefix); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 376; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 375; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_2);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_1 = PyTuple_New(4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 374; goto __pyx_L1;}
+ Py_INCREF(__pyx_n_TAG);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_n_TAG);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_3);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_v_end_mark);
+ __pyx_3 = 0;
+ __pyx_2 = PyObject_CallObject(__pyx_4, __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 374; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_DOCUMENT_START_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":379 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_DocumentStartToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 379; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 379; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_end_mark);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 379; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_DOCUMENT_END_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":381 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_DocumentEndToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 381; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 381; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_end_mark);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 381; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_BLOCK_SEQUENCE_START_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":383 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_BlockSequenceStartToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 383; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 383; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_end_mark);
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 383; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_BLOCK_MAPPING_START_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":385 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_BlockMappingStartToken); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 385; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 385; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_v_end_mark);
+ __pyx_2 = PyObject_CallObject(__pyx_4, __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 385; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_BLOCK_END_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":387 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_BlockEndToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 387; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 387; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_end_mark);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 387; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_FLOW_SEQUENCE_START_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":389 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_FlowSequenceStartToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 389; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 389; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_end_mark);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 389; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_FLOW_SEQUENCE_END_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":391 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_FlowSequenceEndToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 391; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 391; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_end_mark);
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 391; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_FLOW_MAPPING_START_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":393 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_FlowMappingStartToken); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 393; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 393; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_v_end_mark);
+ __pyx_2 = PyObject_CallObject(__pyx_4, __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 393; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_FLOW_MAPPING_END_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":395 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_FlowMappingEndToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 395; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 395; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_end_mark);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 395; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_BLOCK_ENTRY_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":397 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_BlockEntryToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 397; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 397; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_end_mark);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 397; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_FLOW_ENTRY_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":399 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_FlowEntryToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 399; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 399; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_end_mark);
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 399; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_KEY_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":401 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_KeyToken); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 401; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 401; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_v_end_mark);
+ __pyx_2 = PyObject_CallObject(__pyx_4, __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 401; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_VALUE_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":403 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_ValueToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 403; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 403; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_end_mark);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 403; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_ALIAS_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":405 */
+ __pyx_2 = PyUnicode_DecodeUTF8(__pyx_v_token->data.alias.value,strlen(__pyx_v_token->data.alias.value),__pyx_k25); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 405; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":407 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_AliasToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 407; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 407; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_value);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_v_end_mark);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 407; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_ANCHOR_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":409 */
+ __pyx_2 = PyUnicode_DecodeUTF8(__pyx_v_token->data.anchor.value,strlen(__pyx_v_token->data.anchor.value),__pyx_k26); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 409; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":411 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_AnchorToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 411; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 411; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_value);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_v_end_mark);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 411; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_TAG_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":413 */
+ __pyx_2 = PyUnicode_DecodeUTF8(__pyx_v_token->data.tag.handle,strlen(__pyx_v_token->data.tag.handle),__pyx_k27); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 413; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_handle);
+ __pyx_v_handle = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":415 */
+ __pyx_3 = PyUnicode_DecodeUTF8(__pyx_v_token->data.tag.suffix,strlen(__pyx_v_token->data.tag.suffix),__pyx_k28); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 415; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_suffix);
+ __pyx_v_suffix = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":417 */
+ __pyx_5 = PyObject_IsTrue(__pyx_v_handle); if (__pyx_5 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 417; goto __pyx_L1;}
+ __pyx_6 = (!__pyx_5);
+ if (__pyx_6) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":418 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_handle);
+ __pyx_v_handle = Py_None;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":419 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_TagToken); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 419; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 419; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_handle);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_handle);
+ Py_INCREF(__pyx_v_suffix);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_v_suffix);
+ __pyx_2 = PyTuple_New(3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 419; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_1);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 2, __pyx_v_end_mark);
+ __pyx_1 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_4, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 419; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_token->type == YAML_SCALAR_TOKEN);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":421 */
+ __pyx_1 = PyUnicode_DecodeUTF8(__pyx_v_token->data.scalar.value,__pyx_v_token->data.scalar.length,__pyx_k29); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 421; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":423 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 423; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_plain);
+ __pyx_v_plain = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":424 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":425 */
+ __pyx_6 = (__pyx_v_token->data.scalar.style == YAML_PLAIN_SCALAR_STYLE);
+ if (__pyx_6) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":426 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 426; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_plain);
+ __pyx_v_plain = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":427 */
+ Py_INCREF(__pyx_k30p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k30p;
+ goto __pyx_L5;
+ }
+ __pyx_5 = (__pyx_v_token->data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":429 */
+ Py_INCREF(__pyx_k31p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k31p;
+ goto __pyx_L5;
+ }
+ __pyx_6 = (__pyx_v_token->data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE);
+ if (__pyx_6) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":431 */
+ Py_INCREF(__pyx_k32p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k32p;
+ goto __pyx_L5;
+ }
+ __pyx_5 = (__pyx_v_token->data.scalar.style == YAML_LITERAL_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":433 */
+ Py_INCREF(__pyx_k33p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k33p;
+ goto __pyx_L5;
+ }
+ __pyx_6 = (__pyx_v_token->data.scalar.style == YAML_FOLDED_SCALAR_STYLE);
+ if (__pyx_6) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":435 */
+ Py_INCREF(__pyx_k34p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k34p;
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":436 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarToken); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 436; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(5); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 436; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_value);
+ Py_INCREF(__pyx_v_plain);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_v_plain);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_style);
+ PyTuple_SET_ITEM(__pyx_1, 4, __pyx_v_style);
+ __pyx_4 = PyObject_CallObject(__pyx_3, __pyx_1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 436; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":439 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 439; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 439; goto __pyx_L1;}
+ Py_INCREF(__pyx_k35p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k35p);
+ __pyx_1 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 439; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_1, 0, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 439; goto __pyx_L1;}
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser._token_to_object");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_start_mark);
+ Py_DECREF(__pyx_v_end_mark);
+ Py_DECREF(__pyx_v_encoding);
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_handle);
+ Py_DECREF(__pyx_v_suffix);
+ Py_DECREF(__pyx_v_plain);
+ Py_DECREF(__pyx_v_style);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_get_token(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_get_token(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_value;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":442 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":443 */
+ Py_INCREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token);
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":444 */
+ Py_INCREF(Py_None);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token = Py_None;
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":446 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_scan(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 446; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_2;
+ __pyx_2 = 0;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":447 */
+ Py_INCREF(__pyx_v_value);
+ __pyx_r = __pyx_v_value;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml.CParser.get_token");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_peek_token(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_peek_token(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":450 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token == Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":451 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_scan(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 451; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":452 */
+ Py_INCREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token);
+ __pyx_r = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml.CParser.peek_token");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n___class__;
+
+static PyObject *__pyx_f_5_yaml_7CParser_check_token(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_check_token(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_choices = 0;
+ PyObject *__pyx_v_token_class;
+ PyObject *__pyx_v_choice;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ int __pyx_3;
+ PyObject *__pyx_4 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (__Pyx_GetStarArgs(&__pyx_args, &__pyx_kwds, __pyx_argnames, 0, &__pyx_v_choices, 0) < 0) return 0;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) {
+ Py_XDECREF(__pyx_args);
+ Py_XDECREF(__pyx_kwds);
+ Py_XDECREF(__pyx_v_choices);
+ return 0;
+ }
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_token_class = Py_None; Py_INCREF(Py_None);
+ __pyx_v_choice = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":455 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token == Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":456 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_scan(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 456; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":457 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token == Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":458 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 458; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":459 */
+ __pyx_1 = PyObject_IsTrue(__pyx_v_choices); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 459; goto __pyx_L1;}
+ __pyx_3 = (!__pyx_1);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":460 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 460; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":461 */
+ __pyx_2 = PyObject_GetAttr(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_token, __pyx_n___class__); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 461; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_token_class);
+ __pyx_v_token_class = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":462 */
+ __pyx_2 = PyObject_GetIter(__pyx_v_choices); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 462; goto __pyx_L1;}
+ for (;;) {
+ __pyx_L5:;
+ __pyx_4 = PyIter_Next(__pyx_2);
+ if (!__pyx_4) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 462; goto __pyx_L1;}
+ break;
+ }
+ Py_DECREF(__pyx_v_choice);
+ __pyx_v_choice = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":463 */
+ __pyx_1 = __pyx_v_token_class == __pyx_v_choice;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":464 */
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 464; goto __pyx_L1;}
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L7;
+ }
+ __pyx_L7:;
+ }
+ __pyx_L6:;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":465 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 465; goto __pyx_L1;}
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser.check_token");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_XDECREF(__pyx_v_choices);
+ Py_DECREF(__pyx_v_token_class);
+ Py_DECREF(__pyx_v_choice);
+ Py_DECREF(__pyx_v_self);
+ Py_XDECREF(__pyx_args);
+ Py_XDECREF(__pyx_kwds);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_raw_parse(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_raw_parse(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ yaml_event_t __pyx_v_event;
+ int __pyx_v_done;
+ int __pyx_v_count;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":471 */
+ __pyx_v_count = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":472 */
+ __pyx_v_done = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":473 */
+ while (1) {
+ __pyx_L2:;
+ __pyx_1 = (__pyx_v_done == 0);
+ if (!__pyx_1) break;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":474 */
+ __pyx_1 = yaml_parser_parse((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parser),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 474; goto __pyx_L1;}
+ __pyx_2 = (__pyx_1 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":475 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parser_error(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 475; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":476 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 476; goto __pyx_L1;}
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":477 */
+ __pyx_1 = (__pyx_v_event.type == YAML_NO_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":478 */
+ __pyx_v_done = 1;
+ goto __pyx_L5;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":480 */
+ __pyx_v_count = (__pyx_v_count + 1);
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":481 */
+ yaml_event_delete((&__pyx_v_event));
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":482 */
+ __pyx_3 = PyInt_FromLong(__pyx_v_count); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 482; goto __pyx_L1;}
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.CParser.raw_parse");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser__parse(struct __pyx_obj_5_yaml_CParser *__pyx_v_self) {
+ yaml_event_t __pyx_v_event;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_v_event_object;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+ __pyx_v_event_object = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":486 */
+ __pyx_1 = yaml_parser_parse((&__pyx_v_self->parser),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 486; goto __pyx_L1;}
+ __pyx_2 = (__pyx_1 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":487 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parser_error(__pyx_v_self); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 487; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":488 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 488; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":489 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_event_to_object(__pyx_v_self,(&__pyx_v_event)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 489; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_event_object);
+ __pyx_v_event_object = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":490 */
+ yaml_event_delete((&__pyx_v_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":491 */
+ Py_INCREF(__pyx_v_event_object);
+ __pyx_r = __pyx_v_event_object;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.CParser._parse");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_event_object);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k36p;
+static PyObject *__pyx_k37p;
+static PyObject *__pyx_k38p;
+static PyObject *__pyx_k45p;
+static PyObject *__pyx_k46p;
+static PyObject *__pyx_k47p;
+static PyObject *__pyx_k48p;
+static PyObject *__pyx_k49p;
+static PyObject *__pyx_k54p;
+
+static char (__pyx_k36[]) = "utf-8";
+static char (__pyx_k37[]) = "utf-16-le";
+static char (__pyx_k38[]) = "utf-16-be";
+static char (__pyx_k39[]) = "strict";
+static char (__pyx_k40[]) = "strict";
+static char (__pyx_k41[]) = "strict";
+static char (__pyx_k42[]) = "strict";
+static char (__pyx_k43[]) = "strict";
+static char (__pyx_k44[]) = "strict";
+static char (__pyx_k45[]) = "";
+static char (__pyx_k46[]) = "\'";
+static char (__pyx_k47[]) = "\"";
+static char (__pyx_k48[]) = "|";
+static char (__pyx_k49[]) = ">";
+static char (__pyx_k50[]) = "strict";
+static char (__pyx_k51[]) = "strict";
+static char (__pyx_k52[]) = "strict";
+static char (__pyx_k53[]) = "strict";
+static char (__pyx_k54[]) = "unknown token type";
+
+static PyObject *__pyx_f_5_yaml_7CParser__event_to_object(struct __pyx_obj_5_yaml_CParser *__pyx_v_self,yaml_event_t (*__pyx_v_event)) {
+ yaml_tag_directive_t (*__pyx_v_tag_directive);
+ PyObject *__pyx_v_start_mark;
+ PyObject *__pyx_v_end_mark;
+ PyObject *__pyx_v_encoding;
+ PyObject *__pyx_v_explicit;
+ PyObject *__pyx_v_version;
+ PyObject *__pyx_v_tags;
+ PyObject *__pyx_v_handle;
+ PyObject *__pyx_v_prefix;
+ PyObject *__pyx_v_anchor;
+ PyObject *__pyx_v_tag;
+ PyObject *__pyx_v_value;
+ PyObject *__pyx_v_plain_implicit;
+ PyObject *__pyx_v_quoted_implicit;
+ PyObject *__pyx_v_style;
+ PyObject *__pyx_v_implicit;
+ PyObject *__pyx_v_flow_style;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ int __pyx_5;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_start_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_end_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_encoding = Py_None; Py_INCREF(Py_None);
+ __pyx_v_explicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_version = Py_None; Py_INCREF(Py_None);
+ __pyx_v_tags = Py_None; Py_INCREF(Py_None);
+ __pyx_v_handle = Py_None; Py_INCREF(Py_None);
+ __pyx_v_prefix = Py_None; Py_INCREF(Py_None);
+ __pyx_v_anchor = Py_None; Py_INCREF(Py_None);
+ __pyx_v_tag = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+ __pyx_v_plain_implicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_quoted_implicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_style = Py_None; Py_INCREF(Py_None);
+ __pyx_v_implicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_flow_style = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":495 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_event->start_mark.index); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 496; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_event->start_mark.line); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 497; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_event->start_mark.column); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 498; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(6); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 495; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_3);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 5, Py_None);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_1 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 495; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_start_mark);
+ __pyx_v_start_mark = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":500 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_event->end_mark.index); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 501; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_event->end_mark.line); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 502; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_event->end_mark.column); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 503; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(6); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 500; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_4);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 5, Py_None);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_2 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 500; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_v_end_mark);
+ __pyx_v_end_mark = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":505 */
+ __pyx_5 = (__pyx_v_event->type == YAML_NO_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":506 */
+ Py_INCREF(Py_None);
+ __pyx_r = Py_None;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_STREAM_START_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":508 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":509 */
+ __pyx_5 = (__pyx_v_event->data.stream_start.encoding == YAML_UTF8_ENCODING);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":510 */
+ Py_INCREF(__pyx_k36p);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = __pyx_k36p;
+ goto __pyx_L3;
+ }
+ __pyx_5 = (__pyx_v_event->data.stream_start.encoding == YAML_UTF16LE_ENCODING);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":512 */
+ Py_INCREF(__pyx_k37p);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = __pyx_k37p;
+ goto __pyx_L3;
+ }
+ __pyx_5 = (__pyx_v_event->data.stream_start.encoding == YAML_UTF16BE_ENCODING);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":514 */
+ Py_INCREF(__pyx_k38p);
+ Py_DECREF(__pyx_v_encoding);
+ __pyx_v_encoding = __pyx_k38p;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":515 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_StreamStartEvent); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 515; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 515; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_encoding);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_v_encoding);
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 515; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_STREAM_END_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":517 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_StreamEndEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 517; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 517; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_end_mark);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 517; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_DOCUMENT_START_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":520 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 520; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_explicit);
+ __pyx_v_explicit = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":521 */
+ __pyx_5 = (__pyx_v_event->data.document_start.implicit == 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":522 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 522; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_explicit);
+ __pyx_v_explicit = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":523 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_version);
+ __pyx_v_version = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":524 */
+ __pyx_5 = (__pyx_v_event->data.document_start.version_directive != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":525 */
+ __pyx_3 = PyInt_FromLong(__pyx_v_event->data.document_start.version_directive->major); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 525; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_event->data.document_start.version_directive->minor); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 526; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 525; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ Py_DECREF(__pyx_v_version);
+ __pyx_v_version = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":527 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_tags);
+ __pyx_v_tags = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":528 */
+ __pyx_5 = (__pyx_v_event->data.document_start.tag_directives.start != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":529 */
+ __pyx_2 = PyDict_New(); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 529; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tags);
+ __pyx_v_tags = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":530 */
+ __pyx_v_tag_directive = __pyx_v_event->data.document_start.tag_directives.start;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":531 */
+ while (1) {
+ __pyx_L7:;
+ __pyx_5 = (__pyx_v_tag_directive != __pyx_v_event->data.document_start.tag_directives.end);
+ if (!__pyx_5) break;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":532 */
+ __pyx_3 = PyUnicode_DecodeUTF8(__pyx_v_tag_directive->handle,strlen(__pyx_v_tag_directive->handle),__pyx_k39); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 532; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_handle);
+ __pyx_v_handle = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":534 */
+ __pyx_4 = PyUnicode_DecodeUTF8(__pyx_v_tag_directive->prefix,strlen(__pyx_v_tag_directive->prefix),__pyx_k40); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 534; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_prefix);
+ __pyx_v_prefix = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":536 */
+ if (PyObject_SetItem(__pyx_v_tags, __pyx_v_handle, __pyx_v_prefix) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 536; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":537 */
+ __pyx_v_tag_directive = (__pyx_v_tag_directive + 1);
+ }
+ __pyx_L8:;
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":538 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_DocumentStartEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 538; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(5); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 538; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_explicit);
+ PyTuple_SET_ITEM(__pyx_2, 2, __pyx_v_explicit);
+ Py_INCREF(__pyx_v_version);
+ PyTuple_SET_ITEM(__pyx_2, 3, __pyx_v_version);
+ Py_INCREF(__pyx_v_tags);
+ PyTuple_SET_ITEM(__pyx_2, 4, __pyx_v_tags);
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 538; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_DOCUMENT_END_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":541 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 541; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_explicit);
+ __pyx_v_explicit = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":542 */
+ __pyx_5 = (__pyx_v_event->data.document_end.implicit == 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":543 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 543; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_explicit);
+ __pyx_v_explicit = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L9;
+ }
+ __pyx_L9:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":544 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_DocumentEndEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 544; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(3); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 544; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_explicit);
+ PyTuple_SET_ITEM(__pyx_3, 2, __pyx_v_explicit);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 544; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_ALIAS_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":546 */
+ __pyx_1 = PyUnicode_DecodeUTF8(__pyx_v_event->data.alias.anchor,strlen(__pyx_v_event->data.alias.anchor),__pyx_k41); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 546; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":548 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_AliasEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 548; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(3); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 548; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_anchor);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_anchor);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 2, __pyx_v_end_mark);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 548; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_SCALAR_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":550 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":551 */
+ __pyx_5 = (__pyx_v_event->data.scalar.anchor != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":552 */
+ __pyx_1 = PyUnicode_DecodeUTF8(__pyx_v_event->data.scalar.anchor,strlen(__pyx_v_event->data.scalar.anchor),__pyx_k42); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 552; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L10;
+ }
+ __pyx_L10:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":554 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":555 */
+ __pyx_5 = (__pyx_v_event->data.scalar.tag != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":556 */
+ __pyx_2 = PyUnicode_DecodeUTF8(__pyx_v_event->data.scalar.tag,strlen(__pyx_v_event->data.scalar.tag),__pyx_k43); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 556; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L11;
+ }
+ __pyx_L11:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":558 */
+ __pyx_3 = PyUnicode_DecodeUTF8(__pyx_v_event->data.scalar.value,__pyx_v_event->data.scalar.length,__pyx_k44); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 558; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":560 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 560; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_plain_implicit);
+ __pyx_v_plain_implicit = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":561 */
+ __pyx_5 = (__pyx_v_event->data.scalar.plain_implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":562 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 562; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_plain_implicit);
+ __pyx_v_plain_implicit = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L12;
+ }
+ __pyx_L12:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":563 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 563; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_quoted_implicit);
+ __pyx_v_quoted_implicit = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":564 */
+ __pyx_5 = (__pyx_v_event->data.scalar.quoted_implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":565 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 565; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_quoted_implicit);
+ __pyx_v_quoted_implicit = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L13;
+ }
+ __pyx_L13:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":566 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":567 */
+ __pyx_5 = (__pyx_v_event->data.scalar.style == YAML_PLAIN_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":568 */
+ Py_INCREF(__pyx_k45p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k45p;
+ goto __pyx_L14;
+ }
+ __pyx_5 = (__pyx_v_event->data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":570 */
+ Py_INCREF(__pyx_k46p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k46p;
+ goto __pyx_L14;
+ }
+ __pyx_5 = (__pyx_v_event->data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":572 */
+ Py_INCREF(__pyx_k47p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k47p;
+ goto __pyx_L14;
+ }
+ __pyx_5 = (__pyx_v_event->data.scalar.style == YAML_LITERAL_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":574 */
+ Py_INCREF(__pyx_k48p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k48p;
+ goto __pyx_L14;
+ }
+ __pyx_5 = (__pyx_v_event->data.scalar.style == YAML_FOLDED_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":576 */
+ Py_INCREF(__pyx_k49p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k49p;
+ goto __pyx_L14;
+ }
+ __pyx_L14:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":577 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarEvent); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 577; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 578; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_plain_implicit);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_plain_implicit);
+ Py_INCREF(__pyx_v_quoted_implicit);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_v_quoted_implicit);
+ __pyx_2 = PyTuple_New(7); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 577; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_anchor);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_anchor);
+ Py_INCREF(__pyx_v_tag);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_tag);
+ PyTuple_SET_ITEM(__pyx_2, 2, __pyx_1);
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_2, 3, __pyx_v_value);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 4, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 5, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_style);
+ PyTuple_SET_ITEM(__pyx_2, 6, __pyx_v_style);
+ __pyx_1 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_4, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 577; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_SEQUENCE_START_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":581 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":582 */
+ __pyx_5 = (__pyx_v_event->data.sequence_start.anchor != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":583 */
+ __pyx_1 = PyUnicode_DecodeUTF8(__pyx_v_event->data.sequence_start.anchor,strlen(__pyx_v_event->data.sequence_start.anchor),__pyx_k50); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 583; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L15;
+ }
+ __pyx_L15:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":585 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":586 */
+ __pyx_5 = (__pyx_v_event->data.sequence_start.tag != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":587 */
+ __pyx_4 = PyUnicode_DecodeUTF8(__pyx_v_event->data.sequence_start.tag,strlen(__pyx_v_event->data.sequence_start.tag),__pyx_k51); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 587; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L16;
+ }
+ __pyx_L16:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":589 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 589; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":590 */
+ __pyx_5 = (__pyx_v_event->data.sequence_start.implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":591 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 591; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L17;
+ }
+ __pyx_L17:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":592 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":593 */
+ __pyx_5 = (__pyx_v_event->data.sequence_start.style == YAML_FLOW_SEQUENCE_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":594 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 594; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L18;
+ }
+ __pyx_5 = (__pyx_v_event->data.sequence_start.style == YAML_BLOCK_SEQUENCE_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":596 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 596; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L18;
+ }
+ __pyx_L18:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":597 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceStartEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 597; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(6); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 597; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_anchor);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_anchor);
+ Py_INCREF(__pyx_v_tag);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_tag);
+ Py_INCREF(__pyx_v_implicit);
+ PyTuple_SET_ITEM(__pyx_3, 2, __pyx_v_implicit);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 3, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 4, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_flow_style);
+ PyTuple_SET_ITEM(__pyx_3, 5, __pyx_v_flow_style);
+ __pyx_1 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 597; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_MAPPING_START_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":600 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":601 */
+ __pyx_5 = (__pyx_v_event->data.mapping_start.anchor != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":602 */
+ __pyx_4 = PyUnicode_DecodeUTF8(__pyx_v_event->data.mapping_start.anchor,strlen(__pyx_v_event->data.mapping_start.anchor),__pyx_k52); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 602; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L19;
+ }
+ __pyx_L19:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":604 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":605 */
+ __pyx_5 = (__pyx_v_event->data.mapping_start.tag != 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":606 */
+ __pyx_2 = PyUnicode_DecodeUTF8(__pyx_v_event->data.mapping_start.tag,strlen(__pyx_v_event->data.mapping_start.tag),__pyx_k53); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 606; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L20;
+ }
+ __pyx_L20:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":608 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 608; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":609 */
+ __pyx_5 = (__pyx_v_event->data.mapping_start.implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":610 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 610; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L21;
+ }
+ __pyx_L21:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":611 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":612 */
+ __pyx_5 = (__pyx_v_event->data.mapping_start.style == YAML_FLOW_SEQUENCE_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":613 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 613; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L22;
+ }
+ __pyx_5 = (__pyx_v_event->data.mapping_start.style == YAML_BLOCK_SEQUENCE_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":615 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 615; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L22;
+ }
+ __pyx_L22:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":616 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_MappingStartEvent); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 616; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(6); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 616; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_anchor);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_anchor);
+ Py_INCREF(__pyx_v_tag);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_v_tag);
+ Py_INCREF(__pyx_v_implicit);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_v_implicit);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_1, 4, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_flow_style);
+ PyTuple_SET_ITEM(__pyx_1, 5, __pyx_v_flow_style);
+ __pyx_4 = PyObject_CallObject(__pyx_3, __pyx_1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 616; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_SEQUENCE_END_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":619 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceEndEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 619; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 619; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_end_mark);
+ __pyx_1 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 619; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_5 = (__pyx_v_event->type == YAML_MAPPING_END_EVENT);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":621 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_MappingEndEvent); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 621; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 621; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_end_mark);
+ __pyx_3 = PyObject_CallObject(__pyx_4, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 621; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":624 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 624; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 624; goto __pyx_L1;}
+ Py_INCREF(__pyx_k54p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k54p);
+ __pyx_2 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 624; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 624; goto __pyx_L1;}
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser._event_to_object");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_start_mark);
+ Py_DECREF(__pyx_v_end_mark);
+ Py_DECREF(__pyx_v_encoding);
+ Py_DECREF(__pyx_v_explicit);
+ Py_DECREF(__pyx_v_version);
+ Py_DECREF(__pyx_v_tags);
+ Py_DECREF(__pyx_v_handle);
+ Py_DECREF(__pyx_v_prefix);
+ Py_DECREF(__pyx_v_anchor);
+ Py_DECREF(__pyx_v_tag);
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_plain_implicit);
+ Py_DECREF(__pyx_v_quoted_implicit);
+ Py_DECREF(__pyx_v_style);
+ Py_DECREF(__pyx_v_implicit);
+ Py_DECREF(__pyx_v_flow_style);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_get_event(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_get_event(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_value;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":627 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":628 */
+ Py_INCREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event);
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":629 */
+ Py_INCREF(Py_None);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event = Py_None;
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":631 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parse(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 631; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_2;
+ __pyx_2 = 0;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":632 */
+ Py_INCREF(__pyx_v_value);
+ __pyx_r = __pyx_v_value;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml.CParser.get_event");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_peek_event(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_peek_event(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":635 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event == Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":636 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parse(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 636; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":637 */
+ Py_INCREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event);
+ __pyx_r = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml.CParser.peek_event");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_check_event(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_check_event(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_choices = 0;
+ PyObject *__pyx_v_event_class;
+ PyObject *__pyx_v_choice;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ int __pyx_3;
+ PyObject *__pyx_4 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (__Pyx_GetStarArgs(&__pyx_args, &__pyx_kwds, __pyx_argnames, 0, &__pyx_v_choices, 0) < 0) return 0;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) {
+ Py_XDECREF(__pyx_args);
+ Py_XDECREF(__pyx_kwds);
+ Py_XDECREF(__pyx_v_choices);
+ return 0;
+ }
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_event_class = Py_None; Py_INCREF(Py_None);
+ __pyx_v_choice = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":640 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event == Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":641 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parse(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 641; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event);
+ ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":642 */
+ __pyx_1 = ((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event == Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":643 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 643; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":644 */
+ __pyx_1 = PyObject_IsTrue(__pyx_v_choices); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 644; goto __pyx_L1;}
+ __pyx_3 = (!__pyx_1);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":645 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 645; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":646 */
+ __pyx_2 = PyObject_GetAttr(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->current_event, __pyx_n___class__); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 646; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_event_class);
+ __pyx_v_event_class = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":647 */
+ __pyx_2 = PyObject_GetIter(__pyx_v_choices); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 647; goto __pyx_L1;}
+ for (;;) {
+ __pyx_L5:;
+ __pyx_4 = PyIter_Next(__pyx_2);
+ if (!__pyx_4) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 647; goto __pyx_L1;}
+ break;
+ }
+ Py_DECREF(__pyx_v_choice);
+ __pyx_v_choice = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":648 */
+ __pyx_1 = __pyx_v_event_class == __pyx_v_choice;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":649 */
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 649; goto __pyx_L1;}
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L7;
+ }
+ __pyx_L7:;
+ }
+ __pyx_L6:;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":650 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 650; goto __pyx_L1;}
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser.check_event");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_XDECREF(__pyx_v_choices);
+ Py_DECREF(__pyx_v_event_class);
+ Py_DECREF(__pyx_v_choice);
+ Py_DECREF(__pyx_v_self);
+ Py_XDECREF(__pyx_args);
+ Py_XDECREF(__pyx_kwds);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_check_node(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_check_node(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":653 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parse_next_event(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 653; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":654 */
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parsed_event.type == YAML_STREAM_START_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":655 */
+ yaml_event_delete((&((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":656 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parse_next_event(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 656; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":657 */
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parsed_event.type != YAML_STREAM_END_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":658 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 658; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":659 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 659; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml.CParser.check_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser_get_node(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_7CParser_get_node(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":662 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_parse_next_event(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 662; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":663 */
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->parsed_event.type != YAML_STREAM_END_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":664 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)->__pyx_vtab)->_compose_document(((struct __pyx_obj_5_yaml_CParser *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 664; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml.CParser.get_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_7CParser__compose_document(struct __pyx_obj_5_yaml_CParser *__pyx_v_self) {
+ PyObject *__pyx_v_node;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ int __pyx_2;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_node = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":667 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":668 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_compose_node(__pyx_v_self,Py_None,Py_None); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 668; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_node);
+ __pyx_v_node = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":669 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parse_next_event(__pyx_v_self); if (__pyx_2 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 669; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":670 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":671 */
+ __pyx_1 = PyDict_New(); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 671; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_self->anchors);
+ __pyx_v_self->anchors = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":672 */
+ Py_INCREF(__pyx_v_node);
+ __pyx_r = __pyx_v_node;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("_yaml.CParser._compose_document");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_node);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_start_mark;
+static PyObject *__pyx_n_descend_resolver;
+static PyObject *__pyx_n_ascend_resolver;
+
+static PyObject *__pyx_k56p;
+static PyObject *__pyx_k60p;
+static PyObject *__pyx_k61p;
+
+static char (__pyx_k55[]) = "strict";
+static char (__pyx_k56[]) = "found undefined alias";
+static char (__pyx_k57[]) = "strict";
+static char (__pyx_k58[]) = "strict";
+static char (__pyx_k59[]) = "strict";
+static char (__pyx_k60[]) = "found duplicate anchor; first occurence";
+static char (__pyx_k61[]) = "second occurence";
+
+static PyObject *__pyx_f_5_yaml_7CParser__compose_node(struct __pyx_obj_5_yaml_CParser *__pyx_v_self,PyObject *__pyx_v_parent,PyObject *__pyx_v_index) {
+ PyObject *__pyx_v_anchor;
+ PyObject *__pyx_v_mark;
+ PyObject *__pyx_v_node;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_parent);
+ Py_INCREF(__pyx_v_index);
+ __pyx_v_anchor = Py_None; Py_INCREF(Py_None);
+ __pyx_v_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_node = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":675 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parse_next_event(__pyx_v_self); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 675; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":676 */
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_ALIAS_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":677 */
+ __pyx_2 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.alias.anchor,strlen(__pyx_v_self->parsed_event.data.alias.anchor),__pyx_k55); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 677; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":679 */
+ __pyx_1 = PySequence_Contains(__pyx_v_self->anchors, __pyx_v_anchor); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 679; goto __pyx_L1;}
+ __pyx_1 = !__pyx_1;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":680 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.index); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 681; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.line); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 682; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.column); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 683; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(6); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 680; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_5, 2, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 3, __pyx_4);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_5, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_5, 5, Py_None);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_2 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_5); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 680; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_v_mark);
+ __pyx_v_mark = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":685 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_ComposerError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 685; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(4); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 685; goto __pyx_L1;}
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 0, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 1, Py_None);
+ Py_INCREF(__pyx_k56p);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_k56p);
+ Py_INCREF(__pyx_v_mark);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_v_mark);
+ __pyx_5 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 685; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 685; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":686 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":687 */
+ __pyx_2 = PyObject_GetItem(__pyx_v_self->anchors, __pyx_v_anchor); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 687; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":688 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":689 */
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_SCALAR_EVENT);
+ if (__pyx_1) {
+ __pyx_1 = (__pyx_v_self->parsed_event.data.scalar.anchor != 0);
+ }
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":691 */
+ __pyx_3 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.scalar.anchor,strlen(__pyx_v_self->parsed_event.data.scalar.anchor),__pyx_k57); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 691; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_SEQUENCE_START_EVENT);
+ if (__pyx_1) {
+ __pyx_1 = (__pyx_v_self->parsed_event.data.sequence_start.anchor != 0);
+ }
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":695 */
+ __pyx_4 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.sequence_start.anchor,strlen(__pyx_v_self->parsed_event.data.sequence_start.anchor),__pyx_k58); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 695; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_MAPPING_START_EVENT);
+ if (__pyx_1) {
+ __pyx_1 = (__pyx_v_self->parsed_event.data.mapping_start.anchor != 0);
+ }
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":699 */
+ __pyx_5 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.mapping_start.anchor,strlen(__pyx_v_self->parsed_event.data.mapping_start.anchor),__pyx_k59); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 699; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor);
+ __pyx_v_anchor = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":701 */
+ __pyx_1 = __pyx_v_anchor != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":702 */
+ __pyx_1 = PySequence_Contains(__pyx_v_self->anchors, __pyx_v_anchor); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 702; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":703 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.index); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 704; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.line); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 705; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.column); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 706; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(6); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 703; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_5, 2, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 3, __pyx_4);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_5, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_5, 5, Py_None);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_2 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_5); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 703; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_v_mark);
+ __pyx_v_mark = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":708 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_ComposerError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 708; goto __pyx_L1;}
+ __pyx_4 = PyObject_GetItem(__pyx_v_self->anchors, __pyx_v_anchor); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 709; goto __pyx_L1;}
+ __pyx_5 = PyObject_GetAttr(__pyx_4, __pyx_n_start_mark); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 709; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_2 = PyTuple_New(4); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 708; goto __pyx_L1;}
+ Py_INCREF(__pyx_k60p);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_k60p);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_5);
+ Py_INCREF(__pyx_k61p);
+ PyTuple_SET_ITEM(__pyx_2, 2, __pyx_k61p);
+ Py_INCREF(__pyx_v_mark);
+ PyTuple_SET_ITEM(__pyx_2, 3, __pyx_v_mark);
+ __pyx_5 = 0;
+ __pyx_4 = PyObject_CallObject(__pyx_3, __pyx_2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 708; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 708; goto __pyx_L1;}
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":710 */
+ __pyx_5 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_descend_resolver); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 710; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 710; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_parent);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_parent);
+ Py_INCREF(__pyx_v_index);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_index);
+ __pyx_2 = PyObject_CallObject(__pyx_5, __pyx_3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 710; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":711 */
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_SCALAR_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":712 */
+ __pyx_4 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_compose_scalar_node(__pyx_v_self,__pyx_v_anchor); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 712; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_node);
+ __pyx_v_node = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L7;
+ }
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_SEQUENCE_START_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":714 */
+ __pyx_5 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_compose_sequence_node(__pyx_v_self,__pyx_v_anchor); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 714; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_node);
+ __pyx_v_node = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L7;
+ }
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_MAPPING_START_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":716 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_compose_mapping_node(__pyx_v_self,__pyx_v_anchor); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 716; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_node);
+ __pyx_v_node = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L7;
+ }
+ __pyx_L7:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":717 */
+ __pyx_2 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_ascend_resolver); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 717; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(0); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 717; goto __pyx_L1;}
+ __pyx_5 = PyObject_CallObject(__pyx_2, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 717; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":718 */
+ Py_INCREF(__pyx_v_node);
+ __pyx_r = __pyx_v_node;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_yaml.CParser._compose_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_anchor);
+ Py_DECREF(__pyx_v_mark);
+ Py_DECREF(__pyx_v_node);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_parent);
+ Py_DECREF(__pyx_v_index);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_resolve;
+
+static PyObject *__pyx_k64p;
+static PyObject *__pyx_k65p;
+static PyObject *__pyx_k66p;
+static PyObject *__pyx_k67p;
+static PyObject *__pyx_k68p;
+
+static char (__pyx_k62[]) = "strict";
+static char (__pyx_k63[]) = "strict";
+static char (__pyx_k64[]) = "";
+static char (__pyx_k65[]) = "\'";
+static char (__pyx_k66[]) = "\"";
+static char (__pyx_k67[]) = "|";
+static char (__pyx_k68[]) = ">";
+
+static PyObject *__pyx_f_5_yaml_7CParser__compose_scalar_node(struct __pyx_obj_5_yaml_CParser *__pyx_v_self,PyObject *__pyx_v_anchor) {
+ PyObject *__pyx_v_start_mark;
+ PyObject *__pyx_v_end_mark;
+ PyObject *__pyx_v_value;
+ PyObject *__pyx_v_plain_implicit;
+ PyObject *__pyx_v_quoted_implicit;
+ PyObject *__pyx_v_tag;
+ PyObject *__pyx_v_style;
+ PyObject *__pyx_v_node;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ int __pyx_5;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_anchor);
+ __pyx_v_start_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_end_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+ __pyx_v_plain_implicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_quoted_implicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_tag = Py_None; Py_INCREF(Py_None);
+ __pyx_v_style = Py_None; Py_INCREF(Py_None);
+ __pyx_v_node = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":721 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.index); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 722; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.line); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 723; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.column); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 724; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(6); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 721; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_3);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 5, Py_None);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_1 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 721; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_start_mark);
+ __pyx_v_start_mark = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":726 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.index); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 727; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.line); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 728; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.column); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 729; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(6); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 726; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_4);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 5, Py_None);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_2 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 726; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_v_end_mark);
+ __pyx_v_end_mark = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":731 */
+ __pyx_3 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.scalar.value,__pyx_v_self->parsed_event.data.scalar.length,__pyx_k62); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 731; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":733 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 733; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_plain_implicit);
+ __pyx_v_plain_implicit = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":734 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.plain_implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":735 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 735; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_plain_implicit);
+ __pyx_v_plain_implicit = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":736 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 736; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_quoted_implicit);
+ __pyx_v_quoted_implicit = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":737 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.quoted_implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":738 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 738; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_quoted_implicit);
+ __pyx_v_quoted_implicit = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":739 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.tag == 0);
+ if (!__pyx_5) {
+ __pyx_5 = ((__pyx_v_self->parsed_event.data.scalar.tag[0]) == '!');
+ if (__pyx_5) {
+ __pyx_5 = ((__pyx_v_self->parsed_event.data.scalar.tag[1]) == '\0');
+ }
+ }
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":742 */
+ __pyx_4 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_resolve); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 742; goto __pyx_L1;}
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 742; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 742; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_plain_implicit);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_plain_implicit);
+ Py_INCREF(__pyx_v_quoted_implicit);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_quoted_implicit);
+ __pyx_3 = PyTuple_New(3); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 742; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_1);
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_3, 2, __pyx_2);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_1 = PyObject_CallObject(__pyx_4, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 742; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L4;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":744 */
+ __pyx_2 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.scalar.tag,strlen(__pyx_v_self->parsed_event.data.scalar.tag),__pyx_k63); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 744; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_2;
+ __pyx_2 = 0;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":746 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":747 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":748 */
+ Py_INCREF(__pyx_k64p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k64p;
+ goto __pyx_L5;
+ }
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":750 */
+ Py_INCREF(__pyx_k65p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k65p;
+ goto __pyx_L5;
+ }
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":752 */
+ Py_INCREF(__pyx_k66p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k66p;
+ goto __pyx_L5;
+ }
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.style == YAML_LITERAL_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":754 */
+ Py_INCREF(__pyx_k67p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k67p;
+ goto __pyx_L5;
+ }
+ __pyx_5 = (__pyx_v_self->parsed_event.data.scalar.style == YAML_FOLDED_SCALAR_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":756 */
+ Py_INCREF(__pyx_k68p);
+ Py_DECREF(__pyx_v_style);
+ __pyx_v_style = __pyx_k68p;
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":757 */
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarNode); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 757; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(5); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 757; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_tag);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_tag);
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_3, 1, __pyx_v_value);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_3, 2, __pyx_v_start_mark);
+ Py_INCREF(__pyx_v_end_mark);
+ PyTuple_SET_ITEM(__pyx_3, 3, __pyx_v_end_mark);
+ Py_INCREF(__pyx_v_style);
+ PyTuple_SET_ITEM(__pyx_3, 4, __pyx_v_style);
+ __pyx_1 = PyObject_CallObject(__pyx_4, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 757; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_v_node);
+ __pyx_v_node = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":758 */
+ __pyx_5 = __pyx_v_anchor != Py_None;
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":759 */
+ if (PyObject_SetItem(__pyx_v_self->anchors, __pyx_v_anchor, __pyx_v_node) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 759; goto __pyx_L1;}
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":760 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":761 */
+ Py_INCREF(__pyx_v_node);
+ __pyx_r = __pyx_v_node;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser._compose_scalar_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_start_mark);
+ Py_DECREF(__pyx_v_end_mark);
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_plain_implicit);
+ Py_DECREF(__pyx_v_quoted_implicit);
+ Py_DECREF(__pyx_v_tag);
+ Py_DECREF(__pyx_v_style);
+ Py_DECREF(__pyx_v_node);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_anchor);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_append;
+static PyObject *__pyx_n_end_mark;
+
+static char (__pyx_k69[]) = "strict";
+
+static PyObject *__pyx_f_5_yaml_7CParser__compose_sequence_node(struct __pyx_obj_5_yaml_CParser *__pyx_v_self,PyObject *__pyx_v_anchor) {
+ int __pyx_v_index;
+ PyObject *__pyx_v_start_mark;
+ PyObject *__pyx_v_implicit;
+ PyObject *__pyx_v_tag;
+ PyObject *__pyx_v_flow_style;
+ PyObject *__pyx_v_value;
+ PyObject *__pyx_v_node;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ int __pyx_5;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_anchor);
+ __pyx_v_start_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_implicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_tag = Py_None; Py_INCREF(Py_None);
+ __pyx_v_flow_style = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+ __pyx_v_node = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":765 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.index); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 766; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.line); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 767; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.column); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 768; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(6); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 765; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_3);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 5, Py_None);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_1 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 765; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_start_mark);
+ __pyx_v_start_mark = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":770 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 770; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":771 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.sequence_start.implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":772 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 772; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":773 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.sequence_start.tag == 0);
+ if (!__pyx_5) {
+ __pyx_5 = ((__pyx_v_self->parsed_event.data.sequence_start.tag[0]) == '!');
+ if (__pyx_5) {
+ __pyx_5 = ((__pyx_v_self->parsed_event.data.sequence_start.tag[1]) == '\0');
+ }
+ }
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":776 */
+ __pyx_4 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_resolve); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 776; goto __pyx_L1;}
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 776; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 776; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_1);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_2, 1, Py_None);
+ Py_INCREF(__pyx_v_implicit);
+ PyTuple_SET_ITEM(__pyx_2, 2, __pyx_v_implicit);
+ __pyx_1 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_4, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 776; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L3;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":778 */
+ __pyx_1 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.sequence_start.tag,strlen(__pyx_v_self->parsed_event.data.sequence_start.tag),__pyx_k69); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 778; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_1;
+ __pyx_1 = 0;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":780 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":781 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.sequence_start.style == YAML_FLOW_SEQUENCE_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":782 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 782; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_5 = (__pyx_v_self->parsed_event.data.sequence_start.style == YAML_BLOCK_SEQUENCE_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":784 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 784; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":785 */
+ __pyx_3 = PyList_New(0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 785; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":786 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 786; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(5); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 786; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_tag);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_tag);
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_value);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_v_start_mark);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 3, Py_None);
+ Py_INCREF(__pyx_v_flow_style);
+ PyTuple_SET_ITEM(__pyx_4, 4, __pyx_v_flow_style);
+ __pyx_2 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 786; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_node);
+ __pyx_v_node = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":787 */
+ __pyx_5 = __pyx_v_anchor != Py_None;
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":788 */
+ if (PyObject_SetItem(__pyx_v_self->anchors, __pyx_v_anchor, __pyx_v_node) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 788; goto __pyx_L1;}
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":789 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":790 */
+ __pyx_v_index = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":791 */
+ __pyx_5 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parse_next_event(__pyx_v_self); if (__pyx_5 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 791; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":792 */
+ while (1) {
+ __pyx_L6:;
+ __pyx_5 = (__pyx_v_self->parsed_event.type != YAML_SEQUENCE_END_EVENT);
+ if (!__pyx_5) break;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":793 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_value, __pyx_n_append); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 793; goto __pyx_L1;}
+ __pyx_1 = PyInt_FromLong(__pyx_v_index); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 793; goto __pyx_L1;}
+ __pyx_4 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_compose_node(__pyx_v_self,__pyx_v_node,__pyx_1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 793; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_2 = PyTuple_New(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 793; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_4);
+ __pyx_4 = 0;
+ __pyx_1 = PyObject_CallObject(__pyx_3, __pyx_2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 793; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":794 */
+ __pyx_v_index = (__pyx_v_index + 1);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":795 */
+ __pyx_5 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parse_next_event(__pyx_v_self); if (__pyx_5 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 795; goto __pyx_L1;}
+ }
+ __pyx_L7:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":796 */
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.index); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 797; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.line); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 798; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.column); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 799; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(6); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 796; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_4);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_2);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 5, Py_None);
+ __pyx_4 = 0;
+ __pyx_3 = 0;
+ __pyx_2 = 0;
+ __pyx_4 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 796; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_v_node, __pyx_n_end_mark, __pyx_4) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 796; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":801 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":802 */
+ Py_INCREF(__pyx_v_node);
+ __pyx_r = __pyx_v_node;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser._compose_sequence_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_start_mark);
+ Py_DECREF(__pyx_v_implicit);
+ Py_DECREF(__pyx_v_tag);
+ Py_DECREF(__pyx_v_flow_style);
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_node);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_anchor);
+ return __pyx_r;
+}
+
+static char (__pyx_k70[]) = "strict";
+
+static PyObject *__pyx_f_5_yaml_7CParser__compose_mapping_node(struct __pyx_obj_5_yaml_CParser *__pyx_v_self,PyObject *__pyx_v_anchor) {
+ PyObject *__pyx_v_start_mark;
+ PyObject *__pyx_v_implicit;
+ PyObject *__pyx_v_tag;
+ PyObject *__pyx_v_flow_style;
+ PyObject *__pyx_v_value;
+ PyObject *__pyx_v_node;
+ PyObject *__pyx_v_item_key;
+ PyObject *__pyx_v_item_value;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ int __pyx_5;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_anchor);
+ __pyx_v_start_mark = Py_None; Py_INCREF(Py_None);
+ __pyx_v_implicit = Py_None; Py_INCREF(Py_None);
+ __pyx_v_tag = Py_None; Py_INCREF(Py_None);
+ __pyx_v_flow_style = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+ __pyx_v_node = Py_None; Py_INCREF(Py_None);
+ __pyx_v_item_key = Py_None; Py_INCREF(Py_None);
+ __pyx_v_item_value = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":805 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.index); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 806; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.line); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 807; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.start_mark.column); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 808; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(6); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 805; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 3, __pyx_3);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 5, Py_None);
+ __pyx_1 = 0;
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_1 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_4); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 805; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_start_mark);
+ __pyx_v_start_mark = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":810 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 810; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":811 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.mapping_start.implicit == 1);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":812 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 812; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_implicit);
+ __pyx_v_implicit = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":813 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.mapping_start.tag == 0);
+ if (!__pyx_5) {
+ __pyx_5 = ((__pyx_v_self->parsed_event.data.mapping_start.tag[0]) == '!');
+ if (__pyx_5) {
+ __pyx_5 = ((__pyx_v_self->parsed_event.data.mapping_start.tag[1]) == '\0');
+ }
+ }
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":816 */
+ __pyx_4 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_resolve); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 816; goto __pyx_L1;}
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_MappingNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 816; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 816; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_1);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_2, 1, Py_None);
+ Py_INCREF(__pyx_v_implicit);
+ PyTuple_SET_ITEM(__pyx_2, 2, __pyx_v_implicit);
+ __pyx_1 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_4, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 816; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L3;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":818 */
+ __pyx_1 = PyUnicode_DecodeUTF8(__pyx_v_self->parsed_event.data.mapping_start.tag,strlen(__pyx_v_self->parsed_event.data.mapping_start.tag),__pyx_k70); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 818; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag);
+ __pyx_v_tag = __pyx_1;
+ __pyx_1 = 0;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":820 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":821 */
+ __pyx_5 = (__pyx_v_self->parsed_event.data.mapping_start.style == YAML_FLOW_MAPPING_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":822 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 822; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_5 = (__pyx_v_self->parsed_event.data.mapping_start.style == YAML_BLOCK_MAPPING_STYLE);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":824 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 824; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_flow_style);
+ __pyx_v_flow_style = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":825 */
+ __pyx_3 = PyList_New(0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 825; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":826 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_MappingNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 826; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(5); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 826; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_tag);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_tag);
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_value);
+ Py_INCREF(__pyx_v_start_mark);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_v_start_mark);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_4, 3, Py_None);
+ Py_INCREF(__pyx_v_flow_style);
+ PyTuple_SET_ITEM(__pyx_4, 4, __pyx_v_flow_style);
+ __pyx_2 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 826; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_node);
+ __pyx_v_node = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":827 */
+ __pyx_5 = __pyx_v_anchor != Py_None;
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":828 */
+ if (PyObject_SetItem(__pyx_v_self->anchors, __pyx_v_anchor, __pyx_v_node) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 828; goto __pyx_L1;}
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":829 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":830 */
+ __pyx_5 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parse_next_event(__pyx_v_self); if (__pyx_5 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 830; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":831 */
+ while (1) {
+ __pyx_L6:;
+ __pyx_5 = (__pyx_v_self->parsed_event.type != YAML_MAPPING_END_EVENT);
+ if (!__pyx_5) break;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":832 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_compose_node(__pyx_v_self,__pyx_v_node,Py_None); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 832; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_item_key);
+ __pyx_v_item_key = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":833 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_compose_node(__pyx_v_self,__pyx_v_node,__pyx_v_item_key); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 833; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_item_value);
+ __pyx_v_item_value = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":834 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_value, __pyx_n_append); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 834; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 834; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_item_key);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_item_key);
+ Py_INCREF(__pyx_v_item_value);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_v_item_value);
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 834; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_2);
+ __pyx_2 = 0;
+ __pyx_1 = PyObject_CallObject(__pyx_4, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 834; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":835 */
+ __pyx_5 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parse_next_event(__pyx_v_self); if (__pyx_5 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 835; goto __pyx_L1;}
+ }
+ __pyx_L7:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":836 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.index); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 837; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.line); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 838; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_self->parsed_event.end_mark.column); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 839; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(6); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 836; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_v_self->stream_name);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_4);
+ PyTuple_SET_ITEM(__pyx_1, 3, __pyx_3);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 4, Py_None);
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(__pyx_1, 5, Py_None);
+ __pyx_2 = 0;
+ __pyx_4 = 0;
+ __pyx_3 = 0;
+ __pyx_2 = PyObject_CallObject(((PyObject*)__pyx_ptype_5_yaml_Mark), __pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 836; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_v_node, __pyx_n_end_mark, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 836; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":841 */
+ yaml_event_delete((&__pyx_v_self->parsed_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":842 */
+ Py_INCREF(__pyx_v_node);
+ __pyx_r = __pyx_v_node;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CParser._compose_mapping_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_start_mark);
+ Py_DECREF(__pyx_v_implicit);
+ Py_DECREF(__pyx_v_tag);
+ Py_DECREF(__pyx_v_flow_style);
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_node);
+ Py_DECREF(__pyx_v_item_key);
+ Py_DECREF(__pyx_v_item_value);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_anchor);
+ return __pyx_r;
+}
+
+static int __pyx_f_5_yaml_7CParser__parse_next_event(struct __pyx_obj_5_yaml_CParser *__pyx_v_self) {
+ PyObject *__pyx_v_error;
+ int __pyx_r;
+ int __pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":845 */
+ __pyx_1 = (__pyx_v_self->parsed_event.type == YAML_NO_EVENT);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":846 */
+ __pyx_1 = yaml_parser_parse((&__pyx_v_self->parser),(&__pyx_v_self->parsed_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 846; goto __pyx_L1;}
+ __pyx_2 = (__pyx_1 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":847 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CParser *)__pyx_v_self->__pyx_vtab)->_parser_error(__pyx_v_self); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 847; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":848 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 848; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":849 */
+ __pyx_r = 1;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.CParser._parse_next_event");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k71p;
+static PyObject *__pyx_k72p;
+
+static char (__pyx_k71[]) = "a string value is expected";
+static char (__pyx_k72[]) = "a string value it too long";
+
+static int __pyx_f_5_yaml_input_handler(void (*__pyx_v_data),char (*__pyx_v_buffer),int __pyx_v_size,int (*__pyx_v_read)) {
+ struct __pyx_obj_5_yaml_CParser *__pyx_v_parser;
+ PyObject *__pyx_v_value;
+ int __pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ int __pyx_4;
+ __pyx_v_parser = ((struct __pyx_obj_5_yaml_CParser *)Py_None); Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":853 */
+ __pyx_1 = (PyObject *)__pyx_v_data;
+ Py_INCREF(__pyx_1);
+ Py_DECREF(((PyObject *)__pyx_v_parser));
+ __pyx_v_parser = ((struct __pyx_obj_5_yaml_CParser *)__pyx_1);
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":854 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_parser->stream, __pyx_n_read); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 854; goto __pyx_L1;}
+ __pyx_2 = PyInt_FromLong(__pyx_v_size); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 854; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 854; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_2);
+ __pyx_2 = 0;
+ __pyx_2 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 854; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":855 */
+ __pyx_4 = (PyString_CheckExact(__pyx_v_value) == 0);
+ if (__pyx_4) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":856 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 856; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 856; goto __pyx_L1;}
+ Py_INCREF(__pyx_k71p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k71p);
+ __pyx_2 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 856; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 856; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":857 */
+ __pyx_4 = (PyString_GET_SIZE(__pyx_v_value) > __pyx_v_size);
+ if (__pyx_4) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":858 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 858; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 858; goto __pyx_L1;}
+ Py_INCREF(__pyx_k72p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k72p);
+ __pyx_2 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 858; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 858; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":859 */
+ memcpy(__pyx_v_buffer,PyString_AS_STRING(__pyx_v_value),PyString_GET_SIZE(__pyx_v_value));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":860 */
+ (__pyx_v_read[0]) = PyString_GET_SIZE(__pyx_v_value);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":861 */
+ __pyx_r = 1;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.input_handler");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_parser);
+ Py_DECREF(__pyx_v_value);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k73p;
+static PyObject *__pyx_k74p;
+static PyObject *__pyx_k75p;
+static PyObject *__pyx_k76p;
+static PyObject *__pyx_k77p;
+
+static char (__pyx_k73[]) = "\r";
+static char (__pyx_k74[]) = "\n";
+static char (__pyx_k75[]) = "\r\n";
+static char (__pyx_k76[]) = "utf-16-le";
+static char (__pyx_k77[]) = "utf-16-be";
+
+static int __pyx_f_5_yaml_8CEmitter___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_5_yaml_8CEmitter___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_stream = 0;
+ PyObject *__pyx_v_canonical = 0;
+ PyObject *__pyx_v_indent = 0;
+ PyObject *__pyx_v_width = 0;
+ PyObject *__pyx_v_allow_unicode = 0;
+ PyObject *__pyx_v_line_break = 0;
+ PyObject *__pyx_v_encoding = 0;
+ PyObject *__pyx_v_explicit_start = 0;
+ PyObject *__pyx_v_explicit_end = 0;
+ PyObject *__pyx_v_version = 0;
+ PyObject *__pyx_v_tags = 0;
+ int __pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"stream","canonical","indent","width","allow_unicode","line_break","encoding","explicit_start","explicit_end","version","tags",0};
+ __pyx_v_canonical = __pyx_k2;
+ __pyx_v_indent = __pyx_k3;
+ __pyx_v_width = __pyx_k4;
+ __pyx_v_allow_unicode = __pyx_k5;
+ __pyx_v_line_break = __pyx_k6;
+ __pyx_v_encoding = __pyx_k7;
+ __pyx_v_explicit_start = __pyx_k8;
+ __pyx_v_explicit_end = __pyx_k9;
+ __pyx_v_version = __pyx_k10;
+ __pyx_v_tags = __pyx_k11;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O|OOOOOOOOOO", __pyx_argnames, &__pyx_v_stream, &__pyx_v_canonical, &__pyx_v_indent, &__pyx_v_width, &__pyx_v_allow_unicode, &__pyx_v_line_break, &__pyx_v_encoding, &__pyx_v_explicit_start, &__pyx_v_explicit_end, &__pyx_v_version, &__pyx_v_tags)) return -1;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_stream);
+ Py_INCREF(__pyx_v_canonical);
+ Py_INCREF(__pyx_v_indent);
+ Py_INCREF(__pyx_v_width);
+ Py_INCREF(__pyx_v_allow_unicode);
+ Py_INCREF(__pyx_v_line_break);
+ Py_INCREF(__pyx_v_encoding);
+ Py_INCREF(__pyx_v_explicit_start);
+ Py_INCREF(__pyx_v_explicit_end);
+ Py_INCREF(__pyx_v_version);
+ Py_INCREF(__pyx_v_tags);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":883 */
+ __pyx_1 = (yaml_emitter_initialize((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter)) == 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":884 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 884; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, 0, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 884; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":885 */
+ Py_INCREF(__pyx_v_stream);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->stream);
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->stream = __pyx_v_stream;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":886 */
+ yaml_emitter_set_output((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),__pyx_f_5_yaml_output_handler,((void (*))__pyx_v_self));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":887 */
+ __pyx_1 = __pyx_v_canonical != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":888 */
+ yaml_emitter_set_canonical((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),1);
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":889 */
+ __pyx_1 = __pyx_v_indent != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":890 */
+ __pyx_1 = PyInt_AsLong(__pyx_v_indent); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 890; goto __pyx_L1;}
+ yaml_emitter_set_indent((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),__pyx_1);
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":891 */
+ __pyx_1 = __pyx_v_width != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":892 */
+ __pyx_1 = PyInt_AsLong(__pyx_v_width); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 892; goto __pyx_L1;}
+ yaml_emitter_set_width((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),__pyx_1);
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":893 */
+ __pyx_1 = __pyx_v_allow_unicode != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":894 */
+ yaml_emitter_set_unicode((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),1);
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":895 */
+ __pyx_1 = __pyx_v_line_break != Py_None;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":896 */
+ if (PyObject_Cmp(__pyx_v_line_break, __pyx_k73p, &__pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 896; goto __pyx_L1;}
+ __pyx_1 = __pyx_1 == 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":897 */
+ yaml_emitter_set_break((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),YAML_CR_BREAK);
+ goto __pyx_L8;
+ }
+ if (PyObject_Cmp(__pyx_v_line_break, __pyx_k74p, &__pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 898; goto __pyx_L1;}
+ __pyx_1 = __pyx_1 == 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":899 */
+ yaml_emitter_set_break((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),YAML_LN_BREAK);
+ goto __pyx_L8;
+ }
+ if (PyObject_Cmp(__pyx_v_line_break, __pyx_k75p, &__pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 900; goto __pyx_L1;}
+ __pyx_1 = __pyx_1 == 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":901 */
+ yaml_emitter_set_break((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),YAML_CRLN_BREAK);
+ goto __pyx_L8;
+ }
+ __pyx_L8:;
+ goto __pyx_L7;
+ }
+ __pyx_L7:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":902 */
+ if (PyObject_Cmp(__pyx_v_encoding, __pyx_k76p, &__pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 902; goto __pyx_L1;}
+ __pyx_1 = __pyx_1 == 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":903 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_encoding = YAML_UTF16LE_ENCODING;
+ goto __pyx_L9;
+ }
+ if (PyObject_Cmp(__pyx_v_encoding, __pyx_k77p, &__pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 904; goto __pyx_L1;}
+ __pyx_1 = __pyx_1 == 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":905 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_encoding = YAML_UTF16BE_ENCODING;
+ goto __pyx_L9;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":907 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_encoding = YAML_UTF8_ENCODING;
+ }
+ __pyx_L9:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":908 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->document_start_implicit = 1;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":909 */
+ __pyx_1 = PyObject_IsTrue(__pyx_v_explicit_start); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 909; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":910 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->document_start_implicit = 0;
+ goto __pyx_L10;
+ }
+ __pyx_L10:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":911 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->document_end_implicit = 1;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":912 */
+ __pyx_1 = PyObject_IsTrue(__pyx_v_explicit_end); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 912; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":913 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->document_end_implicit = 0;
+ goto __pyx_L11;
+ }
+ __pyx_L11:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":914 */
+ Py_INCREF(__pyx_v_version);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_version);
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_version = __pyx_v_version;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":915 */
+ Py_INCREF(__pyx_v_tags);
+ Py_DECREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_tags);
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_tags = __pyx_v_tags;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":916 */
+ __pyx_2 = PyDict_New(); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 916; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->serialized_nodes);
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->serialized_nodes = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":917 */
+ __pyx_2 = PyDict_New(); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 917; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->anchors);
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->anchors = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":918 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->last_alias_id = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":919 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed = (-1);
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml.CEmitter.__init__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_stream);
+ Py_DECREF(__pyx_v_canonical);
+ Py_DECREF(__pyx_v_indent);
+ Py_DECREF(__pyx_v_width);
+ Py_DECREF(__pyx_v_allow_unicode);
+ Py_DECREF(__pyx_v_line_break);
+ Py_DECREF(__pyx_v_encoding);
+ Py_DECREF(__pyx_v_explicit_start);
+ Py_DECREF(__pyx_v_explicit_end);
+ Py_DECREF(__pyx_v_version);
+ Py_DECREF(__pyx_v_tags);
+ return __pyx_r;
+}
+
+static void __pyx_f_5_yaml_8CEmitter___dealloc__(PyObject *__pyx_v_self); /*proto*/
+static void __pyx_f_5_yaml_8CEmitter___dealloc__(PyObject *__pyx_v_self) {
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":922 */
+ yaml_emitter_delete((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter));
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("_yaml.CEmitter.__dealloc__");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+}
+
+static PyObject *__pyx_k78p;
+
+static char (__pyx_k78[]) = "no emitter error";
+
+static PyObject *__pyx_f_5_yaml_8CEmitter__emitter_error(struct __pyx_obj_5_yaml_CEmitter *__pyx_v_self) {
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":925 */
+ __pyx_1 = (__pyx_v_self->emitter.error == YAML_MEMORY_ERROR);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":926 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 926; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_1 = (__pyx_v_self->emitter.error == YAML_EMITTER_ERROR);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":928 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_EmitterError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 928; goto __pyx_L1;}
+ __pyx_3 = PyString_FromString(__pyx_v_self->emitter.problem); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 928; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 928; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_3);
+ __pyx_3 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_2, __pyx_4); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 928; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":929 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 929; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 929; goto __pyx_L1;}
+ Py_INCREF(__pyx_k78p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k78p);
+ __pyx_3 = PyObject_CallObject(__pyx_2, __pyx_4); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 929; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 929; goto __pyx_L1;}
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CEmitter._emitter_error");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_encoding;
+static PyObject *__pyx_n_version;
+static PyObject *__pyx_n_tags;
+static PyObject *__pyx_n_len;
+static PyObject *__pyx_n_explicit;
+static PyObject *__pyx_n_anchor;
+static PyObject *__pyx_n_tag;
+static PyObject *__pyx_n_value;
+static PyObject *__pyx_n_implicit;
+static PyObject *__pyx_n_style;
+static PyObject *__pyx_n_flow_style;
+
+static PyObject *__pyx_k79p;
+static PyObject *__pyx_k80p;
+static PyObject *__pyx_k81p;
+static PyObject *__pyx_k82p;
+static PyObject *__pyx_k83p;
+static PyObject *__pyx_k84p;
+static PyObject *__pyx_k85p;
+static PyObject *__pyx_k86p;
+static PyObject *__pyx_k87p;
+static PyObject *__pyx_k88p;
+static PyObject *__pyx_k89p;
+static PyObject *__pyx_k90p;
+static PyObject *__pyx_k91p;
+static PyObject *__pyx_k92p;
+static PyObject *__pyx_k93p;
+static PyObject *__pyx_k94p;
+static PyObject *__pyx_k95p;
+static PyObject *__pyx_k96p;
+
+static char (__pyx_k79[]) = "utf-16-le";
+static char (__pyx_k80[]) = "utf-16-be";
+static char (__pyx_k81[]) = "too many tags";
+static char (__pyx_k82[]) = "tag handle must be a string";
+static char (__pyx_k83[]) = "tag prefix must be a string";
+static char (__pyx_k84[]) = "anchor must be a string";
+static char (__pyx_k85[]) = "anchor must be a string";
+static char (__pyx_k86[]) = "tag must be a string";
+static char (__pyx_k87[]) = "value must be a string";
+static char (__pyx_k88[]) = "\'";
+static char (__pyx_k89[]) = "\"";
+static char (__pyx_k90[]) = "|";
+static char (__pyx_k91[]) = ">";
+static char (__pyx_k92[]) = "anchor must be a string";
+static char (__pyx_k93[]) = "tag must be a string";
+static char (__pyx_k94[]) = "anchor must be a string";
+static char (__pyx_k95[]) = "tag must be a string";
+static char (__pyx_k96[]) = "invalid event %s";
+
+static int __pyx_f_5_yaml_8CEmitter__object_to_event(struct __pyx_obj_5_yaml_CEmitter *__pyx_v_self,PyObject *__pyx_v_event_object,yaml_event_t (*__pyx_v_event)) {
+ yaml_encoding_t __pyx_v_encoding;
+ yaml_version_directive_t __pyx_v_version_directive_value;
+ yaml_version_directive_t (*__pyx_v_version_directive);
+ yaml_tag_directive_t (__pyx_v_tag_directives_value[128]);
+ yaml_tag_directive_t (*__pyx_v_tag_directives_start);
+ yaml_tag_directive_t (*__pyx_v_tag_directives_end);
+ int __pyx_v_implicit;
+ int __pyx_v_plain_implicit;
+ int __pyx_v_quoted_implicit;
+ char (*__pyx_v_anchor);
+ char (*__pyx_v_tag);
+ char (*__pyx_v_value);
+ int __pyx_v_length;
+ yaml_scalar_style_t __pyx_v_scalar_style;
+ yaml_sequence_style_t __pyx_v_sequence_style;
+ yaml_mapping_style_t __pyx_v_mapping_style;
+ PyObject *__pyx_v_event_class;
+ PyObject *__pyx_v_cache;
+ PyObject *__pyx_v_handle;
+ PyObject *__pyx_v_prefix;
+ PyObject *__pyx_v_anchor_object;
+ PyObject *__pyx_v_tag_object;
+ PyObject *__pyx_v_value_object;
+ PyObject *__pyx_v_style_object;
+ int __pyx_r;
+ PyObject *__pyx_1 = 0;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_event_object);
+ __pyx_v_event_class = Py_None; Py_INCREF(Py_None);
+ __pyx_v_cache = Py_None; Py_INCREF(Py_None);
+ __pyx_v_handle = Py_None; Py_INCREF(Py_None);
+ __pyx_v_prefix = Py_None; Py_INCREF(Py_None);
+ __pyx_v_anchor_object = Py_None; Py_INCREF(Py_None);
+ __pyx_v_tag_object = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value_object = Py_None; Py_INCREF(Py_None);
+ __pyx_v_style_object = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":948 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n___class__); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 948; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_event_class);
+ __pyx_v_event_class = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":949 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_StreamStartEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 949; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_1;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":950 */
+ __pyx_v_encoding = YAML_UTF8_ENCODING;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":951 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_encoding); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 951; goto __pyx_L1;}
+ if (PyObject_Cmp(__pyx_1, __pyx_k79p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 951; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":952 */
+ __pyx_v_encoding = YAML_UTF16LE_ENCODING;
+ goto __pyx_L3;
+ }
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_encoding); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 953; goto __pyx_L1;}
+ if (PyObject_Cmp(__pyx_1, __pyx_k80p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 953; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":954 */
+ __pyx_v_encoding = YAML_UTF16BE_ENCODING;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":955 */
+ yaml_stream_start_event_initialize(__pyx_v_event,__pyx_v_encoding);
+ goto __pyx_L2;
+ }
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_StreamEndEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 956; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_1;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":957 */
+ yaml_stream_end_event_initialize(__pyx_v_event);
+ goto __pyx_L2;
+ }
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_DocumentStartEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 958; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_1;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":959 */
+ __pyx_v_version_directive = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":960 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_version); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 960; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_1); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 960; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":961 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_version); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 961; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 961; goto __pyx_L1;}
+ __pyx_4 = PyObject_GetItem(__pyx_1, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 961; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_2 = PyInt_AsLong(__pyx_4); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 961; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_v_version_directive_value.major = __pyx_2;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":962 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_version); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 962; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 962; goto __pyx_L1;}
+ __pyx_4 = PyObject_GetItem(__pyx_1, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 962; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_2 = PyInt_AsLong(__pyx_4); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 962; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_v_version_directive_value.minor = __pyx_2;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":963 */
+ __pyx_v_version_directive = (&__pyx_v_version_directive_value);
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":964 */
+ __pyx_v_tag_directives_start = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":965 */
+ __pyx_v_tag_directives_end = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":966 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_tags); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 966; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_1); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 966; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":967 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_len); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 967; goto __pyx_L1;}
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_tags); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 967; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(1); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 967; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_4);
+ __pyx_4 = 0;
+ __pyx_4 = PyObject_CallObject(__pyx_3, __pyx_1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 967; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_3 = PyInt_FromLong(128); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 967; goto __pyx_L1;}
+ if (PyObject_Cmp(__pyx_4, __pyx_3, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 967; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 > 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":968 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 968; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 968; goto __pyx_L1;}
+ Py_INCREF(__pyx_k81p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k81p);
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 968; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 968; goto __pyx_L1;}
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":969 */
+ __pyx_v_tag_directives_start = __pyx_v_tag_directives_value;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":970 */
+ __pyx_v_tag_directives_end = __pyx_v_tag_directives_value;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":971 */
+ __pyx_1 = PyList_New(0); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 971; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_cache);
+ __pyx_v_cache = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":972 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_tags); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 972; goto __pyx_L1;}
+ __pyx_3 = PyObject_GetIter(__pyx_4); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 972; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ for (;;) {
+ __pyx_L7:;
+ __pyx_1 = PyIter_Next(__pyx_3);
+ if (!__pyx_1) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 972; goto __pyx_L1;}
+ break;
+ }
+ Py_DECREF(__pyx_v_handle);
+ __pyx_v_handle = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":973 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_tags); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 973; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetItem(__pyx_4, __pyx_v_handle); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 973; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_v_prefix);
+ __pyx_v_prefix = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":974 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_handle);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":975 */
+ __pyx_4 = PyUnicode_AsUTF8String(__pyx_v_handle); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 975; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_handle);
+ __pyx_v_handle = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":976 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_cache, __pyx_n_append); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 976; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 976; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_handle);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_handle);
+ __pyx_5 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 976; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ goto __pyx_L9;
+ }
+ __pyx_L9:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":977 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_handle));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":978 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 978; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 978; goto __pyx_L1;}
+ Py_INCREF(__pyx_k82p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k82p);
+ __pyx_5 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 978; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 978; goto __pyx_L1;}
+ goto __pyx_L10;
+ }
+ __pyx_L10:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":979 */
+ __pyx_v_tag_directives_end->handle = PyString_AS_STRING(__pyx_v_handle);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":980 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_prefix);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":981 */
+ __pyx_1 = PyUnicode_AsUTF8String(__pyx_v_prefix); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 981; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_prefix);
+ __pyx_v_prefix = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":982 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_cache, __pyx_n_append); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 982; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 982; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_prefix);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_v_prefix);
+ __pyx_1 = PyObject_CallObject(__pyx_4, __pyx_5); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 982; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ goto __pyx_L11;
+ }
+ __pyx_L11:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":983 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_prefix));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":984 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 984; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 984; goto __pyx_L1;}
+ Py_INCREF(__pyx_k83p);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_k83p);
+ __pyx_1 = PyObject_CallObject(__pyx_4, __pyx_5); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 984; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_1, 0, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 984; goto __pyx_L1;}
+ goto __pyx_L12;
+ }
+ __pyx_L12:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":985 */
+ __pyx_v_tag_directives_end->prefix = PyString_AS_STRING(__pyx_v_prefix);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":986 */
+ __pyx_v_tag_directives_end = (__pyx_v_tag_directives_end + 1);
+ }
+ __pyx_L8:;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":987 */
+ __pyx_v_implicit = 1;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":988 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_explicit); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 988; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_4); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 988; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":989 */
+ __pyx_v_implicit = 0;
+ goto __pyx_L13;
+ }
+ __pyx_L13:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":990 */
+ __pyx_2 = (yaml_document_start_event_initialize(__pyx_v_event,__pyx_v_version_directive,__pyx_v_tag_directives_start,__pyx_v_tag_directives_end,__pyx_v_implicit) == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":992 */
+ __pyx_5 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 992; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 992; goto __pyx_L1;}
+ goto __pyx_L14;
+ }
+ __pyx_L14:;
+ goto __pyx_L2;
+ }
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_DocumentEndEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 993; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_1;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":994 */
+ __pyx_v_implicit = 1;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":995 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_explicit); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 995; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_3); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 995; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":996 */
+ __pyx_v_implicit = 0;
+ goto __pyx_L15;
+ }
+ __pyx_L15:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":997 */
+ yaml_document_end_event_initialize(__pyx_v_event,__pyx_v_implicit);
+ goto __pyx_L2;
+ }
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_AliasEvent); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 998; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_4;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":999 */
+ __pyx_v_anchor = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1000 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_anchor); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1000; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_5;
+ __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1001 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_anchor_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1002 */
+ __pyx_1 = PyUnicode_AsUTF8String(__pyx_v_anchor_object); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1002; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L16;
+ }
+ __pyx_L16:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1003 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_anchor_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1004 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1004; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1004; goto __pyx_L1;}
+ Py_INCREF(__pyx_k84p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k84p);
+ __pyx_5 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1004; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1004; goto __pyx_L1;}
+ goto __pyx_L17;
+ }
+ __pyx_L17:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1005 */
+ __pyx_v_anchor = PyString_AS_STRING(__pyx_v_anchor_object);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1006 */
+ __pyx_2 = (yaml_alias_event_initialize(__pyx_v_event,__pyx_v_anchor) == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1007 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1007; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_1, 0, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1007; goto __pyx_L1;}
+ goto __pyx_L18;
+ }
+ __pyx_L18:;
+ goto __pyx_L2;
+ }
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarEvent); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1008; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_3;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1009 */
+ __pyx_v_anchor = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1010 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_anchor); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1010; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1011 */
+ __pyx_2 = __pyx_v_anchor_object != Py_None;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1012 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_anchor_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1013 */
+ __pyx_5 = PyUnicode_AsUTF8String(__pyx_v_anchor_object); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1013; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L20;
+ }
+ __pyx_L20:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1014 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_anchor_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1015 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1015; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1015; goto __pyx_L1;}
+ Py_INCREF(__pyx_k85p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k85p);
+ __pyx_4 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1015; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1015; goto __pyx_L1;}
+ goto __pyx_L21;
+ }
+ __pyx_L21:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1016 */
+ __pyx_v_anchor = PyString_AS_STRING(__pyx_v_anchor_object);
+ goto __pyx_L19;
+ }
+ __pyx_L19:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1017 */
+ __pyx_v_tag = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1018 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_tag); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1018; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_5;
+ __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1019 */
+ __pyx_2 = __pyx_v_tag_object != Py_None;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1020 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_tag_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1021 */
+ __pyx_1 = PyUnicode_AsUTF8String(__pyx_v_tag_object); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1021; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L23;
+ }
+ __pyx_L23:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1022 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_tag_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1023 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1023; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1023; goto __pyx_L1;}
+ Py_INCREF(__pyx_k86p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k86p);
+ __pyx_5 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1023; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1023; goto __pyx_L1;}
+ goto __pyx_L24;
+ }
+ __pyx_L24:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1024 */
+ __pyx_v_tag = PyString_AS_STRING(__pyx_v_tag_object);
+ goto __pyx_L22;
+ }
+ __pyx_L22:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1025 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_value); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1025; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value_object);
+ __pyx_v_value_object = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1026 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_value_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1027 */
+ __pyx_3 = PyUnicode_AsUTF8String(__pyx_v_value_object); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1027; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value_object);
+ __pyx_v_value_object = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L25;
+ }
+ __pyx_L25:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1028 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_value_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1029 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1029; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1029; goto __pyx_L1;}
+ Py_INCREF(__pyx_k87p);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_k87p);
+ __pyx_1 = PyObject_CallObject(__pyx_4, __pyx_5); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1029; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_1, 0, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1029; goto __pyx_L1;}
+ goto __pyx_L26;
+ }
+ __pyx_L26:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1030 */
+ __pyx_v_value = PyString_AS_STRING(__pyx_v_value_object);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1031 */
+ __pyx_v_length = PyString_GET_SIZE(__pyx_v_value_object);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1032 */
+ __pyx_v_plain_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1033 */
+ __pyx_v_quoted_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1034 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_implicit); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1034; goto __pyx_L1;}
+ __pyx_2 = __pyx_3 != Py_None;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1035 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_implicit); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1035; goto __pyx_L1;}
+ __pyx_5 = PyInt_FromLong(0); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1035; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetItem(__pyx_4, __pyx_5); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1035; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __pyx_2 = PyInt_AsLong(__pyx_1); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1035; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_v_plain_implicit = __pyx_2;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1036 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_implicit); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1036; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1036; goto __pyx_L1;}
+ __pyx_5 = PyObject_GetItem(__pyx_3, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1036; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_2 = PyInt_AsLong(__pyx_5); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1036; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __pyx_v_quoted_implicit = __pyx_2;
+ goto __pyx_L27;
+ }
+ __pyx_L27:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1037 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_style); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1037; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_style_object);
+ __pyx_v_style_object = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1038 */
+ __pyx_v_scalar_style = YAML_PLAIN_SCALAR_STYLE;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1039 */
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k88p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1039; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1040 */
+ __pyx_v_scalar_style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
+ goto __pyx_L28;
+ }
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k89p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1041; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1042 */
+ __pyx_v_scalar_style = YAML_DOUBLE_QUOTED_SCALAR_STYLE;
+ goto __pyx_L28;
+ }
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k90p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1043; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1044 */
+ __pyx_v_scalar_style = YAML_LITERAL_SCALAR_STYLE;
+ goto __pyx_L28;
+ }
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k91p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1045; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1046 */
+ __pyx_v_scalar_style = YAML_FOLDED_SCALAR_STYLE;
+ goto __pyx_L28;
+ }
+ __pyx_L28:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1047 */
+ __pyx_2 = (yaml_scalar_event_initialize(__pyx_v_event,__pyx_v_anchor,__pyx_v_tag,__pyx_v_value,__pyx_v_length,__pyx_v_plain_implicit,__pyx_v_quoted_implicit,__pyx_v_scalar_style) == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1049 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1049; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1049; goto __pyx_L1;}
+ goto __pyx_L29;
+ }
+ __pyx_L29:;
+ goto __pyx_L2;
+ }
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceStartEvent); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1050; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_4;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1051 */
+ __pyx_v_anchor = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1052 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_anchor); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1052; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_5;
+ __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1053 */
+ __pyx_2 = __pyx_v_anchor_object != Py_None;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1054 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_anchor_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1055 */
+ __pyx_1 = PyUnicode_AsUTF8String(__pyx_v_anchor_object); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1055; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L31;
+ }
+ __pyx_L31:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1056 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_anchor_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1057 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1057; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1057; goto __pyx_L1;}
+ Py_INCREF(__pyx_k92p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k92p);
+ __pyx_5 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1057; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1057; goto __pyx_L1;}
+ goto __pyx_L32;
+ }
+ __pyx_L32:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1058 */
+ __pyx_v_anchor = PyString_AS_STRING(__pyx_v_anchor_object);
+ goto __pyx_L30;
+ }
+ __pyx_L30:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1059 */
+ __pyx_v_tag = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1060 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_tag); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1060; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1061 */
+ __pyx_2 = __pyx_v_tag_object != Py_None;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1062 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_tag_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1063 */
+ __pyx_3 = PyUnicode_AsUTF8String(__pyx_v_tag_object); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1063; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L34;
+ }
+ __pyx_L34:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1064 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_tag_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1065 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1065; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1065; goto __pyx_L1;}
+ Py_INCREF(__pyx_k93p);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_k93p);
+ __pyx_1 = PyObject_CallObject(__pyx_4, __pyx_5); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1065; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_1, 0, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1065; goto __pyx_L1;}
+ goto __pyx_L35;
+ }
+ __pyx_L35:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1066 */
+ __pyx_v_tag = PyString_AS_STRING(__pyx_v_tag_object);
+ goto __pyx_L33;
+ }
+ __pyx_L33:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1067 */
+ __pyx_v_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1068 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_implicit); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1068; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_3); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1068; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1069 */
+ __pyx_v_implicit = 1;
+ goto __pyx_L36;
+ }
+ __pyx_L36:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1070 */
+ __pyx_v_sequence_style = YAML_BLOCK_SEQUENCE_STYLE;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1071 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_flow_style); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1071; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_4); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1071; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1072 */
+ __pyx_v_sequence_style = YAML_FLOW_SEQUENCE_STYLE;
+ goto __pyx_L37;
+ }
+ __pyx_L37:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1073 */
+ __pyx_2 = (yaml_sequence_start_event_initialize(__pyx_v_event,__pyx_v_anchor,__pyx_v_tag,__pyx_v_implicit,__pyx_v_sequence_style) == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1075 */
+ __pyx_5 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1075; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1075; goto __pyx_L1;}
+ goto __pyx_L38;
+ }
+ __pyx_L38:;
+ goto __pyx_L2;
+ }
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_MappingStartEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1076; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_1;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1077 */
+ __pyx_v_anchor = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1078 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_anchor); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1078; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1079 */
+ __pyx_2 = __pyx_v_anchor_object != Py_None;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1080 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_anchor_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1081 */
+ __pyx_4 = PyUnicode_AsUTF8String(__pyx_v_anchor_object); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1081; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L40;
+ }
+ __pyx_L40:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1082 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_anchor_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1083 */
+ __pyx_5 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1083; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(1); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1083; goto __pyx_L1;}
+ Py_INCREF(__pyx_k94p);
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_k94p);
+ __pyx_3 = PyObject_CallObject(__pyx_5, __pyx_1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1083; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1083; goto __pyx_L1;}
+ goto __pyx_L41;
+ }
+ __pyx_L41:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1084 */
+ __pyx_v_anchor = PyString_AS_STRING(__pyx_v_anchor_object);
+ goto __pyx_L39;
+ }
+ __pyx_L39:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1085 */
+ __pyx_v_tag = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1086 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_tag); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1086; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1087 */
+ __pyx_2 = __pyx_v_tag_object != Py_None;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1088 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_tag_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1089 */
+ __pyx_5 = PyUnicode_AsUTF8String(__pyx_v_tag_object); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1089; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L43;
+ }
+ __pyx_L43:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1090 */
+ __pyx_2 = (!PyString_CheckExact(__pyx_v_tag_object));
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1091 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1091; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1091; goto __pyx_L1;}
+ Py_INCREF(__pyx_k95p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k95p);
+ __pyx_4 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1091; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1091; goto __pyx_L1;}
+ goto __pyx_L44;
+ }
+ __pyx_L44:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1092 */
+ __pyx_v_tag = PyString_AS_STRING(__pyx_v_tag_object);
+ goto __pyx_L42;
+ }
+ __pyx_L42:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1093 */
+ __pyx_v_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1094 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_implicit); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1094; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_5); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1094; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1095 */
+ __pyx_v_implicit = 1;
+ goto __pyx_L45;
+ }
+ __pyx_L45:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1096 */
+ __pyx_v_mapping_style = YAML_BLOCK_MAPPING_STYLE;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1097 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_event_object, __pyx_n_flow_style); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1097; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_1); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1097; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1098 */
+ __pyx_v_mapping_style = YAML_FLOW_MAPPING_STYLE;
+ goto __pyx_L46;
+ }
+ __pyx_L46:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1099 */
+ __pyx_2 = (yaml_mapping_start_event_initialize(__pyx_v_event,__pyx_v_anchor,__pyx_v_tag,__pyx_v_implicit,__pyx_v_mapping_style) == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1101 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1101; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1101; goto __pyx_L1;}
+ goto __pyx_L47;
+ }
+ __pyx_L47:;
+ goto __pyx_L2;
+ }
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceEndEvent); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1102; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_4;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1103 */
+ yaml_sequence_end_event_initialize(__pyx_v_event);
+ goto __pyx_L2;
+ }
+ __pyx_5 = __Pyx_GetName(__pyx_m, __pyx_n_MappingEndEvent); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1104; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_event_class == __pyx_5;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1105 */
+ yaml_mapping_end_event_initialize(__pyx_v_event);
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1107 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1107; goto __pyx_L1;}
+ __pyx_3 = PyNumber_Remainder(__pyx_k96p, __pyx_v_event_object); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1107; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1107; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_3);
+ __pyx_3 = 0;
+ __pyx_5 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1107; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1107; goto __pyx_L1;}
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1108 */
+ __pyx_r = 1;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_yaml.CEmitter._object_to_event");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_event_class);
+ Py_DECREF(__pyx_v_cache);
+ Py_DECREF(__pyx_v_handle);
+ Py_DECREF(__pyx_v_prefix);
+ Py_DECREF(__pyx_v_anchor_object);
+ Py_DECREF(__pyx_v_tag_object);
+ Py_DECREF(__pyx_v_value_object);
+ Py_DECREF(__pyx_v_style_object);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_event_object);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_5_yaml_8CEmitter_emit(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_8CEmitter_emit(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_event_object = 0;
+ yaml_event_t __pyx_v_event;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {"event_object",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_event_object)) return 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_event_object);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1112 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_object_to_event(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self),__pyx_v_event_object,(&__pyx_v_event)); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1112; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1113 */
+ __pyx_1 = yaml_emitter_emit((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1113; goto __pyx_L1;}
+ __pyx_2 = (__pyx_1 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1114 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_emitter_error(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1114; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1115 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1115; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.CEmitter.emit");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_event_object);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k97p;
+static PyObject *__pyx_k98p;
+
+static char (__pyx_k97[]) = "serializer is closed";
+static char (__pyx_k98[]) = "serializer is already opened";
+
+static PyObject *__pyx_f_5_yaml_8CEmitter_open(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_8CEmitter_open(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ yaml_event_t __pyx_v_event;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1119 */
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed == (-1));
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1120 */
+ yaml_stream_start_event_initialize((&__pyx_v_event),((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_encoding);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1121 */
+ __pyx_1 = yaml_emitter_emit((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1121; goto __pyx_L1;}
+ __pyx_2 = (__pyx_1 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1122 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_emitter_error(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1122; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1123 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1123; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1124 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed = 0;
+ goto __pyx_L2;
+ }
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed == 1);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1126 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_SerializerError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1126; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1126; goto __pyx_L1;}
+ Py_INCREF(__pyx_k97p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k97p);
+ __pyx_5 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1126; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1126; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1128 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_SerializerError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1128; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(1); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1128; goto __pyx_L1;}
+ Py_INCREF(__pyx_k98p);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_k98p);
+ __pyx_5 = PyObject_CallObject(__pyx_3, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1128; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1128; goto __pyx_L1;}
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_yaml.CEmitter.open");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k99p;
+
+static char (__pyx_k99[]) = "serializer is not opened";
+
+static PyObject *__pyx_f_5_yaml_8CEmitter_close(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_8CEmitter_close(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ yaml_event_t __pyx_v_event;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ int __pyx_5;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1132 */
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed == (-1));
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1133 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_SerializerError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1133; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1133; goto __pyx_L1;}
+ Py_INCREF(__pyx_k99p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k99p);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1133; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1133; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed == 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1135 */
+ yaml_stream_end_event_initialize((&__pyx_v_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1136 */
+ __pyx_1 = yaml_emitter_emit((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1136; goto __pyx_L1;}
+ __pyx_5 = (__pyx_1 == 0);
+ if (__pyx_5) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1137 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_emitter_error(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1137; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1138 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1138; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1139 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed = 1;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("_yaml.CEmitter.close");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k100p;
+static PyObject *__pyx_k101p;
+static PyObject *__pyx_k102p;
+static PyObject *__pyx_k103p;
+static PyObject *__pyx_k104p;
+
+static char (__pyx_k100[]) = "serializer is not opened";
+static char (__pyx_k101[]) = "serializer is closed";
+static char (__pyx_k102[]) = "too many tags";
+static char (__pyx_k103[]) = "tag handle must be a string";
+static char (__pyx_k104[]) = "tag prefix must be a string";
+
+static PyObject *__pyx_f_5_yaml_8CEmitter_serialize(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_5_yaml_8CEmitter_serialize(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_node = 0;
+ yaml_event_t __pyx_v_event;
+ yaml_version_directive_t __pyx_v_version_directive_value;
+ yaml_version_directive_t (*__pyx_v_version_directive);
+ yaml_tag_directive_t (__pyx_v_tag_directives_value[128]);
+ yaml_tag_directive_t (*__pyx_v_tag_directives_start);
+ yaml_tag_directive_t (*__pyx_v_tag_directives_end);
+ PyObject *__pyx_v_cache;
+ PyObject *__pyx_v_handle;
+ PyObject *__pyx_v_prefix;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ int __pyx_6;
+ static char *__pyx_argnames[] = {"node",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_node)) return 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_node);
+ __pyx_v_cache = Py_None; Py_INCREF(Py_None);
+ __pyx_v_handle = Py_None; Py_INCREF(Py_None);
+ __pyx_v_prefix = Py_None; Py_INCREF(Py_None);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1148 */
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed == (-1));
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1149 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_SerializerError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1149; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1149; goto __pyx_L1;}
+ Py_INCREF(__pyx_k100p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k100p);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1149; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1149; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_1 = (((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->closed == 1);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1151 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_SerializerError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1151; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1151; goto __pyx_L1;}
+ Py_INCREF(__pyx_k101p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k101p);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1151; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1151; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1152 */
+ __pyx_2 = PyList_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1152; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_cache);
+ __pyx_v_cache = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1153 */
+ __pyx_v_version_directive = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1154 */
+ __pyx_1 = PyObject_IsTrue(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_version); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1154; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1155 */
+ __pyx_3 = PyInt_FromLong(0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1155; goto __pyx_L1;}
+ __pyx_4 = PyObject_GetItem(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_version, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1155; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_1 = PyInt_AsLong(__pyx_4); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1155; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_v_version_directive_value.major = __pyx_1;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1156 */
+ __pyx_2 = PyInt_FromLong(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1156; goto __pyx_L1;}
+ __pyx_3 = PyObject_GetItem(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_version, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1156; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_1 = PyInt_AsLong(__pyx_3); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1156; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_v_version_directive_value.minor = __pyx_1;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1157 */
+ __pyx_v_version_directive = (&__pyx_v_version_directive_value);
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1158 */
+ __pyx_v_tag_directives_start = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1159 */
+ __pyx_v_tag_directives_end = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1160 */
+ __pyx_1 = PyObject_IsTrue(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_tags); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1160; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1161 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_len); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1161; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1161; goto __pyx_L1;}
+ Py_INCREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_tags);
+ PyTuple_SET_ITEM(__pyx_2, 0, ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_tags);
+ __pyx_3 = PyObject_CallObject(__pyx_4, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1161; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_4 = PyInt_FromLong(128); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1161; goto __pyx_L1;}
+ if (PyObject_Cmp(__pyx_3, __pyx_4, &__pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1161; goto __pyx_L1;}
+ __pyx_1 = __pyx_1 > 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1162 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1162; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1162; goto __pyx_L1;}
+ Py_INCREF(__pyx_k102p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k102p);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1162; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1162; goto __pyx_L1;}
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1163 */
+ __pyx_v_tag_directives_start = __pyx_v_tag_directives_value;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1164 */
+ __pyx_v_tag_directives_end = __pyx_v_tag_directives_value;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1165 */
+ __pyx_2 = PyObject_GetIter(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_tags); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1165; goto __pyx_L1;}
+ for (;;) {
+ __pyx_L6:;
+ __pyx_3 = PyIter_Next(__pyx_2);
+ if (!__pyx_3) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1165; goto __pyx_L1;}
+ break;
+ }
+ Py_DECREF(__pyx_v_handle);
+ __pyx_v_handle = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1166 */
+ __pyx_4 = PyObject_GetItem(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->use_tags, __pyx_v_handle); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1166; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_prefix);
+ __pyx_v_prefix = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1167 */
+ __pyx_1 = PyUnicode_CheckExact(__pyx_v_handle);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1168 */
+ __pyx_3 = PyUnicode_AsUTF8String(__pyx_v_handle); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1168; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_handle);
+ __pyx_v_handle = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1169 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_cache, __pyx_n_append); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1169; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1169; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_handle);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_v_handle);
+ __pyx_5 = PyObject_CallObject(__pyx_4, __pyx_3); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1169; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ goto __pyx_L8;
+ }
+ __pyx_L8:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1170 */
+ __pyx_1 = (!PyString_CheckExact(__pyx_v_handle));
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1171 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1171; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1171; goto __pyx_L1;}
+ Py_INCREF(__pyx_k103p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k103p);
+ __pyx_5 = PyObject_CallObject(__pyx_4, __pyx_3); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1171; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1171; goto __pyx_L1;}
+ goto __pyx_L9;
+ }
+ __pyx_L9:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1172 */
+ __pyx_v_tag_directives_end->handle = PyString_AS_STRING(__pyx_v_handle);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1173 */
+ __pyx_1 = PyUnicode_CheckExact(__pyx_v_prefix);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1174 */
+ __pyx_4 = PyUnicode_AsUTF8String(__pyx_v_prefix); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1174; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_prefix);
+ __pyx_v_prefix = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1175 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_cache, __pyx_n_append); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1175; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1175; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_prefix);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_v_prefix);
+ __pyx_4 = PyObject_CallObject(__pyx_3, __pyx_5); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1175; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ goto __pyx_L10;
+ }
+ __pyx_L10:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1176 */
+ __pyx_1 = (!PyString_CheckExact(__pyx_v_prefix));
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1177 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1177; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1177; goto __pyx_L1;}
+ Py_INCREF(__pyx_k104p);
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_k104p);
+ __pyx_4 = PyObject_CallObject(__pyx_3, __pyx_5); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1177; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1177; goto __pyx_L1;}
+ goto __pyx_L11;
+ }
+ __pyx_L11:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1178 */
+ __pyx_v_tag_directives_end->prefix = PyString_AS_STRING(__pyx_v_prefix);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1179 */
+ __pyx_v_tag_directives_end = (__pyx_v_tag_directives_end + 1);
+ }
+ __pyx_L7:;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1180 */
+ __pyx_1 = (yaml_document_start_event_initialize((&__pyx_v_event),__pyx_v_version_directive,__pyx_v_tag_directives_start,__pyx_v_tag_directives_end,((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->document_start_implicit) == 0);
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1183 */
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1183; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1183; goto __pyx_L1;}
+ goto __pyx_L12;
+ }
+ __pyx_L12:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1184 */
+ __pyx_1 = yaml_emitter_emit((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1184; goto __pyx_L1;}
+ __pyx_6 = (__pyx_1 == 0);
+ if (__pyx_6) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1185 */
+ __pyx_5 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_emitter_error(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1185; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_5;
+ __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1186 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1186; goto __pyx_L1;}
+ goto __pyx_L13;
+ }
+ __pyx_L13:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1187 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_anchor_node(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self),__pyx_v_node); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1187; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1188 */
+ __pyx_6 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_serialize_node(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self),__pyx_v_node,Py_None,Py_None); if (__pyx_6 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1188; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1189 */
+ yaml_document_end_event_initialize((&__pyx_v_event),((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->document_end_implicit);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1190 */
+ __pyx_1 = yaml_emitter_emit((&((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1190; goto __pyx_L1;}
+ __pyx_6 = (__pyx_1 == 0);
+ if (__pyx_6) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1191 */
+ __pyx_4 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->__pyx_vtab)->_emitter_error(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1191; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1192 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1192; goto __pyx_L1;}
+ goto __pyx_L14;
+ }
+ __pyx_L14:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1193 */
+ __pyx_2 = PyDict_New(); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1193; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->serialized_nodes);
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->serialized_nodes = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1194 */
+ __pyx_3 = PyDict_New(); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1194; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->anchors);
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->anchors = __pyx_3;
+ __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1195 */
+ ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_v_self)->last_alias_id = 0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_yaml.CEmitter.serialize");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_cache);
+ Py_DECREF(__pyx_v_handle);
+ Py_DECREF(__pyx_v_prefix);
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_node);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k105p;
+
+static char (__pyx_k105[]) = "id%03d";
+
+static int __pyx_f_5_yaml_8CEmitter__anchor_node(struct __pyx_obj_5_yaml_CEmitter *__pyx_v_self,PyObject *__pyx_v_node) {
+ PyObject *__pyx_v_node_class;
+ PyObject *__pyx_v_item;
+ PyObject *__pyx_v_key;
+ PyObject *__pyx_v_value;
+ int __pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_node);
+ __pyx_v_node_class = Py_None; Py_INCREF(Py_None);
+ __pyx_v_item = Py_None; Py_INCREF(Py_None);
+ __pyx_v_key = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1198 */
+ __pyx_1 = PySequence_Contains(__pyx_v_self->anchors, __pyx_v_node); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1198; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1199 */
+ __pyx_2 = PyObject_GetItem(__pyx_v_self->anchors, __pyx_v_node); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1199; goto __pyx_L1;}
+ __pyx_1 = __pyx_2 == Py_None;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1200 */
+ __pyx_v_self->last_alias_id = (__pyx_v_self->last_alias_id + 1);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1201 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_self->last_alias_id); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1201; goto __pyx_L1;}
+ __pyx_3 = PyNumber_Remainder(__pyx_k105p, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1201; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetItem(__pyx_v_self->anchors, __pyx_v_node, __pyx_3) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1201; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1203 */
+ if (PyObject_SetItem(__pyx_v_self->anchors, __pyx_v_node, Py_None) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1203; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1204 */
+ __pyx_2 = PyObject_GetAttr(__pyx_v_node, __pyx_n___class__); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1204; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_node_class);
+ __pyx_v_node_class = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1205 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceNode); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1205; goto __pyx_L1;}
+ __pyx_1 = __pyx_v_node_class == __pyx_3;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1206 */
+ __pyx_2 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1206; goto __pyx_L1;}
+ __pyx_3 = PyObject_GetIter(__pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1206; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ for (;;) {
+ __pyx_L5:;
+ __pyx_2 = PyIter_Next(__pyx_3);
+ if (!__pyx_2) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1206; goto __pyx_L1;}
+ break;
+ }
+ Py_DECREF(__pyx_v_item);
+ __pyx_v_item = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1207 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_anchor_node(__pyx_v_self,__pyx_v_item); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1207; goto __pyx_L1;}
+ }
+ __pyx_L6:;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_MappingNode); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1208; goto __pyx_L1;}
+ __pyx_1 = __pyx_v_node_class == __pyx_2;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (__pyx_1) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1209 */
+ __pyx_3 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1209; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetIter(__pyx_3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1209; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ for (;;) {
+ __pyx_L7:;
+ __pyx_3 = PyIter_Next(__pyx_2);
+ if (!__pyx_3) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1209; goto __pyx_L1;}
+ break;
+ }
+ __pyx_4 = __Pyx_UnpackItem(__pyx_3, 0); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1209; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_key);
+ __pyx_v_key = __pyx_4;
+ __pyx_4 = 0;
+ __pyx_5 = __Pyx_UnpackItem(__pyx_3, 1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1209; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_5;
+ __pyx_5 = 0;
+ if (__Pyx_EndUnpack(__pyx_3, 2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1209; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1210 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_anchor_node(__pyx_v_self,__pyx_v_key); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1210; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1211 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_anchor_node(__pyx_v_self,__pyx_v_value); if (__pyx_1 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1211; goto __pyx_L1;}
+ }
+ __pyx_L8:;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1212 */
+ __pyx_r = 1;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_yaml.CEmitter._anchor_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_node_class);
+ Py_DECREF(__pyx_v_item);
+ Py_DECREF(__pyx_v_key);
+ Py_DECREF(__pyx_v_value);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_node);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k106p;
+static PyObject *__pyx_k107p;
+static PyObject *__pyx_k108p;
+static PyObject *__pyx_k109p;
+static PyObject *__pyx_k110p;
+static PyObject *__pyx_k111p;
+static PyObject *__pyx_k112p;
+static PyObject *__pyx_k113p;
+
+static char (__pyx_k106[]) = "tag must be a string";
+static char (__pyx_k107[]) = "value must be a string";
+static char (__pyx_k108[]) = "\'";
+static char (__pyx_k109[]) = "\"";
+static char (__pyx_k110[]) = "|";
+static char (__pyx_k111[]) = ">";
+static char (__pyx_k112[]) = "tag must be a string";
+static char (__pyx_k113[]) = "tag must be a string";
+
+static int __pyx_f_5_yaml_8CEmitter__serialize_node(struct __pyx_obj_5_yaml_CEmitter *__pyx_v_self,PyObject *__pyx_v_node,PyObject *__pyx_v_parent,PyObject *__pyx_v_index) {
+ yaml_event_t __pyx_v_event;
+ int __pyx_v_implicit;
+ int __pyx_v_plain_implicit;
+ int __pyx_v_quoted_implicit;
+ char (*__pyx_v_anchor);
+ char (*__pyx_v_tag);
+ char (*__pyx_v_value);
+ int __pyx_v_length;
+ int __pyx_v_item_index;
+ yaml_scalar_style_t __pyx_v_scalar_style;
+ yaml_sequence_style_t __pyx_v_sequence_style;
+ yaml_mapping_style_t __pyx_v_mapping_style;
+ PyObject *__pyx_v_anchor_object;
+ PyObject *__pyx_v_error;
+ PyObject *__pyx_v_node_class;
+ PyObject *__pyx_v_tag_object;
+ PyObject *__pyx_v_value_object;
+ PyObject *__pyx_v_style_object;
+ PyObject *__pyx_v_item;
+ PyObject *__pyx_v_item_key;
+ PyObject *__pyx_v_item_value;
+ int __pyx_r;
+ PyObject *__pyx_1 = 0;
+ int __pyx_2;
+ int __pyx_3;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ PyObject *__pyx_6 = 0;
+ PyObject *__pyx_7 = 0;
+ PyObject *__pyx_8 = 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_node);
+ Py_INCREF(__pyx_v_parent);
+ Py_INCREF(__pyx_v_index);
+ __pyx_v_anchor_object = Py_None; Py_INCREF(Py_None);
+ __pyx_v_error = Py_None; Py_INCREF(Py_None);
+ __pyx_v_node_class = Py_None; Py_INCREF(Py_None);
+ __pyx_v_tag_object = Py_None; Py_INCREF(Py_None);
+ __pyx_v_value_object = Py_None; Py_INCREF(Py_None);
+ __pyx_v_style_object = Py_None; Py_INCREF(Py_None);
+ __pyx_v_item = Py_None; Py_INCREF(Py_None);
+ __pyx_v_item_key = Py_None; Py_INCREF(Py_None);
+ __pyx_v_item_value = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1227 */
+ __pyx_1 = PyObject_GetItem(__pyx_v_self->anchors, __pyx_v_node); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1227; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_anchor_object);
+ __pyx_v_anchor_object = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1228 */
+ __pyx_v_anchor = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1229 */
+ __pyx_2 = __pyx_v_anchor_object != Py_None;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1230 */
+ __pyx_v_anchor = PyString_AS_STRING(__pyx_v_anchor_object);
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1231 */
+ __pyx_2 = PySequence_Contains(__pyx_v_self->serialized_nodes, __pyx_v_node); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1231; goto __pyx_L1;}
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1232 */
+ __pyx_2 = (yaml_alias_event_initialize((&__pyx_v_event),__pyx_v_anchor) == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1233 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1233; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_1, 0, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1233; goto __pyx_L1;}
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1234 */
+ __pyx_2 = yaml_emitter_emit((&__pyx_v_self->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1234; goto __pyx_L1;}
+ __pyx_3 = (__pyx_2 == 0);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1235 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_emitter_error(__pyx_v_self); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1235; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1236 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1236; goto __pyx_L1;}
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+ goto __pyx_L3;
+ }
+ /*else*/ {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1238 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_node, __pyx_n___class__); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1238; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_node_class);
+ __pyx_v_node_class = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1239 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1239; goto __pyx_L1;}
+ if (PyObject_SetItem(__pyx_v_self->serialized_nodes, __pyx_v_node, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1239; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1240 */
+ __pyx_1 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_descend_resolver); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1240; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1240; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_parent);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_parent);
+ Py_INCREF(__pyx_v_index);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_v_index);
+ __pyx_5 = PyObject_CallObject(__pyx_1, __pyx_4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1240; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1241 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1241; goto __pyx_L1;}
+ __pyx_2 = __pyx_v_node_class == __pyx_1;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1242 */
+ __pyx_v_plain_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1243 */
+ __pyx_v_quoted_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1244 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_node, __pyx_n_tag); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1244; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1245 */
+ __pyx_5 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_resolve); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ __pyx_4 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ __pyx_6 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ __pyx_7 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ __pyx_8 = PyTuple_New(2); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_8, 0, __pyx_6);
+ PyTuple_SET_ITEM(__pyx_8, 1, __pyx_7);
+ __pyx_6 = 0;
+ __pyx_7 = 0;
+ __pyx_6 = PyTuple_New(3); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_6, 1, __pyx_4);
+ PyTuple_SET_ITEM(__pyx_6, 2, __pyx_8);
+ __pyx_1 = 0;
+ __pyx_4 = 0;
+ __pyx_8 = 0;
+ __pyx_7 = PyObject_CallObject(__pyx_5, __pyx_6); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ if (PyObject_Cmp(__pyx_7, __pyx_v_tag_object, &__pyx_3) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1245; goto __pyx_L1;}
+ __pyx_3 = __pyx_3 == 0;
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1246 */
+ __pyx_v_plain_implicit = 1;
+ goto __pyx_L7;
+ }
+ __pyx_L7:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1247 */
+ __pyx_1 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_resolve); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_ScalarNode); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ __pyx_8 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ __pyx_5 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ __pyx_6 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ __pyx_7 = PyTuple_New(2); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_7, 0, __pyx_5);
+ PyTuple_SET_ITEM(__pyx_7, 1, __pyx_6);
+ __pyx_5 = 0;
+ __pyx_6 = 0;
+ __pyx_5 = PyTuple_New(3); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_4);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_8);
+ PyTuple_SET_ITEM(__pyx_5, 2, __pyx_7);
+ __pyx_4 = 0;
+ __pyx_8 = 0;
+ __pyx_7 = 0;
+ __pyx_6 = PyObject_CallObject(__pyx_1, __pyx_5); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ if (PyObject_Cmp(__pyx_6, __pyx_v_tag_object, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1247; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1248 */
+ __pyx_v_quoted_implicit = 1;
+ goto __pyx_L8;
+ }
+ __pyx_L8:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1249 */
+ __pyx_v_tag = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1250 */
+ __pyx_3 = __pyx_v_tag_object != Py_None;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1251 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_tag_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1252 */
+ __pyx_4 = PyUnicode_AsUTF8String(__pyx_v_tag_object); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1252; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L10;
+ }
+ __pyx_L10:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1253 */
+ __pyx_3 = (!PyString_CheckExact(__pyx_v_tag_object));
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1254 */
+ __pyx_8 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1254; goto __pyx_L1;}
+ __pyx_7 = PyTuple_New(1); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1254; goto __pyx_L1;}
+ Py_INCREF(__pyx_k106p);
+ PyTuple_SET_ITEM(__pyx_7, 0, __pyx_k106p);
+ __pyx_1 = PyObject_CallObject(__pyx_8, __pyx_7); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1254; goto __pyx_L1;}
+ Py_DECREF(__pyx_8); __pyx_8 = 0;
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+ __Pyx_Raise(__pyx_1, 0, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1254; goto __pyx_L1;}
+ goto __pyx_L11;
+ }
+ __pyx_L11:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1255 */
+ __pyx_v_tag = PyString_AS_STRING(__pyx_v_tag_object);
+ goto __pyx_L9;
+ }
+ __pyx_L9:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1256 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1256; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value_object);
+ __pyx_v_value_object = __pyx_5;
+ __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1257 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_value_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1258 */
+ __pyx_6 = PyUnicode_AsUTF8String(__pyx_v_value_object); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1258; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value_object);
+ __pyx_v_value_object = __pyx_6;
+ __pyx_6 = 0;
+ goto __pyx_L12;
+ }
+ __pyx_L12:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1259 */
+ __pyx_3 = (!PyString_CheckExact(__pyx_v_value_object));
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1260 */
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1260; goto __pyx_L1;}
+ __pyx_8 = PyTuple_New(1); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1260; goto __pyx_L1;}
+ Py_INCREF(__pyx_k107p);
+ PyTuple_SET_ITEM(__pyx_8, 0, __pyx_k107p);
+ __pyx_7 = PyObject_CallObject(__pyx_4, __pyx_8); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1260; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ Py_DECREF(__pyx_8); __pyx_8 = 0;
+ __Pyx_Raise(__pyx_7, 0, 0);
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1260; goto __pyx_L1;}
+ goto __pyx_L13;
+ }
+ __pyx_L13:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1261 */
+ __pyx_v_value = PyString_AS_STRING(__pyx_v_value_object);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1262 */
+ __pyx_v_length = PyString_GET_SIZE(__pyx_v_value_object);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1263 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_node, __pyx_n_style); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1263; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_style_object);
+ __pyx_v_style_object = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1264 */
+ __pyx_v_scalar_style = YAML_PLAIN_SCALAR_STYLE;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1265 */
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k108p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1265; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1266 */
+ __pyx_v_scalar_style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
+ goto __pyx_L14;
+ }
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k109p, &__pyx_3) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1267; goto __pyx_L1;}
+ __pyx_3 = __pyx_3 == 0;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1268 */
+ __pyx_v_scalar_style = YAML_DOUBLE_QUOTED_SCALAR_STYLE;
+ goto __pyx_L14;
+ }
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k110p, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1269; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1270 */
+ __pyx_v_scalar_style = YAML_LITERAL_SCALAR_STYLE;
+ goto __pyx_L14;
+ }
+ if (PyObject_Cmp(__pyx_v_style_object, __pyx_k111p, &__pyx_3) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1271; goto __pyx_L1;}
+ __pyx_3 = __pyx_3 == 0;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1272 */
+ __pyx_v_scalar_style = YAML_FOLDED_SCALAR_STYLE;
+ goto __pyx_L14;
+ }
+ __pyx_L14:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1273 */
+ __pyx_2 = (yaml_scalar_event_initialize((&__pyx_v_event),__pyx_v_anchor,__pyx_v_tag,__pyx_v_value,__pyx_v_length,__pyx_v_plain_implicit,__pyx_v_quoted_implicit,__pyx_v_scalar_style) == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1275 */
+ __pyx_5 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1275; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1275; goto __pyx_L1;}
+ goto __pyx_L15;
+ }
+ __pyx_L15:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1276 */
+ __pyx_3 = yaml_emitter_emit((&__pyx_v_self->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1276; goto __pyx_L1;}
+ __pyx_2 = (__pyx_3 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1277 */
+ __pyx_6 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_emitter_error(__pyx_v_self); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1277; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_6;
+ __pyx_6 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1278 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1278; goto __pyx_L1;}
+ goto __pyx_L16;
+ }
+ __pyx_L16:;
+ goto __pyx_L6;
+ }
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceNode); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1279; goto __pyx_L1;}
+ __pyx_3 = __pyx_v_node_class == __pyx_4;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1280 */
+ __pyx_v_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1281 */
+ __pyx_8 = PyObject_GetAttr(__pyx_v_node, __pyx_n_tag); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1281; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_8;
+ __pyx_8 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1282 */
+ __pyx_7 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_resolve); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1282; goto __pyx_L1;}
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_SequenceNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1282; goto __pyx_L1;}
+ __pyx_5 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1282; goto __pyx_L1;}
+ __pyx_6 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1282; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1282; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_5);
+ PyTuple_SET_ITEM(__pyx_4, 2, __pyx_6);
+ __pyx_1 = 0;
+ __pyx_5 = 0;
+ __pyx_6 = 0;
+ __pyx_8 = PyObject_CallObject(__pyx_7, __pyx_4); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1282; goto __pyx_L1;}
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (PyObject_Cmp(__pyx_8, __pyx_v_tag_object, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1282; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ Py_DECREF(__pyx_8); __pyx_8 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1283 */
+ __pyx_v_implicit = 1;
+ goto __pyx_L17;
+ }
+ __pyx_L17:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1284 */
+ __pyx_v_tag = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1285 */
+ __pyx_3 = __pyx_v_tag_object != Py_None;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1286 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_tag_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1287 */
+ __pyx_1 = PyUnicode_AsUTF8String(__pyx_v_tag_object); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1287; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L19;
+ }
+ __pyx_L19:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1288 */
+ __pyx_3 = (!PyString_CheckExact(__pyx_v_tag_object));
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1289 */
+ __pyx_5 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1289; goto __pyx_L1;}
+ __pyx_6 = PyTuple_New(1); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1289; goto __pyx_L1;}
+ Py_INCREF(__pyx_k112p);
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_k112p);
+ __pyx_7 = PyObject_CallObject(__pyx_5, __pyx_6); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1289; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ __Pyx_Raise(__pyx_7, 0, 0);
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1289; goto __pyx_L1;}
+ goto __pyx_L20;
+ }
+ __pyx_L20:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1290 */
+ __pyx_v_tag = PyString_AS_STRING(__pyx_v_tag_object);
+ goto __pyx_L18;
+ }
+ __pyx_L18:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1291 */
+ __pyx_v_sequence_style = YAML_BLOCK_SEQUENCE_STYLE;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1292 */
+ __pyx_4 = PyObject_GetAttr(__pyx_v_node, __pyx_n_flow_style); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1292; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_4); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1292; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1293 */
+ __pyx_v_sequence_style = YAML_FLOW_SEQUENCE_STYLE;
+ goto __pyx_L21;
+ }
+ __pyx_L21:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1294 */
+ __pyx_3 = (yaml_sequence_start_event_initialize((&__pyx_v_event),__pyx_v_anchor,__pyx_v_tag,__pyx_v_implicit,__pyx_v_sequence_style) == 0);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1296 */
+ __pyx_8 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1296; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_8, 0, 0);
+ Py_DECREF(__pyx_8); __pyx_8 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1296; goto __pyx_L1;}
+ goto __pyx_L22;
+ }
+ __pyx_L22:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1297 */
+ __pyx_2 = yaml_emitter_emit((&__pyx_v_self->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1297; goto __pyx_L1;}
+ __pyx_3 = (__pyx_2 == 0);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1298 */
+ __pyx_1 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_emitter_error(__pyx_v_self); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1298; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1299 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1299; goto __pyx_L1;}
+ goto __pyx_L23;
+ }
+ __pyx_L23:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1300 */
+ __pyx_v_item_index = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1301 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1301; goto __pyx_L1;}
+ __pyx_6 = PyObject_GetIter(__pyx_5); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1301; goto __pyx_L1;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ for (;;) {
+ __pyx_L24:;
+ __pyx_7 = PyIter_Next(__pyx_6);
+ if (!__pyx_7) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1301; goto __pyx_L1;}
+ break;
+ }
+ Py_DECREF(__pyx_v_item);
+ __pyx_v_item = __pyx_7;
+ __pyx_7 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1302 */
+ __pyx_4 = PyInt_FromLong(__pyx_v_item_index); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1302; goto __pyx_L1;}
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_serialize_node(__pyx_v_self,__pyx_v_item,__pyx_v_node,__pyx_4); if (__pyx_2 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1302; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1303 */
+ __pyx_v_item_index = (__pyx_v_item_index + 1);
+ }
+ __pyx_L25:;
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1304 */
+ yaml_sequence_end_event_initialize((&__pyx_v_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1305 */
+ __pyx_3 = yaml_emitter_emit((&__pyx_v_self->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1305; goto __pyx_L1;}
+ __pyx_2 = (__pyx_3 == 0);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1306 */
+ __pyx_8 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_emitter_error(__pyx_v_self); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1306; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_8;
+ __pyx_8 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1307 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1307; goto __pyx_L1;}
+ goto __pyx_L26;
+ }
+ __pyx_L26:;
+ goto __pyx_L6;
+ }
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_MappingNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1308; goto __pyx_L1;}
+ __pyx_3 = __pyx_v_node_class == __pyx_1;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1309 */
+ __pyx_v_implicit = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1310 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_node, __pyx_n_tag); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1310; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_5;
+ __pyx_5 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1311 */
+ __pyx_7 = PyObject_GetAttr(((PyObject *)__pyx_v_self), __pyx_n_resolve); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1311; goto __pyx_L1;}
+ __pyx_4 = __Pyx_GetName(__pyx_m, __pyx_n_MappingNode); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1311; goto __pyx_L1;}
+ __pyx_6 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1311; goto __pyx_L1;}
+ __pyx_8 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1311; goto __pyx_L1;}
+ __pyx_1 = PyTuple_New(3); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1311; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_1, 0, __pyx_4);
+ PyTuple_SET_ITEM(__pyx_1, 1, __pyx_6);
+ PyTuple_SET_ITEM(__pyx_1, 2, __pyx_8);
+ __pyx_4 = 0;
+ __pyx_6 = 0;
+ __pyx_8 = 0;
+ __pyx_5 = PyObject_CallObject(__pyx_7, __pyx_1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1311; goto __pyx_L1;}
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_Cmp(__pyx_5, __pyx_v_tag_object, &__pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1311; goto __pyx_L1;}
+ __pyx_2 = __pyx_2 == 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1312 */
+ __pyx_v_implicit = 1;
+ goto __pyx_L27;
+ }
+ __pyx_L27:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1313 */
+ __pyx_v_tag = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1314 */
+ __pyx_3 = __pyx_v_tag_object != Py_None;
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1315 */
+ __pyx_2 = PyUnicode_CheckExact(__pyx_v_tag_object);
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1316 */
+ __pyx_4 = PyUnicode_AsUTF8String(__pyx_v_tag_object); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1316; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_tag_object);
+ __pyx_v_tag_object = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L29;
+ }
+ __pyx_L29:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1317 */
+ __pyx_3 = (!PyString_CheckExact(__pyx_v_tag_object));
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1318 */
+ __pyx_6 = __Pyx_GetName(__pyx_b, __pyx_n_TypeError); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1318; goto __pyx_L1;}
+ __pyx_8 = PyTuple_New(1); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1318; goto __pyx_L1;}
+ Py_INCREF(__pyx_k113p);
+ PyTuple_SET_ITEM(__pyx_8, 0, __pyx_k113p);
+ __pyx_7 = PyObject_CallObject(__pyx_6, __pyx_8); if (!__pyx_7) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1318; goto __pyx_L1;}
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ Py_DECREF(__pyx_8); __pyx_8 = 0;
+ __Pyx_Raise(__pyx_7, 0, 0);
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1318; goto __pyx_L1;}
+ goto __pyx_L30;
+ }
+ __pyx_L30:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1319 */
+ __pyx_v_tag = PyString_AS_STRING(__pyx_v_tag_object);
+ goto __pyx_L28;
+ }
+ __pyx_L28:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1320 */
+ __pyx_v_mapping_style = YAML_BLOCK_MAPPING_STYLE;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1321 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_node, __pyx_n_flow_style); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1321; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_1); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1321; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (__pyx_2) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1322 */
+ __pyx_v_mapping_style = YAML_FLOW_MAPPING_STYLE;
+ goto __pyx_L31;
+ }
+ __pyx_L31:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1323 */
+ __pyx_3 = (yaml_mapping_start_event_initialize((&__pyx_v_event),__pyx_v_anchor,__pyx_v_tag,__pyx_v_implicit,__pyx_v_mapping_style) == 0);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1325 */
+ __pyx_5 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1325; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_5, 0, 0);
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1325; goto __pyx_L1;}
+ goto __pyx_L32;
+ }
+ __pyx_L32:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1326 */
+ __pyx_2 = yaml_emitter_emit((&__pyx_v_self->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1326; goto __pyx_L1;}
+ __pyx_3 = (__pyx_2 == 0);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1327 */
+ __pyx_4 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_emitter_error(__pyx_v_self); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1327; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1328 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1328; goto __pyx_L1;}
+ goto __pyx_L33;
+ }
+ __pyx_L33:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1329 */
+ __pyx_6 = PyObject_GetAttr(__pyx_v_node, __pyx_n_value); if (!__pyx_6) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1329; goto __pyx_L1;}
+ __pyx_8 = PyObject_GetIter(__pyx_6); if (!__pyx_8) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1329; goto __pyx_L1;}
+ Py_DECREF(__pyx_6); __pyx_6 = 0;
+ for (;;) {
+ __pyx_L34:;
+ __pyx_7 = PyIter_Next(__pyx_8);
+ if (!__pyx_7) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1329; goto __pyx_L1;}
+ break;
+ }
+ __pyx_1 = __Pyx_UnpackItem(__pyx_7, 0); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1329; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_item_key);
+ __pyx_v_item_key = __pyx_1;
+ __pyx_1 = 0;
+ __pyx_5 = __Pyx_UnpackItem(__pyx_7, 1); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1329; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_item_value);
+ __pyx_v_item_value = __pyx_5;
+ __pyx_5 = 0;
+ if (__Pyx_EndUnpack(__pyx_7, 2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1329; goto __pyx_L1;}
+ Py_DECREF(__pyx_7); __pyx_7 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1330 */
+ __pyx_2 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_serialize_node(__pyx_v_self,__pyx_v_item_key,__pyx_v_node,Py_None); if (__pyx_2 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1330; goto __pyx_L1;}
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1331 */
+ __pyx_3 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_serialize_node(__pyx_v_self,__pyx_v_item_value,__pyx_v_node,__pyx_v_item_key); if (__pyx_3 == 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1331; goto __pyx_L1;}
+ }
+ __pyx_L35:;
+ Py_DECREF(__pyx_8); __pyx_8 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1332 */
+ yaml_mapping_end_event_initialize((&__pyx_v_event));
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1333 */
+ __pyx_2 = yaml_emitter_emit((&__pyx_v_self->emitter),(&__pyx_v_event)); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1333; goto __pyx_L1;}
+ __pyx_3 = (__pyx_2 == 0);
+ if (__pyx_3) {
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1334 */
+ __pyx_4 = ((struct __pyx_vtabstruct_5_yaml_CEmitter *)__pyx_v_self->__pyx_vtab)->_emitter_error(__pyx_v_self); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1334; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_error);
+ __pyx_v_error = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1335 */
+ __Pyx_Raise(__pyx_v_error, 0, 0);
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1335; goto __pyx_L1;}
+ goto __pyx_L36;
+ }
+ __pyx_L36:;
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+ }
+ __pyx_L3:;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1336 */
+ __pyx_r = 1;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ Py_XDECREF(__pyx_6);
+ Py_XDECREF(__pyx_7);
+ Py_XDECREF(__pyx_8);
+ __Pyx_AddTraceback("_yaml.CEmitter._serialize_node");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_anchor_object);
+ Py_DECREF(__pyx_v_error);
+ Py_DECREF(__pyx_v_node_class);
+ Py_DECREF(__pyx_v_tag_object);
+ Py_DECREF(__pyx_v_value_object);
+ Py_DECREF(__pyx_v_style_object);
+ Py_DECREF(__pyx_v_item);
+ Py_DECREF(__pyx_v_item_key);
+ Py_DECREF(__pyx_v_item_value);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_node);
+ Py_DECREF(__pyx_v_parent);
+ Py_DECREF(__pyx_v_index);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_write;
+
+static int __pyx_f_5_yaml_output_handler(void (*__pyx_v_data),char (*__pyx_v_buffer),int __pyx_v_size) {
+ struct __pyx_obj_5_yaml_CEmitter *__pyx_v_emitter;
+ PyObject *__pyx_v_value;
+ int __pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ __pyx_v_emitter = ((struct __pyx_obj_5_yaml_CEmitter *)Py_None); Py_INCREF(Py_None);
+ __pyx_v_value = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1340 */
+ __pyx_1 = (PyObject *)__pyx_v_data;
+ Py_INCREF(__pyx_1);
+ Py_DECREF(((PyObject *)__pyx_v_emitter));
+ __pyx_v_emitter = ((struct __pyx_obj_5_yaml_CEmitter *)__pyx_1);
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1341 */
+ __pyx_1 = PyString_FromStringAndSize(__pyx_v_buffer,__pyx_v_size); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1341; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_value);
+ __pyx_v_value = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1342 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_emitter->stream, __pyx_n_write); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1342; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1342; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_value);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_v_value);
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 1342; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1343 */
+ __pyx_r = 1;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("_yaml.output_handler");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_emitter);
+ Py_DECREF(__pyx_v_value);
+ return __pyx_r;
+}
+
+static __Pyx_InternTabEntry __pyx_intern_tab[] = {
+ {&__pyx_n_AliasEvent, "AliasEvent"},
+ {&__pyx_n_AliasToken, "AliasToken"},
+ {&__pyx_n_AnchorToken, "AnchorToken"},
+ {&__pyx_n_AttributeError, "AttributeError"},
+ {&__pyx_n_BlockEndToken, "BlockEndToken"},
+ {&__pyx_n_BlockEntryToken, "BlockEntryToken"},
+ {&__pyx_n_BlockMappingStartToken, "BlockMappingStartToken"},
+ {&__pyx_n_BlockSequenceStartToken, "BlockSequenceStartToken"},
+ {&__pyx_n_ComposerError, "ComposerError"},
+ {&__pyx_n_ConstructorError, "ConstructorError"},
+ {&__pyx_n_DirectiveToken, "DirectiveToken"},
+ {&__pyx_n_DocumentEndEvent, "DocumentEndEvent"},
+ {&__pyx_n_DocumentEndToken, "DocumentEndToken"},
+ {&__pyx_n_DocumentStartEvent, "DocumentStartEvent"},
+ {&__pyx_n_DocumentStartToken, "DocumentStartToken"},
+ {&__pyx_n_EmitterError, "EmitterError"},
+ {&__pyx_n_False, "False"},
+ {&__pyx_n_FlowEntryToken, "FlowEntryToken"},
+ {&__pyx_n_FlowMappingEndToken, "FlowMappingEndToken"},
+ {&__pyx_n_FlowMappingStartToken, "FlowMappingStartToken"},
+ {&__pyx_n_FlowSequenceEndToken, "FlowSequenceEndToken"},
+ {&__pyx_n_FlowSequenceStartToken, "FlowSequenceStartToken"},
+ {&__pyx_n_KeyToken, "KeyToken"},
+ {&__pyx_n_MappingEndEvent, "MappingEndEvent"},
+ {&__pyx_n_MappingNode, "MappingNode"},
+ {&__pyx_n_MappingStartEvent, "MappingStartEvent"},
+ {&__pyx_n_MemoryError, "MemoryError"},
+ {&__pyx_n_ParserError, "ParserError"},
+ {&__pyx_n_ReaderError, "ReaderError"},
+ {&__pyx_n_RepresenterError, "RepresenterError"},
+ {&__pyx_n_ScalarEvent, "ScalarEvent"},
+ {&__pyx_n_ScalarNode, "ScalarNode"},
+ {&__pyx_n_ScalarToken, "ScalarToken"},
+ {&__pyx_n_ScannerError, "ScannerError"},
+ {&__pyx_n_SequenceEndEvent, "SequenceEndEvent"},
+ {&__pyx_n_SequenceNode, "SequenceNode"},
+ {&__pyx_n_SequenceStartEvent, "SequenceStartEvent"},
+ {&__pyx_n_SerializerError, "SerializerError"},
+ {&__pyx_n_StreamEndEvent, "StreamEndEvent"},
+ {&__pyx_n_StreamEndToken, "StreamEndToken"},
+ {&__pyx_n_StreamStartEvent, "StreamStartEvent"},
+ {&__pyx_n_StreamStartToken, "StreamStartToken"},
+ {&__pyx_n_TAG, "TAG"},
+ {&__pyx_n_TagToken, "TagToken"},
+ {&__pyx_n_True, "True"},
+ {&__pyx_n_TypeError, "TypeError"},
+ {&__pyx_n_ValueError, "ValueError"},
+ {&__pyx_n_ValueToken, "ValueToken"},
+ {&__pyx_n_YAML, "YAML"},
+ {&__pyx_n_YAMLError, "YAMLError"},
+ {&__pyx_n___class__, "__class__"},
+ {&__pyx_n_anchor, "anchor"},
+ {&__pyx_n_append, "append"},
+ {&__pyx_n_ascend_resolver, "ascend_resolver"},
+ {&__pyx_n_composer, "composer"},
+ {&__pyx_n_constructor, "constructor"},
+ {&__pyx_n_descend_resolver, "descend_resolver"},
+ {&__pyx_n_emitter, "emitter"},
+ {&__pyx_n_encoding, "encoding"},
+ {&__pyx_n_end_mark, "end_mark"},
+ {&__pyx_n_error, "error"},
+ {&__pyx_n_events, "events"},
+ {&__pyx_n_explicit, "explicit"},
+ {&__pyx_n_flow_style, "flow_style"},
+ {&__pyx_n_get_version, "get_version"},
+ {&__pyx_n_get_version_string, "get_version_string"},
+ {&__pyx_n_hasattr, "hasattr"},
+ {&__pyx_n_implicit, "implicit"},
+ {&__pyx_n_len, "len"},
+ {&__pyx_n_name, "name"},
+ {&__pyx_n_nodes, "nodes"},
+ {&__pyx_n_parser, "parser"},
+ {&__pyx_n_read, "read"},
+ {&__pyx_n_reader, "reader"},
+ {&__pyx_n_representer, "representer"},
+ {&__pyx_n_resolve, "resolve"},
+ {&__pyx_n_scanner, "scanner"},
+ {&__pyx_n_serializer, "serializer"},
+ {&__pyx_n_start_mark, "start_mark"},
+ {&__pyx_n_style, "style"},
+ {&__pyx_n_tag, "tag"},
+ {&__pyx_n_tags, "tags"},
+ {&__pyx_n_tokens, "tokens"},
+ {&__pyx_n_value, "value"},
+ {&__pyx_n_version, "version"},
+ {&__pyx_n_write, "write"},
+ {&__pyx_n_yaml, "yaml"},
+ {0, 0}
+};
+
+static __Pyx_StringTabEntry __pyx_string_tab[] = {
+ {&__pyx_k12p, __pyx_k12, sizeof(__pyx_k12)},
+ {&__pyx_k14p, __pyx_k14, sizeof(__pyx_k14)},
+ {&__pyx_k15p, __pyx_k15, sizeof(__pyx_k15)},
+ {&__pyx_k16p, __pyx_k16, sizeof(__pyx_k16)},
+ {&__pyx_k17p, __pyx_k17, sizeof(__pyx_k17)},
+ {&__pyx_k18p, __pyx_k18, sizeof(__pyx_k18)},
+ {&__pyx_k19p, __pyx_k19, sizeof(__pyx_k19)},
+ {&__pyx_k20p, __pyx_k20, sizeof(__pyx_k20)},
+ {&__pyx_k21p, __pyx_k21, sizeof(__pyx_k21)},
+ {&__pyx_k22p, __pyx_k22, sizeof(__pyx_k22)},
+ {&__pyx_k30p, __pyx_k30, sizeof(__pyx_k30)},
+ {&__pyx_k31p, __pyx_k31, sizeof(__pyx_k31)},
+ {&__pyx_k32p, __pyx_k32, sizeof(__pyx_k32)},
+ {&__pyx_k33p, __pyx_k33, sizeof(__pyx_k33)},
+ {&__pyx_k34p, __pyx_k34, sizeof(__pyx_k34)},
+ {&__pyx_k35p, __pyx_k35, sizeof(__pyx_k35)},
+ {&__pyx_k36p, __pyx_k36, sizeof(__pyx_k36)},
+ {&__pyx_k37p, __pyx_k37, sizeof(__pyx_k37)},
+ {&__pyx_k38p, __pyx_k38, sizeof(__pyx_k38)},
+ {&__pyx_k45p, __pyx_k45, sizeof(__pyx_k45)},
+ {&__pyx_k46p, __pyx_k46, sizeof(__pyx_k46)},
+ {&__pyx_k47p, __pyx_k47, sizeof(__pyx_k47)},
+ {&__pyx_k48p, __pyx_k48, sizeof(__pyx_k48)},
+ {&__pyx_k49p, __pyx_k49, sizeof(__pyx_k49)},
+ {&__pyx_k54p, __pyx_k54, sizeof(__pyx_k54)},
+ {&__pyx_k56p, __pyx_k56, sizeof(__pyx_k56)},
+ {&__pyx_k60p, __pyx_k60, sizeof(__pyx_k60)},
+ {&__pyx_k61p, __pyx_k61, sizeof(__pyx_k61)},
+ {&__pyx_k64p, __pyx_k64, sizeof(__pyx_k64)},
+ {&__pyx_k65p, __pyx_k65, sizeof(__pyx_k65)},
+ {&__pyx_k66p, __pyx_k66, sizeof(__pyx_k66)},
+ {&__pyx_k67p, __pyx_k67, sizeof(__pyx_k67)},
+ {&__pyx_k68p, __pyx_k68, sizeof(__pyx_k68)},
+ {&__pyx_k71p, __pyx_k71, sizeof(__pyx_k71)},
+ {&__pyx_k72p, __pyx_k72, sizeof(__pyx_k72)},
+ {&__pyx_k73p, __pyx_k73, sizeof(__pyx_k73)},
+ {&__pyx_k74p, __pyx_k74, sizeof(__pyx_k74)},
+ {&__pyx_k75p, __pyx_k75, sizeof(__pyx_k75)},
+ {&__pyx_k76p, __pyx_k76, sizeof(__pyx_k76)},
+ {&__pyx_k77p, __pyx_k77, sizeof(__pyx_k77)},
+ {&__pyx_k78p, __pyx_k78, sizeof(__pyx_k78)},
+ {&__pyx_k79p, __pyx_k79, sizeof(__pyx_k79)},
+ {&__pyx_k80p, __pyx_k80, sizeof(__pyx_k80)},
+ {&__pyx_k81p, __pyx_k81, sizeof(__pyx_k81)},
+ {&__pyx_k82p, __pyx_k82, sizeof(__pyx_k82)},
+ {&__pyx_k83p, __pyx_k83, sizeof(__pyx_k83)},
+ {&__pyx_k84p, __pyx_k84, sizeof(__pyx_k84)},
+ {&__pyx_k85p, __pyx_k85, sizeof(__pyx_k85)},
+ {&__pyx_k86p, __pyx_k86, sizeof(__pyx_k86)},
+ {&__pyx_k87p, __pyx_k87, sizeof(__pyx_k87)},
+ {&__pyx_k88p, __pyx_k88, sizeof(__pyx_k88)},
+ {&__pyx_k89p, __pyx_k89, sizeof(__pyx_k89)},
+ {&__pyx_k90p, __pyx_k90, sizeof(__pyx_k90)},
+ {&__pyx_k91p, __pyx_k91, sizeof(__pyx_k91)},
+ {&__pyx_k92p, __pyx_k92, sizeof(__pyx_k92)},
+ {&__pyx_k93p, __pyx_k93, sizeof(__pyx_k93)},
+ {&__pyx_k94p, __pyx_k94, sizeof(__pyx_k94)},
+ {&__pyx_k95p, __pyx_k95, sizeof(__pyx_k95)},
+ {&__pyx_k96p, __pyx_k96, sizeof(__pyx_k96)},
+ {&__pyx_k97p, __pyx_k97, sizeof(__pyx_k97)},
+ {&__pyx_k98p, __pyx_k98, sizeof(__pyx_k98)},
+ {&__pyx_k99p, __pyx_k99, sizeof(__pyx_k99)},
+ {&__pyx_k100p, __pyx_k100, sizeof(__pyx_k100)},
+ {&__pyx_k101p, __pyx_k101, sizeof(__pyx_k101)},
+ {&__pyx_k102p, __pyx_k102, sizeof(__pyx_k102)},
+ {&__pyx_k103p, __pyx_k103, sizeof(__pyx_k103)},
+ {&__pyx_k104p, __pyx_k104, sizeof(__pyx_k104)},
+ {&__pyx_k105p, __pyx_k105, sizeof(__pyx_k105)},
+ {&__pyx_k106p, __pyx_k106, sizeof(__pyx_k106)},
+ {&__pyx_k107p, __pyx_k107, sizeof(__pyx_k107)},
+ {&__pyx_k108p, __pyx_k108, sizeof(__pyx_k108)},
+ {&__pyx_k109p, __pyx_k109, sizeof(__pyx_k109)},
+ {&__pyx_k110p, __pyx_k110, sizeof(__pyx_k110)},
+ {&__pyx_k111p, __pyx_k111, sizeof(__pyx_k111)},
+ {&__pyx_k112p, __pyx_k112, sizeof(__pyx_k112)},
+ {&__pyx_k113p, __pyx_k113, sizeof(__pyx_k113)},
+ {0, 0, 0}
+};
+
+static PyObject *__pyx_tp_new_5_yaml_Mark(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ struct __pyx_obj_5_yaml_Mark *p = (struct __pyx_obj_5_yaml_Mark *)o;
+ p->name = Py_None; Py_INCREF(Py_None);
+ p->buffer = Py_None; Py_INCREF(Py_None);
+ p->pointer = Py_None; Py_INCREF(Py_None);
+ return o;
+}
+
+static void __pyx_tp_dealloc_5_yaml_Mark(PyObject *o) {
+ struct __pyx_obj_5_yaml_Mark *p = (struct __pyx_obj_5_yaml_Mark *)o;
+ Py_XDECREF(p->name);
+ Py_XDECREF(p->buffer);
+ Py_XDECREF(p->pointer);
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_5_yaml_Mark(PyObject *o, visitproc v, void *a) {
+ int e;
+ struct __pyx_obj_5_yaml_Mark *p = (struct __pyx_obj_5_yaml_Mark *)o;
+ if (p->name) {
+ e = (*v)(p->name, a); if (e) return e;
+ }
+ if (p->buffer) {
+ e = (*v)(p->buffer, a); if (e) return e;
+ }
+ if (p->pointer) {
+ e = (*v)(p->pointer, a); if (e) return e;
+ }
+ return 0;
+}
+
+static int __pyx_tp_clear_5_yaml_Mark(PyObject *o) {
+ struct __pyx_obj_5_yaml_Mark *p = (struct __pyx_obj_5_yaml_Mark *)o;
+ Py_XDECREF(p->name);
+ p->name = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->buffer);
+ p->buffer = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->pointer);
+ p->pointer = Py_None; Py_INCREF(Py_None);
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_5_yaml_Mark[] = {
+ {"get_snippet", (PyCFunction)__pyx_f_5_yaml_4Mark_get_snippet, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static struct PyMemberDef __pyx_members_5_yaml_Mark[] = {
+ {"name", T_OBJECT, offsetof(struct __pyx_obj_5_yaml_Mark, name), READONLY, 0},
+ {"index", T_INT, offsetof(struct __pyx_obj_5_yaml_Mark, index), READONLY, 0},
+ {"line", T_INT, offsetof(struct __pyx_obj_5_yaml_Mark, line), READONLY, 0},
+ {"column", T_INT, offsetof(struct __pyx_obj_5_yaml_Mark, column), READONLY, 0},
+ {"buffer", T_OBJECT, offsetof(struct __pyx_obj_5_yaml_Mark, buffer), READONLY, 0},
+ {"pointer", T_OBJECT, offsetof(struct __pyx_obj_5_yaml_Mark, pointer), READONLY, 0},
+ {0, 0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_Mark = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_Mark = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_Mark = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_Mark = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+PyTypeObject __pyx_type_5_yaml_Mark = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "_yaml.Mark", /*tp_name*/
+ sizeof(struct __pyx_obj_5_yaml_Mark), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_5_yaml_Mark, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_Mark, /*tp_as_number*/
+ &__pyx_tp_as_sequence_Mark, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_Mark, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ __pyx_f_5_yaml_4Mark___str__, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_Mark, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ 0, /*tp_doc*/
+ __pyx_tp_traverse_5_yaml_Mark, /*tp_traverse*/
+ __pyx_tp_clear_5_yaml_Mark, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_5_yaml_Mark, /*tp_methods*/
+ __pyx_members_5_yaml_Mark, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ __pyx_f_5_yaml_4Mark___init__, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_5_yaml_Mark, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+static struct __pyx_vtabstruct_5_yaml_CParser __pyx_vtable_5_yaml_CParser;
+
+static PyObject *__pyx_tp_new_5_yaml_CParser(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ struct __pyx_obj_5_yaml_CParser *p = (struct __pyx_obj_5_yaml_CParser *)o;
+ *(struct __pyx_vtabstruct_5_yaml_CParser **)&p->__pyx_vtab = __pyx_vtabptr_5_yaml_CParser;
+ p->stream = Py_None; Py_INCREF(Py_None);
+ p->stream_name = Py_None; Py_INCREF(Py_None);
+ p->current_token = Py_None; Py_INCREF(Py_None);
+ p->current_event = Py_None; Py_INCREF(Py_None);
+ p->anchors = Py_None; Py_INCREF(Py_None);
+ return o;
+}
+
+static void __pyx_tp_dealloc_5_yaml_CParser(PyObject *o) {
+ struct __pyx_obj_5_yaml_CParser *p = (struct __pyx_obj_5_yaml_CParser *)o;
+ {
+ PyObject *etype, *eval, *etb;
+ PyErr_Fetch(&etype, &eval, &etb);
+ ++o->ob_refcnt;
+ __pyx_f_5_yaml_7CParser___dealloc__(o);
+ if (PyErr_Occurred()) PyErr_WriteUnraisable(o);
+ --o->ob_refcnt;
+ PyErr_Restore(etype, eval, etb);
+ }
+ Py_XDECREF(p->stream);
+ Py_XDECREF(p->stream_name);
+ Py_XDECREF(p->current_token);
+ Py_XDECREF(p->current_event);
+ Py_XDECREF(p->anchors);
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_5_yaml_CParser(PyObject *o, visitproc v, void *a) {
+ int e;
+ struct __pyx_obj_5_yaml_CParser *p = (struct __pyx_obj_5_yaml_CParser *)o;
+ if (p->stream) {
+ e = (*v)(p->stream, a); if (e) return e;
+ }
+ if (p->stream_name) {
+ e = (*v)(p->stream_name, a); if (e) return e;
+ }
+ if (p->current_token) {
+ e = (*v)(p->current_token, a); if (e) return e;
+ }
+ if (p->current_event) {
+ e = (*v)(p->current_event, a); if (e) return e;
+ }
+ if (p->anchors) {
+ e = (*v)(p->anchors, a); if (e) return e;
+ }
+ return 0;
+}
+
+static int __pyx_tp_clear_5_yaml_CParser(PyObject *o) {
+ struct __pyx_obj_5_yaml_CParser *p = (struct __pyx_obj_5_yaml_CParser *)o;
+ Py_XDECREF(p->stream);
+ p->stream = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->stream_name);
+ p->stream_name = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->current_token);
+ p->current_token = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->current_event);
+ p->current_event = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->anchors);
+ p->anchors = Py_None; Py_INCREF(Py_None);
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_5_yaml_CParser[] = {
+ {"raw_scan", (PyCFunction)__pyx_f_5_yaml_7CParser_raw_scan, METH_VARARGS|METH_KEYWORDS, 0},
+ {"get_token", (PyCFunction)__pyx_f_5_yaml_7CParser_get_token, METH_VARARGS|METH_KEYWORDS, 0},
+ {"peek_token", (PyCFunction)__pyx_f_5_yaml_7CParser_peek_token, METH_VARARGS|METH_KEYWORDS, 0},
+ {"check_token", (PyCFunction)__pyx_f_5_yaml_7CParser_check_token, METH_VARARGS|METH_KEYWORDS, 0},
+ {"raw_parse", (PyCFunction)__pyx_f_5_yaml_7CParser_raw_parse, METH_VARARGS|METH_KEYWORDS, 0},
+ {"get_event", (PyCFunction)__pyx_f_5_yaml_7CParser_get_event, METH_VARARGS|METH_KEYWORDS, 0},
+ {"peek_event", (PyCFunction)__pyx_f_5_yaml_7CParser_peek_event, METH_VARARGS|METH_KEYWORDS, 0},
+ {"check_event", (PyCFunction)__pyx_f_5_yaml_7CParser_check_event, METH_VARARGS|METH_KEYWORDS, 0},
+ {"check_node", (PyCFunction)__pyx_f_5_yaml_7CParser_check_node, METH_VARARGS|METH_KEYWORDS, 0},
+ {"get_node", (PyCFunction)__pyx_f_5_yaml_7CParser_get_node, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_CParser = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_CParser = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_CParser = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_CParser = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+PyTypeObject __pyx_type_5_yaml_CParser = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "_yaml.CParser", /*tp_name*/
+ sizeof(struct __pyx_obj_5_yaml_CParser), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_5_yaml_CParser, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_CParser, /*tp_as_number*/
+ &__pyx_tp_as_sequence_CParser, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_CParser, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_CParser, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ 0, /*tp_doc*/
+ __pyx_tp_traverse_5_yaml_CParser, /*tp_traverse*/
+ __pyx_tp_clear_5_yaml_CParser, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_5_yaml_CParser, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ __pyx_f_5_yaml_7CParser___init__, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_5_yaml_CParser, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+static struct __pyx_vtabstruct_5_yaml_CEmitter __pyx_vtable_5_yaml_CEmitter;
+
+static PyObject *__pyx_tp_new_5_yaml_CEmitter(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ struct __pyx_obj_5_yaml_CEmitter *p = (struct __pyx_obj_5_yaml_CEmitter *)o;
+ *(struct __pyx_vtabstruct_5_yaml_CEmitter **)&p->__pyx_vtab = __pyx_vtabptr_5_yaml_CEmitter;
+ p->stream = Py_None; Py_INCREF(Py_None);
+ p->use_version = Py_None; Py_INCREF(Py_None);
+ p->use_tags = Py_None; Py_INCREF(Py_None);
+ p->serialized_nodes = Py_None; Py_INCREF(Py_None);
+ p->anchors = Py_None; Py_INCREF(Py_None);
+ return o;
+}
+
+static void __pyx_tp_dealloc_5_yaml_CEmitter(PyObject *o) {
+ struct __pyx_obj_5_yaml_CEmitter *p = (struct __pyx_obj_5_yaml_CEmitter *)o;
+ {
+ PyObject *etype, *eval, *etb;
+ PyErr_Fetch(&etype, &eval, &etb);
+ ++o->ob_refcnt;
+ __pyx_f_5_yaml_8CEmitter___dealloc__(o);
+ if (PyErr_Occurred()) PyErr_WriteUnraisable(o);
+ --o->ob_refcnt;
+ PyErr_Restore(etype, eval, etb);
+ }
+ Py_XDECREF(p->stream);
+ Py_XDECREF(p->use_version);
+ Py_XDECREF(p->use_tags);
+ Py_XDECREF(p->serialized_nodes);
+ Py_XDECREF(p->anchors);
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_5_yaml_CEmitter(PyObject *o, visitproc v, void *a) {
+ int e;
+ struct __pyx_obj_5_yaml_CEmitter *p = (struct __pyx_obj_5_yaml_CEmitter *)o;
+ if (p->stream) {
+ e = (*v)(p->stream, a); if (e) return e;
+ }
+ if (p->use_version) {
+ e = (*v)(p->use_version, a); if (e) return e;
+ }
+ if (p->use_tags) {
+ e = (*v)(p->use_tags, a); if (e) return e;
+ }
+ if (p->serialized_nodes) {
+ e = (*v)(p->serialized_nodes, a); if (e) return e;
+ }
+ if (p->anchors) {
+ e = (*v)(p->anchors, a); if (e) return e;
+ }
+ return 0;
+}
+
+static int __pyx_tp_clear_5_yaml_CEmitter(PyObject *o) {
+ struct __pyx_obj_5_yaml_CEmitter *p = (struct __pyx_obj_5_yaml_CEmitter *)o;
+ Py_XDECREF(p->stream);
+ p->stream = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->use_version);
+ p->use_version = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->use_tags);
+ p->use_tags = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->serialized_nodes);
+ p->serialized_nodes = Py_None; Py_INCREF(Py_None);
+ Py_XDECREF(p->anchors);
+ p->anchors = Py_None; Py_INCREF(Py_None);
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_5_yaml_CEmitter[] = {
+ {"emit", (PyCFunction)__pyx_f_5_yaml_8CEmitter_emit, METH_VARARGS|METH_KEYWORDS, 0},
+ {"open", (PyCFunction)__pyx_f_5_yaml_8CEmitter_open, METH_VARARGS|METH_KEYWORDS, 0},
+ {"close", (PyCFunction)__pyx_f_5_yaml_8CEmitter_close, METH_VARARGS|METH_KEYWORDS, 0},
+ {"serialize", (PyCFunction)__pyx_f_5_yaml_8CEmitter_serialize, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_CEmitter = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_CEmitter = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_CEmitter = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_CEmitter = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+PyTypeObject __pyx_type_5_yaml_CEmitter = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "_yaml.CEmitter", /*tp_name*/
+ sizeof(struct __pyx_obj_5_yaml_CEmitter), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_5_yaml_CEmitter, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_CEmitter, /*tp_as_number*/
+ &__pyx_tp_as_sequence_CEmitter, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_CEmitter, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_CEmitter, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ 0, /*tp_doc*/
+ __pyx_tp_traverse_5_yaml_CEmitter, /*tp_traverse*/
+ __pyx_tp_clear_5_yaml_CEmitter, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_5_yaml_CEmitter, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ __pyx_f_5_yaml_8CEmitter___init__, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_5_yaml_CEmitter, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+
+static struct PyMethodDef __pyx_methods[] = {
+ {"get_version_string", (PyCFunction)__pyx_f_5_yaml_get_version_string, METH_VARARGS|METH_KEYWORDS, 0},
+ {"get_version", (PyCFunction)__pyx_f_5_yaml_get_version, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static void __pyx_init_filenames(void); /*proto*/
+
+PyMODINIT_FUNC init_yaml(void); /*proto*/
+PyMODINIT_FUNC init_yaml(void) {
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ __pyx_init_filenames();
+ __pyx_m = Py_InitModule4("_yaml", __pyx_methods, 0, 0, PYTHON_API_VERSION);
+ if (!__pyx_m) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;};
+ __pyx_b = PyImport_AddModule("__builtin__");
+ if (!__pyx_b) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;};
+ if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;};
+ if (__Pyx_InternStrings(__pyx_intern_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;};
+ if (__Pyx_InitStrings(__pyx_string_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;};
+ __pyx_type_5_yaml_Mark.tp_free = _PyObject_GC_Del;
+ if (PyType_Ready(&__pyx_type_5_yaml_Mark) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 59; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "Mark", (PyObject *)&__pyx_type_5_yaml_Mark) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 59; goto __pyx_L1;}
+ __pyx_ptype_5_yaml_Mark = &__pyx_type_5_yaml_Mark;
+ __pyx_vtabptr_5_yaml_CParser = &__pyx_vtable_5_yaml_CParser;
+ *(void **)&__pyx_vtable_5_yaml_CParser._parser_error = (void *)__pyx_f_5_yaml_7CParser__parser_error;
+ *(void **)&__pyx_vtable_5_yaml_CParser._scan = (void *)__pyx_f_5_yaml_7CParser__scan;
+ *(void **)&__pyx_vtable_5_yaml_CParser._token_to_object = (void *)__pyx_f_5_yaml_7CParser__token_to_object;
+ *(void **)&__pyx_vtable_5_yaml_CParser._parse = (void *)__pyx_f_5_yaml_7CParser__parse;
+ *(void **)&__pyx_vtable_5_yaml_CParser._event_to_object = (void *)__pyx_f_5_yaml_7CParser__event_to_object;
+ *(void **)&__pyx_vtable_5_yaml_CParser._compose_document = (void *)__pyx_f_5_yaml_7CParser__compose_document;
+ *(void **)&__pyx_vtable_5_yaml_CParser._compose_node = (void *)__pyx_f_5_yaml_7CParser__compose_node;
+ *(void **)&__pyx_vtable_5_yaml_CParser._compose_scalar_node = (void *)__pyx_f_5_yaml_7CParser__compose_scalar_node;
+ *(void **)&__pyx_vtable_5_yaml_CParser._compose_sequence_node = (void *)__pyx_f_5_yaml_7CParser__compose_sequence_node;
+ *(void **)&__pyx_vtable_5_yaml_CParser._compose_mapping_node = (void *)__pyx_f_5_yaml_7CParser__compose_mapping_node;
+ *(void **)&__pyx_vtable_5_yaml_CParser._parse_next_event = (void *)__pyx_f_5_yaml_7CParser__parse_next_event;
+ __pyx_type_5_yaml_CParser.tp_free = _PyObject_GC_Del;
+ if (PyType_Ready(&__pyx_type_5_yaml_CParser) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 242; goto __pyx_L1;}
+ if (__Pyx_SetVtable(__pyx_type_5_yaml_CParser.tp_dict, __pyx_vtabptr_5_yaml_CParser) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 242; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "CParser", (PyObject *)&__pyx_type_5_yaml_CParser) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 242; goto __pyx_L1;}
+ __pyx_ptype_5_yaml_CParser = &__pyx_type_5_yaml_CParser;
+ __pyx_vtabptr_5_yaml_CEmitter = &__pyx_vtable_5_yaml_CEmitter;
+ *(void **)&__pyx_vtable_5_yaml_CEmitter._emitter_error = (void *)__pyx_f_5_yaml_8CEmitter__emitter_error;
+ *(void **)&__pyx_vtable_5_yaml_CEmitter._object_to_event = (void *)__pyx_f_5_yaml_8CEmitter__object_to_event;
+ *(void **)&__pyx_vtable_5_yaml_CEmitter._anchor_node = (void *)__pyx_f_5_yaml_8CEmitter__anchor_node;
+ *(void **)&__pyx_vtable_5_yaml_CEmitter._serialize_node = (void *)__pyx_f_5_yaml_8CEmitter__serialize_node;
+ __pyx_type_5_yaml_CEmitter.tp_free = _PyObject_GC_Del;
+ if (PyType_Ready(&__pyx_type_5_yaml_CEmitter) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 863; goto __pyx_L1;}
+ if (__Pyx_SetVtable(__pyx_type_5_yaml_CEmitter.tp_dict, __pyx_vtabptr_5_yaml_CEmitter) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 863; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "CEmitter", (PyObject *)&__pyx_type_5_yaml_CEmitter) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 863; goto __pyx_L1;}
+ __pyx_ptype_5_yaml_CEmitter = &__pyx_type_5_yaml_CEmitter;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":2 */
+ __pyx_1 = __Pyx_Import(__pyx_n_yaml, 0); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_yaml, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":13 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 13; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_error); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 13; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_YAMLError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 13; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_YAMLError, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 13; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":14 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 14; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_reader); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 14; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_ReaderError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 14; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ReaderError, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 14; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":15 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 15; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_scanner); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 15; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_ScannerError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 15; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ScannerError, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 15; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":16 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 16; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_parser); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 16; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_ParserError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 16; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ParserError, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 16; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":17 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_composer); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_ComposerError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ComposerError, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 17; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":18 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_constructor); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_ConstructorError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ConstructorError, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 18; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":19 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 19; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_emitter); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 19; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_EmitterError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 19; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_EmitterError, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 19; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":20 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_serializer); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_SerializerError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_SerializerError, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 20; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":21 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_representer); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_RepresenterError); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_RepresenterError, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":23 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_StreamStartToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_StreamStartToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 23; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":24 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_StreamEndToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_StreamEndToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 24; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":25 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 25; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 25; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_DirectiveToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 25; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_DirectiveToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 25; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":26 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_DocumentStartToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_DocumentStartToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 26; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":27 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_DocumentEndToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_DocumentEndToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 27; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":28 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 28; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 28; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_BlockSequenceStartToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 28; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_BlockSequenceStartToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 28; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":29 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 29; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 29; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_BlockMappingStartToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 29; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_BlockMappingStartToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 29; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":30 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 30; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 30; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_BlockEndToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 30; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_BlockEndToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 30; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":31 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 31; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 31; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_FlowSequenceStartToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 31; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_FlowSequenceStartToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 31; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":32 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_FlowMappingStartToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_FlowMappingStartToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 32; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":33 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_FlowSequenceEndToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_FlowSequenceEndToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 33; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":34 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 34; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 34; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_FlowMappingEndToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 34; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_FlowMappingEndToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 34; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":35 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_KeyToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_KeyToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 35; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":36 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 36; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 36; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_ValueToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 36; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ValueToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 36; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":37 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 37; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 37; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_BlockEntryToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 37; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_BlockEntryToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 37; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":38 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 38; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 38; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_FlowEntryToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 38; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_FlowEntryToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 38; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":39 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 39; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 39; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_AliasToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 39; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_AliasToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 39; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":40 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 40; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 40; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_AnchorToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 40; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_AnchorToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 40; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":41 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 41; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_tokens); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 41; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_TagToken); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 41; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_TagToken, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 41; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":42 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 42; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_tokens); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 42; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_ScalarToken); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 42; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ScalarToken, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 42; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":44 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 44; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_events); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 44; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_StreamStartEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 44; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_StreamStartEvent, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 44; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":45 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 45; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_events); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 45; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_StreamEndEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 45; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_StreamEndEvent, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 45; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":46 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 46; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_events); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 46; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_DocumentStartEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 46; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_DocumentStartEvent, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 46; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":47 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 47; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_events); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 47; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_DocumentEndEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 47; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_DocumentEndEvent, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 47; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":48 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 48; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_events); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 48; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_AliasEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 48; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_AliasEvent, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 48; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":49 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 49; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_events); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 49; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_ScalarEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 49; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ScalarEvent, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 49; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":50 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 50; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_events); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 50; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_SequenceStartEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 50; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_SequenceStartEvent, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 50; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":51 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 51; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_events); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 51; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_SequenceEndEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 51; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_SequenceEndEvent, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 51; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":52 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 52; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_events); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 52; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_MappingStartEvent); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 52; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_MappingStartEvent, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 52; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":53 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 53; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_events); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 53; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_MappingEndEvent); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 53; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_MappingEndEvent, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 53; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":55 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 55; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_nodes); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 55; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_ScalarNode); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 55; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ScalarNode, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 55; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":56 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 56; goto __pyx_L1;}
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_nodes); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 56; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_SequenceNode); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 56; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_SequenceNode, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 56; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":57 */
+ __pyx_2 = __Pyx_GetName(__pyx_m, __pyx_n_yaml); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 57; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_2, __pyx_n_nodes); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 57; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyObject_GetAttr(__pyx_1, __pyx_n_MappingNode); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 57; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_MappingNode, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 57; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":880 */
+ Py_INCREF(Py_None);
+ __pyx_k2 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k3 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k4 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k5 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k6 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k7 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k8 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k9 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k10 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k11 = Py_None;
+
+ /* "/home/xi/xitology/pyyaml-org/pyyaml/trunk/ext/_yaml.pyx":1338 */
+ return;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("_yaml");
+}
+
+static char *__pyx_filenames[] = {
+ "_yaml.pyx",
+};
+
+/* Runtime support code */
+
+static void __pyx_init_filenames(void) {
+ __pyx_f = __pyx_filenames;
+}
+
+static int __Pyx_GetStarArgs(
+ PyObject **args,
+ PyObject **kwds,
+ char *kwd_list[],
+ Py_ssize_t nargs,
+ PyObject **args2,
+ PyObject **kwds2)
+{
+ PyObject *x = 0, *args1 = 0, *kwds1 = 0;
+
+ if (args2)
+ *args2 = 0;
+ if (kwds2)
+ *kwds2 = 0;
+
+ if (args2) {
+ args1 = PyTuple_GetSlice(*args, 0, nargs);
+ if (!args1)
+ goto bad;
+ *args2 = PyTuple_GetSlice(*args, nargs, PyTuple_Size(*args));
+ if (!*args2)
+ goto bad;
+ }
+ else {
+ args1 = *args;
+ Py_INCREF(args1);
+ }
+
+ if (kwds2) {
+ if (*kwds) {
+ char **p;
+ kwds1 = PyDict_New();
+ if (!kwds)
+ goto bad;
+ *kwds2 = PyDict_Copy(*kwds);
+ if (!*kwds2)
+ goto bad;
+ for (p = kwd_list; *p; p++) {
+ x = PyDict_GetItemString(*kwds, *p);
+ if (x) {
+ if (PyDict_SetItemString(kwds1, *p, x) < 0)
+ goto bad;
+ if (PyDict_DelItemString(*kwds2, *p) < 0)
+ goto bad;
+ }
+ }
+ }
+ else {
+ *kwds2 = PyDict_New();
+ if (!*kwds2)
+ goto bad;
+ }
+ }
+ else {
+ kwds1 = *kwds;
+ Py_XINCREF(kwds1);
+ }
+
+ *args = args1;
+ *kwds = kwds1;
+ return 0;
+bad:
+ Py_XDECREF(args1);
+ Py_XDECREF(kwds1);
+ Py_XDECREF(*args2);
+ Py_XDECREF(*kwds2);
+ return -1;
+}
+
+static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list) {
+ PyObject *__import__ = 0;
+ PyObject *empty_list = 0;
+ PyObject *module = 0;
+ PyObject *global_dict = 0;
+ PyObject *empty_dict = 0;
+ PyObject *list;
+ __import__ = PyObject_GetAttrString(__pyx_b, "__import__");
+ if (!__import__)
+ goto bad;
+ if (from_list)
+ list = from_list;
+ else {
+ empty_list = PyList_New(0);
+ if (!empty_list)
+ goto bad;
+ list = empty_list;
+ }
+ global_dict = PyModule_GetDict(__pyx_m);
+ if (!global_dict)
+ goto bad;
+ empty_dict = PyDict_New();
+ if (!empty_dict)
+ goto bad;
+ module = PyObject_CallFunction(__import__, "OOOO",
+ name, global_dict, empty_dict, list);
+bad:
+ Py_XDECREF(empty_list);
+ Py_XDECREF(__import__);
+ Py_XDECREF(empty_dict);
+ return module;
+}
+
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name) {
+ PyObject *result;
+ result = PyObject_GetAttr(dict, name);
+ if (!result)
+ PyErr_SetObject(PyExc_NameError, name);
+ return result;
+}
+
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb) {
+ Py_XINCREF(type);
+ Py_XINCREF(value);
+ Py_XINCREF(tb);
+ /* First, check the traceback argument, replacing None with NULL. */
+ if (tb == Py_None) {
+ Py_DECREF(tb);
+ tb = 0;
+ }
+ else if (tb != NULL && !PyTraceBack_Check(tb)) {
+ PyErr_SetString(PyExc_TypeError,
+ "raise: arg 3 must be a traceback or None");
+ goto raise_error;
+ }
+ /* Next, replace a missing value with None */
+ if (value == NULL) {
+ value = Py_None;
+ Py_INCREF(value);
+ }
+ /* Next, repeatedly, replace a tuple exception with its first item */
+ while (PyTuple_Check(type) && PyTuple_Size(type) > 0) {
+ PyObject *tmp = type;
+ type = PyTuple_GET_ITEM(type, 0);
+ Py_INCREF(type);
+ Py_DECREF(tmp);
+ }
+ if (PyString_Check(type))
+ ;
+ else if (PyType_Check(type) || PyClass_Check(type))
+ ; /*PyErr_NormalizeException(&type, &value, &tb);*/
+ else if (PyInstance_Check(type)) {
+ /* Raising an instance. The value should be a dummy. */
+ if (value != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "instance exception may not have a separate value");
+ goto raise_error;
+ }
+ else {
+ /* Normalize to raise <class>, <instance> */
+ Py_DECREF(value);
+ value = type;
+ type = (PyObject*) ((PyInstanceObject*)type)->in_class;
+ Py_INCREF(type);
+ }
+ }
+ else {
+ /* Not something you can raise. You get an exception
+ anyway, just not what you specified :-) */
+ PyErr_Format(PyExc_TypeError,
+ "exceptions must be strings, classes, or "
+ "instances, not %s", type->ob_type->tp_name);
+ goto raise_error;
+ }
+ PyErr_Restore(type, value, tb);
+ return;
+raise_error:
+ Py_XDECREF(value);
+ Py_XDECREF(type);
+ Py_XDECREF(tb);
+ return;
+}
+
+static PyObject *__Pyx_GetExcValue(void) {
+ PyObject *type = 0, *value = 0, *tb = 0;
+ PyObject *result = 0;
+ PyThreadState *tstate = PyThreadState_Get();
+ PyErr_Fetch(&type, &value, &tb);
+ PyErr_NormalizeException(&type, &value, &tb);
+ if (PyErr_Occurred())
+ goto bad;
+ if (!value) {
+ value = Py_None;
+ Py_INCREF(value);
+ }
+ Py_XDECREF(tstate->exc_type);
+ Py_XDECREF(tstate->exc_value);
+ Py_XDECREF(tstate->exc_traceback);
+ tstate->exc_type = type;
+ tstate->exc_value = value;
+ tstate->exc_traceback = tb;
+ result = value;
+ Py_XINCREF(result);
+ type = 0;
+ value = 0;
+ tb = 0;
+bad:
+ Py_XDECREF(type);
+ Py_XDECREF(value);
+ Py_XDECREF(tb);
+ return result;
+}
+
+static void __Pyx_UnpackError(void) {
+ PyErr_SetString(PyExc_ValueError, "unpack sequence of wrong size");
+}
+
+static PyObject *__Pyx_UnpackItem(PyObject *seq, Py_ssize_t i) {
+ PyObject *item;
+ if (!(item = PySequence_GetItem(seq, i))) {
+ if (PyErr_ExceptionMatches(PyExc_IndexError))
+ __Pyx_UnpackError();
+ }
+ return item;
+}
+
+static int __Pyx_EndUnpack(PyObject *seq, Py_ssize_t i) {
+ PyObject *item;
+ if (item = PySequence_GetItem(seq, i)) {
+ Py_DECREF(item);
+ __Pyx_UnpackError();
+ return -1;
+ }
+ PyErr_Clear();
+ return 0;
+}
+
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_InternFromString(t->s);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_FromStringAndSize(t->s, t->n - 1);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+static int __Pyx_SetVtable(PyObject *dict, void *vtable) {
+ PyObject *pycobj = 0;
+ int result;
+
+ pycobj = PyCObject_FromVoidPtr(vtable, 0);
+ if (!pycobj)
+ goto bad;
+ if (PyDict_SetItemString(dict, "__pyx_vtable__", pycobj) < 0)
+ goto bad;
+ result = 0;
+ goto done;
+
+bad:
+ result = -1;
+done:
+ Py_XDECREF(pycobj);
+ return result;
+}
+
+#include "compile.h"
+#include "frameobject.h"
+#include "traceback.h"
+
+static void __Pyx_AddTraceback(char *funcname) {
+ PyObject *py_srcfile = 0;
+ PyObject *py_funcname = 0;
+ PyObject *py_globals = 0;
+ PyObject *empty_tuple = 0;
+ PyObject *empty_string = 0;
+ PyCodeObject *py_code = 0;
+ PyFrameObject *py_frame = 0;
+
+ py_srcfile = PyString_FromString(__pyx_filename);
+ if (!py_srcfile) goto bad;
+ py_funcname = PyString_FromString(funcname);
+ if (!py_funcname) goto bad;
+ py_globals = PyModule_GetDict(__pyx_m);
+ if (!py_globals) goto bad;
+ empty_tuple = PyTuple_New(0);
+ if (!empty_tuple) goto bad;
+ empty_string = PyString_FromString("");
+ if (!empty_string) goto bad;
+ py_code = PyCode_New(
+ 0, /*int argcount,*/
+ 0, /*int nlocals,*/
+ 0, /*int stacksize,*/
+ 0, /*int flags,*/
+ empty_string, /*PyObject *code,*/
+ empty_tuple, /*PyObject *consts,*/
+ empty_tuple, /*PyObject *names,*/
+ empty_tuple, /*PyObject *varnames,*/
+ empty_tuple, /*PyObject *freevars,*/
+ empty_tuple, /*PyObject *cellvars,*/
+ py_srcfile, /*PyObject *filename,*/
+ py_funcname, /*PyObject *name,*/
+ __pyx_lineno, /*int firstlineno,*/
+ empty_string /*PyObject *lnotab*/
+ );
+ if (!py_code) goto bad;
+ py_frame = PyFrame_New(
+ PyThreadState_Get(), /*PyThreadState *tstate,*/
+ py_code, /*PyCodeObject *code,*/
+ py_globals, /*PyObject *globals,*/
+ 0 /*PyObject *locals*/
+ );
+ if (!py_frame) goto bad;
+ py_frame->f_lineno = __pyx_lineno;
+ PyTraceBack_Here(py_frame);
+bad:
+ Py_XDECREF(py_srcfile);
+ Py_XDECREF(py_funcname);
+ Py_XDECREF(empty_tuple);
+ Py_XDECREF(empty_string);
+ Py_XDECREF(py_code);
+ Py_XDECREF(py_frame);
+}
diff --git a/google_appengine/lib/yaml/ext/_yaml.h b/google_appengine/lib/yaml/ext/_yaml.h
new file mode 100644
index 0000000..d8070e5
--- /dev/null
+++ b/google_appengine/lib/yaml/ext/_yaml.h
@@ -0,0 +1,3 @@
+
+#include <yaml.h>
+
diff --git a/google_appengine/lib/yaml/ext/_yaml.pxd b/google_appengine/lib/yaml/ext/_yaml.pxd
new file mode 100644
index 0000000..c7936c7
--- /dev/null
+++ b/google_appengine/lib/yaml/ext/_yaml.pxd
@@ -0,0 +1,249 @@
+
+cdef extern from "_yaml.h":
+
+ void malloc(int l)
+ void memcpy(char *d, char *s, int l)
+ int strlen(char *s)
+ int PyString_CheckExact(object o)
+ int PyUnicode_CheckExact(object o)
+ char *PyString_AS_STRING(object o)
+ int PyString_GET_SIZE(object o)
+ object PyString_FromStringAndSize(char *v, int l)
+ object PyUnicode_DecodeUTF8(char *s, int s, char *e)
+ object PyUnicode_AsUTF8String(object o)
+
+ ctypedef enum:
+ SIZEOF_VOID_P
+ ctypedef enum yaml_encoding_t:
+ YAML_ANY_ENCODING
+ YAML_UTF8_ENCODING
+ YAML_UTF16LE_ENCODING
+ YAML_UTF16BE_ENCODING
+ ctypedef enum yaml_break_t:
+ YAML_ANY_BREAK
+ YAML_CR_BREAK
+ YAML_LN_BREAK
+ YAML_CRLN_BREAK
+ ctypedef enum yaml_error_type_t:
+ YAML_NO_ERROR
+ YAML_MEMORY_ERROR
+ YAML_READER_ERROR
+ YAML_SCANNER_ERROR
+ YAML_PARSER_ERROR
+ YAML_WRITER_ERROR
+ YAML_EMITTER_ERROR
+ ctypedef enum yaml_scalar_style_t:
+ YAML_ANY_SCALAR_STYLE
+ YAML_PLAIN_SCALAR_STYLE
+ YAML_SINGLE_QUOTED_SCALAR_STYLE
+ YAML_DOUBLE_QUOTED_SCALAR_STYLE
+ YAML_LITERAL_SCALAR_STYLE
+ YAML_FOLDED_SCALAR_STYLE
+ ctypedef enum yaml_sequence_style_t:
+ YAML_ANY_SEQUENCE_STYLE
+ YAML_BLOCK_SEQUENCE_STYLE
+ YAML_FLOW_SEQUENCE_STYLE
+ ctypedef enum yaml_mapping_style_t:
+ YAML_ANY_MAPPING_STYLE
+ YAML_BLOCK_MAPPING_STYLE
+ YAML_FLOW_MAPPING_STYLE
+ ctypedef enum yaml_token_type_t:
+ YAML_NO_TOKEN
+ YAML_STREAM_START_TOKEN
+ YAML_STREAM_END_TOKEN
+ YAML_VERSION_DIRECTIVE_TOKEN
+ YAML_TAG_DIRECTIVE_TOKEN
+ YAML_DOCUMENT_START_TOKEN
+ YAML_DOCUMENT_END_TOKEN
+ YAML_BLOCK_SEQUENCE_START_TOKEN
+ YAML_BLOCK_MAPPING_START_TOKEN
+ YAML_BLOCK_END_TOKEN
+ YAML_FLOW_SEQUENCE_START_TOKEN
+ YAML_FLOW_SEQUENCE_END_TOKEN
+ YAML_FLOW_MAPPING_START_TOKEN
+ YAML_FLOW_MAPPING_END_TOKEN
+ YAML_BLOCK_ENTRY_TOKEN
+ YAML_FLOW_ENTRY_TOKEN
+ YAML_KEY_TOKEN
+ YAML_VALUE_TOKEN
+ YAML_ALIAS_TOKEN
+ YAML_ANCHOR_TOKEN
+ YAML_TAG_TOKEN
+ YAML_SCALAR_TOKEN
+ ctypedef enum yaml_event_type_t:
+ YAML_NO_EVENT
+ YAML_STREAM_START_EVENT
+ YAML_STREAM_END_EVENT
+ YAML_DOCUMENT_START_EVENT
+ YAML_DOCUMENT_END_EVENT
+ YAML_ALIAS_EVENT
+ YAML_SCALAR_EVENT
+ YAML_SEQUENCE_START_EVENT
+ YAML_SEQUENCE_END_EVENT
+ YAML_MAPPING_START_EVENT
+ YAML_MAPPING_END_EVENT
+
+ ctypedef int yaml_read_handler_t(void *data, char *buffer,
+ int size, int *size_read) except 0
+
+ ctypedef int yaml_write_handler_t(void *data, char *buffer,
+ int size) except 0
+
+ ctypedef struct yaml_mark_t:
+ int index
+ int line
+ int column
+ ctypedef struct yaml_version_directive_t:
+ int major
+ int minor
+ ctypedef struct yaml_tag_directive_t:
+ char *handle
+ char *prefix
+
+ ctypedef struct _yaml_token_stream_start_data_t:
+ yaml_encoding_t encoding
+ ctypedef struct _yaml_token_alias_data_t:
+ char *value
+ ctypedef struct _yaml_token_anchor_data_t:
+ char *value
+ ctypedef struct _yaml_token_tag_data_t:
+ char *handle
+ char *suffix
+ ctypedef struct _yaml_token_scalar_data_t:
+ char *value
+ int length
+ yaml_scalar_style_t style
+ ctypedef struct _yaml_token_version_directive_data_t:
+ int major
+ int minor
+ ctypedef struct _yaml_token_tag_directive_data_t:
+ char *handle
+ char *prefix
+ ctypedef union _yaml_token_data_t:
+ _yaml_token_stream_start_data_t stream_start
+ _yaml_token_alias_data_t alias
+ _yaml_token_anchor_data_t anchor
+ _yaml_token_tag_data_t tag
+ _yaml_token_scalar_data_t scalar
+ _yaml_token_version_directive_data_t version_directive
+ _yaml_token_tag_directive_data_t tag_directive
+ ctypedef struct yaml_token_t:
+ yaml_token_type_t type
+ _yaml_token_data_t data
+ yaml_mark_t start_mark
+ yaml_mark_t end_mark
+
+ ctypedef struct _yaml_event_stream_start_data_t:
+ yaml_encoding_t encoding
+ ctypedef struct _yaml_event_document_start_data_tag_directives_t:
+ yaml_tag_directive_t *start
+ yaml_tag_directive_t *end
+ ctypedef struct _yaml_event_document_start_data_t:
+ yaml_version_directive_t *version_directive
+ _yaml_event_document_start_data_tag_directives_t tag_directives
+ int implicit
+ ctypedef struct _yaml_event_document_end_data_t:
+ int implicit
+ ctypedef struct _yaml_event_alias_data_t:
+ char *anchor
+ ctypedef struct _yaml_event_scalar_data_t:
+ char *anchor
+ char *tag
+ char *value
+ int length
+ int plain_implicit
+ int quoted_implicit
+ yaml_scalar_style_t style
+ ctypedef struct _yaml_event_sequence_start_data_t:
+ char *anchor
+ char *tag
+ int implicit
+ yaml_sequence_style_t style
+ ctypedef struct _yaml_event_mapping_start_data_t:
+ char *anchor
+ char *tag
+ int implicit
+ yaml_mapping_style_t style
+ ctypedef union _yaml_event_data_t:
+ _yaml_event_stream_start_data_t stream_start
+ _yaml_event_document_start_data_t document_start
+ _yaml_event_document_end_data_t document_end
+ _yaml_event_alias_data_t alias
+ _yaml_event_scalar_data_t scalar
+ _yaml_event_sequence_start_data_t sequence_start
+ _yaml_event_mapping_start_data_t mapping_start
+ ctypedef struct yaml_event_t:
+ yaml_event_type_t type
+ _yaml_event_data_t data
+ yaml_mark_t start_mark
+ yaml_mark_t end_mark
+
+ ctypedef struct yaml_parser_t:
+ yaml_error_type_t error
+ char *problem
+ int problem_offset
+ int problem_value
+ yaml_mark_t problem_mark
+ char *context
+ yaml_mark_t context_mark
+
+ ctypedef struct yaml_emitter_t:
+ yaml_error_type_t error
+ char *problem
+
+ char *yaml_get_version_string()
+ void yaml_get_version(int *major, int *minor, int *patch)
+
+ void yaml_token_delete(yaml_token_t *token)
+
+ int yaml_stream_start_event_initialize(yaml_event_t *event,
+ yaml_encoding_t encoding)
+ int yaml_stream_end_event_initialize(yaml_event_t *event)
+ int yaml_document_start_event_initialize(yaml_event_t *event,
+ yaml_version_directive_t *version_directive,
+ yaml_tag_directive_t *tag_directives_start,
+ yaml_tag_directive_t *tag_directives_end,
+ int implicit)
+ int yaml_document_end_event_initialize(yaml_event_t *event,
+ int implicit)
+ int yaml_alias_event_initialize(yaml_event_t *event, char *anchor)
+ int yaml_scalar_event_initialize(yaml_event_t *event,
+ char *anchor, char *tag, char *value, int length,
+ int plain_implicit, int quoted_implicit,
+ yaml_scalar_style_t style)
+ int yaml_sequence_start_event_initialize(yaml_event_t *event,
+ char *anchor, char *tag, int implicit, yaml_sequence_style_t style)
+ int yaml_sequence_end_event_initialize(yaml_event_t *event)
+ int yaml_mapping_start_event_initialize(yaml_event_t *event,
+ char *anchor, char *tag, int implicit, yaml_mapping_style_t style)
+ int yaml_mapping_end_event_initialize(yaml_event_t *event)
+ void yaml_event_delete(yaml_event_t *event)
+
+ int yaml_parser_initialize(yaml_parser_t *parser)
+ void yaml_parser_delete(yaml_parser_t *parser)
+ void yaml_parser_set_input_string(yaml_parser_t *parser,
+ char *input, int size)
+ void yaml_parser_set_input(yaml_parser_t *parser,
+ yaml_read_handler_t *handler, void *data)
+ void yaml_parser_set_encoding(yaml_parser_t *parser,
+ yaml_encoding_t encoding)
+ int yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token) except *
+ int yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event) except *
+
+ int yaml_emitter_initialize(yaml_emitter_t *emitter)
+ void yaml_emitter_delete(yaml_emitter_t *emitter)
+ void yaml_emitter_set_output_string(yaml_emitter_t *emitter,
+ char *output, int size, int *size_written)
+ void yaml_emitter_set_output(yaml_emitter_t *emitter,
+ yaml_write_handler_t *handler, void *data)
+ void yaml_emitter_set_encoding(yaml_emitter_t *emitter,
+ yaml_encoding_t encoding)
+ void yaml_emitter_set_canonical(yaml_emitter_t *emitter, int canonical)
+ void yaml_emitter_set_indent(yaml_emitter_t *emitter, int indent)
+ void yaml_emitter_set_width(yaml_emitter_t *emitter, int width)
+ void yaml_emitter_set_unicode(yaml_emitter_t *emitter, int unicode)
+ void yaml_emitter_set_break(yaml_emitter_t *emitter,
+ yaml_break_t line_break)
+ int yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event) except *
+ int yaml_emitter_flush(yaml_emitter_t *emitter)
+
diff --git a/google_appengine/lib/yaml/ext/_yaml.pyx b/google_appengine/lib/yaml/ext/_yaml.pyx
new file mode 100644
index 0000000..00d8334
--- /dev/null
+++ b/google_appengine/lib/yaml/ext/_yaml.pyx
@@ -0,0 +1,1344 @@
+
+import yaml
+
+def get_version_string():
+ return yaml_get_version_string()
+
+def get_version():
+ cdef int major, minor, patch
+ yaml_get_version(&major, &minor, &patch)
+ return (major, minor, patch)
+
+#Mark = yaml.error.Mark
+YAMLError = yaml.error.YAMLError
+ReaderError = yaml.reader.ReaderError
+ScannerError = yaml.scanner.ScannerError
+ParserError = yaml.parser.ParserError
+ComposerError = yaml.composer.ComposerError
+ConstructorError = yaml.constructor.ConstructorError
+EmitterError = yaml.emitter.EmitterError
+SerializerError = yaml.serializer.SerializerError
+RepresenterError = yaml.representer.RepresenterError
+
+StreamStartToken = yaml.tokens.StreamStartToken
+StreamEndToken = yaml.tokens.StreamEndToken
+DirectiveToken = yaml.tokens.DirectiveToken
+DocumentStartToken = yaml.tokens.DocumentStartToken
+DocumentEndToken = yaml.tokens.DocumentEndToken
+BlockSequenceStartToken = yaml.tokens.BlockSequenceStartToken
+BlockMappingStartToken = yaml.tokens.BlockMappingStartToken
+BlockEndToken = yaml.tokens.BlockEndToken
+FlowSequenceStartToken = yaml.tokens.FlowSequenceStartToken
+FlowMappingStartToken = yaml.tokens.FlowMappingStartToken
+FlowSequenceEndToken = yaml.tokens.FlowSequenceEndToken
+FlowMappingEndToken = yaml.tokens.FlowMappingEndToken
+KeyToken = yaml.tokens.KeyToken
+ValueToken = yaml.tokens.ValueToken
+BlockEntryToken = yaml.tokens.BlockEntryToken
+FlowEntryToken = yaml.tokens.FlowEntryToken
+AliasToken = yaml.tokens.AliasToken
+AnchorToken = yaml.tokens.AnchorToken
+TagToken = yaml.tokens.TagToken
+ScalarToken = yaml.tokens.ScalarToken
+
+StreamStartEvent = yaml.events.StreamStartEvent
+StreamEndEvent = yaml.events.StreamEndEvent
+DocumentStartEvent = yaml.events.DocumentStartEvent
+DocumentEndEvent = yaml.events.DocumentEndEvent
+AliasEvent = yaml.events.AliasEvent
+ScalarEvent = yaml.events.ScalarEvent
+SequenceStartEvent = yaml.events.SequenceStartEvent
+SequenceEndEvent = yaml.events.SequenceEndEvent
+MappingStartEvent = yaml.events.MappingStartEvent
+MappingEndEvent = yaml.events.MappingEndEvent
+
+ScalarNode = yaml.nodes.ScalarNode
+SequenceNode = yaml.nodes.SequenceNode
+MappingNode = yaml.nodes.MappingNode
+
+cdef class Mark:
+ cdef readonly object name
+ cdef readonly int index
+ cdef readonly int line
+ cdef readonly int column
+ cdef readonly buffer
+ cdef readonly pointer
+
+ def __init__(self, object name, int index, int line, int column,
+ object buffer, object pointer):
+ self.name = name
+ self.index = index
+ self.line = line
+ self.column = column
+ self.buffer = buffer
+ self.pointer = pointer
+
+ def get_snippet(self):
+ return None
+
+ def __str__(self):
+ where = " in \"%s\", line %d, column %d" \
+ % (self.name, self.line+1, self.column+1)
+ return where
+
+#class YAMLError(Exception):
+# pass
+#
+#class MarkedYAMLError(YAMLError):
+#
+# def __init__(self, context=None, context_mark=None,
+# problem=None, problem_mark=None, note=None):
+# self.context = context
+# self.context_mark = context_mark
+# self.problem = problem
+# self.problem_mark = problem_mark
+# self.note = note
+#
+# def __str__(self):
+# lines = []
+# if self.context is not None:
+# lines.append(self.context)
+# if self.context_mark is not None \
+# and (self.problem is None or self.problem_mark is None
+# or self.context_mark.name != self.problem_mark.name
+# or self.context_mark.line != self.problem_mark.line
+# or self.context_mark.column != self.problem_mark.column):
+# lines.append(str(self.context_mark))
+# if self.problem is not None:
+# lines.append(self.problem)
+# if self.problem_mark is not None:
+# lines.append(str(self.problem_mark))
+# if self.note is not None:
+# lines.append(self.note)
+# return '\n'.join(lines)
+#
+#class ReaderError(YAMLError):
+#
+# def __init__(self, name, position, character, encoding, reason):
+# self.name = name
+# self.character = character
+# self.position = position
+# self.encoding = encoding
+# self.reason = reason
+#
+# def __str__(self):
+# if isinstance(self.character, str):
+# return "'%s' codec can't decode byte #x%02x: %s\n" \
+# " in \"%s\", position %d" \
+# % (self.encoding, ord(self.character), self.reason,
+# self.name, self.position)
+# else:
+# return "unacceptable character #x%04x: %s\n" \
+# " in \"%s\", position %d" \
+# % (ord(self.character), self.reason,
+# self.name, self.position)
+#
+#class ScannerError(MarkedYAMLError):
+# pass
+#
+#class ParserError(MarkedYAMLError):
+# pass
+#
+#class EmitterError(YAMLError):
+# pass
+#
+#cdef class Token:
+# cdef readonly Mark start_mark
+# cdef readonly Mark end_mark
+# def __init__(self, Mark start_mark, Mark end_mark):
+# self.start_mark = start_mark
+# self.end_mark = end_mark
+#
+#cdef class StreamStartToken(Token):
+# cdef readonly object encoding
+# def __init__(self, Mark start_mark, Mark end_mark, encoding):
+# self.start_mark = start_mark
+# self.end_mark = end_mark
+# self.encoding = encoding
+#
+#cdef class StreamEndToken(Token):
+# pass
+#
+#cdef class DirectiveToken(Token):
+# cdef readonly object name
+# cdef readonly object value
+# def __init__(self, name, value, Mark start_mark, Mark end_mark):
+# self.name = name
+# self.value = value
+# self.start_mark = start_mark
+# self.end_mark = end_mark
+#
+#cdef class DocumentStartToken(Token):
+# pass
+#
+#cdef class DocumentEndToken(Token):
+# pass
+#
+#cdef class BlockSequenceStartToken(Token):
+# pass
+#
+#cdef class BlockMappingStartToken(Token):
+# pass
+#
+#cdef class BlockEndToken(Token):
+# pass
+#
+#cdef class FlowSequenceStartToken(Token):
+# pass
+#
+#cdef class FlowMappingStartToken(Token):
+# pass
+#
+#cdef class FlowSequenceEndToken(Token):
+# pass
+#
+#cdef class FlowMappingEndToken(Token):
+# pass
+#
+#cdef class KeyToken(Token):
+# pass
+#
+#cdef class ValueToken(Token):
+# pass
+#
+#cdef class BlockEntryToken(Token):
+# pass
+#
+#cdef class FlowEntryToken(Token):
+# pass
+#
+#cdef class AliasToken(Token):
+# cdef readonly object value
+# def __init__(self, value, Mark start_mark, Mark end_mark):
+# self.value = value
+# self.start_mark = start_mark
+# self.end_mark = end_mark
+#
+#cdef class AnchorToken(Token):
+# cdef readonly object value
+# def __init__(self, value, Mark start_mark, Mark end_mark):
+# self.value = value
+# self.start_mark = start_mark
+# self.end_mark = end_mark
+#
+#cdef class TagToken(Token):
+# cdef readonly object value
+# def __init__(self, value, Mark start_mark, Mark end_mark):
+# self.value = value
+# self.start_mark = start_mark
+# self.end_mark = end_mark
+#
+#cdef class ScalarToken(Token):
+# cdef readonly object value
+# cdef readonly object plain
+# cdef readonly object style
+# def __init__(self, value, plain, Mark start_mark, Mark end_mark, style=None):
+# self.value = value
+# self.plain = plain
+# self.start_mark = start_mark
+# self.end_mark = end_mark
+# self.style = style
+
+cdef class CParser:
+
+ cdef yaml_parser_t parser
+ cdef yaml_event_t parsed_event
+
+ cdef object stream
+ cdef object stream_name
+ cdef object current_token
+ cdef object current_event
+ cdef object anchors
+
+ def __init__(self, stream):
+ if yaml_parser_initialize(&self.parser) == 0:
+ raise MemoryError
+ self.parsed_event.type = YAML_NO_EVENT
+ if hasattr(stream, 'read'):
+ self.stream = stream
+ try:
+ self.stream_name = stream.name
+ except AttributeError:
+ self.stream_name = '<file>'
+ yaml_parser_set_input(&self.parser, input_handler, <void *>self)
+ else:
+ if PyUnicode_CheckExact(stream) != 0:
+ stream = PyUnicode_AsUTF8String(stream)
+ self.stream_name = '<unicode string>'
+ else:
+ self.stream_name = '<string>'
+ if PyString_CheckExact(stream) == 0:
+ raise TypeError("a string or stream input is required")
+ self.stream = stream
+ yaml_parser_set_input_string(&self.parser, PyString_AS_STRING(stream), PyString_GET_SIZE(stream))
+ self.current_token = None
+ self.current_event = None
+ self.anchors = {}
+
+ def __dealloc__(self):
+ yaml_parser_delete(&self.parser)
+ yaml_event_delete(&self.parsed_event)
+
+ cdef object _parser_error(self):
+ if self.parser.error == YAML_MEMORY_ERROR:
+ raise MemoryError
+ elif self.parser.error == YAML_READER_ERROR:
+ raise ReaderError(self.stream_name, self.parser.problem_offset,
+ self.parser.problem_value, '?', self.parser.problem)
+ elif self.parser.error == YAML_SCANNER_ERROR \
+ or self.parser.error == YAML_PARSER_ERROR:
+ context_mark = None
+ problem_mark = None
+ if self.parser.context != NULL:
+ context_mark = Mark(self.stream_name,
+ self.parser.context_mark.index,
+ self.parser.context_mark.line,
+ self.parser.context_mark.column, None, None)
+ if self.parser.problem != NULL:
+ problem_mark = Mark(self.stream_name,
+ self.parser.problem_mark.index,
+ self.parser.problem_mark.line,
+ self.parser.problem_mark.column, None, None)
+ if self.parser.error == YAML_SCANNER_ERROR:
+ if self.parser.context != NULL:
+ return ScannerError(self.parser.context, context_mark,
+ self.parser.problem, problem_mark)
+ else:
+ return ScannerError(None, None,
+ self.parser.problem, problem_mark)
+ else:
+ if self.parser.context != NULL:
+ return ParserError(self.parser.context, context_mark,
+ self.parser.problem, problem_mark)
+ else:
+ return ParserError(None, None,
+ self.parser.problem, problem_mark)
+ raise ValueError("no parser error")
+
+ def raw_scan(self):
+ cdef yaml_token_t token
+ cdef int done
+ cdef int count
+ count = 0
+ done = 0
+ while done == 0:
+ if yaml_parser_scan(&self.parser, &token) == 0:
+ error = self._parser_error()
+ raise error
+ if token.type == YAML_NO_TOKEN:
+ done = 1
+ else:
+ count = count+1
+ yaml_token_delete(&token)
+ return count
+
+ cdef object _scan(self):
+ cdef yaml_token_t token
+ if yaml_parser_scan(&self.parser, &token) == 0:
+ error = self._parser_error()
+ raise error
+ token_object = self._token_to_object(&token)
+ yaml_token_delete(&token)
+ return token_object
+
+ cdef object _token_to_object(self, yaml_token_t *token):
+ start_mark = Mark(self.stream_name,
+ token.start_mark.index,
+ token.start_mark.line,
+ token.start_mark.column,
+ None, None)
+ end_mark = Mark(self.stream_name,
+ token.end_mark.index,
+ token.end_mark.line,
+ token.end_mark.column,
+ None, None)
+ if token.type == YAML_NO_TOKEN:
+ return None
+ elif token.type == YAML_STREAM_START_TOKEN:
+ encoding = None
+ if token.data.stream_start.encoding == YAML_UTF8_ENCODING:
+ encoding = "utf-8"
+ elif token.data.stream_start.encoding == YAML_UTF16LE_ENCODING:
+ encoding = "utf-16-le"
+ elif token.data.stream_start.encoding == YAML_UTF16BE_ENCODING:
+ encoding = "utf-16-be"
+ return StreamStartToken(start_mark, end_mark, encoding)
+ elif token.type == YAML_STREAM_END_TOKEN:
+ return StreamEndToken(start_mark, end_mark)
+ elif token.type == YAML_VERSION_DIRECTIVE_TOKEN:
+ return DirectiveToken("YAML",
+ (token.data.version_directive.major,
+ token.data.version_directive.minor),
+ start_mark, end_mark)
+ elif token.type == YAML_TAG_DIRECTIVE_TOKEN:
+ return DirectiveToken("TAG",
+ (token.data.tag_directive.handle,
+ token.data.tag_directive.prefix),
+ start_mark, end_mark)
+ elif token.type == YAML_DOCUMENT_START_TOKEN:
+ return DocumentStartToken(start_mark, end_mark)
+ elif token.type == YAML_DOCUMENT_END_TOKEN:
+ return DocumentEndToken(start_mark, end_mark)
+ elif token.type == YAML_BLOCK_SEQUENCE_START_TOKEN:
+ return BlockSequenceStartToken(start_mark, end_mark)
+ elif token.type == YAML_BLOCK_MAPPING_START_TOKEN:
+ return BlockMappingStartToken(start_mark, end_mark)
+ elif token.type == YAML_BLOCK_END_TOKEN:
+ return BlockEndToken(start_mark, end_mark)
+ elif token.type == YAML_FLOW_SEQUENCE_START_TOKEN:
+ return FlowSequenceStartToken(start_mark, end_mark)
+ elif token.type == YAML_FLOW_SEQUENCE_END_TOKEN:
+ return FlowSequenceEndToken(start_mark, end_mark)
+ elif token.type == YAML_FLOW_MAPPING_START_TOKEN:
+ return FlowMappingStartToken(start_mark, end_mark)
+ elif token.type == YAML_FLOW_MAPPING_END_TOKEN:
+ return FlowMappingEndToken(start_mark, end_mark)
+ elif token.type == YAML_BLOCK_ENTRY_TOKEN:
+ return BlockEntryToken(start_mark, end_mark)
+ elif token.type == YAML_FLOW_ENTRY_TOKEN:
+ return FlowEntryToken(start_mark, end_mark)
+ elif token.type == YAML_KEY_TOKEN:
+ return KeyToken(start_mark, end_mark)
+ elif token.type == YAML_VALUE_TOKEN:
+ return ValueToken(start_mark, end_mark)
+ elif token.type == YAML_ALIAS_TOKEN:
+ value = PyUnicode_DecodeUTF8(token.data.alias.value,
+ strlen(token.data.alias.value), 'strict')
+ return AliasToken(value, start_mark, end_mark)
+ elif token.type == YAML_ANCHOR_TOKEN:
+ value = PyUnicode_DecodeUTF8(token.data.anchor.value,
+ strlen(token.data.anchor.value), 'strict')
+ return AnchorToken(value, start_mark, end_mark)
+ elif token.type == YAML_TAG_TOKEN:
+ handle = PyUnicode_DecodeUTF8(token.data.tag.handle,
+ strlen(token.data.tag.handle), 'strict')
+ suffix = PyUnicode_DecodeUTF8(token.data.tag.suffix,
+ strlen(token.data.tag.suffix), 'strict')
+ if not handle:
+ handle = None
+ return TagToken((handle, suffix), start_mark, end_mark)
+ elif token.type == YAML_SCALAR_TOKEN:
+ value = PyUnicode_DecodeUTF8(token.data.scalar.value,
+ token.data.scalar.length, 'strict')
+ plain = False
+ style = None
+ if token.data.scalar.style == YAML_PLAIN_SCALAR_STYLE:
+ plain = True
+ style = ''
+ elif token.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE:
+ style = '\''
+ elif token.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE:
+ style = '"'
+ elif token.data.scalar.style == YAML_LITERAL_SCALAR_STYLE:
+ style = '|'
+ elif token.data.scalar.style == YAML_FOLDED_SCALAR_STYLE:
+ style = '>'
+ return ScalarToken(value, plain,
+ start_mark, end_mark, style)
+ else:
+ raise ValueError("unknown token type")
+
+ def get_token(self):
+ if self.current_token is not None:
+ value = self.current_token
+ self.current_token = None
+ else:
+ value = self._scan()
+ return value
+
+ def peek_token(self):
+ if self.current_token is None:
+ self.current_token = self._scan()
+ return self.current_token
+
+ def check_token(self, *choices):
+ if self.current_token is None:
+ self.current_token = self._scan()
+ if self.current_token is None:
+ return False
+ if not choices:
+ return True
+ token_class = self.current_token.__class__
+ for choice in choices:
+ if token_class is choice:
+ return True
+ return False
+
+ def raw_parse(self):
+ cdef yaml_event_t event
+ cdef int done
+ cdef int count
+ count = 0
+ done = 0
+ while done == 0:
+ if yaml_parser_parse(&self.parser, &event) == 0:
+ error = self._parser_error()
+ raise error
+ if event.type == YAML_NO_EVENT:
+ done = 1
+ else:
+ count = count+1
+ yaml_event_delete(&event)
+ return count
+
+ cdef object _parse(self):
+ cdef yaml_event_t event
+ if yaml_parser_parse(&self.parser, &event) == 0:
+ error = self._parser_error()
+ raise error
+ event_object = self._event_to_object(&event)
+ yaml_event_delete(&event)
+ return event_object
+
+ cdef object _event_to_object(self, yaml_event_t *event):
+ cdef yaml_tag_directive_t *tag_directive
+ start_mark = Mark(self.stream_name,
+ event.start_mark.index,
+ event.start_mark.line,
+ event.start_mark.column,
+ None, None)
+ end_mark = Mark(self.stream_name,
+ event.end_mark.index,
+ event.end_mark.line,
+ event.end_mark.column,
+ None, None)
+ if event.type == YAML_NO_EVENT:
+ return None
+ elif event.type == YAML_STREAM_START_EVENT:
+ encoding = None
+ if event.data.stream_start.encoding == YAML_UTF8_ENCODING:
+ encoding = "utf-8"
+ elif event.data.stream_start.encoding == YAML_UTF16LE_ENCODING:
+ encoding = "utf-16-le"
+ elif event.data.stream_start.encoding == YAML_UTF16BE_ENCODING:
+ encoding = "utf-16-be"
+ return StreamStartEvent(start_mark, end_mark, encoding)
+ elif event.type == YAML_STREAM_END_EVENT:
+ return StreamEndEvent(start_mark, end_mark)
+
+ elif event.type == YAML_DOCUMENT_START_EVENT:
+ explicit = False
+ if event.data.document_start.implicit == 0:
+ explicit = True
+ version = None
+ if event.data.document_start.version_directive != NULL:
+ version = (event.data.document_start.version_directive.major,
+ event.data.document_start.version_directive.minor)
+ tags = None
+ if event.data.document_start.tag_directives.start != NULL:
+ tags = {}
+ tag_directive = event.data.document_start.tag_directives.start
+ while tag_directive != event.data.document_start.tag_directives.end:
+ handle = PyUnicode_DecodeUTF8(tag_directive.handle,
+ strlen(tag_directive.handle), 'strict')
+ prefix = PyUnicode_DecodeUTF8(tag_directive.prefix,
+ strlen(tag_directive.prefix), 'strict')
+ tags[handle] = prefix
+ tag_directive = tag_directive+1
+ return DocumentStartEvent(start_mark, end_mark,
+ explicit, version, tags)
+ elif event.type == YAML_DOCUMENT_END_EVENT:
+ explicit = False
+ if event.data.document_end.implicit == 0:
+ explicit = True
+ return DocumentEndEvent(start_mark, end_mark, explicit)
+ elif event.type == YAML_ALIAS_EVENT:
+ anchor = PyUnicode_DecodeUTF8(event.data.alias.anchor,
+ strlen(event.data.alias.anchor), 'strict')
+ return AliasEvent(anchor, start_mark, end_mark)
+ elif event.type == YAML_SCALAR_EVENT:
+ anchor = None
+ if event.data.scalar.anchor != NULL:
+ anchor = PyUnicode_DecodeUTF8(event.data.scalar.anchor,
+ strlen(event.data.scalar.anchor), 'strict')
+ tag = None
+ if event.data.scalar.tag != NULL:
+ tag = PyUnicode_DecodeUTF8(event.data.scalar.tag,
+ strlen(event.data.scalar.tag), 'strict')
+ value = PyUnicode_DecodeUTF8(event.data.scalar.value,
+ event.data.scalar.length, 'strict')
+ plain_implicit = False
+ if event.data.scalar.plain_implicit == 1:
+ plain_implicit = True
+ quoted_implicit = False
+ if event.data.scalar.quoted_implicit == 1:
+ quoted_implicit = True
+ style = None
+ if event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE:
+ style = ''
+ elif event.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE:
+ style = '\''
+ elif event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE:
+ style = '"'
+ elif event.data.scalar.style == YAML_LITERAL_SCALAR_STYLE:
+ style = '|'
+ elif event.data.scalar.style == YAML_FOLDED_SCALAR_STYLE:
+ style = '>'
+ return ScalarEvent(anchor, tag,
+ (plain_implicit, quoted_implicit),
+ value, start_mark, end_mark, style)
+ elif event.type == YAML_SEQUENCE_START_EVENT:
+ anchor = None
+ if event.data.sequence_start.anchor != NULL:
+ anchor = PyUnicode_DecodeUTF8(event.data.sequence_start.anchor,
+ strlen(event.data.sequence_start.anchor), 'strict')
+ tag = None
+ if event.data.sequence_start.tag != NULL:
+ tag = PyUnicode_DecodeUTF8(event.data.sequence_start.tag,
+ strlen(event.data.sequence_start.tag), 'strict')
+ implicit = False
+ if event.data.sequence_start.implicit == 1:
+ implicit = True
+ flow_style = None
+ if event.data.sequence_start.style == YAML_FLOW_SEQUENCE_STYLE:
+ flow_style = True
+ elif event.data.sequence_start.style == YAML_BLOCK_SEQUENCE_STYLE:
+ flow_style = False
+ return SequenceStartEvent(anchor, tag, implicit,
+ start_mark, end_mark, flow_style)
+ elif event.type == YAML_MAPPING_START_EVENT:
+ anchor = None
+ if event.data.mapping_start.anchor != NULL:
+ anchor = PyUnicode_DecodeUTF8(event.data.mapping_start.anchor,
+ strlen(event.data.mapping_start.anchor), 'strict')
+ tag = None
+ if event.data.mapping_start.tag != NULL:
+ tag = PyUnicode_DecodeUTF8(event.data.mapping_start.tag,
+ strlen(event.data.mapping_start.tag), 'strict')
+ implicit = False
+ if event.data.mapping_start.implicit == 1:
+ implicit = True
+ flow_style = None
+ if event.data.mapping_start.style == YAML_FLOW_SEQUENCE_STYLE:
+ flow_style = True
+ elif event.data.mapping_start.style == YAML_BLOCK_SEQUENCE_STYLE:
+ flow_style = False
+ return MappingStartEvent(anchor, tag, implicit,
+ start_mark, end_mark, flow_style)
+ elif event.type == YAML_SEQUENCE_END_EVENT:
+ return SequenceEndEvent(start_mark, end_mark)
+ elif event.type == YAML_MAPPING_END_EVENT:
+ return MappingEndEvent(start_mark, end_mark)
+
+ else:
+ raise ValueError("unknown token type")
+
+ def get_event(self):
+ if self.current_event is not None:
+ value = self.current_event
+ self.current_event = None
+ else:
+ value = self._parse()
+ return value
+
+ def peek_event(self):
+ if self.current_event is None:
+ self.current_event = self._parse()
+ return self.current_event
+
+ def check_event(self, *choices):
+ if self.current_event is None:
+ self.current_event = self._parse()
+ if self.current_event is None:
+ return False
+ if not choices:
+ return True
+ event_class = self.current_event.__class__
+ for choice in choices:
+ if event_class is choice:
+ return True
+ return False
+
+ def check_node(self):
+ self._parse_next_event()
+ if self.parsed_event.type == YAML_STREAM_START_EVENT:
+ yaml_event_delete(&self.parsed_event)
+ self._parse_next_event()
+ if self.parsed_event.type != YAML_STREAM_END_EVENT:
+ return True
+ return False
+
+ def get_node(self):
+ self._parse_next_event()
+ if self.parsed_event.type != YAML_STREAM_END_EVENT:
+ return self._compose_document()
+
+ cdef object _compose_document(self):
+ yaml_event_delete(&self.parsed_event)
+ node = self._compose_node(None, None)
+ self._parse_next_event()
+ yaml_event_delete(&self.parsed_event)
+ self.anchors = {}
+ return node
+
+ cdef object _compose_node(self, object parent, object index):
+ self._parse_next_event()
+ if self.parsed_event.type == YAML_ALIAS_EVENT:
+ anchor = PyUnicode_DecodeUTF8(self.parsed_event.data.alias.anchor,
+ strlen(self.parsed_event.data.alias.anchor), 'strict')
+ if anchor not in self.anchors:
+ mark = Mark(self.stream_name,
+ self.parsed_event.start_mark.index,
+ self.parsed_event.start_mark.line,
+ self.parsed_event.start_mark.column,
+ None, None)
+ raise ComposerError(None, None, "found undefined alias", mark)
+ yaml_event_delete(&self.parsed_event)
+ return self.anchors[anchor]
+ anchor = None
+ if self.parsed_event.type == YAML_SCALAR_EVENT \
+ and self.parsed_event.data.scalar.anchor != NULL:
+ anchor = PyUnicode_DecodeUTF8(self.parsed_event.data.scalar.anchor,
+ strlen(self.parsed_event.data.scalar.anchor), 'strict')
+ elif self.parsed_event.type == YAML_SEQUENCE_START_EVENT \
+ and self.parsed_event.data.sequence_start.anchor != NULL:
+ anchor = PyUnicode_DecodeUTF8(self.parsed_event.data.sequence_start.anchor,
+ strlen(self.parsed_event.data.sequence_start.anchor), 'strict')
+ elif self.parsed_event.type == YAML_MAPPING_START_EVENT \
+ and self.parsed_event.data.mapping_start.anchor != NULL:
+ anchor = PyUnicode_DecodeUTF8(self.parsed_event.data.mapping_start.anchor,
+ strlen(self.parsed_event.data.mapping_start.anchor), 'strict')
+ if anchor is not None:
+ if anchor in self.anchors:
+ mark = Mark(self.stream_name,
+ self.parsed_event.start_mark.index,
+ self.parsed_event.start_mark.line,
+ self.parsed_event.start_mark.column,
+ None, None)
+ raise ComposerError("found duplicate anchor; first occurence",
+ self.anchors[anchor].start_mark, "second occurence", mark)
+ self.descend_resolver(parent, index)
+ if self.parsed_event.type == YAML_SCALAR_EVENT:
+ node = self._compose_scalar_node(anchor)
+ elif self.parsed_event.type == YAML_SEQUENCE_START_EVENT:
+ node = self._compose_sequence_node(anchor)
+ elif self.parsed_event.type == YAML_MAPPING_START_EVENT:
+ node = self._compose_mapping_node(anchor)
+ self.ascend_resolver()
+ return node
+
+ cdef _compose_scalar_node(self, object anchor):
+ start_mark = Mark(self.stream_name,
+ self.parsed_event.start_mark.index,
+ self.parsed_event.start_mark.line,
+ self.parsed_event.start_mark.column,
+ None, None)
+ end_mark = Mark(self.stream_name,
+ self.parsed_event.end_mark.index,
+ self.parsed_event.end_mark.line,
+ self.parsed_event.end_mark.column,
+ None, None)
+ value = PyUnicode_DecodeUTF8(self.parsed_event.data.scalar.value,
+ self.parsed_event.data.scalar.length, 'strict')
+ plain_implicit = False
+ if self.parsed_event.data.scalar.plain_implicit == 1:
+ plain_implicit = True
+ quoted_implicit = False
+ if self.parsed_event.data.scalar.quoted_implicit == 1:
+ quoted_implicit = True
+ if self.parsed_event.data.scalar.tag == NULL \
+ or (self.parsed_event.data.scalar.tag[0] == c'!'
+ and self.parsed_event.data.scalar.tag[1] == c'\0'):
+ tag = self.resolve(ScalarNode, value, (plain_implicit, quoted_implicit))
+ else:
+ tag = PyUnicode_DecodeUTF8(self.parsed_event.data.scalar.tag,
+ strlen(self.parsed_event.data.scalar.tag), 'strict')
+ style = None
+ if self.parsed_event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE:
+ style = ''
+ elif self.parsed_event.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE:
+ style = '\''
+ elif self.parsed_event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE:
+ style = '"'
+ elif self.parsed_event.data.scalar.style == YAML_LITERAL_SCALAR_STYLE:
+ style = '|'
+ elif self.parsed_event.data.scalar.style == YAML_FOLDED_SCALAR_STYLE:
+ style = '>'
+ node = ScalarNode(tag, value, start_mark, end_mark, style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ yaml_event_delete(&self.parsed_event)
+ return node
+
+ cdef _compose_sequence_node(self, object anchor):
+ cdef int index
+ start_mark = Mark(self.stream_name,
+ self.parsed_event.start_mark.index,
+ self.parsed_event.start_mark.line,
+ self.parsed_event.start_mark.column,
+ None, None)
+ implicit = False
+ if self.parsed_event.data.sequence_start.implicit == 1:
+ implicit = True
+ if self.parsed_event.data.sequence_start.tag == NULL \
+ or (self.parsed_event.data.sequence_start.tag[0] == c'!'
+ and self.parsed_event.data.sequence_start.tag[1] == c'\0'):
+ tag = self.resolve(SequenceNode, None, implicit)
+ else:
+ tag = PyUnicode_DecodeUTF8(self.parsed_event.data.sequence_start.tag,
+ strlen(self.parsed_event.data.sequence_start.tag), 'strict')
+ flow_style = None
+ if self.parsed_event.data.sequence_start.style == YAML_FLOW_SEQUENCE_STYLE:
+ flow_style = True
+ elif self.parsed_event.data.sequence_start.style == YAML_BLOCK_SEQUENCE_STYLE:
+ flow_style = False
+ value = []
+ node = SequenceNode(tag, value, start_mark, None, flow_style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ yaml_event_delete(&self.parsed_event)
+ index = 0
+ self._parse_next_event()
+ while self.parsed_event.type != YAML_SEQUENCE_END_EVENT:
+ value.append(self._compose_node(node, index))
+ index = index+1
+ self._parse_next_event()
+ node.end_mark = Mark(self.stream_name,
+ self.parsed_event.end_mark.index,
+ self.parsed_event.end_mark.line,
+ self.parsed_event.end_mark.column,
+ None, None)
+ yaml_event_delete(&self.parsed_event)
+ return node
+
+ cdef _compose_mapping_node(self, object anchor):
+ start_mark = Mark(self.stream_name,
+ self.parsed_event.start_mark.index,
+ self.parsed_event.start_mark.line,
+ self.parsed_event.start_mark.column,
+ None, None)
+ implicit = False
+ if self.parsed_event.data.mapping_start.implicit == 1:
+ implicit = True
+ if self.parsed_event.data.mapping_start.tag == NULL \
+ or (self.parsed_event.data.mapping_start.tag[0] == c'!'
+ and self.parsed_event.data.mapping_start.tag[1] == c'\0'):
+ tag = self.resolve(MappingNode, None, implicit)
+ else:
+ tag = PyUnicode_DecodeUTF8(self.parsed_event.data.mapping_start.tag,
+ strlen(self.parsed_event.data.mapping_start.tag), 'strict')
+ flow_style = None
+ if self.parsed_event.data.mapping_start.style == YAML_FLOW_MAPPING_STYLE:
+ flow_style = True
+ elif self.parsed_event.data.mapping_start.style == YAML_BLOCK_MAPPING_STYLE:
+ flow_style = False
+ value = []
+ node = MappingNode(tag, value, start_mark, None, flow_style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ yaml_event_delete(&self.parsed_event)
+ self._parse_next_event()
+ while self.parsed_event.type != YAML_MAPPING_END_EVENT:
+ item_key = self._compose_node(node, None)
+ item_value = self._compose_node(node, item_key)
+ value.append((item_key, item_value))
+ self._parse_next_event()
+ node.end_mark = Mark(self.stream_name,
+ self.parsed_event.end_mark.index,
+ self.parsed_event.end_mark.line,
+ self.parsed_event.end_mark.column,
+ None, None)
+ yaml_event_delete(&self.parsed_event)
+ return node
+
+ cdef int _parse_next_event(self) except 0:
+ if self.parsed_event.type == YAML_NO_EVENT:
+ if yaml_parser_parse(&self.parser, &self.parsed_event) == 0:
+ error = self._parser_error()
+ raise error
+ return 1
+
+cdef int input_handler(void *data, char *buffer, int size, int *read) except 0:
+ cdef CParser parser
+ parser = <CParser>data
+ value = parser.stream.read(size)
+ if PyString_CheckExact(value) == 0:
+ raise TypeError("a string value is expected")
+ if PyString_GET_SIZE(value) > size:
+ raise ValueError("a string value it too long")
+ memcpy(buffer, PyString_AS_STRING(value), PyString_GET_SIZE(value))
+ read[0] = PyString_GET_SIZE(value)
+ return 1
+
+cdef class CEmitter:
+
+ cdef yaml_emitter_t emitter
+
+ cdef object stream
+
+ cdef yaml_encoding_t use_encoding
+ cdef int document_start_implicit
+ cdef int document_end_implicit
+ cdef object use_version
+ cdef object use_tags
+
+ cdef object serialized_nodes
+ cdef object anchors
+ cdef int last_alias_id
+ cdef int closed
+
+ def __init__(self, stream, canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None, encoding=None,
+ explicit_start=None, explicit_end=None, version=None, tags=None):
+ if yaml_emitter_initialize(&self.emitter) == 0:
+ raise MemoryError
+ self.stream = stream
+ yaml_emitter_set_output(&self.emitter, output_handler, <void *>self)
+ if canonical is not None:
+ yaml_emitter_set_canonical(&self.emitter, 1)
+ if indent is not None:
+ yaml_emitter_set_indent(&self.emitter, indent)
+ if width is not None:
+ yaml_emitter_set_width(&self.emitter, width)
+ if allow_unicode is not None:
+ yaml_emitter_set_unicode(&self.emitter, 1)
+ if line_break is not None:
+ if line_break == '\r':
+ yaml_emitter_set_break(&self.emitter, YAML_CR_BREAK)
+ elif line_break == '\n':
+ yaml_emitter_set_break(&self.emitter, YAML_LN_BREAK)
+ elif line_break == '\r\n':
+ yaml_emitter_set_break(&self.emitter, YAML_CRLN_BREAK)
+ if encoding == 'utf-16-le':
+ self.use_encoding = YAML_UTF16LE_ENCODING
+ elif encoding == 'utf-16-be':
+ self.use_encoding = YAML_UTF16BE_ENCODING
+ else:
+ self.use_encoding = YAML_UTF8_ENCODING
+ self.document_start_implicit = 1
+ if explicit_start:
+ self.document_start_implicit = 0
+ self.document_end_implicit = 1
+ if explicit_end:
+ self.document_end_implicit = 0
+ self.use_version = version
+ self.use_tags = tags
+ self.serialized_nodes = {}
+ self.anchors = {}
+ self.last_alias_id = 0
+ self.closed = -1
+
+ def __dealloc__(self):
+ yaml_emitter_delete(&self.emitter)
+
+ cdef object _emitter_error(self):
+ if self.emitter.error == YAML_MEMORY_ERROR:
+ return MemoryError
+ elif self.emitter.error == YAML_EMITTER_ERROR:
+ return EmitterError(self.emitter.problem)
+ raise ValueError("no emitter error")
+
+ cdef int _object_to_event(self, object event_object, yaml_event_t *event) except 0:
+ cdef yaml_encoding_t encoding
+ cdef yaml_version_directive_t version_directive_value
+ cdef yaml_version_directive_t *version_directive
+ cdef yaml_tag_directive_t tag_directives_value[128]
+ cdef yaml_tag_directive_t *tag_directives_start
+ cdef yaml_tag_directive_t *tag_directives_end
+ cdef int implicit
+ cdef int plain_implicit
+ cdef int quoted_implicit
+ cdef char *anchor
+ cdef char *tag
+ cdef char *value
+ cdef int length
+ cdef yaml_scalar_style_t scalar_style
+ cdef yaml_sequence_style_t sequence_style
+ cdef yaml_mapping_style_t mapping_style
+ event_class = event_object.__class__
+ if event_class is StreamStartEvent:
+ encoding = YAML_UTF8_ENCODING
+ if event_object.encoding == 'utf-16-le':
+ encoding = YAML_UTF16LE_ENCODING
+ elif event_object.encoding == 'utf-16-be':
+ encoding = YAML_UTF16BE_ENCODING
+ yaml_stream_start_event_initialize(event, encoding)
+ elif event_class is StreamEndEvent:
+ yaml_stream_end_event_initialize(event)
+ elif event_class is DocumentStartEvent:
+ version_directive = NULL
+ if event_object.version:
+ version_directive_value.major = event_object.version[0]
+ version_directive_value.minor = event_object.version[1]
+ version_directive = &version_directive_value
+ tag_directives_start = NULL
+ tag_directives_end = NULL
+ if event_object.tags:
+ if len(event_object.tags) > 128:
+ raise ValueError("too many tags")
+ tag_directives_start = tag_directives_value
+ tag_directives_end = tag_directives_value
+ cache = []
+ for handle in event_object.tags:
+ prefix = event_object.tags[handle]
+ if PyUnicode_CheckExact(handle):
+ handle = PyUnicode_AsUTF8String(handle)
+ cache.append(handle)
+ if not PyString_CheckExact(handle):
+ raise TypeError("tag handle must be a string")
+ tag_directives_end.handle = PyString_AS_STRING(handle)
+ if PyUnicode_CheckExact(prefix):
+ prefix = PyUnicode_AsUTF8String(prefix)
+ cache.append(prefix)
+ if not PyString_CheckExact(prefix):
+ raise TypeError("tag prefix must be a string")
+ tag_directives_end.prefix = PyString_AS_STRING(prefix)
+ tag_directives_end = tag_directives_end+1
+ implicit = 1
+ if event_object.explicit:
+ implicit = 0
+ if yaml_document_start_event_initialize(event, version_directive,
+ tag_directives_start, tag_directives_end, implicit) == 0:
+ raise MemoryError
+ elif event_class is DocumentEndEvent:
+ implicit = 1
+ if event_object.explicit:
+ implicit = 0
+ yaml_document_end_event_initialize(event, implicit)
+ elif event_class is AliasEvent:
+ anchor = NULL
+ anchor_object = event_object.anchor
+ if PyUnicode_CheckExact(anchor_object):
+ anchor_object = PyUnicode_AsUTF8String(anchor_object)
+ if not PyString_CheckExact(anchor_object):
+ raise TypeError("anchor must be a string")
+ anchor = PyString_AS_STRING(anchor_object)
+ if yaml_alias_event_initialize(event, anchor) == 0:
+ raise MemoryError
+ elif event_class is ScalarEvent:
+ anchor = NULL
+ anchor_object = event_object.anchor
+ if anchor_object is not None:
+ if PyUnicode_CheckExact(anchor_object):
+ anchor_object = PyUnicode_AsUTF8String(anchor_object)
+ if not PyString_CheckExact(anchor_object):
+ raise TypeError("anchor must be a string")
+ anchor = PyString_AS_STRING(anchor_object)
+ tag = NULL
+ tag_object = event_object.tag
+ if tag_object is not None:
+ if PyUnicode_CheckExact(tag_object):
+ tag_object = PyUnicode_AsUTF8String(tag_object)
+ if not PyString_CheckExact(tag_object):
+ raise TypeError("tag must be a string")
+ tag = PyString_AS_STRING(tag_object)
+ value_object = event_object.value
+ if PyUnicode_CheckExact(value_object):
+ value_object = PyUnicode_AsUTF8String(value_object)
+ if not PyString_CheckExact(value_object):
+ raise TypeError("value must be a string")
+ value = PyString_AS_STRING(value_object)
+ length = PyString_GET_SIZE(value_object)
+ plain_implicit = 0
+ quoted_implicit = 0
+ if event_object.implicit is not None:
+ plain_implicit = event_object.implicit[0]
+ quoted_implicit = event_object.implicit[1]
+ style_object = event_object.style
+ scalar_style = YAML_PLAIN_SCALAR_STYLE
+ if style_object == "'":
+ scalar_style = YAML_SINGLE_QUOTED_SCALAR_STYLE
+ elif style_object == "\"":
+ scalar_style = YAML_DOUBLE_QUOTED_SCALAR_STYLE
+ elif style_object == "|":
+ scalar_style = YAML_LITERAL_SCALAR_STYLE
+ elif style_object == ">":
+ scalar_style = YAML_FOLDED_SCALAR_STYLE
+ if yaml_scalar_event_initialize(event, anchor, tag, value, length,
+ plain_implicit, quoted_implicit, scalar_style) == 0:
+ raise MemoryError
+ elif event_class is SequenceStartEvent:
+ anchor = NULL
+ anchor_object = event_object.anchor
+ if anchor_object is not None:
+ if PyUnicode_CheckExact(anchor_object):
+ anchor_object = PyUnicode_AsUTF8String(anchor_object)
+ if not PyString_CheckExact(anchor_object):
+ raise TypeError("anchor must be a string")
+ anchor = PyString_AS_STRING(anchor_object)
+ tag = NULL
+ tag_object = event_object.tag
+ if tag_object is not None:
+ if PyUnicode_CheckExact(tag_object):
+ tag_object = PyUnicode_AsUTF8String(tag_object)
+ if not PyString_CheckExact(tag_object):
+ raise TypeError("tag must be a string")
+ tag = PyString_AS_STRING(tag_object)
+ implicit = 0
+ if event_object.implicit:
+ implicit = 1
+ sequence_style = YAML_BLOCK_SEQUENCE_STYLE
+ if event_object.flow_style:
+ sequence_style = YAML_FLOW_SEQUENCE_STYLE
+ if yaml_sequence_start_event_initialize(event, anchor, tag,
+ implicit, sequence_style) == 0:
+ raise MemoryError
+ elif event_class is MappingStartEvent:
+ anchor = NULL
+ anchor_object = event_object.anchor
+ if anchor_object is not None:
+ if PyUnicode_CheckExact(anchor_object):
+ anchor_object = PyUnicode_AsUTF8String(anchor_object)
+ if not PyString_CheckExact(anchor_object):
+ raise TypeError("anchor must be a string")
+ anchor = PyString_AS_STRING(anchor_object)
+ tag = NULL
+ tag_object = event_object.tag
+ if tag_object is not None:
+ if PyUnicode_CheckExact(tag_object):
+ tag_object = PyUnicode_AsUTF8String(tag_object)
+ if not PyString_CheckExact(tag_object):
+ raise TypeError("tag must be a string")
+ tag = PyString_AS_STRING(tag_object)
+ implicit = 0
+ if event_object.implicit:
+ implicit = 1
+ mapping_style = YAML_BLOCK_MAPPING_STYLE
+ if event_object.flow_style:
+ mapping_style = YAML_FLOW_MAPPING_STYLE
+ if yaml_mapping_start_event_initialize(event, anchor, tag,
+ implicit, mapping_style) == 0:
+ raise MemoryError
+ elif event_class is SequenceEndEvent:
+ yaml_sequence_end_event_initialize(event)
+ elif event_class is MappingEndEvent:
+ yaml_mapping_end_event_initialize(event)
+ else:
+ raise TypeError("invalid event %s" % event_object)
+ return 1
+
+ def emit(self, event_object):
+ cdef yaml_event_t event
+ self._object_to_event(event_object, &event)
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+
+ def open(self):
+ cdef yaml_event_t event
+ if self.closed == -1:
+ yaml_stream_start_event_initialize(&event, self.use_encoding)
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ self.closed = 0
+ elif self.closed == 1:
+ raise SerializerError("serializer is closed")
+ else:
+ raise SerializerError("serializer is already opened")
+
+ def close(self):
+ cdef yaml_event_t event
+ if self.closed == -1:
+ raise SerializerError("serializer is not opened")
+ elif self.closed == 0:
+ yaml_stream_end_event_initialize(&event)
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ self.closed = 1
+
+ def serialize(self, node):
+ cdef yaml_event_t event
+ cdef yaml_version_directive_t version_directive_value
+ cdef yaml_version_directive_t *version_directive
+ cdef yaml_tag_directive_t tag_directives_value[128]
+ cdef yaml_tag_directive_t *tag_directives_start
+ cdef yaml_tag_directive_t *tag_directives_end
+ if self.closed == -1:
+ raise SerializerError("serializer is not opened")
+ elif self.closed == 1:
+ raise SerializerError("serializer is closed")
+ cache = []
+ version_directive = NULL
+ if self.use_version:
+ version_directive_value.major = self.use_version[0]
+ version_directive_value.minor = self.use_version[1]
+ version_directive = &version_directive_value
+ tag_directives_start = NULL
+ tag_directives_end = NULL
+ if self.use_tags:
+ if len(self.use_tags) > 128:
+ raise ValueError("too many tags")
+ tag_directives_start = tag_directives_value
+ tag_directives_end = tag_directives_value
+ for handle in self.use_tags:
+ prefix = self.use_tags[handle]
+ if PyUnicode_CheckExact(handle):
+ handle = PyUnicode_AsUTF8String(handle)
+ cache.append(handle)
+ if not PyString_CheckExact(handle):
+ raise TypeError("tag handle must be a string")
+ tag_directives_end.handle = PyString_AS_STRING(handle)
+ if PyUnicode_CheckExact(prefix):
+ prefix = PyUnicode_AsUTF8String(prefix)
+ cache.append(prefix)
+ if not PyString_CheckExact(prefix):
+ raise TypeError("tag prefix must be a string")
+ tag_directives_end.prefix = PyString_AS_STRING(prefix)
+ tag_directives_end = tag_directives_end+1
+ if yaml_document_start_event_initialize(&event, version_directive,
+ tag_directives_start, tag_directives_end,
+ self.document_start_implicit) == 0:
+ raise MemoryError
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ self._anchor_node(node)
+ self._serialize_node(node, None, None)
+ yaml_document_end_event_initialize(&event, self.document_end_implicit)
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ self.serialized_nodes = {}
+ self.anchors = {}
+ self.last_alias_id = 0
+
+ cdef int _anchor_node(self, object node) except 0:
+ if node in self.anchors:
+ if self.anchors[node] is None:
+ self.last_alias_id = self.last_alias_id+1
+ self.anchors[node] = "id%03d" % self.last_alias_id
+ else:
+ self.anchors[node] = None
+ node_class = node.__class__
+ if node_class is SequenceNode:
+ for item in node.value:
+ self._anchor_node(item)
+ elif node_class is MappingNode:
+ for key, value in node.value:
+ self._anchor_node(key)
+ self._anchor_node(value)
+ return 1
+
+ cdef int _serialize_node(self, object node, object parent, object index) except 0:
+ cdef yaml_event_t event
+ cdef int implicit
+ cdef int plain_implicit
+ cdef int quoted_implicit
+ cdef char *anchor
+ cdef char *tag
+ cdef char *value
+ cdef int length
+ cdef int item_index
+ cdef yaml_scalar_style_t scalar_style
+ cdef yaml_sequence_style_t sequence_style
+ cdef yaml_mapping_style_t mapping_style
+ anchor_object = self.anchors[node]
+ anchor = NULL
+ if anchor_object is not None:
+ anchor = PyString_AS_STRING(anchor_object)
+ if node in self.serialized_nodes:
+ if yaml_alias_event_initialize(&event, anchor) == 0:
+ raise MemoryError
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ else:
+ node_class = node.__class__
+ self.serialized_nodes[node] = True
+ self.descend_resolver(parent, index)
+ if node_class is ScalarNode:
+ plain_implicit = 0
+ quoted_implicit = 0
+ tag_object = node.tag
+ if self.resolve(ScalarNode, node.value, (True, False)) == tag_object:
+ plain_implicit = 1
+ if self.resolve(ScalarNode, node.value, (False, True)) == tag_object:
+ quoted_implicit = 1
+ tag = NULL
+ if tag_object is not None:
+ if PyUnicode_CheckExact(tag_object):
+ tag_object = PyUnicode_AsUTF8String(tag_object)
+ if not PyString_CheckExact(tag_object):
+ raise TypeError("tag must be a string")
+ tag = PyString_AS_STRING(tag_object)
+ value_object = node.value
+ if PyUnicode_CheckExact(value_object):
+ value_object = PyUnicode_AsUTF8String(value_object)
+ if not PyString_CheckExact(value_object):
+ raise TypeError("value must be a string")
+ value = PyString_AS_STRING(value_object)
+ length = PyString_GET_SIZE(value_object)
+ style_object = node.style
+ scalar_style = YAML_PLAIN_SCALAR_STYLE
+ if style_object == "'":
+ scalar_style = YAML_SINGLE_QUOTED_SCALAR_STYLE
+ elif style_object == "\"":
+ scalar_style = YAML_DOUBLE_QUOTED_SCALAR_STYLE
+ elif style_object == "|":
+ scalar_style = YAML_LITERAL_SCALAR_STYLE
+ elif style_object == ">":
+ scalar_style = YAML_FOLDED_SCALAR_STYLE
+ if yaml_scalar_event_initialize(&event, anchor, tag, value, length,
+ plain_implicit, quoted_implicit, scalar_style) == 0:
+ raise MemoryError
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ elif node_class is SequenceNode:
+ implicit = 0
+ tag_object = node.tag
+ if self.resolve(SequenceNode, node.value, True) == tag_object:
+ implicit = 1
+ tag = NULL
+ if tag_object is not None:
+ if PyUnicode_CheckExact(tag_object):
+ tag_object = PyUnicode_AsUTF8String(tag_object)
+ if not PyString_CheckExact(tag_object):
+ raise TypeError("tag must be a string")
+ tag = PyString_AS_STRING(tag_object)
+ sequence_style = YAML_BLOCK_SEQUENCE_STYLE
+ if node.flow_style:
+ sequence_style = YAML_FLOW_SEQUENCE_STYLE
+ if yaml_sequence_start_event_initialize(&event, anchor, tag,
+ implicit, sequence_style) == 0:
+ raise MemoryError
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ item_index = 0
+ for item in node.value:
+ self._serialize_node(item, node, item_index)
+ item_index = item_index+1
+ yaml_sequence_end_event_initialize(&event)
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ elif node_class is MappingNode:
+ implicit = 0
+ tag_object = node.tag
+ if self.resolve(MappingNode, node.value, True) == tag_object:
+ implicit = 1
+ tag = NULL
+ if tag_object is not None:
+ if PyUnicode_CheckExact(tag_object):
+ tag_object = PyUnicode_AsUTF8String(tag_object)
+ if not PyString_CheckExact(tag_object):
+ raise TypeError("tag must be a string")
+ tag = PyString_AS_STRING(tag_object)
+ mapping_style = YAML_BLOCK_MAPPING_STYLE
+ if node.flow_style:
+ mapping_style = YAML_FLOW_MAPPING_STYLE
+ if yaml_mapping_start_event_initialize(&event, anchor, tag,
+ implicit, mapping_style) == 0:
+ raise MemoryError
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ for item_key, item_value in node.value:
+ self._serialize_node(item_key, node, None)
+ self._serialize_node(item_value, node, item_key)
+ yaml_mapping_end_event_initialize(&event)
+ if yaml_emitter_emit(&self.emitter, &event) == 0:
+ error = self._emitter_error()
+ raise error
+ return 1
+
+cdef int output_handler(void *data, char *buffer, int size) except 0:
+ cdef CEmitter emitter
+ emitter = <CEmitter>data
+ value = PyString_FromStringAndSize(buffer, size)
+ emitter.stream.write(value)
+ return 1
+
diff --git a/google_appengine/lib/yaml/lib/yaml/__init__.py b/google_appengine/lib/yaml/lib/yaml/__init__.py
new file mode 100755
index 0000000..bd233a8
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/__init__.py
@@ -0,0 +1,290 @@
+
+from error import *
+
+from tokens import *
+from events import *
+from nodes import *
+
+from loader import *
+from dumper import *
+
+try:
+ from cyaml import *
+except ImportError:
+ pass
+
+def scan(stream, Loader=Loader):
+ """
+ Scan a YAML stream and produce scanning tokens.
+ """
+ loader = Loader(stream)
+ while loader.check_token():
+ yield loader.get_token()
+
+def parse(stream, Loader=Loader):
+ """
+ Parse a YAML stream and produce parsing events.
+ """
+ loader = Loader(stream)
+ while loader.check_event():
+ yield loader.get_event()
+
+def compose(stream, Loader=Loader):
+ """
+ Parse the first YAML document in a stream
+ and produce the corresponding representation tree.
+ """
+ loader = Loader(stream)
+ if loader.check_node():
+ return loader.get_node()
+
+def compose_all(stream, Loader=Loader):
+ """
+ Parse all YAML documents in a stream
+ and produce corresponsing representation trees.
+ """
+ loader = Loader(stream)
+ while loader.check_node():
+ yield loader.get_node()
+
+def load_all(stream, Loader=Loader):
+ """
+ Parse all YAML documents in a stream
+ and produce corresponding Python objects.
+ """
+ loader = Loader(stream)
+ while loader.check_data():
+ yield loader.get_data()
+
+def load(stream, Loader=Loader):
+ """
+ Parse the first YAML document in a stream
+ and produce the corresponding Python object.
+ """
+ loader = Loader(stream)
+ if loader.check_data():
+ return loader.get_data()
+
+def safe_load_all(stream):
+ """
+ Parse all YAML documents in a stream
+ and produce corresponding Python objects.
+ Resolve only basic YAML tags.
+ """
+ return load_all(stream, SafeLoader)
+
+def safe_load(stream):
+ """
+ Parse the first YAML document in a stream
+ and produce the corresponding Python object.
+ Resolve only basic YAML tags.
+ """
+ return load(stream, SafeLoader)
+
+def emit(events, stream=None, Dumper=Dumper,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None):
+ """
+ Emit YAML parsing events into a stream.
+ If stream is None, return the produced string instead.
+ """
+ getvalue = None
+ if stream is None:
+ try:
+ from cStringIO import StringIO
+ except ImportError:
+ from StringIO import StringIO
+ stream = StringIO()
+ getvalue = stream.getvalue
+ dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break)
+ for event in events:
+ dumper.emit(event)
+ if getvalue:
+ return getvalue()
+
+def serialize_all(nodes, stream=None, Dumper=Dumper,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding='utf-8', explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ """
+ Serialize a sequence of representation trees into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ getvalue = None
+ if stream is None:
+ try:
+ from cStringIO import StringIO
+ except ImportError:
+ from StringIO import StringIO
+ stream = StringIO()
+ getvalue = stream.getvalue
+ dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break,
+ encoding=encoding, version=version, tags=tags,
+ explicit_start=explicit_start, explicit_end=explicit_end)
+ dumper.open()
+ for node in nodes:
+ dumper.serialize(node)
+ dumper.close()
+ if getvalue:
+ return getvalue()
+
+def serialize(node, stream=None, Dumper=Dumper, **kwds):
+ """
+ Serialize a representation tree into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ return serialize_all([node], stream, Dumper=Dumper, **kwds)
+
+def dump_all(documents, stream=None, Dumper=Dumper,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding='utf-8', explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ """
+ Serialize a sequence of Python objects into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ getvalue = None
+ if stream is None:
+ try:
+ from cStringIO import StringIO
+ except ImportError:
+ from StringIO import StringIO
+ stream = StringIO()
+ getvalue = stream.getvalue
+ dumper = Dumper(stream, default_style=default_style,
+ default_flow_style=default_flow_style,
+ canonical=canonical, indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break,
+ encoding=encoding, version=version, tags=tags,
+ explicit_start=explicit_start, explicit_end=explicit_end)
+ dumper.open()
+ for data in documents:
+ dumper.represent(data)
+ dumper.close()
+ if getvalue:
+ return getvalue()
+
+def dump(data, stream=None, Dumper=Dumper, **kwds):
+ """
+ Serialize a Python object into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ return dump_all([data], stream, Dumper=Dumper, **kwds)
+
+def safe_dump_all(documents, stream=None, **kwds):
+ """
+ Serialize a sequence of Python objects into a YAML stream.
+ Produce only basic YAML tags.
+ If stream is None, return the produced string instead.
+ """
+ return dump_all(documents, stream, Dumper=SafeDumper, **kwds)
+
+def safe_dump(data, stream=None, **kwds):
+ """
+ Serialize a Python object into a YAML stream.
+ Produce only basic YAML tags.
+ If stream is None, return the produced string instead.
+ """
+ return dump_all([data], stream, Dumper=SafeDumper, **kwds)
+
+def add_implicit_resolver(tag, regexp, first=None,
+ Loader=Loader, Dumper=Dumper):
+ """
+ Add an implicit scalar detector.
+ If an implicit scalar value matches the given regexp,
+ the corresponding tag is assigned to the scalar.
+ first is a sequence of possible initial characters or None.
+ """
+ Loader.add_implicit_resolver(tag, regexp, first)
+ Dumper.add_implicit_resolver(tag, regexp, first)
+
+def add_path_resolver(tag, path, kind=None, Loader=Loader, Dumper=Dumper):
+ """
+ Add a path based resolver for the given tag.
+ A path is a list of keys that forms a path
+ to a node in the representation tree.
+ Keys can be string values, integers, or None.
+ """
+ Loader.add_path_resolver(tag, path, kind)
+ Dumper.add_path_resolver(tag, path, kind)
+
+def add_constructor(tag, constructor, Loader=Loader):
+ """
+ Add a constructor for the given tag.
+ Constructor is a function that accepts a Loader instance
+ and a node object and produces the corresponding Python object.
+ """
+ Loader.add_constructor(tag, constructor)
+
+def add_multi_constructor(tag_prefix, multi_constructor, Loader=Loader):
+ """
+ Add a multi-constructor for the given tag prefix.
+ Multi-constructor is called for a node if its tag starts with tag_prefix.
+ Multi-constructor accepts a Loader instance, a tag suffix,
+ and a node object and produces the corresponding Python object.
+ """
+ Loader.add_multi_constructor(tag_prefix, multi_constructor)
+
+def add_representer(data_type, representer, Dumper=Dumper):
+ """
+ Add a representer for the given type.
+ Representer is a function accepting a Dumper instance
+ and an instance of the given data type
+ and producing the corresponding representation node.
+ """
+ Dumper.add_representer(data_type, representer)
+
+def add_multi_representer(data_type, multi_representer, Dumper=Dumper):
+ """
+ Add a representer for the given type.
+ Multi-representer is a function accepting a Dumper instance
+ and an instance of the given data type or subtype
+ and producing the corresponding representation node.
+ """
+ Dumper.add_multi_representer(data_type, multi_representer)
+
+class YAMLObjectMetaclass(type):
+ """
+ The metaclass for YAMLObject.
+ """
+ def __init__(cls, name, bases, kwds):
+ super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
+ if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
+ cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
+ cls.yaml_dumper.add_representer(cls, cls.to_yaml)
+
+class YAMLObject(object):
+ """
+ An object that can dump itself to a YAML stream
+ and load itself from a YAML stream.
+ """
+
+ __metaclass__ = YAMLObjectMetaclass
+ __slots__ = () # no direct instantiation, so allow immutable subclasses
+
+ yaml_loader = Loader
+ yaml_dumper = Dumper
+
+ yaml_tag = None
+ yaml_flow_style = None
+
+ def from_yaml(cls, loader, node):
+ """
+ Convert a representation node to a Python object.
+ """
+ return loader.construct_yaml_object(node, cls)
+ from_yaml = classmethod(from_yaml)
+
+ def to_yaml(cls, dumper, data):
+ """
+ Convert a Python object to a representation node.
+ """
+ return dumper.represent_yaml_object(cls.yaml_tag, data, cls,
+ flow_style=cls.yaml_flow_style)
+ to_yaml = classmethod(to_yaml)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/__init__.pyc b/google_appengine/lib/yaml/lib/yaml/__init__.pyc
new file mode 100644
index 0000000..6d712fa
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/__init__.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/composer.py b/google_appengine/lib/yaml/lib/yaml/composer.py
new file mode 100755
index 0000000..9f5cd87
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/composer.py
@@ -0,0 +1,118 @@
+
+__all__ = ['Composer', 'ComposerError']
+
+from error import MarkedYAMLError
+from events import *
+from nodes import *
+
+class ComposerError(MarkedYAMLError):
+ pass
+
+class Composer(object):
+
+ def __init__(self):
+ self.anchors = {}
+
+ def check_node(self):
+ # Drop the STREAM-START event.
+ if self.check_event(StreamStartEvent):
+ self.get_event()
+
+ # If there are more documents available?
+ return not self.check_event(StreamEndEvent)
+
+ def get_node(self):
+ # Get the root node of the next document.
+ if not self.check_event(StreamEndEvent):
+ return self.compose_document()
+
+ def compose_document(self):
+ # Drop the DOCUMENT-START event.
+ self.get_event()
+
+ # Compose the root node.
+ node = self.compose_node(None, None)
+
+ # Drop the DOCUMENT-END event.
+ self.get_event()
+
+ self.anchors = {}
+ return node
+
+ def compose_node(self, parent, index):
+ if self.check_event(AliasEvent):
+ event = self.get_event()
+ anchor = event.anchor
+ if anchor not in self.anchors:
+ raise ComposerError(None, None, "found undefined alias %r"
+ % anchor.encode('utf-8'), event.start_mark)
+ return self.anchors[anchor]
+ event = self.peek_event()
+ anchor = event.anchor
+ if anchor is not None:
+ if anchor in self.anchors:
+ raise ComposerError("found duplicate anchor %r; first occurence"
+ % anchor.encode('utf-8'), self.anchors[anchor].start_mark,
+ "second occurence", event.start_mark)
+ self.descend_resolver(parent, index)
+ if self.check_event(ScalarEvent):
+ node = self.compose_scalar_node(anchor)
+ elif self.check_event(SequenceStartEvent):
+ node = self.compose_sequence_node(anchor)
+ elif self.check_event(MappingStartEvent):
+ node = self.compose_mapping_node(anchor)
+ self.ascend_resolver()
+ return node
+
+ def compose_scalar_node(self, anchor):
+ event = self.get_event()
+ tag = event.tag
+ if tag is None or tag == u'!':
+ tag = self.resolve(ScalarNode, event.value, event.implicit)
+ node = ScalarNode(tag, event.value,
+ event.start_mark, event.end_mark, style=event.style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ return node
+
+ def compose_sequence_node(self, anchor):
+ start_event = self.get_event()
+ tag = start_event.tag
+ if tag is None or tag == u'!':
+ tag = self.resolve(SequenceNode, None, start_event.implicit)
+ node = SequenceNode(tag, [],
+ start_event.start_mark, None,
+ flow_style=start_event.flow_style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ index = 0
+ while not self.check_event(SequenceEndEvent):
+ node.value.append(self.compose_node(node, index))
+ index += 1
+ end_event = self.get_event()
+ node.end_mark = end_event.end_mark
+ return node
+
+ def compose_mapping_node(self, anchor):
+ start_event = self.get_event()
+ tag = start_event.tag
+ if tag is None or tag == u'!':
+ tag = self.resolve(MappingNode, None, start_event.implicit)
+ node = MappingNode(tag, [],
+ start_event.start_mark, None,
+ flow_style=start_event.flow_style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ while not self.check_event(MappingEndEvent):
+ #key_event = self.peek_event()
+ item_key = self.compose_node(node, None)
+ #if item_key in node.value:
+ # raise ComposerError("while composing a mapping", start_event.start_mark,
+ # "found duplicate key", key_event.start_mark)
+ item_value = self.compose_node(node, item_key)
+ #node.value[item_key] = item_value
+ node.value.append((item_key, item_value))
+ end_event = self.get_event()
+ node.end_mark = end_event.end_mark
+ return node
+
diff --git a/google_appengine/lib/yaml/lib/yaml/composer.pyc b/google_appengine/lib/yaml/lib/yaml/composer.pyc
new file mode 100644
index 0000000..09f3353
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/composer.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/constructor.py b/google_appengine/lib/yaml/lib/yaml/constructor.py
new file mode 100755
index 0000000..a1295c8
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/constructor.py
@@ -0,0 +1,675 @@
+
+__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
+ 'ConstructorError']
+
+from error import *
+from nodes import *
+
+import datetime
+
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+import binascii, re, sys, types
+
+class ConstructorError(MarkedYAMLError):
+ pass
+
+class BaseConstructor(object):
+
+ yaml_constructors = {}
+ yaml_multi_constructors = {}
+
+ def __init__(self):
+ self.constructed_objects = {}
+ self.recursive_objects = {}
+ self.state_generators = []
+ self.deep_construct = False
+
+ def check_data(self):
+ # If there are more documents available?
+ return self.check_node()
+
+ def get_data(self):
+ # Construct and return the next document.
+ if self.check_node():
+ return self.construct_document(self.get_node())
+
+ def construct_document(self, node):
+ data = self.construct_object(node)
+ while self.state_generators:
+ state_generators = self.state_generators
+ self.state_generators = []
+ for generator in state_generators:
+ for dummy in generator:
+ pass
+ self.constructed_objects = {}
+ self.recursive_objects = {}
+ self.deep_construct = False
+ return data
+
+ def construct_object(self, node, deep=False):
+ if deep:
+ old_deep = self.deep_construct
+ self.deep_construct = True
+ if node in self.constructed_objects:
+ return self.constructed_objects[node]
+ if node in self.recursive_objects:
+ raise ConstructorError(None, None,
+ "found unconstructable recursive node", node.start_mark)
+ self.recursive_objects[node] = None
+ constructor = None
+ state_constructor = None
+ tag_suffix = None
+ if node.tag in self.yaml_constructors:
+ constructor = self.yaml_constructors[node.tag]
+ else:
+ for tag_prefix in self.yaml_multi_constructors:
+ if node.tag.startswith(tag_prefix):
+ tag_suffix = node.tag[len(tag_prefix):]
+ constructor = self.yaml_multi_constructors[tag_prefix]
+ break
+ else:
+ if None in self.yaml_multi_constructors:
+ tag_suffix = node.tag
+ constructor = self.yaml_multi_constructors[None]
+ elif None in self.yaml_constructors:
+ constructor = self.yaml_constructors[None]
+ elif isinstance(node, ScalarNode):
+ constructor = self.__class__.construct_scalar
+ elif isinstance(node, SequenceNode):
+ constructor = self.__class__.construct_sequence
+ elif isinstance(node, MappingNode):
+ constructor = self.__class__.construct_mapping
+ if tag_suffix is None:
+ data = constructor(self, node)
+ else:
+ data = constructor(self, tag_suffix, node)
+ if isinstance(data, types.GeneratorType):
+ generator = data
+ data = generator.next()
+ if self.deep_construct:
+ for dummy in generator:
+ pass
+ else:
+ self.state_generators.append(generator)
+ self.constructed_objects[node] = data
+ del self.recursive_objects[node]
+ if deep:
+ self.deep_construct = old_deep
+ return data
+
+ def construct_scalar(self, node):
+ if not isinstance(node, ScalarNode):
+ raise ConstructorError(None, None,
+ "expected a scalar node, but found %s" % node.id,
+ node.start_mark)
+ return node.value
+
+ def construct_sequence(self, node, deep=False):
+ if not isinstance(node, SequenceNode):
+ raise ConstructorError(None, None,
+ "expected a sequence node, but found %s" % node.id,
+ node.start_mark)
+ return [self.construct_object(child, deep=deep)
+ for child in node.value]
+
+ def construct_mapping(self, node, deep=False):
+ if not isinstance(node, MappingNode):
+ raise ConstructorError(None, None,
+ "expected a mapping node, but found %s" % node.id,
+ node.start_mark)
+ mapping = {}
+ for key_node, value_node in node.value:
+ key = self.construct_object(key_node, deep=deep)
+ try:
+ hash(key)
+ except TypeError, exc:
+ raise ConstructorError("while constructing a mapping", node.start_mark,
+ "found unacceptable key (%s)" % exc, key_node.start_mark)
+ value = self.construct_object(value_node, deep=deep)
+ mapping[key] = value
+ return mapping
+
+ def construct_pairs(self, node, deep=False):
+ if not isinstance(node, MappingNode):
+ raise ConstructorError(None, None,
+ "expected a mapping node, but found %s" % node.id,
+ node.start_mark)
+ pairs = []
+ for key_node, value_node in node.value:
+ key = self.construct_object(key_node, deep=deep)
+ value = self.construct_object(value_node, deep=deep)
+ pairs.append((key, value))
+ return pairs
+
+ def add_constructor(cls, tag, constructor):
+ if not 'yaml_constructors' in cls.__dict__:
+ cls.yaml_constructors = cls.yaml_constructors.copy()
+ cls.yaml_constructors[tag] = constructor
+ add_constructor = classmethod(add_constructor)
+
+ def add_multi_constructor(cls, tag_prefix, multi_constructor):
+ if not 'yaml_multi_constructors' in cls.__dict__:
+ cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
+ cls.yaml_multi_constructors[tag_prefix] = multi_constructor
+ add_multi_constructor = classmethod(add_multi_constructor)
+
+class SafeConstructor(BaseConstructor):
+
+ def construct_scalar(self, node):
+ if isinstance(node, MappingNode):
+ for key_node, value_node in node.value:
+ if key_node.tag == u'tag:yaml.org,2002:value':
+ return self.construct_scalar(value_node)
+ return BaseConstructor.construct_scalar(self, node)
+
+ def flatten_mapping(self, node):
+ merge = []
+ index = 0
+ while index < len(node.value):
+ key_node, value_node = node.value[index]
+ if key_node.tag == u'tag:yaml.org,2002:merge':
+ del node.value[index]
+ if isinstance(value_node, MappingNode):
+ self.flatten_mapping(value_node)
+ merge.extend(value_node.value)
+ elif isinstance(value_node, SequenceNode):
+ submerge = []
+ for subnode in value_node.value:
+ if not isinstance(subnode, MappingNode):
+ raise ConstructorError("while constructing a mapping",
+ node.start_mark,
+ "expected a mapping for merging, but found %s"
+ % subnode.id, subnode.start_mark)
+ self.flatten_mapping(subnode)
+ submerge.append(subnode.value)
+ submerge.reverse()
+ for value in submerge:
+ merge.extend(value)
+ else:
+ raise ConstructorError("while constructing a mapping", node.start_mark,
+ "expected a mapping or list of mappings for merging, but found %s"
+ % value_node.id, value_node.start_mark)
+ elif key_node.tag == u'tag:yaml.org,2002:value':
+ key_node.tag = u'tag:yaml.org,2002:str'
+ index += 1
+ else:
+ index += 1
+ if merge:
+ node.value = merge + node.value
+
+ def construct_mapping(self, node, deep=False):
+ if isinstance(node, MappingNode):
+ self.flatten_mapping(node)
+ return BaseConstructor.construct_mapping(self, node, deep=deep)
+
+ def construct_yaml_null(self, node):
+ self.construct_scalar(node)
+ return None
+
+ bool_values = {
+ u'yes': True,
+ u'no': False,
+ u'true': True,
+ u'false': False,
+ u'on': True,
+ u'off': False,
+ }
+
+ def construct_yaml_bool(self, node):
+ value = self.construct_scalar(node)
+ return self.bool_values[value.lower()]
+
+ def construct_yaml_int(self, node):
+ value = str(self.construct_scalar(node))
+ value = value.replace('_', '')
+ sign = +1
+ if value[0] == '-':
+ sign = -1
+ if value[0] in '+-':
+ value = value[1:]
+ if value == '0':
+ return 0
+ elif value.startswith('0b'):
+ return sign*int(value[2:], 2)
+ elif value.startswith('0x'):
+ return sign*int(value[2:], 16)
+ elif value[0] == '0':
+ return sign*int(value, 8)
+ elif ':' in value:
+ digits = [int(part) for part in value.split(':')]
+ digits.reverse()
+ base = 1
+ value = 0
+ for digit in digits:
+ value += digit*base
+ base *= 60
+ return sign*value
+ else:
+ return sign*int(value)
+
+ inf_value = 1e300
+ while inf_value != inf_value*inf_value:
+ inf_value *= inf_value
+ nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99).
+
+ def construct_yaml_float(self, node):
+ value = str(self.construct_scalar(node))
+ value = value.replace('_', '').lower()
+ sign = +1
+ if value[0] == '-':
+ sign = -1
+ if value[0] in '+-':
+ value = value[1:]
+ if value == '.inf':
+ return sign*self.inf_value
+ elif value == '.nan':
+ return self.nan_value
+ elif ':' in value:
+ digits = [float(part) for part in value.split(':')]
+ digits.reverse()
+ base = 1
+ value = 0.0
+ for digit in digits:
+ value += digit*base
+ base *= 60
+ return sign*value
+ else:
+ return sign*float(value)
+
+ def construct_yaml_binary(self, node):
+ value = self.construct_scalar(node)
+ try:
+ return str(value).decode('base64')
+ except (binascii.Error, UnicodeEncodeError), exc:
+ raise ConstructorError(None, None,
+ "failed to decode base64 data: %s" % exc, node.start_mark)
+
+ timestamp_regexp = re.compile(
+ ur'''^(?P<year>[0-9][0-9][0-9][0-9])
+ -(?P<month>[0-9][0-9]?)
+ -(?P<day>[0-9][0-9]?)
+ (?:(?:[Tt]|[ \t]+)
+ (?P<hour>[0-9][0-9]?)
+ :(?P<minute>[0-9][0-9])
+ :(?P<second>[0-9][0-9])
+ (?:\.(?P<fraction>[0-9]*))?
+ (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
+ (?::(?P<tz_minute>[0-9][0-9]))?))?)?$''', re.X)
+
+ def construct_yaml_timestamp(self, node):
+ value = self.construct_scalar(node)
+ match = self.timestamp_regexp.match(node.value)
+ values = match.groupdict()
+ year = int(values['year'])
+ month = int(values['month'])
+ day = int(values['day'])
+ if not values['hour']:
+ return datetime.date(year, month, day)
+ hour = int(values['hour'])
+ minute = int(values['minute'])
+ second = int(values['second'])
+ fraction = 0
+ if values['fraction']:
+ fraction = int(values['fraction'][:6].ljust(6, '0'))
+ delta = None
+ if values['tz_sign']:
+ tz_hour = int(values['tz_hour'])
+ tz_minute = int(values['tz_minute'] or 0)
+ delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute)
+ if values['tz_sign'] == '-':
+ delta = -delta
+ data = datetime.datetime(year, month, day, hour, minute, second, fraction)
+ if delta:
+ data -= delta
+ return data
+
+ def construct_yaml_omap(self, node):
+ # Note: we do not check for duplicate keys, because it's too
+ # CPU-expensive.
+ omap = []
+ yield omap
+ if not isinstance(node, SequenceNode):
+ raise ConstructorError("while constructing an ordered map", node.start_mark,
+ "expected a sequence, but found %s" % node.id, node.start_mark)
+ for subnode in node.value:
+ if not isinstance(subnode, MappingNode):
+ raise ConstructorError("while constructing an ordered map", node.start_mark,
+ "expected a mapping of length 1, but found %s" % subnode.id,
+ subnode.start_mark)
+ if len(subnode.value) != 1:
+ raise ConstructorError("while constructing an ordered map", node.start_mark,
+ "expected a single mapping item, but found %d items" % len(subnode.value),
+ subnode.start_mark)
+ key_node, value_node = subnode.value[0]
+ key = self.construct_object(key_node)
+ value = self.construct_object(value_node)
+ omap.append((key, value))
+
+ def construct_yaml_pairs(self, node):
+ # Note: the same code as `construct_yaml_omap`.
+ pairs = []
+ yield pairs
+ if not isinstance(node, SequenceNode):
+ raise ConstructorError("while constructing pairs", node.start_mark,
+ "expected a sequence, but found %s" % node.id, node.start_mark)
+ for subnode in node.value:
+ if not isinstance(subnode, MappingNode):
+ raise ConstructorError("while constructing pairs", node.start_mark,
+ "expected a mapping of length 1, but found %s" % subnode.id,
+ subnode.start_mark)
+ if len(subnode.value) != 1:
+ raise ConstructorError("while constructing pairs", node.start_mark,
+ "expected a single mapping item, but found %d items" % len(subnode.value),
+ subnode.start_mark)
+ key_node, value_node = subnode.value[0]
+ key = self.construct_object(key_node)
+ value = self.construct_object(value_node)
+ pairs.append((key, value))
+
+ def construct_yaml_set(self, node):
+ data = set()
+ yield data
+ value = self.construct_mapping(node)
+ data.update(value)
+
+ def construct_yaml_str(self, node):
+ value = self.construct_scalar(node)
+ try:
+ return value.encode('ascii')
+ except UnicodeEncodeError:
+ return value
+
+ def construct_yaml_seq(self, node):
+ data = []
+ yield data
+ data.extend(self.construct_sequence(node))
+
+ def construct_yaml_map(self, node):
+ data = {}
+ yield data
+ value = self.construct_mapping(node)
+ data.update(value)
+
+ def construct_yaml_object(self, node, cls):
+ data = cls.__new__(cls)
+ yield data
+ if hasattr(data, '__setstate__'):
+ state = self.construct_mapping(node, deep=True)
+ data.__setstate__(state)
+ else:
+ state = self.construct_mapping(node)
+ data.__dict__.update(state)
+
+ def construct_undefined(self, node):
+ raise ConstructorError(None, None,
+ "could not determine a constructor for the tag %r" % node.tag.encode('utf-8'),
+ node.start_mark)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:null',
+ SafeConstructor.construct_yaml_null)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:bool',
+ SafeConstructor.construct_yaml_bool)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:int',
+ SafeConstructor.construct_yaml_int)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:float',
+ SafeConstructor.construct_yaml_float)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:binary',
+ SafeConstructor.construct_yaml_binary)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:timestamp',
+ SafeConstructor.construct_yaml_timestamp)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:omap',
+ SafeConstructor.construct_yaml_omap)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:pairs',
+ SafeConstructor.construct_yaml_pairs)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:set',
+ SafeConstructor.construct_yaml_set)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:str',
+ SafeConstructor.construct_yaml_str)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:seq',
+ SafeConstructor.construct_yaml_seq)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:map',
+ SafeConstructor.construct_yaml_map)
+
+SafeConstructor.add_constructor(None,
+ SafeConstructor.construct_undefined)
+
+class Constructor(SafeConstructor):
+
+ def construct_python_str(self, node):
+ return self.construct_scalar(node).encode('utf-8')
+
+ def construct_python_unicode(self, node):
+ return self.construct_scalar(node)
+
+ def construct_python_long(self, node):
+ return long(self.construct_yaml_int(node))
+
+ def construct_python_complex(self, node):
+ return complex(self.construct_scalar(node))
+
+ def construct_python_tuple(self, node):
+ return tuple(self.construct_sequence(node))
+
+ def find_python_module(self, name, mark):
+ if not name:
+ raise ConstructorError("while constructing a Python module", mark,
+ "expected non-empty name appended to the tag", mark)
+ try:
+ __import__(name)
+ except ImportError, exc:
+ raise ConstructorError("while constructing a Python module", mark,
+ "cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
+ return sys.modules[name]
+
+ def find_python_name(self, name, mark):
+ if not name:
+ raise ConstructorError("while constructing a Python object", mark,
+ "expected non-empty name appended to the tag", mark)
+ if u'.' in name:
+ # Python 2.4 only
+ #module_name, object_name = name.rsplit('.', 1)
+ items = name.split('.')
+ object_name = items.pop()
+ module_name = '.'.join(items)
+ else:
+ module_name = '__builtin__'
+ object_name = name
+ try:
+ __import__(module_name)
+ except ImportError, exc:
+ raise ConstructorError("while constructing a Python object", mark,
+ "cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
+ module = sys.modules[module_name]
+ if not hasattr(module, object_name):
+ raise ConstructorError("while constructing a Python object", mark,
+ "cannot find %r in the module %r" % (object_name.encode('utf-8'),
+ module.__name__), mark)
+ return getattr(module, object_name)
+
+ def construct_python_name(self, suffix, node):
+ value = self.construct_scalar(node)
+ if value:
+ raise ConstructorError("while constructing a Python name", node.start_mark,
+ "expected the empty value, but found %r" % value.encode('utf-8'),
+ node.start_mark)
+ return self.find_python_name(suffix, node.start_mark)
+
+ def construct_python_module(self, suffix, node):
+ value = self.construct_scalar(node)
+ if value:
+ raise ConstructorError("while constructing a Python module", node.start_mark,
+ "expected the empty value, but found %r" % value.encode('utf-8'),
+ node.start_mark)
+ return self.find_python_module(suffix, node.start_mark)
+
+ class classobj: pass
+
+ def make_python_instance(self, suffix, node,
+ args=None, kwds=None, newobj=False):
+ if not args:
+ args = []
+ if not kwds:
+ kwds = {}
+ cls = self.find_python_name(suffix, node.start_mark)
+ if newobj and isinstance(cls, type(self.classobj)) \
+ and not args and not kwds:
+ instance = self.classobj()
+ instance.__class__ = cls
+ return instance
+ elif newobj and isinstance(cls, type):
+ return cls.__new__(cls, *args, **kwds)
+ else:
+ return cls(*args, **kwds)
+
+ def set_python_instance_state(self, instance, state):
+ if hasattr(instance, '__setstate__'):
+ instance.__setstate__(state)
+ else:
+ slotstate = {}
+ if isinstance(state, tuple) and len(state) == 2:
+ state, slotstate = state
+ if hasattr(instance, '__dict__'):
+ instance.__dict__.update(state)
+ elif state:
+ slotstate.update(state)
+ for key, value in slotstate.items():
+ setattr(object, key, value)
+
+ def construct_python_object(self, suffix, node):
+ # Format:
+ # !!python/object:module.name { ... state ... }
+ instance = self.make_python_instance(suffix, node, newobj=True)
+ yield instance
+ deep = hasattr(instance, '__setstate__')
+ state = self.construct_mapping(node, deep=deep)
+ self.set_python_instance_state(instance, state)
+
+ def construct_python_object_apply(self, suffix, node, newobj=False):
+ # Format:
+ # !!python/object/apply # (or !!python/object/new)
+ # args: [ ... arguments ... ]
+ # kwds: { ... keywords ... }
+ # state: ... state ...
+ # listitems: [ ... listitems ... ]
+ # dictitems: { ... dictitems ... }
+ # or short format:
+ # !!python/object/apply [ ... arguments ... ]
+ # The difference between !!python/object/apply and !!python/object/new
+ # is how an object is created, check make_python_instance for details.
+ if isinstance(node, SequenceNode):
+ args = self.construct_sequence(node, deep=True)
+ kwds = {}
+ state = {}
+ listitems = []
+ dictitems = {}
+ else:
+ value = self.construct_mapping(node, deep=True)
+ args = value.get('args', [])
+ kwds = value.get('kwds', {})
+ state = value.get('state', {})
+ listitems = value.get('listitems', [])
+ dictitems = value.get('dictitems', {})
+ instance = self.make_python_instance(suffix, node, args, kwds, newobj)
+ if state:
+ self.set_python_instance_state(instance, state)
+ if listitems:
+ instance.extend(listitems)
+ if dictitems:
+ for key in dictitems:
+ instance[key] = dictitems[key]
+ return instance
+
+ def construct_python_object_new(self, suffix, node):
+ return self.construct_python_object_apply(suffix, node, newobj=True)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/none',
+ Constructor.construct_yaml_null)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/bool',
+ Constructor.construct_yaml_bool)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/str',
+ Constructor.construct_python_str)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/unicode',
+ Constructor.construct_python_unicode)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/int',
+ Constructor.construct_yaml_int)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/long',
+ Constructor.construct_python_long)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/float',
+ Constructor.construct_yaml_float)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/complex',
+ Constructor.construct_python_complex)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/list',
+ Constructor.construct_yaml_seq)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/tuple',
+ Constructor.construct_python_tuple)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/dict',
+ Constructor.construct_yaml_map)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/name:',
+ Constructor.construct_python_name)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/module:',
+ Constructor.construct_python_module)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/object:',
+ Constructor.construct_python_object)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/object/apply:',
+ Constructor.construct_python_object_apply)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/object/new:',
+ Constructor.construct_python_object_new)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/constructor.pyc b/google_appengine/lib/yaml/lib/yaml/constructor.pyc
new file mode 100644
index 0000000..81f8a18
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/constructor.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/cyaml.py b/google_appengine/lib/yaml/lib/yaml/cyaml.py
new file mode 100755
index 0000000..14acb07
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/cyaml.py
@@ -0,0 +1,85 @@
+
+__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader',
+ 'CBaseDumper', 'CSafeDumper', 'CDumper']
+
+from _yaml import CParser, CEmitter
+
+from constructor import *
+
+from serializer import *
+from representer import *
+
+from resolver import *
+
+class CBaseLoader(CParser, BaseConstructor, BaseResolver):
+
+ def __init__(self, stream):
+ CParser.__init__(self, stream)
+ BaseConstructor.__init__(self)
+ BaseResolver.__init__(self)
+
+class CSafeLoader(CParser, SafeConstructor, Resolver):
+
+ def __init__(self, stream):
+ CParser.__init__(self, stream)
+ SafeConstructor.__init__(self)
+ Resolver.__init__(self)
+
+class CLoader(CParser, Constructor, Resolver):
+
+ def __init__(self, stream):
+ CParser.__init__(self, stream)
+ Constructor.__init__(self)
+ Resolver.__init__(self)
+
+class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ CEmitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class CSafeDumper(CEmitter, SafeRepresenter, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ CEmitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ SafeRepresenter.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class CDumper(CEmitter, Serializer, Representer, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ CEmitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/cyaml.pyc b/google_appengine/lib/yaml/lib/yaml/cyaml.pyc
new file mode 100644
index 0000000..0a81c33
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/cyaml.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/dumper.py b/google_appengine/lib/yaml/lib/yaml/dumper.py
new file mode 100755
index 0000000..355c1e2
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/dumper.py
@@ -0,0 +1,62 @@
+
+__all__ = ['BaseDumper', 'SafeDumper', 'Dumper']
+
+from emitter import *
+from serializer import *
+from representer import *
+from resolver import *
+
+class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ Emitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_uncode=allow_unicode, line_break=line_break)
+ Serializer.__init__(self, encoding=encoding,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ Emitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break)
+ Serializer.__init__(self, encoding=encoding,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ SafeRepresenter.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class Dumper(Emitter, Serializer, Representer, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ Emitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break)
+ Serializer.__init__(self, encoding=encoding,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/dumper.pyc b/google_appengine/lib/yaml/lib/yaml/dumper.pyc
new file mode 100644
index 0000000..157ac1d
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/dumper.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/emitter.py b/google_appengine/lib/yaml/lib/yaml/emitter.py
new file mode 100755
index 0000000..d9d1bf8
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/emitter.py
@@ -0,0 +1,1163 @@
+
+# Emitter expects events obeying the following grammar:
+# stream ::= STREAM-START document* STREAM-END
+# document ::= DOCUMENT-START node DOCUMENT-END
+# node ::= SCALAR | sequence | mapping
+# sequence ::= SEQUENCE-START node* SEQUENCE-END
+# mapping ::= MAPPING-START (node node)* MAPPING-END
+
+__all__ = ['Emitter', 'EmitterError']
+
+from error import YAMLError
+from events import *
+
+import re
+
+class EmitterError(YAMLError):
+ pass
+
+class ScalarAnalysis(object):
+ def __init__(self, scalar, empty, multiline,
+ allow_flow_plain, allow_block_plain,
+ allow_single_quoted, allow_double_quoted,
+ allow_block):
+ self.scalar = scalar
+ self.empty = empty
+ self.multiline = multiline
+ self.allow_flow_plain = allow_flow_plain
+ self.allow_block_plain = allow_block_plain
+ self.allow_single_quoted = allow_single_quoted
+ self.allow_double_quoted = allow_double_quoted
+ self.allow_block = allow_block
+
+class Emitter(object):
+
+ DEFAULT_TAG_PREFIXES = {
+ u'!' : u'!',
+ u'tag:yaml.org,2002:' : u'!!',
+ }
+
+ def __init__(self, stream, canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None):
+
+ # The stream should have the methods `write` and possibly `flush`.
+ self.stream = stream
+
+ # Encoding can be overriden by STREAM-START.
+ self.encoding = None
+
+ # Emitter is a state machine with a stack of states to handle nested
+ # structures.
+ self.states = []
+ self.state = self.expect_stream_start
+
+ # Current event and the event queue.
+ self.events = []
+ self.event = None
+
+ # The current indentation level and the stack of previous indents.
+ self.indents = []
+ self.indent = None
+
+ # Flow level.
+ self.flow_level = 0
+
+ # Contexts.
+ self.root_context = False
+ self.sequence_context = False
+ self.mapping_context = False
+ self.simple_key_context = False
+
+ # Characteristics of the last emitted character:
+ # - current position.
+ # - is it a whitespace?
+ # - is it an indention character
+ # (indentation space, '-', '?', or ':')?
+ self.line = 0
+ self.column = 0
+ self.whitespace = True
+ self.indention = True
+
+ # Formatting details.
+ self.canonical = canonical
+ self.allow_unicode = allow_unicode
+ self.best_indent = 2
+ if indent and 1 < indent < 10:
+ self.best_indent = indent
+ self.best_width = 80
+ if width and width > self.best_indent*2:
+ self.best_width = width
+ self.best_line_break = u'\n'
+ if line_break in [u'\r', u'\n', u'\r\n']:
+ self.best_line_break = line_break
+
+ # Tag prefixes.
+ self.tag_prefixes = None
+
+ # Prepared anchor and tag.
+ self.prepared_anchor = None
+ self.prepared_tag = None
+
+ # Scalar analysis and style.
+ self.analysis = None
+ self.style = None
+
+ def emit(self, event):
+ self.events.append(event)
+ while not self.need_more_events():
+ self.event = self.events.pop(0)
+ self.state()
+ self.event = None
+
+ # In some cases, we wait for a few next events before emitting.
+
+ def need_more_events(self):
+ if not self.events:
+ return True
+ event = self.events[0]
+ if isinstance(event, DocumentStartEvent):
+ return self.need_events(1)
+ elif isinstance(event, SequenceStartEvent):
+ return self.need_events(2)
+ elif isinstance(event, MappingStartEvent):
+ return self.need_events(3)
+ else:
+ return False
+
+ def need_events(self, count):
+ level = 0
+ for event in self.events[1:]:
+ if isinstance(event, (DocumentStartEvent, CollectionStartEvent)):
+ level += 1
+ elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)):
+ level -= 1
+ elif isinstance(event, StreamEndEvent):
+ level = -1
+ if level < 0:
+ return False
+ return (len(self.events) < count+1)
+
+ def increase_indent(self, flow=False, indentless=False):
+ self.indents.append(self.indent)
+ if self.indent is None:
+ if flow:
+ self.indent = self.best_indent
+ else:
+ self.indent = 0
+ elif not indentless:
+ self.indent += self.best_indent
+
+ # States.
+
+ # Stream handlers.
+
+ def expect_stream_start(self):
+ if isinstance(self.event, StreamStartEvent):
+ if self.event.encoding:
+ self.encoding = self.event.encoding
+ self.write_stream_start()
+ self.state = self.expect_first_document_start
+ else:
+ raise EmitterError("expected StreamStartEvent, but got %s"
+ % self.event)
+
+ def expect_nothing(self):
+ raise EmitterError("expected nothing, but got %s" % self.event)
+
+ # Document handlers.
+
+ def expect_first_document_start(self):
+ return self.expect_document_start(first=True)
+
+ def expect_document_start(self, first=False):
+ if isinstance(self.event, DocumentStartEvent):
+ if self.event.version:
+ version_text = self.prepare_version(self.event.version)
+ self.write_version_directive(version_text)
+ self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy()
+ if self.event.tags:
+ handles = self.event.tags.keys()
+ handles.sort()
+ for handle in handles:
+ prefix = self.event.tags[handle]
+ self.tag_prefixes[prefix] = handle
+ handle_text = self.prepare_tag_handle(handle)
+ prefix_text = self.prepare_tag_prefix(prefix)
+ self.write_tag_directive(handle_text, prefix_text)
+ implicit = (first and not self.event.explicit and not self.canonical
+ and not self.event.version and not self.event.tags
+ and not self.check_empty_document())
+ if not implicit:
+ self.write_indent()
+ self.write_indicator(u'---', True)
+ if self.canonical:
+ self.write_indent()
+ self.state = self.expect_document_root
+ elif isinstance(self.event, StreamEndEvent):
+ self.write_stream_end()
+ self.state = self.expect_nothing
+ else:
+ raise EmitterError("expected DocumentStartEvent, but got %s"
+ % self.event)
+
+ def expect_document_end(self):
+ if isinstance(self.event, DocumentEndEvent):
+ self.write_indent()
+ if self.event.explicit:
+ self.write_indicator(u'...', True)
+ self.write_indent()
+ self.flush_stream()
+ self.state = self.expect_document_start
+ else:
+ raise EmitterError("expected DocumentEndEvent, but got %s"
+ % self.event)
+
+ def expect_document_root(self):
+ self.states.append(self.expect_document_end)
+ self.expect_node(root=True)
+
+ # Node handlers.
+
+ def expect_node(self, root=False, sequence=False, mapping=False,
+ simple_key=False):
+ self.root_context = root
+ self.sequence_context = sequence
+ self.mapping_context = mapping
+ self.simple_key_context = simple_key
+ if isinstance(self.event, AliasEvent):
+ self.expect_alias()
+ elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)):
+ self.process_anchor(u'&')
+ self.process_tag()
+ if isinstance(self.event, ScalarEvent):
+ self.expect_scalar()
+ elif isinstance(self.event, SequenceStartEvent):
+ if self.flow_level or self.canonical or self.event.flow_style \
+ or self.check_empty_sequence():
+ self.expect_flow_sequence()
+ else:
+ self.expect_block_sequence()
+ elif isinstance(self.event, MappingStartEvent):
+ if self.flow_level or self.canonical or self.event.flow_style \
+ or self.check_empty_mapping():
+ self.expect_flow_mapping()
+ else:
+ self.expect_block_mapping()
+ else:
+ raise EmitterError("expected NodeEvent, but got %s" % self.event)
+
+ def expect_alias(self):
+ if self.event.anchor is None:
+ raise EmitterError("anchor is not specified for alias")
+ self.process_anchor(u'*')
+ self.state = self.states.pop()
+
+ def expect_scalar(self):
+ self.increase_indent(flow=True)
+ self.process_scalar()
+ self.indent = self.indents.pop()
+ self.state = self.states.pop()
+
+ # Flow sequence handlers.
+
+ def expect_flow_sequence(self):
+ self.write_indicator(u'[', True, whitespace=True)
+ self.flow_level += 1
+ self.increase_indent(flow=True)
+ self.state = self.expect_first_flow_sequence_item
+
+ def expect_first_flow_sequence_item(self):
+ if isinstance(self.event, SequenceEndEvent):
+ self.indent = self.indents.pop()
+ self.flow_level -= 1
+ self.write_indicator(u']', False)
+ self.state = self.states.pop()
+ else:
+ if self.canonical or self.column > self.best_width:
+ self.write_indent()
+ self.states.append(self.expect_flow_sequence_item)
+ self.expect_node(sequence=True)
+
+ def expect_flow_sequence_item(self):
+ if isinstance(self.event, SequenceEndEvent):
+ self.indent = self.indents.pop()
+ self.flow_level -= 1
+ if self.canonical:
+ self.write_indicator(u',', False)
+ self.write_indent()
+ self.write_indicator(u']', False)
+ self.state = self.states.pop()
+ else:
+ self.write_indicator(u',', False)
+ if self.canonical or self.column > self.best_width:
+ self.write_indent()
+ self.states.append(self.expect_flow_sequence_item)
+ self.expect_node(sequence=True)
+
+ # Flow mapping handlers.
+
+ def expect_flow_mapping(self):
+ self.write_indicator(u'{', True, whitespace=True)
+ self.flow_level += 1
+ self.increase_indent(flow=True)
+ self.state = self.expect_first_flow_mapping_key
+
+ def expect_first_flow_mapping_key(self):
+ if isinstance(self.event, MappingEndEvent):
+ self.indent = self.indents.pop()
+ self.flow_level -= 1
+ self.write_indicator(u'}', False)
+ self.state = self.states.pop()
+ else:
+ if self.canonical or self.column > self.best_width:
+ self.write_indent()
+ if not self.canonical and self.check_simple_key():
+ self.states.append(self.expect_flow_mapping_simple_value)
+ self.expect_node(mapping=True, simple_key=True)
+ else:
+ self.write_indicator(u'?', True)
+ self.states.append(self.expect_flow_mapping_value)
+ self.expect_node(mapping=True)
+
+ def expect_flow_mapping_key(self):
+ if isinstance(self.event, MappingEndEvent):
+ self.indent = self.indents.pop()
+ self.flow_level -= 1
+ if self.canonical:
+ self.write_indicator(u',', False)
+ self.write_indent()
+ self.write_indicator(u'}', False)
+ self.state = self.states.pop()
+ else:
+ self.write_indicator(u',', False)
+ if self.canonical or self.column > self.best_width:
+ self.write_indent()
+ if not self.canonical and self.check_simple_key():
+ self.states.append(self.expect_flow_mapping_simple_value)
+ self.expect_node(mapping=True, simple_key=True)
+ else:
+ self.write_indicator(u'?', True)
+ self.states.append(self.expect_flow_mapping_value)
+ self.expect_node(mapping=True)
+
+ def expect_flow_mapping_simple_value(self):
+ self.write_indicator(u':', False)
+ self.states.append(self.expect_flow_mapping_key)
+ self.expect_node(mapping=True)
+
+ def expect_flow_mapping_value(self):
+ if self.canonical or self.column > self.best_width:
+ self.write_indent()
+ self.write_indicator(u':', True)
+ self.states.append(self.expect_flow_mapping_key)
+ self.expect_node(mapping=True)
+
+ # Block sequence handlers.
+
+ def expect_block_sequence(self):
+ indentless = (self.mapping_context and not self.indention)
+ self.increase_indent(flow=False, indentless=indentless)
+ self.state = self.expect_first_block_sequence_item
+
+ def expect_first_block_sequence_item(self):
+ return self.expect_block_sequence_item(first=True)
+
+ def expect_block_sequence_item(self, first=False):
+ if not first and isinstance(self.event, SequenceEndEvent):
+ self.indent = self.indents.pop()
+ self.state = self.states.pop()
+ else:
+ self.write_indent()
+ self.write_indicator(u'-', True, indention=True)
+ self.states.append(self.expect_block_sequence_item)
+ self.expect_node(sequence=True)
+
+ # Block mapping handlers.
+
+ def expect_block_mapping(self):
+ self.increase_indent(flow=False)
+ self.state = self.expect_first_block_mapping_key
+
+ def expect_first_block_mapping_key(self):
+ return self.expect_block_mapping_key(first=True)
+
+ def expect_block_mapping_key(self, first=False):
+ if not first and isinstance(self.event, MappingEndEvent):
+ self.indent = self.indents.pop()
+ self.state = self.states.pop()
+ else:
+ self.write_indent()
+ if self.check_simple_key():
+ self.states.append(self.expect_block_mapping_simple_value)
+ self.expect_node(mapping=True, simple_key=True)
+ else:
+ self.write_indicator(u'?', True, indention=True)
+ self.states.append(self.expect_block_mapping_value)
+ self.expect_node(mapping=True)
+
+ def expect_block_mapping_simple_value(self):
+ self.write_indicator(u':', False)
+ self.states.append(self.expect_block_mapping_key)
+ self.expect_node(mapping=True)
+
+ def expect_block_mapping_value(self):
+ self.write_indent()
+ self.write_indicator(u':', True, indention=True)
+ self.states.append(self.expect_block_mapping_key)
+ self.expect_node(mapping=True)
+
+ # Checkers.
+
+ def check_empty_sequence(self):
+ return (isinstance(self.event, SequenceStartEvent) and self.events
+ and isinstance(self.events[0], SequenceEndEvent))
+
+ def check_empty_mapping(self):
+ return (isinstance(self.event, MappingStartEvent) and self.events
+ and isinstance(self.events[0], MappingEndEvent))
+
+ def check_empty_document(self):
+ if not isinstance(self.event, DocumentStartEvent) or not self.events:
+ return False
+ event = self.events[0]
+ return (isinstance(event, ScalarEvent) and event.anchor is None
+ and event.tag is None and event.implicit and event.value == u'')
+
+ def check_simple_key(self):
+ length = 0
+ if isinstance(self.event, NodeEvent) and self.event.anchor is not None:
+ if self.prepared_anchor is None:
+ self.prepared_anchor = self.prepare_anchor(self.event.anchor)
+ length += len(self.prepared_anchor)
+ if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \
+ and self.event.tag is not None:
+ if self.prepared_tag is None:
+ self.prepared_tag = self.prepare_tag(self.event.tag)
+ length += len(self.prepared_tag)
+ if isinstance(self.event, ScalarEvent):
+ if self.analysis is None:
+ self.analysis = self.analyze_scalar(self.event.value)
+ length += len(self.analysis.scalar)
+ return (length < 128 and (isinstance(self.event, AliasEvent)
+ or (isinstance(self.event, ScalarEvent)
+ and not self.analysis.empty and not self.analysis.multiline)
+ or self.check_empty_sequence() or self.check_empty_mapping()))
+
+ # Anchor, Tag, and Scalar processors.
+
+ def process_anchor(self, indicator):
+ if self.event.anchor is None:
+ self.prepared_anchor = None
+ return
+ if self.prepared_anchor is None:
+ self.prepared_anchor = self.prepare_anchor(self.event.anchor)
+ if self.prepared_anchor:
+ self.write_indicator(indicator+self.prepared_anchor, True)
+ self.prepared_anchor = None
+
+ def process_tag(self):
+ tag = self.event.tag
+ if isinstance(self.event, ScalarEvent):
+ if self.style is None:
+ self.style = self.choose_scalar_style()
+ if ((not self.canonical or tag is None) and
+ ((self.style == '' and self.event.implicit[0])
+ or (self.style != '' and self.event.implicit[1]))):
+ self.prepared_tag = None
+ return
+ if self.event.implicit[0] and tag is None:
+ tag = u'!'
+ self.prepared_tag = None
+ else:
+ if (not self.canonical or tag is None) and self.event.implicit:
+ self.prepared_tag = None
+ return
+ if tag is None:
+ raise EmitterError("tag is not specified")
+ if self.prepared_tag is None:
+ self.prepared_tag = self.prepare_tag(tag)
+ if self.prepared_tag:
+ self.write_indicator(self.prepared_tag, True)
+ self.prepared_tag = None
+
+ def choose_scalar_style(self):
+ if self.analysis is None:
+ self.analysis = self.analyze_scalar(self.event.value)
+ if self.event.style == '"' or self.canonical:
+ return '"'
+ if not self.event.style and self.event.implicit[0]:
+ if (not (self.simple_key_context and
+ (self.analysis.empty or self.analysis.multiline))
+ and (self.flow_level and self.analysis.allow_flow_plain
+ or (not self.flow_level and self.analysis.allow_block_plain))):
+ return ''
+ if self.event.style and self.event.style in '|>':
+ if (not self.flow_level and not self.simple_key_context
+ and self.analysis.allow_block):
+ return self.event.style
+ if not self.event.style or self.event.style == '\'':
+ if (self.analysis.allow_single_quoted and
+ not (self.simple_key_context and self.analysis.multiline)):
+ return '\''
+ return '"'
+
+ def process_scalar(self):
+ if self.analysis is None:
+ self.analysis = self.analyze_scalar(self.event.value)
+ if self.style is None:
+ self.style = self.choose_scalar_style()
+ split = (not self.simple_key_context)
+ #if self.analysis.multiline and split \
+ # and (not self.style or self.style in '\'\"'):
+ # self.write_indent()
+ if self.style == '"':
+ self.write_double_quoted(self.analysis.scalar, split)
+ elif self.style == '\'':
+ self.write_single_quoted(self.analysis.scalar, split)
+ elif self.style == '>':
+ self.write_folded(self.analysis.scalar)
+ elif self.style == '|':
+ self.write_literal(self.analysis.scalar)
+ else:
+ self.write_plain(self.analysis.scalar, split)
+ self.analysis = None
+ self.style = None
+
+ # Analyzers.
+
+ def prepare_version(self, version):
+ major, minor = version
+ if major != 1:
+ raise EmitterError("unsupported YAML version: %d.%d" % (major, minor))
+ return u'%d.%d' % (major, minor)
+
+ def prepare_tag_handle(self, handle):
+ if not handle:
+ raise EmitterError("tag handle must not be empty")
+ if handle[0] != u'!' or handle[-1] != u'!':
+ raise EmitterError("tag handle must start and end with '!': %r"
+ % (handle.encode('utf-8')))
+ for ch in handle[1:-1]:
+ if not (u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-_'):
+ raise EmitterError("invalid character %r in the tag handle: %r"
+ % (ch.encode('utf-8'), handle.encode('utf-8')))
+ return handle
+
+ def prepare_tag_prefix(self, prefix):
+ if not prefix:
+ raise EmitterError("tag prefix must not be empty")
+ chunks = []
+ start = end = 0
+ if prefix[0] == u'!':
+ end = 1
+ while end < len(prefix):
+ ch = prefix[end]
+ if u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-;/?!:@&=+$,_.~*\'()[]':
+ end += 1
+ else:
+ if start < end:
+ chunks.append(prefix[start:end])
+ start = end = end+1
+ data = ch.encode('utf-8')
+ for ch in data:
+ chunks.append(u'%%%02X' % ord(ch))
+ if start < end:
+ chunks.append(prefix[start:end])
+ return u''.join(chunks)
+
+ def prepare_tag(self, tag):
+ if not tag:
+ raise EmitterError("tag must not be empty")
+ if tag == u'!':
+ return tag
+ handle = None
+ suffix = tag
+ for prefix in self.tag_prefixes:
+ if tag.startswith(prefix) \
+ and (prefix == u'!' or len(prefix) < len(tag)):
+ handle = self.tag_prefixes[prefix]
+ suffix = tag[len(prefix):]
+ chunks = []
+ start = end = 0
+ while end < len(suffix):
+ ch = suffix[end]
+ if u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-;/?:@&=+$,_.~*\'()[]' \
+ or (ch == u'!' and handle != u'!'):
+ end += 1
+ else:
+ if start < end:
+ chunks.append(suffix[start:end])
+ start = end = end+1
+ data = ch.encode('utf-8')
+ for ch in data:
+ chunks.append(u'%%%02X' % ord(ch))
+ if start < end:
+ chunks.append(suffix[start:end])
+ suffix_text = u''.join(chunks)
+ if handle:
+ return u'%s%s' % (handle, suffix_text)
+ else:
+ return u'!<%s>' % suffix_text
+
+ def prepare_anchor(self, anchor):
+ if not anchor:
+ raise EmitterError("anchor must not be empty")
+ for ch in anchor:
+ if not (u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-_'):
+ raise EmitterError("invalid character %r in the anchor: %r"
+ % (ch.encode('utf-8'), anchor.encode('utf-8')))
+ return anchor
+
+ def analyze_scalar(self, scalar):
+
+ # Empty scalar is a special case.
+ if not scalar:
+ return ScalarAnalysis(scalar=scalar, empty=True, multiline=False,
+ allow_flow_plain=False, allow_block_plain=True,
+ allow_single_quoted=True, allow_double_quoted=True,
+ allow_block=False)
+
+ # Indicators and special characters.
+ block_indicators = False
+ flow_indicators = False
+ line_breaks = False
+ special_characters = False
+
+ # Whitespaces.
+ inline_spaces = False # non-space space+ non-space
+ inline_breaks = False # non-space break+ non-space
+ leading_spaces = False # ^ space+ (non-space | $)
+ leading_breaks = False # ^ break+ (non-space | $)
+ trailing_spaces = False # (^ | non-space) space+ $
+ trailing_breaks = False # (^ | non-space) break+ $
+ inline_breaks_spaces = False # non-space break+ space+ non-space
+ mixed_breaks_spaces = False # anything else
+
+ # Check document indicators.
+ if scalar.startswith(u'---') or scalar.startswith(u'...'):
+ block_indicators = True
+ flow_indicators = True
+
+ # First character or preceded by a whitespace.
+ preceeded_by_space = True
+
+ # Last character or followed by a whitespace.
+ followed_by_space = (len(scalar) == 1 or
+ scalar[1] in u'\0 \t\r\n\x85\u2028\u2029')
+
+ # The current series of whitespaces contain plain spaces.
+ spaces = False
+
+ # The current series of whitespaces contain line breaks.
+ breaks = False
+
+ # The current series of whitespaces contain a space followed by a
+ # break.
+ mixed = False
+
+ # The current series of whitespaces start at the beginning of the
+ # scalar.
+ leading = False
+
+ index = 0
+ while index < len(scalar):
+ ch = scalar[index]
+
+ # Check for indicators.
+
+ if index == 0:
+ # Leading indicators are special characters.
+ if ch in u'#,[]{}&*!|>\'\"%@`':
+ flow_indicators = True
+ block_indicators = True
+ if ch in u'?:':
+ flow_indicators = True
+ if followed_by_space:
+ block_indicators = True
+ if ch == u'-' and followed_by_space:
+ flow_indicators = True
+ block_indicators = True
+ else:
+ # Some indicators cannot appear within a scalar as well.
+ if ch in u',?[]{}':
+ flow_indicators = True
+ if ch == u':':
+ flow_indicators = True
+ if followed_by_space:
+ block_indicators = True
+ if ch == u'#' and preceeded_by_space:
+ flow_indicators = True
+ block_indicators = True
+
+ # Check for line breaks, special, and unicode characters.
+
+ if ch in u'\n\x85\u2028\u2029':
+ line_breaks = True
+ if not (ch == u'\n' or u'\x20' <= ch <= u'\x7E'):
+ if (ch == u'\x85' or u'\xA0' <= ch <= u'\uD7FF'
+ or u'\uE000' <= ch <= u'\uFFFD') and ch != u'\uFEFF':
+ unicode_characters = True
+ if not self.allow_unicode:
+ special_characters = True
+ else:
+ special_characters = True
+
+ # Spaces, line breaks, and how they are mixed. State machine.
+
+ # Start or continue series of whitespaces.
+ if ch in u' \n\x85\u2028\u2029':
+ if spaces and breaks:
+ if ch != u' ': # break+ (space+ break+) => mixed
+ mixed = True
+ elif spaces:
+ if ch != u' ': # (space+ break+) => mixed
+ breaks = True
+ mixed = True
+ elif breaks:
+ if ch == u' ': # break+ space+
+ spaces = True
+ else:
+ leading = (index == 0)
+ if ch == u' ': # space+
+ spaces = True
+ else: # break+
+ breaks = True
+
+ # Series of whitespaces ended with a non-space.
+ elif spaces or breaks:
+ if leading:
+ if spaces and breaks:
+ mixed_breaks_spaces = True
+ elif spaces:
+ leading_spaces = True
+ elif breaks:
+ leading_breaks = True
+ else:
+ if mixed:
+ mixed_breaks_spaces = True
+ elif spaces and breaks:
+ inline_breaks_spaces = True
+ elif spaces:
+ inline_spaces = True
+ elif breaks:
+ inline_breaks = True
+ spaces = breaks = mixed = leading = False
+
+ # Series of whitespaces reach the end.
+ if (spaces or breaks) and (index == len(scalar)-1):
+ if spaces and breaks:
+ mixed_breaks_spaces = True
+ elif spaces:
+ trailing_spaces = True
+ if leading:
+ leading_spaces = True
+ elif breaks:
+ trailing_breaks = True
+ if leading:
+ leading_breaks = True
+ spaces = breaks = mixed = leading = False
+
+ # Prepare for the next character.
+ index += 1
+ preceeded_by_space = (ch in u'\0 \t\r\n\x85\u2028\u2029')
+ followed_by_space = (index+1 >= len(scalar) or
+ scalar[index+1] in u'\0 \t\r\n\x85\u2028\u2029')
+
+ # Let's decide what styles are allowed.
+ allow_flow_plain = True
+ allow_block_plain = True
+ allow_single_quoted = True
+ allow_double_quoted = True
+ allow_block = True
+
+ # Leading and trailing whitespace are bad for plain scalars. We also
+ # do not want to mess with leading whitespaces for block scalars.
+ if leading_spaces or leading_breaks or trailing_spaces:
+ allow_flow_plain = allow_block_plain = allow_block = False
+
+ # Trailing breaks are fine for block scalars, but unacceptable for
+ # plain scalars.
+ if trailing_breaks:
+ allow_flow_plain = allow_block_plain = False
+
+ # The combination of (space+ break+) is only acceptable for block
+ # scalars.
+ if inline_breaks_spaces:
+ allow_flow_plain = allow_block_plain = allow_single_quoted = False
+
+ # Mixed spaces and breaks, as well as special character are only
+ # allowed for double quoted scalars.
+ if mixed_breaks_spaces or special_characters:
+ allow_flow_plain = allow_block_plain = \
+ allow_single_quoted = allow_block = False
+
+ # We don't emit multiline plain scalars.
+ if line_breaks:
+ allow_flow_plain = allow_block_plain = False
+
+ # Flow indicators are forbidden for flow plain scalars.
+ if flow_indicators:
+ allow_flow_plain = False
+
+ # Block indicators are forbidden for block plain scalars.
+ if block_indicators:
+ allow_block_plain = False
+
+ return ScalarAnalysis(scalar=scalar,
+ empty=False, multiline=line_breaks,
+ allow_flow_plain=allow_flow_plain,
+ allow_block_plain=allow_block_plain,
+ allow_single_quoted=allow_single_quoted,
+ allow_double_quoted=allow_double_quoted,
+ allow_block=allow_block)
+
+ # Writers.
+
+ def flush_stream(self):
+ if hasattr(self.stream, 'flush'):
+ self.stream.flush()
+
+ def write_stream_start(self):
+ # Write BOM if needed.
+ if self.encoding and self.encoding.startswith('utf-16'):
+ self.stream.write(u'\xFF\xFE'.encode(self.encoding))
+
+ def write_stream_end(self):
+ self.flush_stream()
+
+ def write_indicator(self, indicator, need_whitespace,
+ whitespace=False, indention=False):
+ if self.whitespace or not need_whitespace:
+ data = indicator
+ else:
+ data = u' '+indicator
+ self.whitespace = whitespace
+ self.indention = self.indention and indention
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+
+ def write_indent(self):
+ indent = self.indent or 0
+ if not self.indention or self.column > indent \
+ or (self.column == indent and not self.whitespace):
+ self.write_line_break()
+ if self.column < indent:
+ self.whitespace = True
+ data = u' '*(indent-self.column)
+ self.column = indent
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+
+ def write_line_break(self, data=None):
+ if data is None:
+ data = self.best_line_break
+ self.whitespace = True
+ self.indention = True
+ self.line += 1
+ self.column = 0
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+
+ def write_version_directive(self, version_text):
+ data = u'%%YAML %s' % version_text
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ self.write_line_break()
+
+ def write_tag_directive(self, handle_text, prefix_text):
+ data = u'%%TAG %s %s' % (handle_text, prefix_text)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ self.write_line_break()
+
+ # Scalar streams.
+
+ def write_single_quoted(self, text, split=True):
+ self.write_indicator(u'\'', True)
+ spaces = False
+ breaks = False
+ start = end = 0
+ while end <= len(text):
+ ch = None
+ if end < len(text):
+ ch = text[end]
+ if spaces:
+ if ch is None or ch != u' ':
+ if start+1 == end and self.column > self.best_width and split \
+ and start != 0 and end != len(text):
+ self.write_indent()
+ else:
+ data = text[start:end]
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end
+ elif breaks:
+ if ch is None or ch not in u'\n\x85\u2028\u2029':
+ if text[start] == u'\n':
+ self.write_line_break()
+ for br in text[start:end]:
+ if br == u'\n':
+ self.write_line_break()
+ else:
+ self.write_line_break(br)
+ self.write_indent()
+ start = end
+ else:
+ if ch is None or ch in u' \n\x85\u2028\u2029' or ch == u'\'':
+ if start < end:
+ data = text[start:end]
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end
+ if ch == u'\'':
+ data = u'\'\''
+ self.column += 2
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end + 1
+ if ch is not None:
+ spaces = (ch == u' ')
+ breaks = (ch in u'\n\x85\u2028\u2029')
+ end += 1
+ self.write_indicator(u'\'', False)
+
+ ESCAPE_REPLACEMENTS = {
+ u'\0': u'0',
+ u'\x07': u'a',
+ u'\x08': u'b',
+ u'\x09': u't',
+ u'\x0A': u'n',
+ u'\x0B': u'v',
+ u'\x0C': u'f',
+ u'\x0D': u'r',
+ u'\x1B': u'e',
+ u'\"': u'\"',
+ u'\\': u'\\',
+ u'\x85': u'N',
+ u'\xA0': u'_',
+ u'\u2028': u'L',
+ u'\u2029': u'P',
+ }
+
+ def write_double_quoted(self, text, split=True):
+ self.write_indicator(u'"', True)
+ start = end = 0
+ while end <= len(text):
+ ch = None
+ if end < len(text):
+ ch = text[end]
+ if ch is None or ch in u'"\\\x85\u2028\u2029\uFEFF' \
+ or not (u'\x20' <= ch <= u'\x7E'
+ or (self.allow_unicode
+ and (u'\xA0' <= ch <= u'\uD7FF'
+ or u'\uE000' <= ch <= u'\uFFFD'))):
+ if start < end:
+ data = text[start:end]
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end
+ if ch is not None:
+ if ch in self.ESCAPE_REPLACEMENTS:
+ data = u'\\'+self.ESCAPE_REPLACEMENTS[ch]
+ elif ch <= u'\xFF':
+ data = u'\\x%02X' % ord(ch)
+ elif ch <= u'\uFFFF':
+ data = u'\\u%04X' % ord(ch)
+ else:
+ data = u'\\U%08X' % ord(ch)
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end+1
+ if 0 < end < len(text)-1 and (ch == u' ' or start >= end) \
+ and self.column+(end-start) > self.best_width and split:
+ data = text[start:end]+u'\\'
+ if start < end:
+ start = end
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ self.write_indent()
+ self.whitespace = False
+ self.indention = False
+ if text[start] == u' ':
+ data = u'\\'
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ end += 1
+ self.write_indicator(u'"', False)
+
+ def determine_chomp(self, text):
+ tail = text[-2:]
+ while len(tail) < 2:
+ tail = u' '+tail
+ if tail[-1] in u'\n\x85\u2028\u2029':
+ if tail[-2] in u'\n\x85\u2028\u2029':
+ return u'+'
+ else:
+ return u''
+ else:
+ return u'-'
+
+ def write_folded(self, text):
+ chomp = self.determine_chomp(text)
+ self.write_indicator(u'>'+chomp, True)
+ self.write_indent()
+ leading_space = False
+ spaces = False
+ breaks = False
+ start = end = 0
+ while end <= len(text):
+ ch = None
+ if end < len(text):
+ ch = text[end]
+ if breaks:
+ if ch is None or ch not in u'\n\x85\u2028\u2029':
+ if not leading_space and ch is not None and ch != u' ' \
+ and text[start] == u'\n':
+ self.write_line_break()
+ leading_space = (ch == u' ')
+ for br in text[start:end]:
+ if br == u'\n':
+ self.write_line_break()
+ else:
+ self.write_line_break(br)
+ if ch is not None:
+ self.write_indent()
+ start = end
+ elif spaces:
+ if ch != u' ':
+ if start+1 == end and self.column > self.best_width:
+ self.write_indent()
+ else:
+ data = text[start:end]
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end
+ else:
+ if ch is None or ch in u' \n\x85\u2028\u2029':
+ data = text[start:end]
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ if ch is None:
+ self.write_line_break()
+ start = end
+ if ch is not None:
+ breaks = (ch in u'\n\x85\u2028\u2029')
+ spaces = (ch == u' ')
+ end += 1
+
+ def write_literal(self, text):
+ chomp = self.determine_chomp(text)
+ self.write_indicator(u'|'+chomp, True)
+ self.write_indent()
+ breaks = False
+ start = end = 0
+ while end <= len(text):
+ ch = None
+ if end < len(text):
+ ch = text[end]
+ if breaks:
+ if ch is None or ch not in u'\n\x85\u2028\u2029':
+ for br in text[start:end]:
+ if br == u'\n':
+ self.write_line_break()
+ else:
+ self.write_line_break(br)
+ if ch is not None:
+ self.write_indent()
+ start = end
+ else:
+ if ch is None or ch in u'\n\x85\u2028\u2029':
+ data = text[start:end]
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ if ch is None:
+ self.write_line_break()
+ start = end
+ if ch is not None:
+ breaks = (ch in u'\n\x85\u2028\u2029')
+ end += 1
+
+ def write_plain(self, text, split=True):
+ if not text:
+ return
+ if not self.whitespace:
+ data = u' '
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ self.writespace = False
+ self.indention = False
+ spaces = False
+ breaks = False
+ start = end = 0
+ while end <= len(text):
+ ch = None
+ if end < len(text):
+ ch = text[end]
+ if spaces:
+ if ch != u' ':
+ if start+1 == end and self.column > self.best_width and split:
+ self.write_indent()
+ self.writespace = False
+ self.indention = False
+ else:
+ data = text[start:end]
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end
+ elif breaks:
+ if ch not in u'\n\x85\u2028\u2029':
+ if text[start] == u'\n':
+ self.write_line_break()
+ for br in text[start:end]:
+ if br == u'\n':
+ self.write_line_break()
+ else:
+ self.write_line_break(br)
+ self.write_indent()
+ self.whitespace = False
+ self.indention = False
+ start = end
+ else:
+ if ch is None or ch in u' \n\x85\u2028\u2029':
+ data = text[start:end]
+ self.column += len(data)
+ if self.encoding:
+ data = data.encode(self.encoding)
+ self.stream.write(data)
+ start = end
+ if ch is not None:
+ spaces = (ch == u' ')
+ breaks = (ch in u'\n\x85\u2028\u2029')
+ end += 1
+
diff --git a/google_appengine/lib/yaml/lib/yaml/emitter.pyc b/google_appengine/lib/yaml/lib/yaml/emitter.pyc
new file mode 100644
index 0000000..11b393c
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/emitter.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/error.py b/google_appengine/lib/yaml/lib/yaml/error.py
new file mode 100755
index 0000000..577686d
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/error.py
@@ -0,0 +1,75 @@
+
+__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError']
+
+class Mark(object):
+
+ def __init__(self, name, index, line, column, buffer, pointer):
+ self.name = name
+ self.index = index
+ self.line = line
+ self.column = column
+ self.buffer = buffer
+ self.pointer = pointer
+
+ def get_snippet(self, indent=4, max_length=75):
+ if self.buffer is None:
+ return None
+ head = ''
+ start = self.pointer
+ while start > 0 and self.buffer[start-1] not in u'\0\r\n\x85\u2028\u2029':
+ start -= 1
+ if self.pointer-start > max_length/2-1:
+ head = ' ... '
+ start += 5
+ break
+ tail = ''
+ end = self.pointer
+ while end < len(self.buffer) and self.buffer[end] not in u'\0\r\n\x85\u2028\u2029':
+ end += 1
+ if end-self.pointer > max_length/2-1:
+ tail = ' ... '
+ end -= 5
+ break
+ snippet = self.buffer[start:end].encode('utf-8')
+ return ' '*indent + head + snippet + tail + '\n' \
+ + ' '*(indent+self.pointer-start+len(head)) + '^'
+
+ def __str__(self):
+ snippet = self.get_snippet()
+ where = " in \"%s\", line %d, column %d" \
+ % (self.name, self.line+1, self.column+1)
+ if snippet is not None:
+ where += ":\n"+snippet
+ return where
+
+class YAMLError(Exception):
+ pass
+
+class MarkedYAMLError(YAMLError):
+
+ def __init__(self, context=None, context_mark=None,
+ problem=None, problem_mark=None, note=None):
+ self.context = context
+ self.context_mark = context_mark
+ self.problem = problem
+ self.problem_mark = problem_mark
+ self.note = note
+
+ def __str__(self):
+ lines = []
+ if self.context is not None:
+ lines.append(self.context)
+ if self.context_mark is not None \
+ and (self.problem is None or self.problem_mark is None
+ or self.context_mark.name != self.problem_mark.name
+ or self.context_mark.line != self.problem_mark.line
+ or self.context_mark.column != self.problem_mark.column):
+ lines.append(str(self.context_mark))
+ if self.problem is not None:
+ lines.append(self.problem)
+ if self.problem_mark is not None:
+ lines.append(str(self.problem_mark))
+ if self.note is not None:
+ lines.append(self.note)
+ return '\n'.join(lines)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/error.pyc b/google_appengine/lib/yaml/lib/yaml/error.pyc
new file mode 100644
index 0000000..48b4865
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/error.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/events.py b/google_appengine/lib/yaml/lib/yaml/events.py
new file mode 100755
index 0000000..f79ad38
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/events.py
@@ -0,0 +1,86 @@
+
+# Abstract classes.
+
+class Event(object):
+ def __init__(self, start_mark=None, end_mark=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ def __repr__(self):
+ attributes = [key for key in ['anchor', 'tag', 'implicit', 'value']
+ if hasattr(self, key)]
+ arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
+ for key in attributes])
+ return '%s(%s)' % (self.__class__.__name__, arguments)
+
+class NodeEvent(Event):
+ def __init__(self, anchor, start_mark=None, end_mark=None):
+ self.anchor = anchor
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+class CollectionStartEvent(NodeEvent):
+ def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None,
+ flow_style=None):
+ self.anchor = anchor
+ self.tag = tag
+ self.implicit = implicit
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.flow_style = flow_style
+
+class CollectionEndEvent(Event):
+ pass
+
+# Implementations.
+
+class StreamStartEvent(Event):
+ def __init__(self, start_mark=None, end_mark=None, encoding=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.encoding = encoding
+
+class StreamEndEvent(Event):
+ pass
+
+class DocumentStartEvent(Event):
+ def __init__(self, start_mark=None, end_mark=None,
+ explicit=None, version=None, tags=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.explicit = explicit
+ self.version = version
+ self.tags = tags
+
+class DocumentEndEvent(Event):
+ def __init__(self, start_mark=None, end_mark=None,
+ explicit=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.explicit = explicit
+
+class AliasEvent(NodeEvent):
+ pass
+
+class ScalarEvent(NodeEvent):
+ def __init__(self, anchor, tag, implicit, value,
+ start_mark=None, end_mark=None, style=None):
+ self.anchor = anchor
+ self.tag = tag
+ self.implicit = implicit
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.style = style
+
+class SequenceStartEvent(CollectionStartEvent):
+ pass
+
+class SequenceEndEvent(CollectionEndEvent):
+ pass
+
+class MappingStartEvent(CollectionStartEvent):
+ pass
+
+class MappingEndEvent(CollectionEndEvent):
+ pass
+
diff --git a/google_appengine/lib/yaml/lib/yaml/events.pyc b/google_appengine/lib/yaml/lib/yaml/events.pyc
new file mode 100644
index 0000000..d4d6662
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/events.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/loader.py b/google_appengine/lib/yaml/lib/yaml/loader.py
new file mode 100755
index 0000000..293ff46
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/loader.py
@@ -0,0 +1,40 @@
+
+__all__ = ['BaseLoader', 'SafeLoader', 'Loader']
+
+from reader import *
+from scanner import *
+from parser import *
+from composer import *
+from constructor import *
+from resolver import *
+
+class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver):
+
+ def __init__(self, stream):
+ Reader.__init__(self, stream)
+ Scanner.__init__(self)
+ Parser.__init__(self)
+ Composer.__init__(self)
+ BaseConstructor.__init__(self)
+ BaseResolver.__init__(self)
+
+class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):
+
+ def __init__(self, stream):
+ Reader.__init__(self, stream)
+ Scanner.__init__(self)
+ Parser.__init__(self)
+ Composer.__init__(self)
+ SafeConstructor.__init__(self)
+ Resolver.__init__(self)
+
+class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
+
+ def __init__(self, stream):
+ Reader.__init__(self, stream)
+ Scanner.__init__(self)
+ Parser.__init__(self)
+ Composer.__init__(self)
+ Constructor.__init__(self)
+ Resolver.__init__(self)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/loader.pyc b/google_appengine/lib/yaml/lib/yaml/loader.pyc
new file mode 100644
index 0000000..ac28aca
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/loader.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/nodes.py b/google_appengine/lib/yaml/lib/yaml/nodes.py
new file mode 100755
index 0000000..c4f070c
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/nodes.py
@@ -0,0 +1,49 @@
+
+class Node(object):
+ def __init__(self, tag, value, start_mark, end_mark):
+ self.tag = tag
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ def __repr__(self):
+ value = self.value
+ #if isinstance(value, list):
+ # if len(value) == 0:
+ # value = '<empty>'
+ # elif len(value) == 1:
+ # value = '<1 item>'
+ # else:
+ # value = '<%d items>' % len(value)
+ #else:
+ # if len(value) > 75:
+ # value = repr(value[:70]+u' ... ')
+ # else:
+ # value = repr(value)
+ value = repr(value)
+ return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value)
+
+class ScalarNode(Node):
+ id = 'scalar'
+ def __init__(self, tag, value,
+ start_mark=None, end_mark=None, style=None):
+ self.tag = tag
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.style = style
+
+class CollectionNode(Node):
+ def __init__(self, tag, value,
+ start_mark=None, end_mark=None, flow_style=None):
+ self.tag = tag
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.flow_style = flow_style
+
+class SequenceNode(CollectionNode):
+ id = 'sequence'
+
+class MappingNode(CollectionNode):
+ id = 'mapping'
+
diff --git a/google_appengine/lib/yaml/lib/yaml/nodes.pyc b/google_appengine/lib/yaml/lib/yaml/nodes.pyc
new file mode 100644
index 0000000..e6d2e1c
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/nodes.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/parser.py b/google_appengine/lib/yaml/lib/yaml/parser.py
new file mode 100755
index 0000000..a46bb9e
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/parser.py
@@ -0,0 +1,586 @@
+
+# The following YAML grammar is LL(1) and is parsed by a recursive descent
+# parser.
+#
+# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
+# implicit_document ::= block_node DOCUMENT-END*
+# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+# block_node_or_indentless_sequence ::=
+# ALIAS
+# | properties (block_content | indentless_block_sequence)?
+# | block_content
+# | indentless_block_sequence
+# block_node ::= ALIAS
+# | properties block_content?
+# | block_content
+# flow_node ::= ALIAS
+# | properties flow_content?
+# | flow_content
+# properties ::= TAG ANCHOR? | ANCHOR TAG?
+# block_content ::= block_collection | flow_collection | SCALAR
+# flow_content ::= flow_collection | SCALAR
+# block_collection ::= block_sequence | block_mapping
+# flow_collection ::= flow_sequence | flow_mapping
+# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
+# indentless_sequence ::= (BLOCK-ENTRY block_node?)+
+# block_mapping ::= BLOCK-MAPPING_START
+# ((KEY block_node_or_indentless_sequence?)?
+# (VALUE block_node_or_indentless_sequence?)?)*
+# BLOCK-END
+# flow_sequence ::= FLOW-SEQUENCE-START
+# (flow_sequence_entry FLOW-ENTRY)*
+# flow_sequence_entry?
+# FLOW-SEQUENCE-END
+# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+# flow_mapping ::= FLOW-MAPPING-START
+# (flow_mapping_entry FLOW-ENTRY)*
+# flow_mapping_entry?
+# FLOW-MAPPING-END
+# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+#
+# FIRST sets:
+#
+# stream: { STREAM-START }
+# explicit_document: { DIRECTIVE DOCUMENT-START }
+# implicit_document: FIRST(block_node)
+# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
+# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
+# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
+# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
+# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
+# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
+# block_sequence: { BLOCK-SEQUENCE-START }
+# block_mapping: { BLOCK-MAPPING-START }
+# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY }
+# indentless_sequence: { ENTRY }
+# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
+# flow_sequence: { FLOW-SEQUENCE-START }
+# flow_mapping: { FLOW-MAPPING-START }
+# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
+# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY }
+
+__all__ = ['Parser', 'ParserError']
+
+from error import MarkedYAMLError
+from tokens import *
+from events import *
+from scanner import *
+
+class ParserError(MarkedYAMLError):
+ pass
+
+class Parser(object):
+ # Since writing a recursive-descendant parser is a straightforward task, we
+ # do not give many comments here.
+ # Note that we use Python generators. If you rewrite the parser in another
+ # language, you may replace all 'yield'-s with event handler calls.
+
+ DEFAULT_TAGS = {
+ u'!': u'!',
+ u'!!': u'tag:yaml.org,2002:',
+ }
+
+ def __init__(self):
+ self.current_event = None
+ self.yaml_version = None
+ self.tag_handles = {}
+ self.states = []
+ self.marks = []
+ self.state = self.parse_stream_start
+
+ def check_event(self, *choices):
+ # Check the type of the next event.
+ if self.current_event is None:
+ if self.state:
+ self.current_event = self.state()
+ if self.current_event is not None:
+ if not choices:
+ return True
+ for choice in choices:
+ if isinstance(self.current_event, choice):
+ return True
+ return False
+
+ def peek_event(self):
+ # Get the next event.
+ if self.current_event is None:
+ if self.state:
+ self.current_event = self.state()
+ return self.current_event
+
+ def get_event(self):
+ # Get the next event and proceed further.
+ if self.current_event is None:
+ if self.state:
+ self.current_event = self.state()
+ value = self.current_event
+ self.current_event = None
+ return value
+
+ # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
+ # implicit_document ::= block_node DOCUMENT-END*
+ # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+
+ def parse_stream_start(self):
+
+ # Parse the stream start.
+ token = self.get_token()
+ event = StreamStartEvent(token.start_mark, token.end_mark,
+ encoding=token.encoding)
+
+ # Prepare the next state.
+ self.state = self.parse_implicit_document_start
+
+ return event
+
+ def parse_implicit_document_start(self):
+
+ # Parse an implicit document.
+ if not self.check_token(DirectiveToken, DocumentStartToken,
+ StreamEndToken):
+ self.tag_handles = self.DEFAULT_TAGS
+ token = self.peek_token()
+ start_mark = end_mark = token.start_mark
+ event = DocumentStartEvent(start_mark, end_mark,
+ explicit=False)
+
+ # Prepare the next state.
+ self.states.append(self.parse_document_end)
+ self.state = self.parse_block_node
+
+ return event
+
+ else:
+ return self.parse_document_start()
+
+ def parse_document_start(self):
+
+ # Parse any extra document end indicators.
+ while self.check_token(DocumentEndToken):
+ self.get_token()
+
+ # Parse an explicit document.
+ if not self.check_token(StreamEndToken):
+ token = self.peek_token()
+ start_mark = token.start_mark
+ version, tags = self.process_directives()
+ if not self.check_token(DocumentStartToken):
+ raise ParserError(None, None,
+ "expected '<document start>', but found %r"
+ % self.peek_token().id,
+ self.peek_token().start_mark)
+ token = self.get_token()
+ end_mark = token.end_mark
+ event = DocumentStartEvent(start_mark, end_mark,
+ explicit=True, version=version, tags=tags)
+ self.states.append(self.parse_document_end)
+ self.state = self.parse_document_content
+ else:
+ # Parse the end of the stream.
+ token = self.get_token()
+ event = StreamEndEvent(token.start_mark, token.end_mark)
+ assert not self.states
+ assert not self.marks
+ self.state = None
+ return event
+
+ def parse_document_end(self):
+
+ # Parse the document end.
+ token = self.peek_token()
+ start_mark = end_mark = token.start_mark
+ explicit = False
+ if self.check_token(DocumentEndToken):
+ token = self.get_token()
+ end_mark = token.end_mark
+ explicit = True
+ event = DocumentEndEvent(start_mark, end_mark,
+ explicit=explicit)
+
+ # Prepare the next state.
+ self.state = self.parse_document_start
+
+ return event
+
+ def parse_document_content(self):
+ if self.check_token(DirectiveToken,
+ DocumentStartToken, DocumentEndToken, StreamEndToken):
+ event = self.process_empty_scalar(self.peek_token().start_mark)
+ self.state = self.states.pop()
+ return event
+ else:
+ return self.parse_block_node()
+
+ def process_directives(self):
+ self.yaml_version = None
+ self.tag_handles = {}
+ while self.check_token(DirectiveToken):
+ token = self.get_token()
+ if token.name == u'YAML':
+ if self.yaml_version is not None:
+ raise ParserError(None, None,
+ "found duplicate YAML directive", token.start_mark)
+ major, minor = token.value
+ if major != 1:
+ raise ParserError(None, None,
+ "found incompatible YAML document (version 1.* is required)",
+ token.start_mark)
+ self.yaml_version = token.value
+ elif token.name == u'TAG':
+ handle, prefix = token.value
+ if handle in self.tag_handles:
+ raise ParserError(None, None,
+ "duplicate tag handle %r" % handle.encode('utf-8'),
+ token.start_mark)
+ self.tag_handles[handle] = prefix
+ if self.tag_handles:
+ value = self.yaml_version, self.tag_handles.copy()
+ else:
+ value = self.yaml_version, None
+ for key in self.DEFAULT_TAGS:
+ if key not in self.tag_handles:
+ self.tag_handles[key] = self.DEFAULT_TAGS[key]
+ return value
+
+ # block_node_or_indentless_sequence ::= ALIAS
+ # | properties (block_content | indentless_block_sequence)?
+ # | block_content
+ # | indentless_block_sequence
+ # block_node ::= ALIAS
+ # | properties block_content?
+ # | block_content
+ # flow_node ::= ALIAS
+ # | properties flow_content?
+ # | flow_content
+ # properties ::= TAG ANCHOR? | ANCHOR TAG?
+ # block_content ::= block_collection | flow_collection | SCALAR
+ # flow_content ::= flow_collection | SCALAR
+ # block_collection ::= block_sequence | block_mapping
+ # flow_collection ::= flow_sequence | flow_mapping
+
+ def parse_block_node(self):
+ return self.parse_node(block=True)
+
+ def parse_flow_node(self):
+ return self.parse_node()
+
+ def parse_block_node_or_indentless_sequence(self):
+ return self.parse_node(block=True, indentless_sequence=True)
+
+ def parse_node(self, block=False, indentless_sequence=False):
+ if self.check_token(AliasToken):
+ token = self.get_token()
+ event = AliasEvent(token.value, token.start_mark, token.end_mark)
+ self.state = self.states.pop()
+ else:
+ anchor = None
+ tag = None
+ start_mark = end_mark = tag_mark = None
+ if self.check_token(AnchorToken):
+ token = self.get_token()
+ start_mark = token.start_mark
+ end_mark = token.end_mark
+ anchor = token.value
+ if self.check_token(TagToken):
+ token = self.get_token()
+ tag_mark = token.start_mark
+ end_mark = token.end_mark
+ tag = token.value
+ elif self.check_token(TagToken):
+ token = self.get_token()
+ start_mark = tag_mark = token.start_mark
+ end_mark = token.end_mark
+ tag = token.value
+ if self.check_token(AnchorToken):
+ token = self.get_token()
+ end_mark = token.end_mark
+ anchor = token.value
+ if tag is not None:
+ handle, suffix = tag
+ if handle is not None:
+ if handle not in self.tag_handles:
+ raise ParserError("while parsing a node", start_mark,
+ "found undefined tag handle %r" % handle.encode('utf-8'),
+ tag_mark)
+ tag = self.tag_handles[handle]+suffix
+ else:
+ tag = suffix
+ #if tag == u'!':
+ # raise ParserError("while parsing a node", start_mark,
+ # "found non-specific tag '!'", tag_mark,
+ # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.")
+ if start_mark is None:
+ start_mark = end_mark = self.peek_token().start_mark
+ event = None
+ implicit = (tag is None or tag == u'!')
+ if indentless_sequence and self.check_token(BlockEntryToken):
+ end_mark = self.peek_token().end_mark
+ event = SequenceStartEvent(anchor, tag, implicit,
+ start_mark, end_mark)
+ self.state = self.parse_indentless_sequence_entry
+ else:
+ if self.check_token(ScalarToken):
+ token = self.get_token()
+ end_mark = token.end_mark
+ if (token.plain and tag is None) or tag == u'!':
+ implicit = (True, False)
+ elif tag is None:
+ implicit = (False, True)
+ else:
+ implicit = (False, False)
+ event = ScalarEvent(anchor, tag, implicit, token.value,
+ start_mark, end_mark, style=token.style)
+ self.state = self.states.pop()
+ elif self.check_token(FlowSequenceStartToken):
+ end_mark = self.peek_token().end_mark
+ event = SequenceStartEvent(anchor, tag, implicit,
+ start_mark, end_mark, flow_style=True)
+ self.state = self.parse_flow_sequence_first_entry
+ elif self.check_token(FlowMappingStartToken):
+ end_mark = self.peek_token().end_mark
+ event = MappingStartEvent(anchor, tag, implicit,
+ start_mark, end_mark, flow_style=True)
+ self.state = self.parse_flow_mapping_first_key
+ elif block and self.check_token(BlockSequenceStartToken):
+ end_mark = self.peek_token().start_mark
+ event = SequenceStartEvent(anchor, tag, implicit,
+ start_mark, end_mark, flow_style=False)
+ self.state = self.parse_block_sequence_first_entry
+ elif block and self.check_token(BlockMappingStartToken):
+ end_mark = self.peek_token().start_mark
+ event = MappingStartEvent(anchor, tag, implicit,
+ start_mark, end_mark, flow_style=False)
+ self.state = self.parse_block_mapping_first_key
+ elif anchor is not None or tag is not None:
+ # Empty scalars are allowed even if a tag or an anchor is
+ # specified.
+ event = ScalarEvent(anchor, tag, (implicit, False), u'',
+ start_mark, end_mark)
+ self.state = self.states.pop()
+ else:
+ if block:
+ node = 'block'
+ else:
+ node = 'flow'
+ token = self.peek_token()
+ raise ParserError("while parsing a %s node" % node, start_mark,
+ "expected the node content, but found %r" % token.id,
+ token.start_mark)
+ return event
+
+ # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
+
+ def parse_block_sequence_first_entry(self):
+ token = self.get_token()
+ self.marks.append(token.start_mark)
+ return self.parse_block_sequence_entry()
+
+ def parse_block_sequence_entry(self):
+ if self.check_token(BlockEntryToken):
+ token = self.get_token()
+ if not self.check_token(BlockEntryToken, BlockEndToken):
+ self.states.append(self.parse_block_sequence_entry)
+ return self.parse_block_node()
+ else:
+ self.state = self.parse_block_sequence_entry
+ return self.process_empty_scalar(token.end_mark)
+ if not self.check_token(BlockEndToken):
+ token = self.peek_token()
+ raise ParserError("while parsing a block collection", self.marks[-1],
+ "expected <block end>, but found %r" % token.id, token.start_mark)
+ token = self.get_token()
+ event = SequenceEndEvent(token.start_mark, token.end_mark)
+ self.state = self.states.pop()
+ self.marks.pop()
+ return event
+
+ # indentless_sequence ::= (BLOCK-ENTRY block_node?)+
+
+ def parse_indentless_sequence_entry(self):
+ if self.check_token(BlockEntryToken):
+ token = self.get_token()
+ if not self.check_token(BlockEntryToken,
+ KeyToken, ValueToken, BlockEndToken):
+ self.states.append(self.parse_indentless_sequence_entry)
+ return self.parse_block_node()
+ else:
+ self.state = self.parse_indentless_sequence_entry
+ return self.process_empty_scalar(token.end_mark)
+ token = self.peek_token()
+ event = SequenceEndEvent(token.start_mark, token.start_mark)
+ self.state = self.states.pop()
+ return event
+
+ # block_mapping ::= BLOCK-MAPPING_START
+ # ((KEY block_node_or_indentless_sequence?)?
+ # (VALUE block_node_or_indentless_sequence?)?)*
+ # BLOCK-END
+
+ def parse_block_mapping_first_key(self):
+ token = self.get_token()
+ self.marks.append(token.start_mark)
+ return self.parse_block_mapping_key()
+
+ def parse_block_mapping_key(self):
+ if self.check_token(KeyToken):
+ token = self.get_token()
+ if not self.check_token(KeyToken, ValueToken, BlockEndToken):
+ self.states.append(self.parse_block_mapping_value)
+ return self.parse_block_node_or_indentless_sequence()
+ else:
+ self.state = self.parse_block_mapping_value
+ return self.process_empty_scalar(token.end_mark)
+ if not self.check_token(BlockEndToken):
+ token = self.peek_token()
+ raise ParserError("while parsing a block mapping", self.marks[-1],
+ "expected <block end>, but found %r" % token.id, token.start_mark)
+ token = self.get_token()
+ event = MappingEndEvent(token.start_mark, token.end_mark)
+ self.state = self.states.pop()
+ self.marks.pop()
+ return event
+
+ def parse_block_mapping_value(self):
+ if self.check_token(ValueToken):
+ token = self.get_token()
+ if not self.check_token(KeyToken, ValueToken, BlockEndToken):
+ self.states.append(self.parse_block_mapping_key)
+ return self.parse_block_node_or_indentless_sequence()
+ else:
+ self.state = self.parse_block_mapping_key
+ return self.process_empty_scalar(token.end_mark)
+ else:
+ self.state = self.parse_block_mapping_key
+ token = self.peek_token()
+ return self.process_empty_scalar(token.start_mark)
+
+ # flow_sequence ::= FLOW-SEQUENCE-START
+ # (flow_sequence_entry FLOW-ENTRY)*
+ # flow_sequence_entry?
+ # FLOW-SEQUENCE-END
+ # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+ #
+ # Note that while production rules for both flow_sequence_entry and
+ # flow_mapping_entry are equal, their interpretations are different.
+ # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
+ # generate an inline mapping (set syntax).
+
+ def parse_flow_sequence_first_entry(self):
+ token = self.get_token()
+ self.marks.append(token.start_mark)
+ return self.parse_flow_sequence_entry(first=True)
+
+ def parse_flow_sequence_entry(self, first=False):
+ if not self.check_token(FlowSequenceEndToken):
+ if not first:
+ if self.check_token(FlowEntryToken):
+ self.get_token()
+ else:
+ token = self.peek_token()
+ raise ParserError("while parsing a flow sequence", self.marks[-1],
+ "expected ',' or ']', but got %r" % token.id, token.start_mark)
+
+ if self.check_token(KeyToken):
+ token = self.peek_token()
+ event = MappingStartEvent(None, None, True,
+ token.start_mark, token.end_mark,
+ flow_style=True)
+ self.state = self.parse_flow_sequence_entry_mapping_key
+ return event
+ elif not self.check_token(FlowSequenceEndToken):
+ self.states.append(self.parse_flow_sequence_entry)
+ return self.parse_flow_node()
+ token = self.get_token()
+ event = SequenceEndEvent(token.start_mark, token.end_mark)
+ self.state = self.states.pop()
+ self.marks.pop()
+ return event
+
+ def parse_flow_sequence_entry_mapping_key(self):
+ token = self.get_token()
+ if not self.check_token(ValueToken,
+ FlowEntryToken, FlowSequenceEndToken):
+ self.states.append(self.parse_flow_sequence_entry_mapping_value)
+ return self.parse_flow_node()
+ else:
+ self.state = self.parse_flow_sequence_entry_mapping_value
+ return self.process_empty_scalar(token.end_mark)
+
+ def parse_flow_sequence_entry_mapping_value(self):
+ if self.check_token(ValueToken):
+ token = self.get_token()
+ if not self.check_token(FlowEntryToken, FlowSequenceEndToken):
+ self.states.append(self.parse_flow_sequence_entry_mapping_end)
+ return self.parse_flow_node()
+ else:
+ self.state = self.parse_flow_sequence_entry_mapping_end
+ return self.process_empty_scalar(token.end_mark)
+ else:
+ self.state = self.parse_flow_sequence_entry_mapping_end
+ token = self.peek_token()
+ return self.process_empty_scalar(token.start_mark)
+
+ def parse_flow_sequence_entry_mapping_end(self):
+ self.state = self.parse_flow_sequence_entry
+ token = self.peek_token()
+ return MappingEndEvent(token.start_mark, token.start_mark)
+
+ # flow_mapping ::= FLOW-MAPPING-START
+ # (flow_mapping_entry FLOW-ENTRY)*
+ # flow_mapping_entry?
+ # FLOW-MAPPING-END
+ # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
+
+ def parse_flow_mapping_first_key(self):
+ token = self.get_token()
+ self.marks.append(token.start_mark)
+ return self.parse_flow_mapping_key(first=True)
+
+ def parse_flow_mapping_key(self, first=False):
+ if not self.check_token(FlowMappingEndToken):
+ if not first:
+ if self.check_token(FlowEntryToken):
+ self.get_token()
+ else:
+ token = self.peek_token()
+ raise ParserError("while parsing a flow mapping", self.marks[-1],
+ "expected ',' or '}', but got %r" % token.id, token.start_mark)
+ if self.check_token(KeyToken):
+ token = self.get_token()
+ if not self.check_token(ValueToken,
+ FlowEntryToken, FlowMappingEndToken):
+ self.states.append(self.parse_flow_mapping_value)
+ return self.parse_flow_node()
+ else:
+ self.state = self.parse_flow_mapping_value
+ return self.process_empty_scalar(token.end_mark)
+ elif not self.check_token(FlowMappingEndToken):
+ self.states.append(self.parse_flow_mapping_empty_value)
+ return self.parse_flow_node()
+ token = self.get_token()
+ event = MappingEndEvent(token.start_mark, token.end_mark)
+ self.state = self.states.pop()
+ self.marks.pop()
+ return event
+
+ def parse_flow_mapping_value(self):
+ if self.check_token(ValueToken):
+ token = self.get_token()
+ if not self.check_token(FlowEntryToken, FlowMappingEndToken):
+ self.states.append(self.parse_flow_mapping_key)
+ return self.parse_flow_node()
+ else:
+ self.state = self.parse_flow_mapping_key
+ return self.process_empty_scalar(token.end_mark)
+ else:
+ self.state = self.parse_flow_mapping_key
+ token = self.peek_token()
+ return self.process_empty_scalar(token.start_mark)
+
+ def parse_flow_mapping_empty_value(self):
+ self.state = self.parse_flow_mapping_key
+ return self.process_empty_scalar(self.peek_token().start_mark)
+
+ def process_empty_scalar(self, mark):
+ return ScalarEvent(None, None, (True, False), u'', mark, mark)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/parser.pyc b/google_appengine/lib/yaml/lib/yaml/parser.pyc
new file mode 100644
index 0000000..b36f25c
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/parser.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/reader.py b/google_appengine/lib/yaml/lib/yaml/reader.py
new file mode 100755
index 0000000..1d4667c
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/reader.py
@@ -0,0 +1,225 @@
+# This module contains abstractions for the input stream. You don't have to
+# looks further, there are no pretty code.
+#
+# We define two classes here.
+#
+# Mark(source, line, column)
+# It's just a record and its only use is producing nice error messages.
+# Parser does not use it for any other purposes.
+#
+# Reader(source, data)
+# Reader determines the encoding of `data` and converts it to unicode.
+# Reader provides the following methods and attributes:
+# reader.peek(length=1) - return the next `length` characters
+# reader.forward(length=1) - move the current position to `length` characters.
+# reader.index - the number of the current character.
+# reader.line, stream.column - the line and the column of the current character.
+
+__all__ = ['Reader', 'ReaderError']
+
+from error import YAMLError, Mark
+
+import codecs, re
+
+# Unfortunately, codec functions in Python 2.3 does not support the `finish`
+# arguments, so we have to write our own wrappers.
+
+try:
+ codecs.utf_8_decode('', 'strict', False)
+ from codecs import utf_8_decode, utf_16_le_decode, utf_16_be_decode
+
+except TypeError:
+
+ def utf_16_le_decode(data, errors, finish=False):
+ if not finish and len(data) % 2 == 1:
+ data = data[:-1]
+ return codecs.utf_16_le_decode(data, errors)
+
+ def utf_16_be_decode(data, errors, finish=False):
+ if not finish and len(data) % 2 == 1:
+ data = data[:-1]
+ return codecs.utf_16_be_decode(data, errors)
+
+ def utf_8_decode(data, errors, finish=False):
+ if not finish:
+ # We are trying to remove a possible incomplete multibyte character
+ # from the suffix of the data.
+ # The first byte of a multi-byte sequence is in the range 0xc0 to 0xfd.
+ # All further bytes are in the range 0x80 to 0xbf.
+ # UTF-8 encoded UCS characters may be up to six bytes long.
+ count = 0
+ while count < 5 and count < len(data) \
+ and '\x80' <= data[-count-1] <= '\xBF':
+ count -= 1
+ if count < 5 and count < len(data) \
+ and '\xC0' <= data[-count-1] <= '\xFD':
+ data = data[:-count-1]
+ return codecs.utf_8_decode(data, errors)
+
+class ReaderError(YAMLError):
+
+ def __init__(self, name, position, character, encoding, reason):
+ self.name = name
+ self.character = character
+ self.position = position
+ self.encoding = encoding
+ self.reason = reason
+
+ def __str__(self):
+ if isinstance(self.character, str):
+ return "'%s' codec can't decode byte #x%02x: %s\n" \
+ " in \"%s\", position %d" \
+ % (self.encoding, ord(self.character), self.reason,
+ self.name, self.position)
+ else:
+ return "unacceptable character #x%04x: %s\n" \
+ " in \"%s\", position %d" \
+ % (ord(self.character), self.reason,
+ self.name, self.position)
+
+class Reader(object):
+ # Reader:
+ # - determines the data encoding and converts it to unicode,
+ # - checks if characters are in allowed range,
+ # - adds '\0' to the end.
+
+ # Reader accepts
+ # - a `str` object,
+ # - a `unicode` object,
+ # - a file-like object with its `read` method returning `str`,
+ # - a file-like object with its `read` method returning `unicode`.
+
+ # Yeah, it's ugly and slow.
+
+ def __init__(self, stream):
+ self.name = None
+ self.stream = None
+ self.stream_pointer = 0
+ self.eof = True
+ self.buffer = u''
+ self.pointer = 0
+ self.raw_buffer = None
+ self.raw_decode = None
+ self.encoding = None
+ self.index = 0
+ self.line = 0
+ self.column = 0
+ if isinstance(stream, unicode):
+ self.name = "<unicode string>"
+ self.check_printable(stream)
+ self.buffer = stream+u'\0'
+ elif isinstance(stream, str):
+ self.name = "<string>"
+ self.raw_buffer = stream
+ self.determine_encoding()
+ else:
+ self.stream = stream
+ self.name = getattr(stream, 'name', "<file>")
+ self.eof = False
+ self.raw_buffer = ''
+ self.determine_encoding()
+
+ def peek(self, index=0):
+ try:
+ return self.buffer[self.pointer+index]
+ except IndexError:
+ self.update(index+1)
+ return self.buffer[self.pointer+index]
+
+ def prefix(self, length=1):
+ if self.pointer+length >= len(self.buffer):
+ self.update(length)
+ return self.buffer[self.pointer:self.pointer+length]
+
+ def forward(self, length=1):
+ if self.pointer+length+1 >= len(self.buffer):
+ self.update(length+1)
+ while length:
+ ch = self.buffer[self.pointer]
+ self.pointer += 1
+ self.index += 1
+ if ch in u'\n\x85\u2028\u2029' \
+ or (ch == u'\r' and self.buffer[self.pointer] != u'\n'):
+ self.line += 1
+ self.column = 0
+ elif ch != u'\uFEFF':
+ self.column += 1
+ length -= 1
+
+ def get_mark(self):
+ if self.stream is None:
+ return Mark(self.name, self.index, self.line, self.column,
+ self.buffer, self.pointer)
+ else:
+ return Mark(self.name, self.index, self.line, self.column,
+ None, None)
+
+ def determine_encoding(self):
+ while not self.eof and len(self.raw_buffer) < 2:
+ self.update_raw()
+ if not isinstance(self.raw_buffer, unicode):
+ if self.raw_buffer.startswith(codecs.BOM_UTF16_LE):
+ self.raw_decode = utf_16_le_decode
+ self.encoding = 'utf-16-le'
+ elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE):
+ self.raw_decode = utf_16_be_decode
+ self.encoding = 'utf-16-be'
+ else:
+ self.raw_decode = utf_8_decode
+ self.encoding = 'utf-8'
+ self.update(1)
+
+ NON_PRINTABLE = re.compile(u'[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD]')
+ def check_printable(self, data):
+ match = self.NON_PRINTABLE.search(data)
+ if match:
+ character = match.group()
+ position = self.index+(len(self.buffer)-self.pointer)+match.start()
+ raise ReaderError(self.name, position, character,
+ 'unicode', "special characters are not allowed")
+
+ def update(self, length):
+ if self.raw_buffer is None:
+ return
+ self.buffer = self.buffer[self.pointer:]
+ self.pointer = 0
+ while len(self.buffer) < length:
+ if not self.eof:
+ self.update_raw()
+ if self.raw_decode is not None:
+ try:
+ data, converted = self.raw_decode(self.raw_buffer,
+ 'strict', self.eof)
+ except UnicodeDecodeError, exc:
+ character = exc.object[exc.start]
+ if self.stream is not None:
+ position = self.stream_pointer-len(self.raw_buffer)+exc.start
+ else:
+ position = exc.start
+ raise ReaderError(self.name, position, character,
+ exc.encoding, exc.reason)
+ else:
+ data = self.raw_buffer
+ converted = len(data)
+ self.check_printable(data)
+ self.buffer += data
+ self.raw_buffer = self.raw_buffer[converted:]
+ if self.eof:
+ self.buffer += u'\0'
+ self.raw_buffer = None
+ break
+
+ def update_raw(self, size=1024):
+ data = self.stream.read(size)
+ if data:
+ self.raw_buffer += data
+ self.stream_pointer += len(data)
+ else:
+ self.eof = True
+
+#try:
+# import psyco
+# psyco.bind(Reader)
+#except ImportError:
+# pass
+
diff --git a/google_appengine/lib/yaml/lib/yaml/reader.pyc b/google_appengine/lib/yaml/lib/yaml/reader.pyc
new file mode 100644
index 0000000..787a976
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/reader.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/representer.py b/google_appengine/lib/yaml/lib/yaml/representer.py
new file mode 100755
index 0000000..1f4fe59
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/representer.py
@@ -0,0 +1,488 @@
+
+__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
+ 'RepresenterError']
+
+from error import *
+from nodes import *
+
+import datetime
+
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+import sys, copy_reg, types
+
+class RepresenterError(YAMLError):
+ pass
+
+class BaseRepresenter(object):
+
+ yaml_representers = {}
+ yaml_multi_representers = {}
+
+ def __init__(self, default_style=None, default_flow_style=None):
+ self.default_style = default_style
+ self.default_flow_style = default_flow_style
+ self.represented_objects = {}
+ self.object_keeper = []
+ self.alias_key = None
+
+ def represent(self, data):
+ node = self.represent_data(data)
+ self.serialize(node)
+ self.represented_objects = {}
+ self.object_keeper = []
+ self.alias_key = None
+
+ def get_classobj_bases(self, cls):
+ bases = [cls]
+ for base in cls.__bases__:
+ bases.extend(self.get_classobj_bases(base))
+ return bases
+
+ def represent_data(self, data):
+ if self.ignore_aliases(data):
+ self.alias_key = None
+ else:
+ self.alias_key = id(data)
+ if self.alias_key is not None:
+ if self.alias_key in self.represented_objects:
+ node = self.represented_objects[self.alias_key]
+ #if node is None:
+ # raise RepresenterError("recursive objects are not allowed: %r" % data)
+ return node
+ #self.represented_objects[alias_key] = None
+ self.object_keeper.append(data)
+ data_types = type(data).__mro__
+ if type(data) is types.InstanceType:
+ data_types = self.get_classobj_bases(data.__class__)+list(data_types)
+ if data_types[0] in self.yaml_representers:
+ node = self.yaml_representers[data_types[0]](self, data)
+ else:
+ for data_type in data_types:
+ if data_type in self.yaml_multi_representers:
+ node = self.yaml_multi_representers[data_type](self, data)
+ break
+ else:
+ if None in self.yaml_multi_representers:
+ node = self.yaml_multi_representers[None](self, data)
+ elif None in self.yaml_representers:
+ node = self.yaml_representers[None](self, data)
+ else:
+ node = ScalarNode(None, unicode(data))
+ #if alias_key is not None:
+ # self.represented_objects[alias_key] = node
+ return node
+
+ def add_representer(cls, data_type, representer):
+ if not 'yaml_representers' in cls.__dict__:
+ cls.yaml_representers = cls.yaml_representers.copy()
+ cls.yaml_representers[data_type] = representer
+ add_representer = classmethod(add_representer)
+
+ def add_multi_representer(cls, data_type, representer):
+ if not 'yaml_multi_representers' in cls.__dict__:
+ cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
+ cls.yaml_multi_representers[data_type] = representer
+ add_multi_representer = classmethod(add_multi_representer)
+
+ def represent_scalar(self, tag, value, style=None):
+ if style is None:
+ style = self.default_style
+ node = ScalarNode(tag, value, style=style)
+ if self.alias_key is not None:
+ self.represented_objects[self.alias_key] = node
+ return node
+
+ def represent_sequence(self, tag, sequence, flow_style=None):
+ value = []
+ node = SequenceNode(tag, value, flow_style=flow_style)
+ if self.alias_key is not None:
+ self.represented_objects[self.alias_key] = node
+ best_style = True
+ for item in sequence:
+ node_item = self.represent_data(item)
+ if not (isinstance(node_item, ScalarNode) and not node_item.style):
+ best_style = False
+ value.append(node_item)
+ if flow_style is None:
+ if self.default_flow_style is not None:
+ node.flow_style = self.default_flow_style
+ else:
+ node.flow_style = best_style
+ return node
+
+ def represent_mapping(self, tag, mapping, flow_style=None):
+ value = []
+ node = MappingNode(tag, value, flow_style=flow_style)
+ if self.alias_key is not None:
+ self.represented_objects[self.alias_key] = node
+ best_style = True
+ if hasattr(mapping, 'items'):
+ mapping = mapping.items()
+ mapping.sort()
+ for item_key, item_value in mapping:
+ node_key = self.represent_data(item_key)
+ node_value = self.represent_data(item_value)
+ if not (isinstance(node_key, ScalarNode) and not node_key.style):
+ best_style = False
+ if not (isinstance(node_value, ScalarNode) and not node_value.style):
+ best_style = False
+ value.append((node_key, node_value))
+ if flow_style is None:
+ if self.default_flow_style is not None:
+ node.flow_style = self.default_flow_style
+ else:
+ node.flow_style = best_style
+ return node
+
+ def ignore_aliases(self, data):
+ return False
+
+class SafeRepresenter(BaseRepresenter):
+
+ def ignore_aliases(self, data):
+ if data in [None, ()]:
+ return True
+ if isinstance(data, (str, unicode, bool, int, float)):
+ return True
+
+ def represent_none(self, data):
+ return self.represent_scalar(u'tag:yaml.org,2002:null',
+ u'null')
+
+ def represent_str(self, data):
+ tag = None
+ style = None
+ try:
+ data = unicode(data, 'ascii')
+ tag = u'tag:yaml.org,2002:str'
+ except UnicodeDecodeError:
+ try:
+ data = unicode(data, 'utf-8')
+ tag = u'tag:yaml.org,2002:str'
+ except UnicodeDecodeError:
+ data = data.encode('base64')
+ tag = u'tag:yaml.org,2002:binary'
+ style = '|'
+ return self.represent_scalar(tag, data, style=style)
+
+ def represent_unicode(self, data):
+ return self.represent_scalar(u'tag:yaml.org,2002:str', data)
+
+ def represent_bool(self, data):
+ if data:
+ value = u'true'
+ else:
+ value = u'false'
+ return self.represent_scalar(u'tag:yaml.org,2002:bool', value)
+
+ def represent_int(self, data):
+ return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data))
+
+ def represent_long(self, data):
+ return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data))
+
+ inf_value = 1e300
+ while repr(inf_value) != repr(inf_value*inf_value):
+ inf_value *= inf_value
+
+ def represent_float(self, data):
+ if data != data or (data == 0.0 and data == 1.0):
+ value = u'.nan'
+ elif data == self.inf_value:
+ value = u'.inf'
+ elif data == -self.inf_value:
+ value = u'-.inf'
+ else:
+ value = unicode(repr(data)).lower()
+ # Note that in some cases `repr(data)` represents a float number
+ # without the decimal parts. For instance:
+ # >>> repr(1e17)
+ # '1e17'
+ # Unfortunately, this is not a valid float representation according
+ # to the definition of the `!!float` tag. We fix this by adding
+ # '.0' before the 'e' symbol.
+ if u'.' not in value and u'e' in value:
+ value = value.replace(u'e', u'.0e', 1)
+ return self.represent_scalar(u'tag:yaml.org,2002:float', value)
+
+ def represent_list(self, data):
+ #pairs = (len(data) > 0 and isinstance(data, list))
+ #if pairs:
+ # for item in data:
+ # if not isinstance(item, tuple) or len(item) != 2:
+ # pairs = False
+ # break
+ #if not pairs:
+ return self.represent_sequence(u'tag:yaml.org,2002:seq', data)
+ #value = []
+ #for item_key, item_value in data:
+ # value.append(self.represent_mapping(u'tag:yaml.org,2002:map',
+ # [(item_key, item_value)]))
+ #return SequenceNode(u'tag:yaml.org,2002:pairs', value)
+
+ def represent_dict(self, data):
+ return self.represent_mapping(u'tag:yaml.org,2002:map', data)
+
+ def represent_set(self, data):
+ value = {}
+ for key in data:
+ value[key] = None
+ return self.represent_mapping(u'tag:yaml.org,2002:set', value)
+
+ def represent_date(self, data):
+ value = unicode(data.isoformat())
+ return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
+
+ def represent_datetime(self, data):
+ value = unicode(data.isoformat(' '))
+ return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
+
+ def represent_yaml_object(self, tag, data, cls, flow_style=None):
+ if hasattr(data, '__getstate__'):
+ state = data.__getstate__()
+ else:
+ state = data.__dict__.copy()
+ return self.represent_mapping(tag, state, flow_style=flow_style)
+
+ def represent_undefined(self, data):
+ raise RepresenterError("cannot represent an object: %s" % data)
+
+SafeRepresenter.add_representer(type(None),
+ SafeRepresenter.represent_none)
+
+SafeRepresenter.add_representer(str,
+ SafeRepresenter.represent_str)
+
+SafeRepresenter.add_representer(unicode,
+ SafeRepresenter.represent_unicode)
+
+SafeRepresenter.add_representer(bool,
+ SafeRepresenter.represent_bool)
+
+SafeRepresenter.add_representer(int,
+ SafeRepresenter.represent_int)
+
+SafeRepresenter.add_representer(long,
+ SafeRepresenter.represent_long)
+
+SafeRepresenter.add_representer(float,
+ SafeRepresenter.represent_float)
+
+SafeRepresenter.add_representer(list,
+ SafeRepresenter.represent_list)
+
+SafeRepresenter.add_representer(tuple,
+ SafeRepresenter.represent_list)
+
+SafeRepresenter.add_representer(dict,
+ SafeRepresenter.represent_dict)
+
+SafeRepresenter.add_representer(set,
+ SafeRepresenter.represent_set)
+
+SafeRepresenter.add_representer(datetime.date,
+ SafeRepresenter.represent_date)
+SafeRepresenter.add_representer(datetime.datetime,
+ SafeRepresenter.represent_datetime)
+
+SafeRepresenter.add_representer(None,
+ SafeRepresenter.represent_undefined)
+
+class Representer(SafeRepresenter):
+
+ def represent_str(self, data):
+ tag = None
+ style = None
+ try:
+ data = unicode(data, 'ascii')
+ tag = u'tag:yaml.org,2002:str'
+ except UnicodeDecodeError:
+ try:
+ data = unicode(data, 'utf-8')
+ tag = u'tag:yaml.org,2002:python/str'
+ except UnicodeDecodeError:
+ data = data.encode('base64')
+ tag = u'tag:yaml.org,2002:binary'
+ style = '|'
+ return self.represent_scalar(tag, data, style=style)
+
+ def represent_unicode(self, data):
+ tag = None
+ try:
+ data.encode('ascii')
+ tag = u'tag:yaml.org,2002:python/unicode'
+ except UnicodeEncodeError:
+ tag = u'tag:yaml.org,2002:str'
+ return self.represent_scalar(tag, data)
+
+ def represent_long(self, data):
+ tag = u'tag:yaml.org,2002:int'
+ if int(data) is not data:
+ tag = u'tag:yaml.org,2002:python/long'
+ return self.represent_scalar(tag, unicode(data))
+
+ def represent_complex(self, data):
+ if data.imag == 0.0:
+ data = u'%r' % data.real
+ elif data.real == 0.0:
+ data = u'%rj' % data.imag
+ elif data.imag > 0:
+ data = u'%r+%rj' % (data.real, data.imag)
+ else:
+ data = u'%r%rj' % (data.real, data.imag)
+ return self.represent_scalar(u'tag:yaml.org,2002:python/complex', data)
+
+ def represent_tuple(self, data):
+ return self.represent_sequence(u'tag:yaml.org,2002:python/tuple', data)
+
+ def represent_name(self, data):
+ name = u'%s.%s' % (data.__module__, data.__name__)
+ return self.represent_scalar(u'tag:yaml.org,2002:python/name:'+name, u'')
+
+ def represent_module(self, data):
+ return self.represent_scalar(
+ u'tag:yaml.org,2002:python/module:'+data.__name__, u'')
+
+ def represent_instance(self, data):
+ # For instances of classic classes, we use __getinitargs__ and
+ # __getstate__ to serialize the data.
+
+ # If data.__getinitargs__ exists, the object must be reconstructed by
+ # calling cls(**args), where args is a tuple returned by
+ # __getinitargs__. Otherwise, the cls.__init__ method should never be
+ # called and the class instance is created by instantiating a trivial
+ # class and assigning to the instance's __class__ variable.
+
+ # If data.__getstate__ exists, it returns the state of the object.
+ # Otherwise, the state of the object is data.__dict__.
+
+ # We produce either a !!python/object or !!python/object/new node.
+ # If data.__getinitargs__ does not exist and state is a dictionary, we
+ # produce a !!python/object node . Otherwise we produce a
+ # !!python/object/new node.
+
+ cls = data.__class__
+ class_name = u'%s.%s' % (cls.__module__, cls.__name__)
+ args = None
+ state = None
+ if hasattr(data, '__getinitargs__'):
+ args = list(data.__getinitargs__())
+ if hasattr(data, '__getstate__'):
+ state = data.__getstate__()
+ else:
+ state = data.__dict__
+ if args is None and isinstance(state, dict):
+ return self.represent_mapping(
+ u'tag:yaml.org,2002:python/object:'+class_name, state)
+ if isinstance(state, dict) and not state:
+ return self.represent_sequence(
+ u'tag:yaml.org,2002:python/object/new:'+class_name, args)
+ value = {}
+ if args:
+ value['args'] = args
+ value['state'] = state
+ return self.represent_mapping(
+ u'tag:yaml.org,2002:python/object/new:'+class_name, value)
+
+ def represent_object(self, data):
+ # We use __reduce__ API to save the data. data.__reduce__ returns
+ # a tuple of length 2-5:
+ # (function, args, state, listitems, dictitems)
+
+ # For reconstructing, we calls function(*args), then set its state,
+ # listitems, and dictitems if they are not None.
+
+ # A special case is when function.__name__ == '__newobj__'. In this
+ # case we create the object with args[0].__new__(*args).
+
+ # Another special case is when __reduce__ returns a string - we don't
+ # support it.
+
+ # We produce a !!python/object, !!python/object/new or
+ # !!python/object/apply node.
+
+ cls = type(data)
+ if cls in copy_reg.dispatch_table:
+ reduce = copy_reg.dispatch_table[cls](data)
+ elif hasattr(data, '__reduce_ex__'):
+ reduce = data.__reduce_ex__(2)
+ elif hasattr(data, '__reduce__'):
+ reduce = data.__reduce__()
+ else:
+ raise RepresenterError("cannot represent object: %r" % data)
+ reduce = (list(reduce)+[None]*5)[:5]
+ function, args, state, listitems, dictitems = reduce
+ args = list(args)
+ if state is None:
+ state = {}
+ if listitems is not None:
+ listitems = list(listitems)
+ if dictitems is not None:
+ dictitems = dict(dictitems)
+ if function.__name__ == '__newobj__':
+ function = args[0]
+ args = args[1:]
+ tag = u'tag:yaml.org,2002:python/object/new:'
+ newobj = True
+ else:
+ tag = u'tag:yaml.org,2002:python/object/apply:'
+ newobj = False
+ function_name = u'%s.%s' % (function.__module__, function.__name__)
+ if not args and not listitems and not dictitems \
+ and isinstance(state, dict) and newobj:
+ return self.represent_mapping(
+ u'tag:yaml.org,2002:python/object:'+function_name, state)
+ if not listitems and not dictitems \
+ and isinstance(state, dict) and not state:
+ return self.represent_sequence(tag+function_name, args)
+ value = {}
+ if args:
+ value['args'] = args
+ if state or not isinstance(state, dict):
+ value['state'] = state
+ if listitems:
+ value['listitems'] = listitems
+ if dictitems:
+ value['dictitems'] = dictitems
+ return self.represent_mapping(tag+function_name, value)
+
+Representer.add_representer(str,
+ Representer.represent_str)
+
+Representer.add_representer(unicode,
+ Representer.represent_unicode)
+
+Representer.add_representer(long,
+ Representer.represent_long)
+
+Representer.add_representer(complex,
+ Representer.represent_complex)
+
+Representer.add_representer(tuple,
+ Representer.represent_tuple)
+
+Representer.add_representer(type,
+ Representer.represent_name)
+
+Representer.add_representer(types.ClassType,
+ Representer.represent_name)
+
+Representer.add_representer(types.FunctionType,
+ Representer.represent_name)
+
+Representer.add_representer(types.BuiltinFunctionType,
+ Representer.represent_name)
+
+Representer.add_representer(types.ModuleType,
+ Representer.represent_module)
+
+Representer.add_multi_representer(types.InstanceType,
+ Representer.represent_instance)
+
+Representer.add_multi_representer(object,
+ Representer.represent_object)
+
diff --git a/google_appengine/lib/yaml/lib/yaml/representer.pyc b/google_appengine/lib/yaml/lib/yaml/representer.pyc
new file mode 100644
index 0000000..6da6ce7
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/representer.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/resolver.py b/google_appengine/lib/yaml/lib/yaml/resolver.py
new file mode 100755
index 0000000..5cbf6b3
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/resolver.py
@@ -0,0 +1,223 @@
+
+__all__ = ['BaseResolver', 'Resolver']
+
+from error import *
+from nodes import *
+
+import re
+
+class ResolverError(YAMLError):
+ pass
+
+class BaseResolver(object):
+
+ DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
+ DEFAULT_SEQUENCE_TAG = u'tag:yaml.org,2002:seq'
+ DEFAULT_MAPPING_TAG = u'tag:yaml.org,2002:map'
+
+ yaml_implicit_resolvers = {}
+ yaml_path_resolvers = {}
+
+ def __init__(self):
+ self.resolver_exact_paths = []
+ self.resolver_prefix_paths = []
+
+ def add_implicit_resolver(cls, tag, regexp, first):
+ if not 'yaml_implicit_resolvers' in cls.__dict__:
+ cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy()
+ if first is None:
+ first = [None]
+ for ch in first:
+ cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
+ add_implicit_resolver = classmethod(add_implicit_resolver)
+
+ def add_path_resolver(cls, tag, path, kind=None):
+ # Note: `add_path_resolver` is experimental. The API could be changed.
+ # `new_path` is a pattern that is matched against the path from the
+ # root to the node that is being considered. `node_path` elements are
+ # tuples `(node_check, index_check)`. `node_check` is a node class:
+ # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None`
+ # matches any kind of a node. `index_check` could be `None`, a boolean
+ # value, a string value, or a number. `None` and `False` match against
+ # any _value_ of sequence and mapping nodes. `True` matches against
+ # any _key_ of a mapping node. A string `index_check` matches against
+ # a mapping value that corresponds to a scalar key which content is
+ # equal to the `index_check` value. An integer `index_check` matches
+ # against a sequence value with the index equal to `index_check`.
+ if not 'yaml_path_resolvers' in cls.__dict__:
+ cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy()
+ new_path = []
+ for element in path:
+ if isinstance(element, (list, tuple)):
+ if len(element) == 2:
+ node_check, index_check = element
+ elif len(element) == 1:
+ node_check = element[0]
+ index_check = True
+ else:
+ raise ResolverError("Invalid path element: %s" % element)
+ else:
+ node_check = None
+ index_check = element
+ if node_check is str:
+ node_check = ScalarNode
+ elif node_check is list:
+ node_check = SequenceNode
+ elif node_check is dict:
+ node_check = MappingNode
+ elif node_check not in [ScalarNode, SequenceNode, MappingNode] \
+ and not isinstance(node_check, basestring) \
+ and node_check is not None:
+ raise ResolverError("Invalid node checker: %s" % node_check)
+ if not isinstance(index_check, (basestring, int)) \
+ and index_check is not None:
+ raise ResolverError("Invalid index checker: %s" % index_check)
+ new_path.append((node_check, index_check))
+ if kind is str:
+ kind = ScalarNode
+ elif kind is list:
+ kind = SequenceNode
+ elif kind is dict:
+ kind = MappingNode
+ elif kind not in [ScalarNode, SequenceNode, MappingNode] \
+ and kind is not None:
+ raise ResolverError("Invalid node kind: %s" % kind)
+ cls.yaml_path_resolvers[tuple(new_path), kind] = tag
+ add_path_resolver = classmethod(add_path_resolver)
+
+ def descend_resolver(self, current_node, current_index):
+ if not self.yaml_path_resolvers:
+ return
+ exact_paths = {}
+ prefix_paths = []
+ if current_node:
+ depth = len(self.resolver_prefix_paths)
+ for path, kind in self.resolver_prefix_paths[-1]:
+ if self.check_resolver_prefix(depth, path, kind,
+ current_node, current_index):
+ if len(path) > depth:
+ prefix_paths.append((path, kind))
+ else:
+ exact_paths[kind] = self.yaml_path_resolvers[path, kind]
+ else:
+ for path, kind in self.yaml_path_resolvers:
+ if not path:
+ exact_paths[kind] = self.yaml_path_resolvers[path, kind]
+ else:
+ prefix_paths.append((path, kind))
+ self.resolver_exact_paths.append(exact_paths)
+ self.resolver_prefix_paths.append(prefix_paths)
+
+ def ascend_resolver(self):
+ if not self.yaml_path_resolvers:
+ return
+ self.resolver_exact_paths.pop()
+ self.resolver_prefix_paths.pop()
+
+ def check_resolver_prefix(self, depth, path, kind,
+ current_node, current_index):
+ node_check, index_check = path[depth-1]
+ if isinstance(node_check, basestring):
+ if current_node.tag != node_check:
+ return
+ elif node_check is not None:
+ if not isinstance(current_node, node_check):
+ return
+ if index_check is True and current_index is not None:
+ return
+ if (index_check is False or index_check is None) \
+ and current_index is None:
+ return
+ if isinstance(index_check, basestring):
+ if not (isinstance(current_index, ScalarNode)
+ and index_check == current_index.value):
+ return
+ elif isinstance(index_check, int) and not isinstance(index_check, bool):
+ if index_check != current_index:
+ return
+ return True
+
+ def resolve(self, kind, value, implicit):
+ if kind is ScalarNode and implicit[0]:
+ if value == u'':
+ resolvers = self.yaml_implicit_resolvers.get(u'', [])
+ else:
+ resolvers = self.yaml_implicit_resolvers.get(value[0], [])
+ resolvers += self.yaml_implicit_resolvers.get(None, [])
+ for tag, regexp in resolvers:
+ if regexp.match(value):
+ return tag
+ implicit = implicit[1]
+ if self.yaml_path_resolvers:
+ exact_paths = self.resolver_exact_paths[-1]
+ if kind in exact_paths:
+ return exact_paths[kind]
+ if None in exact_paths:
+ return exact_paths[None]
+ if kind is ScalarNode:
+ return self.DEFAULT_SCALAR_TAG
+ elif kind is SequenceNode:
+ return self.DEFAULT_SEQUENCE_TAG
+ elif kind is MappingNode:
+ return self.DEFAULT_MAPPING_TAG
+
+class Resolver(BaseResolver):
+ pass
+
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:bool',
+ re.compile(ur'''^(?:yes|Yes|YES|no|No|NO
+ |true|True|TRUE|false|False|FALSE
+ |on|On|ON|off|Off|OFF)$''', re.X),
+ list(u'yYnNtTfFoO'))
+
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:float',
+ re.compile(ur'''^(?:[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*(?:[eE][-+][0-9]+)?
+ |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
+ |[-+]?\.(?:inf|Inf|INF)
+ |\.(?:nan|NaN|NAN))$''', re.X),
+ list(u'-+0123456789.'))
+
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:int',
+ re.compile(ur'''^(?:[-+]?0b[0-1_]+
+ |[-+]?0[0-7_]+
+ |[-+]?(?:0|[1-9][0-9_]*)
+ |[-+]?0x[0-9a-fA-F_]+
+ |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X),
+ list(u'-+0123456789'))
+
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:merge',
+ re.compile(ur'^(?:<<)$'),
+ ['<'])
+
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:null',
+ re.compile(ur'''^(?: ~
+ |null|Null|NULL
+ | )$''', re.X),
+ [u'~', u'n', u'N', u''])
+
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:timestamp',
+ re.compile(ur'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
+ |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?
+ (?:[Tt]|[ \t]+)[0-9][0-9]?
+ :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)?
+ (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X),
+ list(u'0123456789'))
+
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:value',
+ re.compile(ur'^(?:=)$'),
+ ['='])
+
+# The following resolver is only for documentation purposes. It cannot work
+# because plain scalars cannot start with '!', '&', or '*'.
+Resolver.add_implicit_resolver(
+ u'tag:yaml.org,2002:yaml',
+ re.compile(ur'^(?:!|&|\*)$'),
+ list(u'!&*'))
+
diff --git a/google_appengine/lib/yaml/lib/yaml/resolver.pyc b/google_appengine/lib/yaml/lib/yaml/resolver.pyc
new file mode 100644
index 0000000..4831c62
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/resolver.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/scanner.py b/google_appengine/lib/yaml/lib/yaml/scanner.py
new file mode 100755
index 0000000..a3ecdd0
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/scanner.py
@@ -0,0 +1,1456 @@
+
+# Scanner produces tokens of the following types:
+# STREAM-START
+# STREAM-END
+# DIRECTIVE(name, value)
+# DOCUMENT-START
+# DOCUMENT-END
+# BLOCK-SEQUENCE-START
+# BLOCK-MAPPING-START
+# BLOCK-END
+# FLOW-SEQUENCE-START
+# FLOW-MAPPING-START
+# FLOW-SEQUENCE-END
+# FLOW-MAPPING-END
+# BLOCK-ENTRY
+# FLOW-ENTRY
+# KEY
+# VALUE
+# ALIAS(value)
+# ANCHOR(value)
+# TAG(value)
+# SCALAR(value, plain, style)
+#
+# Read comments in the Scanner code for more details.
+#
+
+__all__ = ['Scanner', 'ScannerError']
+
+from error import MarkedYAMLError
+from tokens import *
+
+class ScannerError(MarkedYAMLError):
+ pass
+
+class SimpleKey(object):
+ # See below simple keys treatment.
+
+ def __init__(self, token_number, required, index, line, column, mark):
+ self.token_number = token_number
+ self.required = required
+ self.index = index
+ self.line = line
+ self.column = column
+ self.mark = mark
+
+class Scanner(object):
+
+ def __init__(self):
+ """Initialize the scanner."""
+ # It is assumed that Scanner and Reader will have a common descendant.
+ # Reader do the dirty work of checking for BOM and converting the
+ # input data to Unicode. It also adds NUL to the end.
+ #
+ # Reader supports the following methods
+ # self.peek(i=0) # peek the next i-th character
+ # self.prefix(l=1) # peek the next l characters
+ # self.forward(l=1) # read the next l characters and move the pointer.
+
+ # Had we reached the end of the stream?
+ self.done = False
+
+ # The number of unclosed '{' and '['. `flow_level == 0` means block
+ # context.
+ self.flow_level = 0
+
+ # List of processed tokens that are not yet emitted.
+ self.tokens = []
+
+ # Add the STREAM-START token.
+ self.fetch_stream_start()
+
+ # Number of tokens that were emitted through the `get_token` method.
+ self.tokens_taken = 0
+
+ # The current indentation level.
+ self.indent = -1
+
+ # Past indentation levels.
+ self.indents = []
+
+ # Variables related to simple keys treatment.
+
+ # A simple key is a key that is not denoted by the '?' indicator.
+ # Example of simple keys:
+ # ---
+ # block simple key: value
+ # ? not a simple key:
+ # : { flow simple key: value }
+ # We emit the KEY token before all keys, so when we find a potential
+ # simple key, we try to locate the corresponding ':' indicator.
+ # Simple keys should be limited to a single line and 1024 characters.
+
+ # Can a simple key start at the current position? A simple key may
+ # start:
+ # - at the beginning of the line, not counting indentation spaces
+ # (in block context),
+ # - after '{', '[', ',' (in the flow context),
+ # - after '?', ':', '-' (in the block context).
+ # In the block context, this flag also signifies if a block collection
+ # may start at the current position.
+ self.allow_simple_key = True
+
+ # Keep track of possible simple keys. This is a dictionary. The key
+ # is `flow_level`; there can be no more that one possible simple key
+ # for each level. The value is a SimpleKey record:
+ # (token_number, required, index, line, column, mark)
+ # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow),
+ # '[', or '{' tokens.
+ self.possible_simple_keys = {}
+
+ # Public methods.
+
+ def check_token(self, *choices):
+ # Check if the next token is one of the given types.
+ while self.need_more_tokens():
+ self.fetch_more_tokens()
+ if self.tokens:
+ if not choices:
+ return True
+ for choice in choices:
+ if isinstance(self.tokens[0], choice):
+ return True
+ return False
+
+ def peek_token(self):
+ # Return the next token, but do not delete if from the queue.
+ while self.need_more_tokens():
+ self.fetch_more_tokens()
+ if self.tokens:
+ return self.tokens[0]
+
+ def get_token(self):
+ # Return the next token.
+ while self.need_more_tokens():
+ self.fetch_more_tokens()
+ if self.tokens:
+ self.tokens_taken += 1
+ return self.tokens.pop(0)
+
+ # Private methods.
+
+ def need_more_tokens(self):
+ if self.done:
+ return False
+ if not self.tokens:
+ return True
+ # The current token may be a potential simple key, so we
+ # need to look further.
+ self.stale_possible_simple_keys()
+ if self.next_possible_simple_key() == self.tokens_taken:
+ return True
+
+ def fetch_more_tokens(self):
+
+ # Eat whitespaces and comments until we reach the next token.
+ self.scan_to_next_token()
+
+ # Remove obsolete possible simple keys.
+ self.stale_possible_simple_keys()
+
+ # Compare the current indentation and column. It may add some tokens
+ # and decrease the current indentation level.
+ self.unwind_indent(self.column)
+
+ # Peek the next character.
+ ch = self.peek()
+
+ # Is it the end of stream?
+ if ch == u'\0':
+ return self.fetch_stream_end()
+
+ # Is it a directive?
+ if ch == u'%' and self.check_directive():
+ return self.fetch_directive()
+
+ # Is it the document start?
+ if ch == u'-' and self.check_document_start():
+ return self.fetch_document_start()
+
+ # Is it the document end?
+ if ch == u'.' and self.check_document_end():
+ return self.fetch_document_end()
+
+ # TODO: support for BOM within a stream.
+ #if ch == u'\uFEFF':
+ # return self.fetch_bom() <-- issue BOMToken
+
+ # Note: the order of the following checks is NOT significant.
+
+ # Is it the flow sequence start indicator?
+ if ch == u'[':
+ return self.fetch_flow_sequence_start()
+
+ # Is it the flow mapping start indicator?
+ if ch == u'{':
+ return self.fetch_flow_mapping_start()
+
+ # Is it the flow sequence end indicator?
+ if ch == u']':
+ return self.fetch_flow_sequence_end()
+
+ # Is it the flow mapping end indicator?
+ if ch == u'}':
+ return self.fetch_flow_mapping_end()
+
+ # Is it the flow entry indicator?
+ if ch == u',':
+ return self.fetch_flow_entry()
+
+ # Is it the block entry indicator?
+ if ch == u'-' and self.check_block_entry():
+ return self.fetch_block_entry()
+
+ # Is it the key indicator?
+ if ch == u'?' and self.check_key():
+ return self.fetch_key()
+
+ # Is it the value indicator?
+ if ch == u':' and self.check_value():
+ return self.fetch_value()
+
+ # Is it an alias?
+ if ch == u'*':
+ return self.fetch_alias()
+
+ # Is it an anchor?
+ if ch == u'&':
+ return self.fetch_anchor()
+
+ # Is it a tag?
+ if ch == u'!':
+ return self.fetch_tag()
+
+ # Is it a literal scalar?
+ if ch == u'|' and not self.flow_level:
+ return self.fetch_literal()
+
+ # Is it a folded scalar?
+ if ch == u'>' and not self.flow_level:
+ return self.fetch_folded()
+
+ # Is it a single quoted scalar?
+ if ch == u'\'':
+ return self.fetch_single()
+
+ # Is it a double quoted scalar?
+ if ch == u'\"':
+ return self.fetch_double()
+
+ # It must be a plain scalar then.
+ if self.check_plain():
+ return self.fetch_plain()
+
+ # No? It's an error. Let's produce a nice error message.
+ raise ScannerError("while scanning for the next token", None,
+ "found character %r that cannot start any token"
+ % ch.encode('utf-8'), self.get_mark())
+
+ # Simple keys treatment.
+
+ def next_possible_simple_key(self):
+ # Return the number of the nearest possible simple key. Actually we
+ # don't need to loop through the whole dictionary. We may replace it
+ # with the following code:
+ # if not self.possible_simple_keys:
+ # return None
+ # return self.possible_simple_keys[
+ # min(self.possible_simple_keys.keys())].token_number
+ min_token_number = None
+ for level in self.possible_simple_keys:
+ key = self.possible_simple_keys[level]
+ if min_token_number is None or key.token_number < min_token_number:
+ min_token_number = key.token_number
+ return min_token_number
+
+ def stale_possible_simple_keys(self):
+ # Remove entries that are no longer possible simple keys. According to
+ # the YAML specification, simple keys
+ # - should be limited to a single line,
+ # - should be no longer than 1024 characters.
+ # Disabling this procedure will allow simple keys of any length and
+ # height (may cause problems if indentation is broken though).
+ for level in self.possible_simple_keys.keys():
+ key = self.possible_simple_keys[level]
+ if key.line != self.line \
+ or self.index-key.index > 1024:
+ if key.required:
+ raise ScannerError("while scanning a simple key", key.mark,
+ "could not found expected ':'", self.get_mark())
+ del self.possible_simple_keys[level]
+
+ def save_possible_simple_key(self):
+ # The next token may start a simple key. We check if it's possible
+ # and save its position. This function is called for
+ # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'.
+
+ # Check if a simple key is required at the current position.
+ required = not self.flow_level and self.indent == self.column
+
+ # A simple key is required only if it is the first token in the current
+ # line. Therefore it is always allowed.
+ assert self.allow_simple_key or not required
+
+ # The next token might be a simple key. Let's save it's number and
+ # position.
+ if self.allow_simple_key:
+ self.remove_possible_simple_key()
+ token_number = self.tokens_taken+len(self.tokens)
+ key = SimpleKey(token_number, required,
+ self.index, self.line, self.column, self.get_mark())
+ self.possible_simple_keys[self.flow_level] = key
+
+ def remove_possible_simple_key(self):
+ # Remove the saved possible key position at the current flow level.
+ if self.flow_level in self.possible_simple_keys:
+ key = self.possible_simple_keys[self.flow_level]
+
+ if key.required:
+ raise ScannerError("while scanning a simple key", key.mark,
+ "could not found expected ':'", self.get_mark())
+
+ del self.possible_simple_keys[self.flow_level]
+
+ # Indentation functions.
+
+ def unwind_indent(self, column):
+
+ ## In flow context, tokens should respect indentation.
+ ## Actually the condition should be `self.indent >= column` according to
+ ## the spec. But this condition will prohibit intuitively correct
+ ## constructions such as
+ ## key : {
+ ## }
+ #if self.flow_level and self.indent > column:
+ # raise ScannerError(None, None,
+ # "invalid intendation or unclosed '[' or '{'",
+ # self.get_mark())
+
+ # In the flow context, indentation is ignored. We make the scanner less
+ # restrictive then specification requires.
+ if self.flow_level:
+ return
+
+ # In block context, we may need to issue the BLOCK-END tokens.
+ while self.indent > column:
+ mark = self.get_mark()
+ self.indent = self.indents.pop()
+ self.tokens.append(BlockEndToken(mark, mark))
+
+ def add_indent(self, column):
+ # Check if we need to increase indentation.
+ if self.indent < column:
+ self.indents.append(self.indent)
+ self.indent = column
+ return True
+ return False
+
+ # Fetchers.
+
+ def fetch_stream_start(self):
+ # We always add STREAM-START as the first token and STREAM-END as the
+ # last token.
+
+ # Read the token.
+ mark = self.get_mark()
+
+ # Add STREAM-START.
+ self.tokens.append(StreamStartToken(mark, mark,
+ encoding=self.encoding))
+
+
+ def fetch_stream_end(self):
+
+ # Set the current intendation to -1.
+ self.unwind_indent(-1)
+
+ # Reset everything (not really needed).
+ self.allow_simple_key = False
+ self.possible_simple_keys = {}
+
+ # Read the token.
+ mark = self.get_mark()
+
+ # Add STREAM-END.
+ self.tokens.append(StreamEndToken(mark, mark))
+
+ # The steam is finished.
+ self.done = True
+
+ def fetch_directive(self):
+
+ # Set the current intendation to -1.
+ self.unwind_indent(-1)
+
+ # Reset simple keys.
+ self.remove_possible_simple_key()
+ self.allow_simple_key = False
+
+ # Scan and add DIRECTIVE.
+ self.tokens.append(self.scan_directive())
+
+ def fetch_document_start(self):
+ self.fetch_document_indicator(DocumentStartToken)
+
+ def fetch_document_end(self):
+ self.fetch_document_indicator(DocumentEndToken)
+
+ def fetch_document_indicator(self, TokenClass):
+
+ # Set the current intendation to -1.
+ self.unwind_indent(-1)
+
+ # Reset simple keys. Note that there could not be a block collection
+ # after '---'.
+ self.remove_possible_simple_key()
+ self.allow_simple_key = False
+
+ # Add DOCUMENT-START or DOCUMENT-END.
+ start_mark = self.get_mark()
+ self.forward(3)
+ end_mark = self.get_mark()
+ self.tokens.append(TokenClass(start_mark, end_mark))
+
+ def fetch_flow_sequence_start(self):
+ self.fetch_flow_collection_start(FlowSequenceStartToken)
+
+ def fetch_flow_mapping_start(self):
+ self.fetch_flow_collection_start(FlowMappingStartToken)
+
+ def fetch_flow_collection_start(self, TokenClass):
+
+ # '[' and '{' may start a simple key.
+ self.save_possible_simple_key()
+
+ # Increase the flow level.
+ self.flow_level += 1
+
+ # Simple keys are allowed after '[' and '{'.
+ self.allow_simple_key = True
+
+ # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(TokenClass(start_mark, end_mark))
+
+ def fetch_flow_sequence_end(self):
+ self.fetch_flow_collection_end(FlowSequenceEndToken)
+
+ def fetch_flow_mapping_end(self):
+ self.fetch_flow_collection_end(FlowMappingEndToken)
+
+ def fetch_flow_collection_end(self, TokenClass):
+
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Decrease the flow level.
+ self.flow_level -= 1
+
+ # No simple keys after ']' or '}'.
+ self.allow_simple_key = False
+
+ # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(TokenClass(start_mark, end_mark))
+
+ def fetch_flow_entry(self):
+
+ # Simple keys are allowed after ','.
+ self.allow_simple_key = True
+
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Add FLOW-ENTRY.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(FlowEntryToken(start_mark, end_mark))
+
+ def fetch_block_entry(self):
+
+ # Block context needs additional checks.
+ if not self.flow_level:
+
+ # Are we allowed to start a new entry?
+ if not self.allow_simple_key:
+ raise ScannerError(None, None,
+ "sequence entries are not allowed here",
+ self.get_mark())
+
+ # We may need to add BLOCK-SEQUENCE-START.
+ if self.add_indent(self.column):
+ mark = self.get_mark()
+ self.tokens.append(BlockSequenceStartToken(mark, mark))
+
+ # It's an error for the block entry to occur in the flow context,
+ # but we let the parser detect this.
+ else:
+ pass
+
+ # Simple keys are allowed after '-'.
+ self.allow_simple_key = True
+
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Add BLOCK-ENTRY.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(BlockEntryToken(start_mark, end_mark))
+
+ def fetch_key(self):
+
+ # Block context needs additional checks.
+ if not self.flow_level:
+
+ # Are we allowed to start a key (not nessesary a simple)?
+ if not self.allow_simple_key:
+ raise ScannerError(None, None,
+ "mapping keys are not allowed here",
+ self.get_mark())
+
+ # We may need to add BLOCK-MAPPING-START.
+ if self.add_indent(self.column):
+ mark = self.get_mark()
+ self.tokens.append(BlockMappingStartToken(mark, mark))
+
+ # Simple keys are allowed after '?' in the block context.
+ self.allow_simple_key = not self.flow_level
+
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Add KEY.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(KeyToken(start_mark, end_mark))
+
+ def fetch_value(self):
+
+ # Do we determine a simple key?
+ if self.flow_level in self.possible_simple_keys:
+
+ # Add KEY.
+ key = self.possible_simple_keys[self.flow_level]
+ del self.possible_simple_keys[self.flow_level]
+ self.tokens.insert(key.token_number-self.tokens_taken,
+ KeyToken(key.mark, key.mark))
+
+ # If this key starts a new block mapping, we need to add
+ # BLOCK-MAPPING-START.
+ if not self.flow_level:
+ if self.add_indent(key.column):
+ self.tokens.insert(key.token_number-self.tokens_taken,
+ BlockMappingStartToken(key.mark, key.mark))
+
+ # There cannot be two simple keys one after another.
+ self.allow_simple_key = False
+
+ # It must be a part of a complex key.
+ else:
+
+ # Block context needs additional checks.
+ # (Do we really need them? They will be catched by the parser
+ # anyway.)
+ if not self.flow_level:
+
+ # We are allowed to start a complex value if and only if
+ # we can start a simple key.
+ if not self.allow_simple_key:
+ raise ScannerError(None, None,
+ "mapping values are not allowed here",
+ self.get_mark())
+
+ # If this value starts a new block mapping, we need to add
+ # BLOCK-MAPPING-START. It will be detected as an error later by
+ # the parser.
+ if not self.flow_level:
+ if self.add_indent(self.column):
+ mark = self.get_mark()
+ self.tokens.append(BlockMappingStartToken(mark, mark))
+
+ # Simple keys are allowed after ':' in the block context.
+ self.allow_simple_key = not self.flow_level
+
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Add VALUE.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(ValueToken(start_mark, end_mark))
+
+ def fetch_alias(self):
+
+ # ALIAS could be a simple key.
+ self.save_possible_simple_key()
+
+ # No simple keys after ALIAS.
+ self.allow_simple_key = False
+
+ # Scan and add ALIAS.
+ self.tokens.append(self.scan_anchor(AliasToken))
+
+ def fetch_anchor(self):
+
+ # ANCHOR could start a simple key.
+ self.save_possible_simple_key()
+
+ # No simple keys after ANCHOR.
+ self.allow_simple_key = False
+
+ # Scan and add ANCHOR.
+ self.tokens.append(self.scan_anchor(AnchorToken))
+
+ def fetch_tag(self):
+
+ # TAG could start a simple key.
+ self.save_possible_simple_key()
+
+ # No simple keys after TAG.
+ self.allow_simple_key = False
+
+ # Scan and add TAG.
+ self.tokens.append(self.scan_tag())
+
+ def fetch_literal(self):
+ self.fetch_block_scalar(style='|')
+
+ def fetch_folded(self):
+ self.fetch_block_scalar(style='>')
+
+ def fetch_block_scalar(self, style):
+
+ # A simple key may follow a block scalar.
+ self.allow_simple_key = True
+
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Scan and add SCALAR.
+ self.tokens.append(self.scan_block_scalar(style))
+
+ def fetch_single(self):
+ self.fetch_flow_scalar(style='\'')
+
+ def fetch_double(self):
+ self.fetch_flow_scalar(style='"')
+
+ def fetch_flow_scalar(self, style):
+
+ # A flow scalar could be a simple key.
+ self.save_possible_simple_key()
+
+ # No simple keys after flow scalars.
+ self.allow_simple_key = False
+
+ # Scan and add SCALAR.
+ self.tokens.append(self.scan_flow_scalar(style))
+
+ def fetch_plain(self):
+
+ # A plain scalar could be a simple key.
+ self.save_possible_simple_key()
+
+ # No simple keys after plain scalars. But note that `scan_plain` will
+ # change this flag if the scan is finished at the beginning of the
+ # line.
+ self.allow_simple_key = False
+
+ # Scan and add SCALAR. May change `allow_simple_key`.
+ self.tokens.append(self.scan_plain())
+
+ # Checkers.
+
+ def check_directive(self):
+
+ # DIRECTIVE: ^ '%' ...
+ # The '%' indicator is already checked.
+ if self.column == 0:
+ return True
+
+ def check_document_start(self):
+
+ # DOCUMENT-START: ^ '---' (' '|'\n')
+ if self.column == 0:
+ if self.prefix(3) == u'---' \
+ and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029':
+ return True
+
+ def check_document_end(self):
+
+ # DOCUMENT-END: ^ '...' (' '|'\n')
+ if self.column == 0:
+ if self.prefix(3) == u'...' \
+ and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029':
+ return True
+
+ def check_block_entry(self):
+
+ # BLOCK-ENTRY: '-' (' '|'\n')
+ return self.peek(1) in u'\0 \t\r\n\x85\u2028\u2029'
+
+ def check_key(self):
+
+ # KEY(flow context): '?'
+ if self.flow_level:
+ return True
+
+ # KEY(block context): '?' (' '|'\n')
+ else:
+ return self.peek(1) in u'\0 \t\r\n\x85\u2028\u2029'
+
+ def check_value(self):
+
+ # VALUE(flow context): ':'
+ if self.flow_level:
+ return True
+
+ # VALUE(block context): ':' (' '|'\n')
+ else:
+ return self.peek(1) in u'\0 \t\r\n\x85\u2028\u2029'
+
+ def check_plain(self):
+
+ # A plain scalar may start with any non-space character except:
+ # '-', '?', ':', ',', '[', ']', '{', '}',
+ # '#', '&', '*', '!', '|', '>', '\'', '\"',
+ # '%', '@', '`'.
+ #
+ # It may also start with
+ # '-', '?', ':'
+ # if it is followed by a non-space character.
+ #
+ # Note that we limit the last rule to the block context (except the
+ # '-' character) because we want the flow context to be space
+ # independent.
+ ch = self.peek()
+ return ch not in u'\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \
+ or (self.peek(1) not in u'\0 \t\r\n\x85\u2028\u2029'
+ and (ch == u'-' or (not self.flow_level and ch in u'?:')))
+
+ # Scanners.
+
+ def scan_to_next_token(self):
+ # We ignore spaces, line breaks and comments.
+ # If we find a line break in the block context, we set the flag
+ # `allow_simple_key` on.
+ # The byte order mark is stripped if it's the first character in the
+ # stream. We do not yet support BOM inside the stream as the
+ # specification requires. Any such mark will be considered as a part
+ # of the document.
+ #
+ # TODO: We need to make tab handling rules more sane. A good rule is
+ # Tabs cannot precede tokens
+ # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END,
+ # KEY(block), VALUE(block), BLOCK-ENTRY
+ # So the checking code is
+ # if <TAB>:
+ # self.allow_simple_keys = False
+ # We also need to add the check for `allow_simple_keys == True` to
+ # `unwind_indent` before issuing BLOCK-END.
+ # Scanners for block, flow, and plain scalars need to be modified.
+
+ if self.index == 0 and self.peek() == u'\uFEFF':
+ self.forward()
+ found = False
+ while not found:
+ while self.peek() == u' ':
+ self.forward()
+ if self.peek() == u'#':
+ while self.peek() not in u'\0\r\n\x85\u2028\u2029':
+ self.forward()
+ if self.scan_line_break():
+ if not self.flow_level:
+ self.allow_simple_key = True
+ else:
+ found = True
+
+ def scan_directive(self):
+ # See the specification for details.
+ start_mark = self.get_mark()
+ self.forward()
+ name = self.scan_directive_name(start_mark)
+ value = None
+ if name == u'YAML':
+ value = self.scan_yaml_directive_value(start_mark)
+ end_mark = self.get_mark()
+ elif name == u'TAG':
+ value = self.scan_tag_directive_value(start_mark)
+ end_mark = self.get_mark()
+ else:
+ end_mark = self.get_mark()
+ while self.peek() not in u'\0\r\n\x85\u2028\u2029':
+ self.forward()
+ self.scan_directive_ignored_line(start_mark)
+ return DirectiveToken(name, value, start_mark, end_mark)
+
+ def scan_directive_name(self, start_mark):
+ # See the specification for details.
+ length = 0
+ ch = self.peek(length)
+ while u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-_':
+ length += 1
+ ch = self.peek(length)
+ if not length:
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected alphabetic or numeric character, but found %r"
+ % ch.encode('utf-8'), self.get_mark())
+ value = self.prefix(length)
+ self.forward(length)
+ ch = self.peek()
+ if ch not in u'\0 \r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected alphabetic or numeric character, but found %r"
+ % ch.encode('utf-8'), self.get_mark())
+ return value
+
+ def scan_yaml_directive_value(self, start_mark):
+ # See the specification for details.
+ while self.peek() == u' ':
+ self.forward()
+ major = self.scan_yaml_directive_number(start_mark)
+ if self.peek() != '.':
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected a digit or '.', but found %r"
+ % self.peek().encode('utf-8'),
+ self.get_mark())
+ self.forward()
+ minor = self.scan_yaml_directive_number(start_mark)
+ if self.peek() not in u'\0 \r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected a digit or ' ', but found %r"
+ % self.peek().encode('utf-8'),
+ self.get_mark())
+ return (major, minor)
+
+ def scan_yaml_directive_number(self, start_mark):
+ # See the specification for details.
+ ch = self.peek()
+ if not (u'0' <= ch <= '9'):
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected a digit, but found %r" % ch.encode('utf-8'),
+ self.get_mark())
+ length = 0
+ while u'0' <= self.peek(length) <= u'9':
+ length += 1
+ value = int(self.prefix(length))
+ self.forward(length)
+ return value
+
+ def scan_tag_directive_value(self, start_mark):
+ # See the specification for details.
+ while self.peek() == u' ':
+ self.forward()
+ handle = self.scan_tag_directive_handle(start_mark)
+ while self.peek() == u' ':
+ self.forward()
+ prefix = self.scan_tag_directive_prefix(start_mark)
+ return (handle, prefix)
+
+ def scan_tag_directive_handle(self, start_mark):
+ # See the specification for details.
+ value = self.scan_tag_handle('directive', start_mark)
+ ch = self.peek()
+ if ch != u' ':
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected ' ', but found %r" % ch.encode('utf-8'),
+ self.get_mark())
+ return value
+
+ def scan_tag_directive_prefix(self, start_mark):
+ # See the specification for details.
+ value = self.scan_tag_uri('directive', start_mark)
+ ch = self.peek()
+ if ch not in u'\0 \r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected ' ', but found %r" % ch.encode('utf-8'),
+ self.get_mark())
+ return value
+
+ def scan_directive_ignored_line(self, start_mark):
+ # See the specification for details.
+ while self.peek() == u' ':
+ self.forward()
+ if self.peek() == u'#':
+ while self.peek() not in u'\0\r\n\x85\u2028\u2029':
+ self.forward()
+ ch = self.peek()
+ if ch not in u'\0\r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a directive", start_mark,
+ "expected a comment or a line break, but found %r"
+ % ch.encode('utf-8'), self.get_mark())
+ self.scan_line_break()
+
+ def scan_anchor(self, TokenClass):
+ # The specification does not restrict characters for anchors and
+ # aliases. This may lead to problems, for instance, the document:
+ # [ *alias, value ]
+ # can be interpteted in two ways, as
+ # [ "value" ]
+ # and
+ # [ *alias , "value" ]
+ # Therefore we restrict aliases to numbers and ASCII letters.
+ start_mark = self.get_mark()
+ indicator = self.peek()
+ if indicator == '*':
+ name = 'alias'
+ else:
+ name = 'anchor'
+ self.forward()
+ length = 0
+ ch = self.peek(length)
+ while u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-_':
+ length += 1
+ ch = self.peek(length)
+ if not length:
+ raise ScannerError("while scanning an %s" % name, start_mark,
+ "expected alphabetic or numeric character, but found %r"
+ % ch.encode('utf-8'), self.get_mark())
+ value = self.prefix(length)
+ self.forward(length)
+ ch = self.peek()
+ if ch not in u'\0 \t\r\n\x85\u2028\u2029?:,]}%@`':
+ raise ScannerError("while scanning an %s" % name, start_mark,
+ "expected alphabetic or numeric character, but found %r"
+ % ch.encode('utf-8'), self.get_mark())
+ end_mark = self.get_mark()
+ return TokenClass(value, start_mark, end_mark)
+
+ def scan_tag(self):
+ # See the specification for details.
+ start_mark = self.get_mark()
+ ch = self.peek(1)
+ if ch == u'<':
+ handle = None
+ self.forward(2)
+ suffix = self.scan_tag_uri('tag', start_mark)
+ if self.peek() != u'>':
+ raise ScannerError("while parsing a tag", start_mark,
+ "expected '>', but found %r" % self.peek().encode('utf-8'),
+ self.get_mark())
+ self.forward()
+ elif ch in u'\0 \t\r\n\x85\u2028\u2029':
+ handle = None
+ suffix = u'!'
+ self.forward()
+ else:
+ length = 1
+ use_handle = False
+ while ch not in u'\0 \r\n\x85\u2028\u2029':
+ if ch == u'!':
+ use_handle = True
+ break
+ length += 1
+ ch = self.peek(length)
+ handle = u'!'
+ if use_handle:
+ handle = self.scan_tag_handle('tag', start_mark)
+ else:
+ handle = u'!'
+ self.forward()
+ suffix = self.scan_tag_uri('tag', start_mark)
+ ch = self.peek()
+ if ch not in u'\0 \r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a tag", start_mark,
+ "expected ' ', but found %r" % ch.encode('utf-8'),
+ self.get_mark())
+ value = (handle, suffix)
+ end_mark = self.get_mark()
+ return TagToken(value, start_mark, end_mark)
+
+ def scan_block_scalar(self, style):
+ # See the specification for details.
+
+ if style == '>':
+ folded = True
+ else:
+ folded = False
+
+ chunks = []
+ start_mark = self.get_mark()
+
+ # Scan the header.
+ self.forward()
+ chomping, increment = self.scan_block_scalar_indicators(start_mark)
+ self.scan_block_scalar_ignored_line(start_mark)
+
+ # Determine the indentation level and go to the first non-empty line.
+ min_indent = self.indent+1
+ if min_indent < 1:
+ min_indent = 1
+ if increment is None:
+ breaks, max_indent, end_mark = self.scan_block_scalar_indentation()
+ indent = max(min_indent, max_indent)
+ else:
+ indent = min_indent+increment-1
+ breaks, end_mark = self.scan_block_scalar_breaks(indent)
+ line_break = u''
+
+ # Scan the inner part of the block scalar.
+ while self.column == indent and self.peek() != u'\0':
+ chunks.extend(breaks)
+ leading_non_space = self.peek() not in u' \t'
+ length = 0
+ while self.peek(length) not in u'\0\r\n\x85\u2028\u2029':
+ length += 1
+ chunks.append(self.prefix(length))
+ self.forward(length)
+ line_break = self.scan_line_break()
+ breaks, end_mark = self.scan_block_scalar_breaks(indent)
+ if self.column == indent and self.peek() != u'\0':
+
+ # Unfortunately, folding rules are ambiguous.
+ #
+ # This is the folding according to the specification:
+
+ if folded and line_break == u'\n' \
+ and leading_non_space and self.peek() not in u' \t':
+ if not breaks:
+ chunks.append(u' ')
+ else:
+ chunks.append(line_break)
+
+ # This is Clark Evans's interpretation (also in the spec
+ # examples):
+ #
+ #if folded and line_break == u'\n':
+ # if not breaks:
+ # if self.peek() not in ' \t':
+ # chunks.append(u' ')
+ # else:
+ # chunks.append(line_break)
+ #else:
+ # chunks.append(line_break)
+ else:
+ break
+
+ # Chomp the tail.
+ if chomping is not False:
+ chunks.append(line_break)
+ if chomping is True:
+ chunks.extend(breaks)
+
+ # We are done.
+ return ScalarToken(u''.join(chunks), False, start_mark, end_mark,
+ style)
+
+ def scan_block_scalar_indicators(self, start_mark):
+ # See the specification for details.
+ chomping = None
+ increment = None
+ ch = self.peek()
+ if ch in u'+-':
+ if ch == '+':
+ chomping = True
+ else:
+ chomping = False
+ self.forward()
+ ch = self.peek()
+ if ch in u'0123456789':
+ increment = int(ch)
+ if increment == 0:
+ raise ScannerError("while scanning a block scalar", start_mark,
+ "expected indentation indicator in the range 1-9, but found 0",
+ self.get_mark())
+ self.forward()
+ elif ch in u'0123456789':
+ increment = int(ch)
+ if increment == 0:
+ raise ScannerError("while scanning a block scalar", start_mark,
+ "expected indentation indicator in the range 1-9, but found 0",
+ self.get_mark())
+ self.forward()
+ ch = self.peek()
+ if ch in u'+-':
+ if ch == '+':
+ chomping = True
+ else:
+ chomping = False
+ self.forward()
+ ch = self.peek()
+ if ch not in u'\0 \r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a block scalar", start_mark,
+ "expected chomping or indentation indicators, but found %r"
+ % ch.encode('utf-8'), self.get_mark())
+ return chomping, increment
+
+ def scan_block_scalar_ignored_line(self, start_mark):
+ # See the specification for details.
+ while self.peek() == u' ':
+ self.forward()
+ if self.peek() == u'#':
+ while self.peek() not in u'\0\r\n\x85\u2028\u2029':
+ self.forward()
+ ch = self.peek()
+ if ch not in u'\0\r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a block scalar", start_mark,
+ "expected a comment or a line break, but found %r"
+ % ch.encode('utf-8'), self.get_mark())
+ self.scan_line_break()
+
+ def scan_block_scalar_indentation(self):
+ # See the specification for details.
+ chunks = []
+ max_indent = 0
+ end_mark = self.get_mark()
+ while self.peek() in u' \r\n\x85\u2028\u2029':
+ if self.peek() != u' ':
+ chunks.append(self.scan_line_break())
+ end_mark = self.get_mark()
+ else:
+ self.forward()
+ if self.column > max_indent:
+ max_indent = self.column
+ return chunks, max_indent, end_mark
+
+ def scan_block_scalar_breaks(self, indent):
+ # See the specification for details.
+ chunks = []
+ end_mark = self.get_mark()
+ while self.column < indent and self.peek() == u' ':
+ self.forward()
+ while self.peek() in u'\r\n\x85\u2028\u2029':
+ chunks.append(self.scan_line_break())
+ end_mark = self.get_mark()
+ while self.column < indent and self.peek() == u' ':
+ self.forward()
+ return chunks, end_mark
+
+ def scan_flow_scalar(self, style):
+ # See the specification for details.
+ # Note that we loose indentation rules for quoted scalars. Quoted
+ # scalars don't need to adhere indentation because " and ' clearly
+ # mark the beginning and the end of them. Therefore we are less
+ # restrictive then the specification requires. We only need to check
+ # that document separators are not included in scalars.
+ if style == '"':
+ double = True
+ else:
+ double = False
+ chunks = []
+ start_mark = self.get_mark()
+ quote = self.peek()
+ self.forward()
+ chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark))
+ while self.peek() != quote:
+ chunks.extend(self.scan_flow_scalar_spaces(double, start_mark))
+ chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark))
+ self.forward()
+ end_mark = self.get_mark()
+ return ScalarToken(u''.join(chunks), False, start_mark, end_mark,
+ style)
+
+ ESCAPE_REPLACEMENTS = {
+ u'0': u'\0',
+ u'a': u'\x07',
+ u'b': u'\x08',
+ u't': u'\x09',
+ u'\t': u'\x09',
+ u'n': u'\x0A',
+ u'v': u'\x0B',
+ u'f': u'\x0C',
+ u'r': u'\x0D',
+ u'e': u'\x1B',
+ u' ': u'\x20',
+ u'\"': u'\"',
+ u'\\': u'\\',
+ u'N': u'\x85',
+ u'_': u'\xA0',
+ u'L': u'\u2028',
+ u'P': u'\u2029',
+ }
+
+ ESCAPE_CODES = {
+ u'x': 2,
+ u'u': 4,
+ u'U': 8,
+ }
+
+ def scan_flow_scalar_non_spaces(self, double, start_mark):
+ # See the specification for details.
+ chunks = []
+ while True:
+ length = 0
+ while self.peek(length) not in u'\'\"\\\0 \t\r\n\x85\u2028\u2029':
+ length += 1
+ if length:
+ chunks.append(self.prefix(length))
+ self.forward(length)
+ ch = self.peek()
+ if not double and ch == u'\'' and self.peek(1) == u'\'':
+ chunks.append(u'\'')
+ self.forward(2)
+ elif (double and ch == u'\'') or (not double and ch in u'\"\\'):
+ chunks.append(ch)
+ self.forward()
+ elif double and ch == u'\\':
+ self.forward()
+ ch = self.peek()
+ if ch in self.ESCAPE_REPLACEMENTS:
+ chunks.append(self.ESCAPE_REPLACEMENTS[ch])
+ self.forward()
+ elif ch in self.ESCAPE_CODES:
+ length = self.ESCAPE_CODES[ch]
+ self.forward()
+ for k in range(length):
+ if self.peek(k) not in u'0123456789ABCDEFabcdef':
+ raise ScannerError("while scanning a double-quoted scalar", start_mark,
+ "expected escape sequence of %d hexdecimal numbers, but found %r" %
+ (length, self.peek(k).encode('utf-8')), self.get_mark())
+ code = int(self.prefix(length), 16)
+ chunks.append(unichr(code))
+ self.forward(length)
+ elif ch in u'\r\n\x85\u2028\u2029':
+ self.scan_line_break()
+ chunks.extend(self.scan_flow_scalar_breaks(double, start_mark))
+ else:
+ raise ScannerError("while scanning a double-quoted scalar", start_mark,
+ "found unknown escape character %r" % ch.encode('utf-8'), self.get_mark())
+ else:
+ return chunks
+
+ def scan_flow_scalar_spaces(self, double, start_mark):
+ # See the specification for details.
+ chunks = []
+ length = 0
+ while self.peek(length) in u' \t':
+ length += 1
+ whitespaces = self.prefix(length)
+ self.forward(length)
+ ch = self.peek()
+ if ch == u'\0':
+ raise ScannerError("while scanning a quoted scalar", start_mark,
+ "found unexpected end of stream", self.get_mark())
+ elif ch in u'\r\n\x85\u2028\u2029':
+ line_break = self.scan_line_break()
+ breaks = self.scan_flow_scalar_breaks(double, start_mark)
+ if line_break != u'\n':
+ chunks.append(line_break)
+ elif not breaks:
+ chunks.append(u' ')
+ chunks.extend(breaks)
+ else:
+ chunks.append(whitespaces)
+ return chunks
+
+ def scan_flow_scalar_breaks(self, double, start_mark):
+ # See the specification for details.
+ chunks = []
+ while True:
+ # Instead of checking indentation, we check for document
+ # separators.
+ prefix = self.prefix(3)
+ if (prefix == u'---' or prefix == u'...') \
+ and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029':
+ raise ScannerError("while scanning a quoted scalar", start_mark,
+ "found unexpected document separator", self.get_mark())
+ while self.peek() in u' \t':
+ self.forward()
+ if self.peek() in u'\r\n\x85\u2028\u2029':
+ chunks.append(self.scan_line_break())
+ else:
+ return chunks
+
+ def scan_plain(self):
+ # See the specification for details.
+ # We add an additional restriction for the flow context:
+ # plain scalars in the flow context cannot contain ',', ':' and '?'.
+ # We also keep track of the `allow_simple_key` flag here.
+ # Indentation rules are loosed for the flow context.
+ chunks = []
+ start_mark = self.get_mark()
+ end_mark = start_mark
+ indent = self.indent+1
+ # We allow zero indentation for scalars, but then we need to check for
+ # document separators at the beginning of the line.
+ #if indent == 0:
+ # indent = 1
+ spaces = []
+ while True:
+ length = 0
+ if self.peek() == u'#':
+ break
+ while True:
+ ch = self.peek(length)
+ if ch in u'\0 \t\r\n\x85\u2028\u2029' \
+ or (not self.flow_level and ch == u':' and
+ self.peek(length+1) in u'\0 \t\r\n\x85\u2028\u2029') \
+ or (self.flow_level and ch in u',:?[]{}'):
+ break
+ length += 1
+ # It's not clear what we should do with ':' in the flow context.
+ if (self.flow_level and ch == u':'
+ and self.peek(length+1) not in u'\0 \t\r\n\x85\u2028\u2029,[]{}'):
+ self.forward(length)
+ raise ScannerError("while scanning a plain scalar", start_mark,
+ "found unexpected ':'", self.get_mark(),
+ "Please check http://pyyaml.org/wiki/YAMLColonInFlowContext for details.")
+ if length == 0:
+ break
+ self.allow_simple_key = False
+ chunks.extend(spaces)
+ chunks.append(self.prefix(length))
+ self.forward(length)
+ end_mark = self.get_mark()
+ spaces = self.scan_plain_spaces(indent, start_mark)
+ if not spaces or self.peek() == u'#' \
+ or (not self.flow_level and self.column < indent):
+ break
+ return ScalarToken(u''.join(chunks), True, start_mark, end_mark)
+
+ def scan_plain_spaces(self, indent, start_mark):
+ # See the specification for details.
+ # The specification is really confusing about tabs in plain scalars.
+ # We just forbid them completely. Do not use tabs in YAML!
+ chunks = []
+ length = 0
+ while self.peek(length) in u' ':
+ length += 1
+ whitespaces = self.prefix(length)
+ self.forward(length)
+ ch = self.peek()
+ if ch in u'\r\n\x85\u2028\u2029':
+ line_break = self.scan_line_break()
+ self.allow_simple_key = True
+ prefix = self.prefix(3)
+ if (prefix == u'---' or prefix == u'...') \
+ and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029':
+ return
+ breaks = []
+ while self.peek() in u' \r\n\x85\u2028\u2029':
+ if self.peek() == ' ':
+ self.forward()
+ else:
+ breaks.append(self.scan_line_break())
+ prefix = self.prefix(3)
+ if (prefix == u'---' or prefix == u'...') \
+ and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029':
+ return
+ if line_break != u'\n':
+ chunks.append(line_break)
+ elif not breaks:
+ chunks.append(u' ')
+ chunks.extend(breaks)
+ elif whitespaces:
+ chunks.append(whitespaces)
+ return chunks
+
+ def scan_tag_handle(self, name, start_mark):
+ # See the specification for details.
+ # For some strange reasons, the specification does not allow '_' in
+ # tag handles. I have allowed it anyway.
+ ch = self.peek()
+ if ch != u'!':
+ raise ScannerError("while scanning a %s" % name, start_mark,
+ "expected '!', but found %r" % ch.encode('utf-8'),
+ self.get_mark())
+ length = 1
+ ch = self.peek(length)
+ if ch != u' ':
+ while u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-_':
+ length += 1
+ ch = self.peek(length)
+ if ch != u'!':
+ self.forward(length)
+ raise ScannerError("while scanning a %s" % name, start_mark,
+ "expected '!', but found %r" % ch.encode('utf-8'),
+ self.get_mark())
+ length += 1
+ value = self.prefix(length)
+ self.forward(length)
+ return value
+
+ def scan_tag_uri(self, name, start_mark):
+ # See the specification for details.
+ # Note: we do not check if URI is well-formed.
+ chunks = []
+ length = 0
+ ch = self.peek(length)
+ while u'0' <= ch <= u'9' or u'A' <= ch <= 'Z' or u'a' <= ch <= 'z' \
+ or ch in u'-;/?:@&=+$,_.!~*\'()[]%':
+ if ch == u'%':
+ chunks.append(self.prefix(length))
+ self.forward(length)
+ length = 0
+ chunks.append(self.scan_uri_escapes(name, start_mark))
+ else:
+ length += 1
+ ch = self.peek(length)
+ if length:
+ chunks.append(self.prefix(length))
+ self.forward(length)
+ length = 0
+ if not chunks:
+ raise ScannerError("while parsing a %s" % name, start_mark,
+ "expected URI, but found %r" % ch.encode('utf-8'),
+ self.get_mark())
+ return u''.join(chunks)
+
+ def scan_uri_escapes(self, name, start_mark):
+ # See the specification for details.
+ bytes = []
+ mark = self.get_mark()
+ while self.peek() == u'%':
+ self.forward()
+ for k in range(2):
+ if self.peek(k) not in u'0123456789ABCDEFabcdef':
+ raise ScannerError("while scanning a %s" % name, start_mark,
+ "expected URI escape sequence of 2 hexdecimal numbers, but found %r" %
+ (self.peek(k).encode('utf-8')), self.get_mark())
+ bytes.append(chr(int(self.prefix(2), 16)))
+ self.forward(2)
+ try:
+ value = unicode(''.join(bytes), 'utf-8')
+ except UnicodeDecodeError, exc:
+ raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark)
+ return value
+
+ def scan_line_break(self):
+ # Transforms:
+ # '\r\n' : '\n'
+ # '\r' : '\n'
+ # '\n' : '\n'
+ # '\x85' : '\n'
+ # '\u2028' : '\u2028'
+ # '\u2029 : '\u2029'
+ # default : ''
+ ch = self.peek()
+ if ch in u'\r\n\x85':
+ if self.prefix(2) == u'\r\n':
+ self.forward(2)
+ else:
+ self.forward()
+ return u'\n'
+ elif ch in u'\u2028\u2029':
+ self.forward()
+ return ch
+ return u''
+
+#try:
+# import psyco
+# psyco.bind(Scanner)
+#except ImportError:
+# pass
+
diff --git a/google_appengine/lib/yaml/lib/yaml/scanner.pyc b/google_appengine/lib/yaml/lib/yaml/scanner.pyc
new file mode 100644
index 0000000..55a01dd
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/scanner.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/serializer.py b/google_appengine/lib/yaml/lib/yaml/serializer.py
new file mode 100755
index 0000000..2101f95
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/serializer.py
@@ -0,0 +1,111 @@
+
+__all__ = ['Serializer', 'SerializerError']
+
+from error import YAMLError
+from events import *
+from nodes import *
+
+class SerializerError(YAMLError):
+ pass
+
+class Serializer(object):
+
+ ANCHOR_TEMPLATE = u'id%03d'
+
+ def __init__(self, encoding=None,
+ explicit_start=None, explicit_end=None, version=None, tags=None):
+ self.use_encoding = encoding
+ self.use_explicit_start = explicit_start
+ self.use_explicit_end = explicit_end
+ self.use_version = version
+ self.use_tags = tags
+ self.serialized_nodes = {}
+ self.anchors = {}
+ self.last_anchor_id = 0
+ self.closed = None
+
+ def open(self):
+ if self.closed is None:
+ self.emit(StreamStartEvent(encoding=self.use_encoding))
+ self.closed = False
+ elif self.closed:
+ raise SerializerError("serializer is closed")
+ else:
+ raise SerializerError("serializer is already opened")
+
+ def close(self):
+ if self.closed is None:
+ raise SerializerError("serializer is not opened")
+ elif not self.closed:
+ self.emit(StreamEndEvent())
+ self.closed = True
+
+ #def __del__(self):
+ # self.close()
+
+ def serialize(self, node):
+ if self.closed is None:
+ raise SerializerError("serializer is not opened")
+ elif self.closed:
+ raise SerializerError("serializer is closed")
+ self.emit(DocumentStartEvent(explicit=self.use_explicit_start,
+ version=self.use_version, tags=self.use_tags))
+ self.anchor_node(node)
+ self.serialize_node(node, None, None)
+ self.emit(DocumentEndEvent(explicit=self.use_explicit_end))
+ self.serialized_nodes = {}
+ self.anchors = {}
+ self.last_alias_id = 0
+
+ def anchor_node(self, node):
+ if node in self.anchors:
+ if self.anchors[node] is None:
+ self.anchors[node] = self.generate_anchor(node)
+ else:
+ self.anchors[node] = None
+ if isinstance(node, SequenceNode):
+ for item in node.value:
+ self.anchor_node(item)
+ elif isinstance(node, MappingNode):
+ for key, value in node.value:
+ self.anchor_node(key)
+ self.anchor_node(value)
+
+ def generate_anchor(self, node):
+ self.last_anchor_id += 1
+ return self.ANCHOR_TEMPLATE % self.last_anchor_id
+
+ def serialize_node(self, node, parent, index):
+ alias = self.anchors[node]
+ if node in self.serialized_nodes:
+ self.emit(AliasEvent(alias))
+ else:
+ self.serialized_nodes[node] = True
+ self.descend_resolver(parent, index)
+ if isinstance(node, ScalarNode):
+ detected_tag = self.resolve(ScalarNode, node.value, (True, False))
+ default_tag = self.resolve(ScalarNode, node.value, (False, True))
+ implicit = (node.tag == detected_tag), (node.tag == default_tag)
+ self.emit(ScalarEvent(alias, node.tag, implicit, node.value,
+ style=node.style))
+ elif isinstance(node, SequenceNode):
+ implicit = (node.tag
+ == self.resolve(SequenceNode, node.value, True))
+ self.emit(SequenceStartEvent(alias, node.tag, implicit,
+ flow_style=node.flow_style))
+ index = 0
+ for item in node.value:
+ self.serialize_node(item, node, index)
+ index += 1
+ self.emit(SequenceEndEvent())
+ elif isinstance(node, MappingNode):
+ implicit = (node.tag
+ == self.resolve(MappingNode, node.value, True))
+ self.emit(MappingStartEvent(alias, node.tag, implicit,
+ flow_style=node.flow_style))
+ for key, value in node.value:
+ self.serialize_node(key, node, None)
+ self.serialize_node(value, node, key)
+ self.emit(MappingEndEvent())
+ self.ascend_resolver()
+
diff --git a/google_appengine/lib/yaml/lib/yaml/serializer.pyc b/google_appengine/lib/yaml/lib/yaml/serializer.pyc
new file mode 100644
index 0000000..4fbc4d2
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/serializer.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/lib/yaml/tokens.py b/google_appengine/lib/yaml/lib/yaml/tokens.py
new file mode 100755
index 0000000..4d0b48a
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/tokens.py
@@ -0,0 +1,104 @@
+
+class Token(object):
+ def __init__(self, start_mark, end_mark):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ def __repr__(self):
+ attributes = [key for key in self.__dict__
+ if not key.endswith('_mark')]
+ attributes.sort()
+ arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
+ for key in attributes])
+ return '%s(%s)' % (self.__class__.__name__, arguments)
+
+#class BOMToken(Token):
+# id = '<byte order mark>'
+
+class DirectiveToken(Token):
+ id = '<directive>'
+ def __init__(self, name, value, start_mark, end_mark):
+ self.name = name
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+class DocumentStartToken(Token):
+ id = '<document start>'
+
+class DocumentEndToken(Token):
+ id = '<document end>'
+
+class StreamStartToken(Token):
+ id = '<stream start>'
+ def __init__(self, start_mark=None, end_mark=None,
+ encoding=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.encoding = encoding
+
+class StreamEndToken(Token):
+ id = '<stream end>'
+
+class BlockSequenceStartToken(Token):
+ id = '<block sequence start>'
+
+class BlockMappingStartToken(Token):
+ id = '<block mapping start>'
+
+class BlockEndToken(Token):
+ id = '<block end>'
+
+class FlowSequenceStartToken(Token):
+ id = '['
+
+class FlowMappingStartToken(Token):
+ id = '{'
+
+class FlowSequenceEndToken(Token):
+ id = ']'
+
+class FlowMappingEndToken(Token):
+ id = '}'
+
+class KeyToken(Token):
+ id = '?'
+
+class ValueToken(Token):
+ id = ':'
+
+class BlockEntryToken(Token):
+ id = '-'
+
+class FlowEntryToken(Token):
+ id = ','
+
+class AliasToken(Token):
+ id = '<alias>'
+ def __init__(self, value, start_mark, end_mark):
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+class AnchorToken(Token):
+ id = '<anchor>'
+ def __init__(self, value, start_mark, end_mark):
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+class TagToken(Token):
+ id = '<tag>'
+ def __init__(self, value, start_mark, end_mark):
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+class ScalarToken(Token):
+ id = '<scalar>'
+ def __init__(self, value, plain, start_mark, end_mark, style=None):
+ self.value = value
+ self.plain = plain
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.style = style
+
diff --git a/google_appengine/lib/yaml/lib/yaml/tokens.pyc b/google_appengine/lib/yaml/lib/yaml/tokens.pyc
new file mode 100644
index 0000000..83f0719
--- /dev/null
+++ b/google_appengine/lib/yaml/lib/yaml/tokens.pyc
Binary files differ
diff --git a/google_appengine/lib/yaml/setup.cfg b/google_appengine/lib/yaml/setup.cfg
new file mode 100644
index 0000000..118501d
--- /dev/null
+++ b/google_appengine/lib/yaml/setup.cfg
@@ -0,0 +1,28 @@
+
+# The INCLUDE and LIB directories to build the '_yaml' extension.
+# You may also set them using the options '-I' and '-L'.
+[build_ext]
+
+# List of directories to search for 'yaml.h' (separated by ':').
+#include_dirs=/usr/local/include:../../include
+
+# List of directories to search for 'libyaml.a' (separated by ':').
+#library_dirs=/usr/local/lib:../../lib
+
+# An alternative compiler to build the extention.
+#compiler=mingw32
+
+# Additional preprocessor definitions might be required.
+#define=YAML_DECLARE_STATIC
+
+# The following options are used to build PyYAML Windows installer
+# for Python 2.3 on my PC:
+#include_dirs=../../libyaml/trunk/include
+#library_dirs=../../libyaml/trunk/win32/vc6/output/release/lib
+#define=YAML_DECLARE_STATIC
+
+# The following options are used to build PyYAML Windows installer
+# for Python 2.4 and Python 2.5 on my PC:
+#include_dirs=../../libyaml/trunk/include
+#library_dirs=../../libyaml/trunk/win32/vs2003/output/release/lib
+#define=YAML_DECLARE_STATIC
diff --git a/google_appengine/lib/yaml/setup.py b/google_appengine/lib/yaml/setup.py
new file mode 100755
index 0000000..8b08a4a
--- /dev/null
+++ b/google_appengine/lib/yaml/setup.py
@@ -0,0 +1,53 @@
+
+NAME = 'PyYAML'
+VERSION = '3.05'
+DESCRIPTION = "YAML parser and emitter for Python"
+LONG_DESCRIPTION = """\
+YAML is a data serialization format designed for human readability and
+interaction with scripting languages. PyYAML is a YAML parser and
+emitter for Python.
+
+PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
+support, capable extension API, and sensible error messages. PyYAML
+supports standard YAML tags and provides Python-specific tags that allow
+to represent an arbitrary Python object.
+
+PyYAML is applicable for a broad range of tasks from complex
+configuration files to object serialization and persistance."""
+AUTHOR = "Kirill Simonov"
+AUTHOR_EMAIL = 'xi@resolvent.net'
+LICENSE = "MIT"
+PLATFORMS = "Any"
+URL = "http://pyyaml.org/wiki/PyYAML"
+DOWNLOAD_URL = "http://pyyaml.org/download/pyyaml/%s-%s.tar.gz" % (NAME, VERSION)
+CLASSIFIERS = [
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Text Processing :: Markup",
+]
+
+from distutils.core import setup
+
+if __name__ == '__main__':
+
+ setup(
+ name=NAME,
+ version=VERSION,
+ description=DESCRIPTION,
+ long_description=LONG_DESCRIPTION,
+ author=AUTHOR,
+ author_email=AUTHOR_EMAIL,
+ license=LICENSE,
+ platforms=PLATFORMS,
+ url=URL,
+ download_url=DOWNLOAD_URL,
+ classifiers=CLASSIFIERS,
+
+ package_dir={'': 'lib'},
+ packages=['yaml'],
+ )
+
diff --git a/google_appengine/lib/yaml/setup_with_libyaml.py b/google_appengine/lib/yaml/setup_with_libyaml.py
new file mode 100755
index 0000000..a084ca7
--- /dev/null
+++ b/google_appengine/lib/yaml/setup_with_libyaml.py
@@ -0,0 +1,31 @@
+
+from setup import *
+
+from distutils.core import setup
+from distutils.extension import Extension
+from Pyrex.Distutils import build_ext
+
+if __name__ == '__main__':
+
+ setup(
+ name=NAME,
+ version=VERSION,
+ description=DESCRIPTION,
+ long_description=LONG_DESCRIPTION,
+ author=AUTHOR,
+ author_email=AUTHOR_EMAIL,
+ license=LICENSE,
+ platforms=PLATFORMS,
+ url=URL,
+ download_url=DOWNLOAD_URL,
+ classifiers=CLASSIFIERS,
+
+ package_dir={'': 'lib'},
+ packages=['yaml'],
+ ext_modules=[
+ Extension("_yaml", ["ext/_yaml.pyx"], libraries=['yaml']),
+ ],
+
+ cmdclass = {'build_ext': build_ext}
+ )
+
diff --git a/google_appengine/new_project_template/app.yaml b/google_appengine/new_project_template/app.yaml
new file mode 100644
index 0000000..b52b311
--- /dev/null
+++ b/google_appengine/new_project_template/app.yaml
@@ -0,0 +1,8 @@
+application: new-project-template
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: .*
+ script: main.py
diff --git a/google_appengine/new_project_template/index.yaml b/google_appengine/new_project_template/index.yaml
new file mode 100644
index 0000000..8e6046d
--- /dev/null
+++ b/google_appengine/new_project_template/index.yaml
@@ -0,0 +1,12 @@
+indexes:
+
+# AUTOGENERATED
+
+# 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.
+
diff --git a/google_appengine/new_project_template/main.py b/google_appengine/new_project_template/main.py
new file mode 100755
index 0000000..c940824
--- /dev/null
+++ b/google_appengine/new_project_template/main.py
@@ -0,0 +1,40 @@
+#!/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 wsgiref.handlers
+
+
+from google.appengine.ext import webapp
+
+
+class MainHandler(webapp.RequestHandler):
+
+ def get(self):
+ self.response.out.write('Hello world!')
+
+
+def main():
+ application = webapp.WSGIApplication([('/', MainHandler)],
+ debug=True)
+ wsgiref.handlers.CGIHandler().run(application)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/google_appengine/remote_api_shell.py b/google_appengine/remote_api_shell.py
new file mode 100755
index 0000000..9bda605
--- /dev/null
+++ b/google_appengine/remote_api_shell.py
@@ -0,0 +1,60 @@
+#!/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', 'webob'),
+ os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'),
+]
+
+SCRIPT_EXCEPTIONS = {
+ "dev_appserver.py" : "dev_appserver_main.py"
+}
+
+def run_file(file_path, globals_, script_dir=SCRIPT_DIR):
+ """Execute the file at the specified path with the passed-in globals."""
+ sys.path = EXTRA_PATHS + 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/templates/logging_console.js b/google_appengine/templates/logging_console.js
new file mode 100644
index 0000000..ae5525a
--- /dev/null
+++ b/google_appengine/templates/logging_console.js
@@ -0,0 +1,257 @@
+// Copyright 2007 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.
+
+/**
+ * @fileoverview Defines the code for the application logging console.
+ */
+
+/**
+ * The namespace we're using for all javascript classes and functions related
+ * to the logging console.
+ */
+var AH = {};
+
+
+/**
+ * A collection of utility functions for reading and writing cookies.
+ * @constructor
+ */
+AH.CookieUtil = function() {};
+
+
+/**
+ * Creates and adds a cookie with the specified expiration.
+ * @param {String} name The name of the desired cookie.
+ * @param {String} value The value of the desired cookie.
+ * @param {Number} opt_days If non-negative, the expiration time in days of the
+ * desired cookie. If not provided, the default value is 1.
+ */
+AH.CookieUtil.prototype.setCookie = function(name, value, opt_days) {
+ if (opt_days == null) {
+ opt_days = 1;
+ }
+
+ var expires = '';
+ if (opt_days < 0) {
+ var date = new Date;
+ date.setTime(date.getTime() + (opt_days * 24 * 60 * 60 * 1000));
+ expires = '; expires=' + date.toGMTString();
+ }
+ document.cookie = name + '=' + value + expires + '; path=/';
+};
+
+
+/**
+ * Returns the value of the requested cookie if it is available, and otherwise
+ * returns a default value.
+ * @param {String} name The name of the requested cookie.
+ * @param {String} defaultValue The value to return if the requested cookie
+ * cannot be found.
+ * @return {String} The requested cookie's value, or the default value.
+ */
+AH.CookieUtil.prototype.getCookie = function(name, defaultValue) {
+ var nameEQ = name + '=';
+ var cookiePieces = document.cookie.split(';');
+ for (var i = 0; i < cookiePieces.length; i++) {
+ var c = cookiePieces[i];
+ c = c.replace(/^\s+/, '');
+ if (c.indexOf(nameEQ) == 0) {
+ return c.substring(nameEQ.length, c.length);
+ }
+ }
+ return defaultValue;
+};
+
+
+/**
+ * Deletes the specified cookie.
+ * @param {String} name The name of the specified cookie.
+ */
+AH.CookieUtil.prototype.removeCookie = function(name) {
+ this.setCookie(name, '', -100);
+};
+
+
+/**
+ * The logging console is a div that displays log statements generated by the
+ * application during it's execution. It can be moved around, and the verbosity
+ * can be adjusted.
+ * @constructor
+ */
+AH.LoggingConsole = function() {
+ this.baseDiv = document.getElementById('_ah_base');
+ this.logSeverityLevels = ['debug', 'info', 'warning', 'error', 'critical'];
+ this.cookieUtil = new AH.CookieUtil;
+};
+
+
+/**
+ * Creates and positions the logging console based on preferences available from
+ * the cookies '_ah_severity' and '_ah_position'.
+ */
+AH.LoggingConsole.prototype.initConsole = function() {
+ // Define the font colors for the different log severity levels.
+ this.addCssRule('._ah_logline_debug_prefix', 'color:#110000');
+ this.addCssRule('._ah_logline_info_prefix', 'color:#440000');
+ this.addCssRule('._ah_logline_warning_prefix', 'color:#880000');
+ this.addCssRule('._ah_logline_error_prefix', 'color:#CC0000');
+ this.addCssRule('._ah_logline_critical_prefix', 'color:#FF0000');
+
+ // Change to the severity level stored in the cookie, defaulting to the lowest
+ // severity level.
+ this.changeLogSeverity(this.cookieUtil.getCookie('_ah_severity', 'debug'));
+
+ // Move the console to position stored in the cookie, defaulting to the
+ // bottom right position.
+ this.moveBaseDiv(this.cookieUtil.getCookie('_ah_position', 'down_right'));
+};
+
+
+/**
+ * Add CSS rules to the document to modify the presentation for elements of the
+ * specified class name. This works for IE and Firefox, but does not work for
+ * Safari.
+ * @param {String} selector A selector for the style class rule to modify.
+ * @param {String} styleRules The rules to add to the specified style class.
+ */
+AH.LoggingConsole.prototype.addCssRule = function(selector, styleRules) {
+ // If no sheet exists for the document, create one.
+ var sheet;
+ if (document.createStyleSheet) {
+ // For IE:
+ sheet = document.createStyleSheet();
+ } else {
+ // For Firefox:
+ var styleElement = document.createElement('style');
+ document.getElementsByTagName('head')[0].appendChild(styleElement);
+ sheet = (styleElement.styleSheet ?
+ styleElement.styleSheet :
+ styleElement.sheet);
+ }
+
+ // Add the new style rules to the style sheet.
+ if (sheet.addRule) {
+ // For IE:
+ sheet.addRule(selector, styleRules);
+ } else if (sheet.insertRule) {
+ // For Firefox:
+ sheet.insertRule(selector + ' { ' + styleRules + ' }',
+ sheet.cssRules.length);
+ }
+};
+
+
+/**
+ * Change the log severity level, and persist the change to the '_ah_severity'
+ * cookie.
+ * @param {String} newSeverity The desired log severity level.
+ */
+AH.LoggingConsole.prototype.changeLogSeverity = function(newSeverity) {
+ // First, find the numeric level for the provided severity.
+ var severityLevel = -1;
+ for (var i = 0; i < this.logSeverityLevels.length; i++) {
+ if (newSeverity == this.logSeverityLevels[i]) {
+ severityLevel = i;
+ }
+ }
+
+ // An unknown logging severity was provided, so ignore the call.
+ if (severityLevel == -1) {
+ return;
+ }
+
+ // Display log lines if they have severity greater than or equal to the
+ // desired severity.
+ for (var i = 0; i < this.logSeverityLevels.length; i++) {
+ var selector =
+ '._ah_logline_' + this.logSeverityLevels[i];
+ if (i < severityLevel) {
+ this.addCssRule(selector, 'display:none');
+ } else {
+ this.addCssRule(selector, 'display:block');
+ }
+ }
+
+ // Update the link text colors for the severity controls, and blur the links.
+ for (var i = 0; i < this.logSeverityLevels.length; i++) {
+ var linkToUpdate = document.getElementById(
+ '_ah_show_' + this.logSeverityLevels[i]);
+ if (i == severityLevel) {
+ linkToUpdate.style.color = 'red';
+ } else {
+ linkToUpdate.style.color = 'blue';
+ }
+ linkToUpdate.blur();
+ }
+
+ // Save the new severity level to a cookie.
+ this.cookieUtil.setCookie('_ah_severity', newSeverity);
+};
+
+
+/**
+ * Set the colors for the whole navigation table to white.
+ */
+AH.LoggingConsole.prototype.clearNavigationTable = function() {
+ document.getElementById('_ah_up_left').style.backgroundColor = 'white';
+ document.getElementById('_ah_up_right').style.backgroundColor = 'white';
+ document.getElementById('_ah_down_left').style.backgroundColor = 'white';
+ document.getElementById('_ah_down_right').style.backgroundColor = 'white';
+};
+
+
+/**
+ * Moves the logging console to the desired position.
+ * @param {String} newPosition The desired position, which must be one of
+ * 'up_left', 'up_right', 'down_left', or 'down_right'.
+ */
+AH.LoggingConsole.prototype.moveBaseDiv = function(newPosition) {
+ // Move the logging console to the desired position on the page.
+ var newPositionPieces = newPosition.split('_');
+ var newVerticalPosition = newPositionPieces[0];
+ var newHorizontalPosition = newPositionPieces[1];
+ if (newVerticalPosition == 'up') {
+ this.baseDiv.style.top = '10px';
+ this.baseDiv.style.bottom = '';
+ } else {
+ this.baseDiv.style.top = '';
+ this.baseDiv.style.bottom = '10px';
+ }
+ if (newHorizontalPosition == 'left') {
+ this.baseDiv.style.left = '10px';
+ this.baseDiv.style.right = '';
+ } else {
+ this.baseDiv.style.left = '';
+ this.baseDiv.style.right = '10px';
+ }
+
+ // Update the navigation table cell colors to reflect the new position.
+ this.clearNavigationTable();
+ document.getElementById('_ah_' + newPosition).style.backgroundColor = 'red';
+
+ // Save the new position to a cookie.
+ this.cookieUtil.setCookie('_ah_position', newPosition);
+};
+
+
+/**
+ * Disable the logging console, and delete all cookies which were storing
+ * logging console preferences.
+ */
+AH.LoggingConsole.prototype.closeEverything = function() {
+ this.cookieUtil.removeCookie('_ah_severity');
+ this.cookieUtil.removeCookie('_ah_position');
+ this.baseDiv.style.display = 'none';
+};
diff --git a/google_appengine/templates/logging_console_footer.html b/google_appengine/templates/logging_console_footer.html
new file mode 100644
index 0000000..e0a709c
--- /dev/null
+++ b/google_appengine/templates/logging_console_footer.html
@@ -0,0 +1,4 @@
+
+// Display a logging console on top of this page.
+(new AH.LoggingConsole()).initConsole();
+</script>
diff --git a/google_appengine/templates/logging_console_header.html b/google_appengine/templates/logging_console_header.html
new file mode 100644
index 0000000..cb89479
--- /dev/null
+++ b/google_appengine/templates/logging_console_header.html
@@ -0,0 +1,71 @@
+<!-- Copyright 2007 Google Inc. All Rights Reserved -->
+
+<!--
+ The below HTML code was generated and appended by Google. It's purpose is
+ to display a logging console in the browser. It's appearing here because
+ either:
+
+ (a) the CGI parameter "debug" was in the URL
+ (b) the cookie "_ah_severity" was present in the HTTP request
+
+ To stop this from being appended, you should remove the CGI parameter
+ "debug" from the request URL and then either click the "Close" link on the
+ logging console, or delete the "_ah_severity" cookie.
+
+ All element ids used by the code for this logging console are prefixed
+ with "_ah", all cookies used are prefixed with "_ah", and all classes and
+ functions are in the namespace "AH".
+ -->
+
+<div id="_ah_base"
+ style="z-index: 99999; position: absolute; right: 10px;
+ background-color: white; color: black; width: 600px;
+ border: 1px solid black; padding: 10px; font-family: courier;
+ font-size: medium; margin: 10px;">
+
+ <table><tr><td>
+ <table cellspacing=0 cellpadding=0 rules=all border=frame>
+ <tr>
+ <td style="background-color: white; cursor: pointer; height: 15px;
+ width: 15px;"
+ id="_ah_up_left"
+ onClick="(new AH.LoggingConsole).moveBaseDiv('up_left')"></td>
+ <td style="background-color: white; cursor: pointer; height: 15px;
+ width: 15px;"
+ id="_ah_up_right"
+ onClick="(new AH.LoggingConsole).moveBaseDiv('up_right')"></td>
+ </tr>
+ <tr>
+ <td style="background-color: white; cursor: pointer; height: 15px;
+ width: 15px;"
+ id="_ah_down_left"
+ onClick="(new AH.LoggingConsole).moveBaseDiv('down_left')"></td>
+ <td style="background-color: white; cursor: pointer; height: 15px;
+ width: 15px;"
+ id="_ah_down_right"
+ onClick="(new AH.LoggingConsole).moveBaseDiv('down_right')"></td>
+ </tr>
+ </table>
+ </td><td valign=top>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ <a id="_ah_show_debug"
+ href="javascript:(new AH.LoggingConsole).changeLogSeverity('debug')">
+ DEBUG</a>
+ <a id="_ah_show_info"
+ href="javascript:(new AH.LoggingConsole).changeLogSeverity('info')">
+ INFO</a>
+ <a id="_ah_show_warning"
+ href="javascript:(new AH.LoggingConsole).changeLogSeverity('warning')">
+ WARNING</a>
+ <a id="_ah_show_error"
+ href="javascript:(new AH.LoggingConsole).changeLogSeverity('error')">
+ ERROR</a>
+ <a id="_ah_show_critical"
+ href="javascript:(new AH.LoggingConsole).changeLogSeverity('critical')">
+ CRITICAL</a>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ <a id="_ah_close"
+ href="javascript:(new AH.LoggingConsole).closeEverything()">Close</a>
+ </td></tr></table>
+
+ <div id="_ah_loglines" style="margin: 10px; height: 300px; overflow: auto;">
diff --git a/google_appengine/templates/logging_console_middle.html b/google_appengine/templates/logging_console_middle.html
new file mode 100644
index 0000000..6fca900
--- /dev/null
+++ b/google_appengine/templates/logging_console_middle.html
@@ -0,0 +1,4 @@
+ </div>
+</div>
+
+<script>
diff --git a/google_appengine/tools/bulkload_client.py b/google_appengine/tools/bulkload_client.py
new file mode 100755
index 0000000..d28a227
--- /dev/null
+++ b/google_appengine/tools/bulkload_client.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.
+#
+"""Convenience wrapper for starting bulkload_client.py"""
+
+
+import os
+import sys
+
+sys.stderr.write("This version of bulkload_client.py has been deprecated; "
+ "please use the version at the root of your Google App "
+ "Engine SDK install.")
+
+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')
+
+BULKLOAD_CLIENT_PATH = 'google/appengine/tools/bulkload_client.py'
+
+DIR_PATH = os.path.abspath(os.path.dirname(
+ os.path.dirname(os.path.realpath(__file__))))
+
+EXTRA_PATHS = [
+ DIR_PATH,
+ os.path.join(DIR_PATH, 'lib', 'django'),
+ os.path.join(DIR_PATH, 'lib', 'webob'),
+ os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'),
+]
+
+if __name__ == '__main__':
+ sys.path = EXTRA_PATHS + sys.path
+ script_path = os.path.join(DIR_PATH, BULKLOAD_CLIENT_PATH)
+ execfile(script_path, globals())