summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS4
-rw-r--r--django/__init__.py2
-rw-r--r--django/bin/daily_cleanup.py10
-rw-r--r--django/conf/__init__.py73
-rw-r--r--django/conf/app_template/models.py3
-rw-r--r--django/conf/app_template/models/__init__.py1
-rw-r--r--django/conf/app_template/models/app_name.py3
-rw-r--r--django/conf/global_settings.py20
-rw-r--r--django/conf/project_template/settings.py15
-rw-r--r--django/conf/project_template/urls.py2
-rw-r--r--django/conf/settings.py77
-rw-r--r--django/conf/urls/registration.py14
-rw-r--r--django/contrib/admin/filterspecs.py20
-rw-r--r--django/contrib/admin/media/css/base.css15
-rw-r--r--django/contrib/admin/media/css/changelists.css18
-rw-r--r--django/contrib/admin/media/css/dashboard.css10
-rw-r--r--django/contrib/admin/media/css/forms.css60
-rw-r--r--django/contrib/admin/media/css/global.css304
-rw-r--r--django/contrib/admin/media/css/layout.css29
-rw-r--r--django/contrib/admin/media/css/login.css13
-rw-r--r--django/contrib/admin/media/css/patch-iewin.css5
-rw-r--r--django/contrib/admin/media/css/widgets.css101
-rw-r--r--django/contrib/admin/media/img/admin/deleted-overlay.gifbin0 -> 45 bytes
-rw-r--r--django/contrib/admin/media/img/admin/inline-delete-8bit.pngbin0 -> 477 bytes
-rw-r--r--django/contrib/admin/media/img/admin/inline-delete.pngbin0 -> 781 bytes
-rw-r--r--django/contrib/admin/media/img/admin/inline-restore-8bit.pngbin0 -> 447 bytes
-rw-r--r--django/contrib/admin/media/img/admin/inline-restore.pngbin0 -> 623 bytes
-rw-r--r--django/contrib/admin/media/img/admin/inline-splitter-bg.gifbin0 -> 102 bytes
-rw-r--r--django/contrib/admin/media/js/admin/RelatedObjectLookups.js2
-rw-r--r--django/contrib/admin/models.py51
-rw-r--r--django/contrib/admin/models/__init__.py1
-rw-r--r--django/contrib/admin/models/admin.py50
-rw-r--r--django/contrib/admin/templates/admin/404.html2
-rw-r--r--django/contrib/admin/templates/admin/500.html2
-rw-r--r--django/contrib/admin/templates/admin/base_site.html2
-rw-r--r--django/contrib/admin/templates/admin/change_form.html47
-rw-r--r--django/contrib/admin/templates/admin/change_list.html5
-rw-r--r--django/contrib/admin/templates/admin/delete_confirmation.html12
-rw-r--r--django/contrib/admin/templates/admin/edit_inline_stacked.html2
-rw-r--r--django/contrib/admin/templates/admin/field_line.html8
-rw-r--r--django/contrib/admin/templates/admin/index.html13
-rw-r--r--django/contrib/admin/templates/admin/login.html31
-rw-r--r--django/contrib/admin/templates/admin/object_history.html12
-rw-r--r--django/contrib/admin/templates/admin/search_form.html2
-rw-r--r--django/contrib/admin/templates/admin/template_validator.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/bookmarklets.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/index.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/missing_docutils.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/model_detail.html4
-rw-r--r--django/contrib/admin/templates/admin_doc/model_index.html17
-rw-r--r--django/contrib/admin/templates/admin_doc/template_detail.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/template_filter_index.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/template_tag_index.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/view_detail.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/view_index.html2
-rw-r--r--django/contrib/admin/templates/registration/logged_out.html2
-rw-r--r--django/contrib/admin/templates/registration/password_change_done.html2
-rw-r--r--django/contrib/admin/templates/registration/password_change_form.html2
-rw-r--r--django/contrib/admin/templates/registration/password_reset_done.html2
-rw-r--r--django/contrib/admin/templates/registration/password_reset_form.html2
-rw-r--r--django/contrib/admin/templates/widget/foreign.html20
-rw-r--r--django/contrib/admin/templates/widget/many_to_many.html2
-rw-r--r--django/contrib/admin/templates/widget/one_to_one.html3
-rw-r--r--django/contrib/admin/templatetags/admin_list.py112
-rw-r--r--django/contrib/admin/templatetags/admin_modify.py123
-rw-r--r--django/contrib/admin/templatetags/adminapplist.py19
-rw-r--r--django/contrib/admin/templatetags/adminmedia.py7
-rw-r--r--django/contrib/admin/templatetags/log.py6
-rw-r--r--django/contrib/admin/urls.py31
-rw-r--r--django/contrib/admin/urls/admin.py58
-rw-r--r--django/contrib/admin/utils.py6
-rw-r--r--django/contrib/admin/views/decorators.py30
-rw-r--r--django/contrib/admin/views/doc.py145
-rw-r--r--django/contrib/admin/views/main.py835
-rw-r--r--django/contrib/admin/views/template.py24
-rw-r--r--django/contrib/auth/__init__.py2
-rw-r--r--django/contrib/auth/create_superuser.py84
-rw-r--r--django/contrib/auth/decorators.py (renamed from django/views/decorators/auth.py)8
-rw-r--r--django/contrib/auth/forms.py108
-rw-r--r--django/contrib/auth/handlers/modpython.py12
-rw-r--r--django/contrib/auth/management.py53
-rw-r--r--django/contrib/auth/middleware.py19
-rw-r--r--django/contrib/auth/models.py264
-rw-r--r--django/contrib/auth/views.py84
-rw-r--r--django/contrib/comments/feeds.py36
-rw-r--r--django/contrib/comments/models.py285
-rw-r--r--django/contrib/comments/models/__init__.py1
-rw-r--r--django/contrib/comments/models/comments.py287
-rw-r--r--django/contrib/comments/templatetags/comments.py80
-rw-r--r--django/contrib/comments/views/comments.py111
-rw-r--r--django/contrib/comments/views/karma.py19
-rw-r--r--django/contrib/comments/views/userflags.py47
-rw-r--r--django/contrib/contenttypes/__init__.py (renamed from django/contrib/admin/urls/__init__.py)0
-rw-r--r--django/contrib/contenttypes/models.py49
-rw-r--r--django/contrib/flatpages/middleware.py6
-rw-r--r--django/contrib/flatpages/models.py33
-rw-r--r--django/contrib/flatpages/models/__init__.py1
-rw-r--r--django/contrib/flatpages/models/flatpages.py33
-rw-r--r--django/contrib/flatpages/views.py24
-rw-r--r--django/contrib/markup/templatetags/markup.py9
-rw-r--r--django/contrib/redirects/middleware.py22
-rw-r--r--django/contrib/redirects/models.py (renamed from django/contrib/redirects/models/redirects.py)23
-rw-r--r--django/contrib/redirects/models/__init__.py1
-rw-r--r--django/contrib/sessions/__init__.py (renamed from django/core/db/backends/__init__.py)0
-rw-r--r--django/contrib/sessions/middleware.py (renamed from django/middleware/sessions.py)24
-rw-r--r--django/contrib/sessions/models.py55
-rw-r--r--django/contrib/sites/__init__.py (renamed from django/core/template/loaders/__init__.py)0
-rw-r--r--django/contrib/sites/management.py16
-rw-r--r--django/contrib/sites/models.py23
-rw-r--r--django/contrib/syndication/feeds.py14
-rw-r--r--django/contrib/syndication/views.py3
-rw-r--r--django/core/cache/backends/db.py16
-rw-r--r--django/core/context_processors.py14
-rw-r--r--django/core/db/__init__.py43
-rw-r--r--django/core/db/backends/mysql.py235
-rw-r--r--django/core/db/backends/postgresql.py238
-rw-r--r--django/core/db/backends/sqlite3.py230
-rw-r--r--django/core/db/base.py32
-rw-r--r--django/core/db/dicthelpers.py24
-rw-r--r--django/core/db/typecasts.py55
-rw-r--r--django/core/exceptions.py9
-rw-r--r--django/core/extensions.py82
-rw-r--r--django/core/handlers/base.py38
-rw-r--r--django/core/handlers/modpython.py51
-rw-r--r--django/core/handlers/wsgi.py51
-rw-r--r--django/core/mail.py15
-rw-r--r--django/core/management.py1279
-rw-r--r--django/core/meta/__init__.py1983
-rw-r--r--django/core/paginator.py38
-rw-r--r--django/core/servers/basehttp.py8
-rw-r--r--django/core/signals.py3
-rw-r--r--django/core/template_loader.py6
-rw-r--r--django/core/urlresolvers.py3
-rw-r--r--django/core/validators.py77
-rw-r--r--django/core/xheaders.py13
-rw-r--r--django/db/__init__.py45
-rw-r--r--django/db/backends/__init__.py (renamed from django/parts/__init__.py)0
-rw-r--r--django/db/backends/ado_mssql/__init__.py (renamed from django/parts/auth/__init__.py)0
-rw-r--r--django/db/backends/ado_mssql/base.py (renamed from django/core/db/backends/ado_mssql.py)80
-rw-r--r--django/db/backends/ado_mssql/client.py2
-rw-r--r--django/db/backends/ado_mssql/creation.py26
-rw-r--r--django/db/backends/ado_mssql/introspection.py13
-rw-r--r--django/db/backends/dummy/__init__.py (renamed from django/parts/media/__init__.py)0
-rw-r--r--django/db/backends/dummy/base.py37
-rw-r--r--django/db/backends/dummy/client.py3
-rw-r--r--django/db/backends/dummy/creation.py1
-rw-r--r--django/db/backends/dummy/introspection.py8
-rw-r--r--django/db/backends/mysql/__init__.py (renamed from django/views/auth/__init__.py)0
-rw-r--r--django/db/backends/mysql/base.py167
-rw-r--r--django/db/backends/mysql/client.py14
-rw-r--r--django/db/backends/mysql/creation.py30
-rw-r--r--django/db/backends/mysql/introspection.py94
-rw-r--r--django/db/backends/postgresql/__init__.py (renamed from django/views/registration/__init__.py)0
-rw-r--r--django/db/backends/postgresql/base.py128
-rw-r--r--django/db/backends/postgresql/client.py14
-rw-r--r--django/db/backends/postgresql/creation.py30
-rw-r--r--django/db/backends/postgresql/introspection.py85
-rw-r--r--django/db/backends/sqlite3/__init__.py (renamed from tests/testapp/__init__.py)0
-rw-r--r--django/db/backends/sqlite3/base.py150
-rw-r--r--django/db/backends/sqlite3/client.py6
-rw-r--r--django/db/backends/sqlite3/creation.py29
-rw-r--r--django/db/backends/sqlite3/introspection.py50
-rw-r--r--django/db/backends/util.py114
-rw-r--r--django/db/models/__init__.py40
-rw-r--r--django/db/models/base.py401
-rw-r--r--django/db/models/fields/__init__.py (renamed from django/core/meta/fields.py)674
-rw-r--r--django/db/models/fields/related.py718
-rw-r--r--django/db/models/loading.py71
-rw-r--r--django/db/models/manager.py101
-rw-r--r--django/db/models/manipulators.py330
-rw-r--r--django/db/models/options.py269
-rw-r--r--django/db/models/query.py888
-rw-r--r--django/db/models/related.py132
-rw-r--r--django/db/models/signals.py12
-rw-r--r--django/db/transaction.py219
-rw-r--r--django/dispatch/__init__.py6
-rw-r--r--django/dispatch/dispatcher.py497
-rw-r--r--django/dispatch/errors.py10
-rw-r--r--django/dispatch/license.txt34
-rw-r--r--django/dispatch/robust.py57
-rw-r--r--django/dispatch/robustapply.py49
-rw-r--r--django/dispatch/saferef.py165
-rw-r--r--django/forms/__init__.py (renamed from django/core/formfields.py)144
-rw-r--r--django/http/__init__.py (renamed from django/utils/httpwrappers.py)10
-rw-r--r--django/middleware/cache.py2
-rw-r--r--django/middleware/common.py13
-rw-r--r--django/middleware/doc.py4
-rw-r--r--django/middleware/transaction.py28
-rw-r--r--django/models/__init__.py95
-rw-r--r--django/models/auth.py219
-rw-r--r--django/models/core.py121
-rw-r--r--django/parts/auth/anonymoususers.py44
-rw-r--r--django/parts/auth/formfields.py46
-rw-r--r--django/parts/media/photos.py6
-rw-r--r--django/shortcuts/__init__.py23
-rw-r--r--django/template/__init__.py (renamed from django/core/template/__init__.py)128
-rw-r--r--django/template/context.py97
-rw-r--r--django/template/defaultfilters.py (renamed from django/core/template/defaultfilters.py)17
-rw-r--r--django/template/defaulttags.py (renamed from django/core/template/defaulttags.py)22
-rw-r--r--django/template/loader.py (renamed from django/core/template/loader.py)10
-rw-r--r--django/template/loader_tags.py (renamed from django/core/template/loader_tags.py)31
-rw-r--r--django/template/loaders/__init__.py0
-rw-r--r--django/template/loaders/app_directories.py (renamed from django/core/template/loaders/app_directories.py)8
-rw-r--r--django/template/loaders/eggs.py (renamed from django/core/template/loaders/eggs.py)8
-rw-r--r--django/template/loaders/filesystem.py (renamed from django/core/template/loaders/filesystem.py)8
-rw-r--r--django/templatetags/__init__.py4
-rw-r--r--django/templatetags/i18n.py10
-rw-r--r--django/utils/cache.py9
-rw-r--r--django/utils/datastructures.py39
-rw-r--r--django/utils/feedgenerator.py8
-rw-r--r--django/utils/functional.py16
-rw-r--r--django/utils/html.py2
-rw-r--r--django/utils/termcolors.py70
-rw-r--r--django/utils/text.py4
-rw-r--r--django/utils/timesince.py1
-rw-r--r--django/utils/translation.py30
-rw-r--r--django/views/auth/login.py49
-rw-r--r--django/views/debug.py10
-rw-r--r--django/views/decorators/cache.py12
-rw-r--r--django/views/decorators/http.py2
-rw-r--r--django/views/defaults.py82
-rw-r--r--django/views/generic/create_update.py81
-rw-r--r--django/views/generic/date_based.py168
-rw-r--r--django/views/generic/list_detail.py61
-rw-r--r--django/views/generic/simple.py7
-rw-r--r--django/views/i18n.py6
-rw-r--r--django/views/registration/passwords.py100
-rw-r--r--django/views/static.py15
-rw-r--r--docs/add_ons.txt4
-rw-r--r--docs/admin_css.txt57
-rw-r--r--docs/authentication.txt227
-rw-r--r--docs/cache.txt331
-rw-r--r--docs/db-api.txt1648
-rw-r--r--docs/design_philosophies.txt22
-rw-r--r--docs/django-admin.txt181
-rw-r--r--docs/email.txt46
-rw-r--r--docs/faq.txt150
-rw-r--r--docs/flatpages.txt37
-rw-r--r--docs/forms.txt255
-rw-r--r--docs/generic_views.txt1092
-rw-r--r--docs/i18n.txt10
-rw-r--r--docs/install.txt6
-rw-r--r--docs/legacy_databases.txt44
-rw-r--r--docs/middleware.txt34
-rw-r--r--docs/model-api.txt1385
-rw-r--r--docs/modpython.txt18
-rw-r--r--docs/outputting_csv.txt14
-rw-r--r--docs/outputting_pdf.txt2
-rw-r--r--docs/overview.txt203
-rw-r--r--docs/redirects.txt13
-rw-r--r--docs/request_response.txt58
-rw-r--r--docs/sessions.txt42
-rw-r--r--docs/settings.txt71
-rw-r--r--docs/static_files.txt16
-rw-r--r--docs/syndication_feeds.txt26
-rw-r--r--docs/templates.txt130
-rw-r--r--docs/templates_python.txt144
-rw-r--r--docs/transactions.txt146
-rw-r--r--docs/tutorial01.txt329
-rw-r--r--docs/tutorial02.txt99
-rw-r--r--docs/tutorial03.txt125
-rw-r--r--docs/tutorial04.txt92
-rw-r--r--docs/url_dispatch.txt57
-rw-r--r--examples/__init__.py0
-rw-r--r--examples/hello/__init__.py0
-rw-r--r--examples/hello/urls.py10
-rw-r--r--examples/hello/views.py55
-rw-r--r--examples/manage.py11
-rw-r--r--examples/settings.py5
-rw-r--r--examples/urls.py6
-rw-r--r--examples/views.py12
-rw-r--r--extras/README.TXT1
-rw-r--r--extras/django_bash_completion105
-rw-r--r--setup.py5
-rw-r--r--tests/modeltests/__init__.py0
-rw-r--r--tests/modeltests/basic/__init__.py0
-rw-r--r--tests/modeltests/basic/models.py339
-rw-r--r--tests/modeltests/choices/__init__.py0
-rw-r--r--tests/modeltests/choices/models.py39
-rw-r--r--tests/modeltests/custom_columns/__init__.py0
-rw-r--r--tests/modeltests/custom_columns/models.py (renamed from tests/testapp/models/custom_columns.py)22
-rw-r--r--tests/modeltests/custom_managers/__init__.py0
-rw-r--r--tests/modeltests/custom_managers/models.py100
-rw-r--r--tests/modeltests/custom_methods/__init__.py0
-rw-r--r--tests/modeltests/custom_methods/models.py58
-rw-r--r--tests/modeltests/custom_pk/__init__.py0
-rw-r--r--tests/modeltests/custom_pk/models.py90
-rw-r--r--tests/modeltests/field_defaults/__init__.py0
-rw-r--r--tests/modeltests/field_defaults/models.py48
-rw-r--r--tests/modeltests/get_latest/__init__.py0
-rw-r--r--tests/modeltests/get_latest/models.py79
-rw-r--r--tests/modeltests/invalid_models/__init__.py1
-rw-r--r--tests/modeltests/invalid_models/models.py117
-rw-r--r--tests/modeltests/lookup/__init__.py0
-rw-r--r--tests/modeltests/lookup/models.py179
-rw-r--r--tests/modeltests/m2m_and_m2o/__init__.py0
-rw-r--r--tests/modeltests/m2m_and_m2o/models.py66
-rw-r--r--tests/modeltests/m2m_intermediary/__init__.py0
-rw-r--r--tests/modeltests/m2m_intermediary/models.py68
-rw-r--r--tests/modeltests/m2m_multiple/__init__.py0
-rw-r--r--tests/modeltests/m2m_multiple/models.py79
-rw-r--r--tests/modeltests/m2m_recursive/__init__.py0
-rw-r--r--tests/modeltests/m2m_recursive/models.py192
-rw-r--r--tests/modeltests/m2o_recursive/__init__.py0
-rw-r--r--tests/modeltests/m2o_recursive/models.py40
-rw-r--r--tests/modeltests/m2o_recursive2/__init__.py0
-rw-r--r--tests/modeltests/m2o_recursive2/models.py43
-rw-r--r--tests/modeltests/manipulators/__init__.py0
-rw-r--r--tests/modeltests/manipulators/models.py89
-rw-r--r--tests/modeltests/many_to_many/__init__.py0
-rw-r--r--tests/modeltests/many_to_many/models.py206
-rw-r--r--tests/modeltests/many_to_one/__init__.py0
-rw-r--r--tests/modeltests/many_to_one/models.py232
-rw-r--r--tests/modeltests/many_to_one_null/__init__.py0
-rw-r--r--tests/modeltests/many_to_one_null/models.py124
-rw-r--r--tests/modeltests/model_inheritance/__init__.py0
-rw-r--r--tests/modeltests/model_inheritance/models.py52
-rw-r--r--tests/modeltests/mutually_referential/__init__.py0
-rw-r--r--tests/modeltests/mutually_referential/models.py32
-rw-r--r--tests/modeltests/one_to_one/__init__.py0
-rw-r--r--tests/modeltests/one_to_one/models.py130
-rw-r--r--tests/modeltests/or_lookups/__init__.py0
-rw-r--r--tests/modeltests/or_lookups/models.py89
-rw-r--r--tests/modeltests/ordering/__init__.py0
-rw-r--r--tests/modeltests/ordering/models.py (renamed from tests/testapp/models/ordering.py)36
-rw-r--r--tests/modeltests/pagination/__init__.py0
-rw-r--r--tests/modeltests/pagination/models.py59
-rw-r--r--tests/modeltests/properties/__init__.py0
-rw-r--r--tests/modeltests/properties/models.py26
-rw-r--r--tests/modeltests/repr/__init__.py0
-rw-r--r--tests/modeltests/repr/models.py (renamed from tests/testapp/models/repr.py)10
-rw-r--r--tests/modeltests/reserved_names/__init__.py0
-rw-r--r--tests/modeltests/reserved_names/models.py56
-rw-r--r--tests/modeltests/reverse_lookup/__init__.py0
-rw-r--r--tests/modeltests/reverse_lookup/models.py56
-rw-r--r--tests/modeltests/save_delete_hooks/__init__.py0
-rw-r--r--tests/modeltests/save_delete_hooks/models.py42
-rw-r--r--tests/modeltests/transactions/__init__.py0
-rw-r--r--tests/modeltests/transactions/models.py92
-rw-r--r--tests/modeltests/validation/__init__.py0
-rw-r--r--tests/modeltests/validation/models.py147
-rw-r--r--tests/othertests/dateformat.py25
-rw-r--r--tests/othertests/db_typecasts.py4
-rw-r--r--tests/othertests/defaultfilters.py60
-rw-r--r--tests/othertests/httpwrappers.py2
-rw-r--r--tests/othertests/markup.py2
-rw-r--r--tests/othertests/templates.py221
-rwxr-xr-xtests/runtests.py102
-rw-r--r--tests/testapp/models/__init__.py5
-rw-r--r--tests/testapp/models/basic.py204
-rw-r--r--tests/testapp/models/custom_methods.py72
-rw-r--r--tests/testapp/models/custom_pk.py69
-rw-r--r--tests/testapp/models/get_latest.py43
-rw-r--r--tests/testapp/models/lookup.py153
-rw-r--r--tests/testapp/models/m2m_intermediary.py68
-rw-r--r--tests/testapp/models/m2m_multiple.py99
-rw-r--r--tests/testapp/models/m2o_recursive.py44
-rw-r--r--tests/testapp/models/m2o_recursive2.py43
-rw-r--r--tests/testapp/models/many_to_many.py82
-rw-r--r--tests/testapp/models/many_to_one.py98
-rw-r--r--tests/testapp/models/many_to_one_null.py78
-rw-r--r--tests/testapp/models/one_to_one.py80
-rw-r--r--tests/testapp/models/or_lookups.py57
-rw-r--r--tests/testapp/models/reserved_names.py47
-rw-r--r--tests/testapp/models/save_delete_hooks.py49
-rw-r--r--tests/testapp/models/subclassing.py180
366 files changed, 17721 insertions, 11087 deletions
diff --git a/AUTHORS b/AUTHORS
index edd7d50726..6b25a39035 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -71,6 +71,7 @@ answer newbie questions, and generally made Django that much better:
lakin.wecker@gmail.com
Stuart Langridge <http://www.kryogenix.org/>
Eugene Lazutkin <http://lazutkin.com/blog/>
+ Christopher Lenz <http://www.cmlenz.net/>
limodou
Martin Maney <http://www.chipy.org/Martin_Maney>
Manuzhai
@@ -79,6 +80,7 @@ answer newbie questions, and generally made Django that much better:
mattycakes@gmail.com
Jason McBrayer <http://www.carcosa.net/jason/>
michael.mcewan@gmail.com
+ mir@noris.de
mmarshall
Eric Moritz <http://eric.themoritzfamily.com/>
Robin Munn <http://www.geekforgod.com/>
@@ -102,7 +104,9 @@ answer newbie questions, and generally made Django that much better:
Aaron Swartz <http://www.aaronsw.com/>
Tom Tobin
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
+ Malcolm Tredinnick
Amit Upadhyay
+ Geert Vanderkelen
Milton Waddams
Rachel Willmer <http://www.willmer.com/kb/>
wojtek
diff --git a/django/__init__.py b/django/__init__.py
index 593e2f46e4..00c6f82478 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1 +1 @@
-VERSION = (0, 9, 1, 'SVN')
+VERSION = (0, 95, 'post-magic-removal')
diff --git a/django/bin/daily_cleanup.py b/django/bin/daily_cleanup.py
index 52e2ef43fd..6eb5c17feb 100644
--- a/django/bin/daily_cleanup.py
+++ b/django/bin/daily_cleanup.py
@@ -1,17 +1,17 @@
"Daily cleanup file"
-from django.core.db import db
+from django.db import backend, connection, transaction
DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
def clean_up():
# Clean up old database records
- cursor = db.cursor()
+ cursor = connection.cursor()
cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \
- (db.quote_name('core_sessions'), db.quote_name('expire_date')))
+ (backend.quote_name('core_sessions'), backend.quote_name('expire_date')))
cursor.execute("DELETE FROM %s WHERE %s < NOW() - INTERVAL '1 week'" % \
- (db.quote_name('registration_challenges'), db.quote_name('request_date')))
- db.commit()
+ (backend.quote_name('registration_challenges'), backend.quote_name('request_date')))
+ transaction.commit_unless_managed()
if __name__ == "__main__":
clean_up()
diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index e69de29bb2..291ba8ce3f 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -0,0 +1,73 @@
+"""
+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 sys
+from django.conf import global_settings
+
+ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
+
+class Settings:
+
+ 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?): %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
+
+ # move the time zone info into os.environ
+ os.environ['TZ'] = self.TIME_ZONE
+
+# try to load DJANGO_SETTINGS_MODULE
+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
+
+# instantiate the configuration object
+settings = Settings(settings_module)
+
+# install the translation machinery so that it is available
+from django.utils import translation
+translation.install()
+
diff --git a/django/conf/app_template/models.py b/django/conf/app_template/models.py
new file mode 100644
index 0000000000..71a8362390
--- /dev/null
+++ b/django/conf/app_template/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/django/conf/app_template/models/__init__.py b/django/conf/app_template/models/__init__.py
deleted file mode 100644
index 502a7d0738..0000000000
--- a/django/conf/app_template/models/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['{{ app_name }}']
diff --git a/django/conf/app_template/models/app_name.py b/django/conf/app_template/models/app_name.py
deleted file mode 100644
index 6fce302e01..0000000000
--- a/django/conf/app_template/models/app_name.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.core import meta
-
-# Create your models here.
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index f6ec3d2611..cfa174287b 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -79,7 +79,7 @@ SERVER_EMAIL = 'root@localhost'
SEND_BROKEN_LINK_EMAILS = False
# Database connection info.
-DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_ENGINE = '' # '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.
@@ -102,19 +102,16 @@ INSTALLED_APPS = ()
# List of locations of the template source files, in search order.
TEMPLATE_DIRS = ()
-# Extension on all templates.
-TEMPLATE_FILE_EXTENSION = '.html'
-
# 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.core.template.loaders.filesystem.load_template_source',
- 'django.core.template.loaders.app_directories.load_template_source',
-# 'django.core.template.loaders.eggs.load_template_source',
+ '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 DjangoContext to populate the context.
+# 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 = (
@@ -205,6 +202,10 @@ TIME_FORMAT = 'P'
# http://psyco.sourceforge.net/
ENABLE_PSYCO = False
+# Do you want to manage transactions manually?
+# Hint: you really don't!
+TRANSACTIONS_MANAGED = False
+
##############
# MIDDLEWARE #
##############
@@ -213,7 +214,8 @@ ENABLE_PSYCO = False
# 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.middleware.sessions.SessionMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
# "django.middleware.http.ConditionalGetMiddleware",
# "django.middleware.gzip.GZipMiddleware",
"django.middleware.common.CommonMiddleware",
diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
index 71d4da472d..800d1023d9 100644
--- a/django/conf/project_template/settings.py
+++ b/django/conf/project_template/settings.py
@@ -9,7 +9,7 @@ ADMINS = (
MANAGERS = ADMINS
-DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_ENGINE = '' # '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.
@@ -45,14 +45,15 @@ SECRET_KEY = ''
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
- 'django.core.template.loaders.filesystem.load_template_source',
- 'django.core.template.loaders.app_directories.load_template_source',
-# 'django.core.template.loaders.eggs.load_template_source',
+ '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.middleware.sessions.SessionMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.middleware.doc.XViewMiddleware",
)
@@ -64,4 +65,8 @@ TEMPLATE_DIRS = (
)
INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
)
diff --git a/django/conf/project_template/urls.py b/django/conf/project_template/urls.py
index fe77ea1867..4483014173 100644
--- a/django/conf/project_template/urls.py
+++ b/django/conf/project_template/urls.py
@@ -5,5 +5,5 @@ urlpatterns = patterns('',
# (r'^{{ project_name }}/', include('{{ project_name }}.apps.foo.urls.foo')),
# Uncomment this for admin:
-# (r'^admin/', include('django.contrib.admin.urls.admin')),
+# (r'^admin/', include('django.contrib.admin.urls')),
)
diff --git a/django/conf/settings.py b/django/conf/settings.py
deleted file mode 100644
index f455f65afe..0000000000
--- a/django/conf/settings.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""
-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 sys
-from django.conf import global_settings
-
-ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
-
-# get a reference to this module (why isn't there a __module__ magic var?)
-me = sys.modules[__name__]
-
-# update this dict from global settings (but only for ALL_CAPS settings)
-for setting in dir(global_settings):
- if setting == setting.upper():
- setattr(me, setting, getattr(global_settings, setting))
-
-# try to load DJANGO_SETTINGS_MODULE
-try:
- me.SETTINGS_MODULE = os.environ[ENVIRONMENT_VARIABLE]
- if not me.SETTINGS_MODULE: # If it's set but is an empty string.
- raise KeyError
-except KeyError:
- raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE
-
-try:
- mod = __import__(me.SETTINGS_MODULE, '', '', [''])
-except ImportError, e:
- raise EnvironmentError, "Could not import %s '%s' (is it on sys.path?): %s" % (ENVIRONMENT_VARIABLE, me.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(me, setting, setting_value)
-
-# Expand entries in INSTALLED_APPS like "django.contrib.*" to a list
-# of all those apps.
-new_installed_apps = []
-for app in me.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)
-me.INSTALLED_APPS = new_installed_apps
-
-# save DJANGO_SETTINGS_MODULE in case anyone in the future cares
-me.SETTINGS_MODULE = os.environ.get(ENVIRONMENT_VARIABLE, '')
-
-# move the time zone info into os.environ
-os.environ['TZ'] = me.TIME_ZONE
-
-# finally, clean up my namespace
-for k in dir(me):
- if not k.startswith('_') and k != 'me' and k != k.upper():
- delattr(me, k)
-del me, k
-
-# as the last step, install the translation machinery and
-# remove the module again to not clutter the namespace.
-from django.utils import translation
-translation.install()
-del translation
-
diff --git a/django/conf/urls/registration.py b/django/conf/urls/registration.py
index 5a56fe5e05..2d733a898b 100644
--- a/django/conf/urls/registration.py
+++ b/django/conf/urls/registration.py
@@ -1,9 +1,9 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
- (r'^login/$', 'django.views.auth.login.login'),
- (r'^logout/$', 'django.views.auth.login.logout'),
- (r'^login_another/$', 'django.views.auth.login.logout_then_login'),
+ (r'^login/$', 'django.contrib.auth.view.login'),
+ (r'^logout/$', 'django.contrib.auth.views.logout'),
+ (r'^login_another/$', 'django.contrib.auth.views.logout_then_login'),
(r'^register/$', 'ellington.registration.views.registration.signup'),
(r'^register/(?P<challenge_string>\w{32})/$', 'ellington.registration.views.registration.register_form'),
@@ -12,8 +12,8 @@ urlpatterns = patterns('',
(r'^profile/welcome/$', 'ellington.registration.views.profile.profile_welcome'),
(r'^profile/edit/$', 'ellington.registration.views.profile.edit_profile'),
- (r'^password_reset/$', 'django.views.registration.passwords.password_reset'),
- (r'^password_reset/done/$', 'django.views.registration.passwords.password_reset_done'),
- (r'^password_change/$', 'django.views.registration.passwords.password_change'),
- (r'^password_change/done/$', 'django.views.registration.passwords.password_change_done'),
+ (r'^password_reset/$', 'django.contrib.auth.views.password_reset'),
+ (r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'),
+ (r'^password_change/$', 'django.contrib.auth.views.password_change'),
+ (r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
)
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py
index 31a6ba37c9..0284f13114 100644
--- a/django/contrib/admin/filterspecs.py
+++ b/django/contrib/admin/filterspecs.py
@@ -6,7 +6,7 @@ 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.core import meta
+from django.db import models
import datetime
class FilterSpec(object):
@@ -50,13 +50,13 @@ class FilterSpec(object):
class RelatedFilterSpec(FilterSpec):
def __init__(self, f, request, params):
super(RelatedFilterSpec, self).__init__(f, request, params)
- if isinstance(f, meta.ManyToManyField):
- self.lookup_title = f.rel.to.verbose_name
+ 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.pk.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.get_model_module().get_list()
+ self.lookup_choices = f.rel.to._default_manager.all()
def has_output(self):
return len(self.lookup_choices) > 1
@@ -69,7 +69,7 @@ class RelatedFilterSpec(FilterSpec):
'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.pk.attname)
+ 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}
@@ -103,7 +103,7 @@ class DateFieldFilterSpec(FilterSpec):
today = datetime.date.today()
one_week_ago = today - datetime.timedelta(days=7)
- today_str = isinstance(self.field, meta.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
+ 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'), {}),
@@ -126,7 +126,7 @@ class DateFieldFilterSpec(FilterSpec):
'query_string': cl.get_query_string( param_dict, self.field_generic),
'display': title}
-FilterSpec.register(lambda f: isinstance(f, meta.DateField), DateFieldFilterSpec)
+FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
class BooleanFieldFilterSpec(FilterSpec):
def __init__(self, f, request, params):
@@ -144,9 +144,9 @@ class BooleanFieldFilterSpec(FilterSpec):
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, meta.NullBooleanField):
+ 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, meta.BooleanField) or isinstance(f, meta.NullBooleanField), BooleanFieldFilterSpec)
+FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)
diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css
index 7c4437f454..88f7d9a95a 100644
--- a/django/contrib/admin/media/css/base.css
+++ b/django/contrib/admin/media/css/base.css
@@ -1,3 +1,14 @@
-@import url(global.css);
-@import url(changelists.css);
+/*
+ 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/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css
index 7ff59c5e6b..2269c9fe20 100644
--- a/django/contrib/admin/media/css/changelists.css
+++ b/django/contrib/admin/media/css/changelists.css
@@ -1,16 +1,13 @@
-/*
- DJANGO Admin Changelist Styles
- by Wilson Miner wilson@lawrence.com
- Copyright (c) 2005 Lawrence Journal-World
-*/
+@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; _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:10px; }
+.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; }
@@ -42,3 +39,12 @@
.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/django/contrib/admin/media/css/dashboard.css b/django/contrib/admin/media/css/dashboard.css
new file mode 100644
index 0000000000..d27797324b
--- /dev/null
+++ b/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/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css
new file mode 100644
index 0000000000..b66f268fec
--- /dev/null
+++ b/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; } \ No newline at end of file
diff --git a/django/contrib/admin/media/css/global.css b/django/contrib/admin/media/css/global.css
index 765e752d48..67e37324e5 100644
--- a/django/contrib/admin/media/css/global.css
+++ b/django/contrib/admin/media/css/global.css
@@ -1,19 +1,14 @@
-/*
- DJANGO Admin Global Styles
- by Wilson Miner wilson@lawrence.com
- Copyright (c) 2005 Lawrence Journal-World
-*/
-
-body { margin:0; padding:0; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
+body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","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; font-size:12px; }
+/* 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; }
@@ -21,6 +16,7 @@ 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; }
@@ -28,150 +24,83 @@ 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; }
-/* PAGE STRUCTURE */
-#container { position:relative; width:100%; min-width:760px; }
-#content { margin:10px 15px; }
-#header { width:100%; }
-#content-main { float:left; width:100%; }
-#content-related { float:right; width:220px; position:relative; margin-right:-230px; }
-#footer { clear:both; padding:10px; }
-
-/* COLUMN TYPES */
-.colMS { margin-right:245px !important; }
-.colSM { margin-left:245px !important; }
-.colSM #content-related { float:left; margin-right:0; margin-left:-230px; }
-.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; min-height:2.4em; overflow:hidden; }
-#header a:link, #header a:visited { color:white; }
-#header a:hover { text-decoration:underline; }
-#branding h1 { padding:0.5em 10px 0 10px; font-size:18px; margin:0; font-weight:normal; color:#f4f379; }
-#branding h2 { padding:0 10px 0.8em 10px; font-size:14px; margin: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; }
+/* 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 */
+/* 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; }
-thead th { font-weight:bold; 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; }
-thead th:first-child { border-left:none !important; }
-.superwide table th, .superwide table td, .superwide table input, .superwide table select { font-size:10px; }
-.module table { border-collapse: collapse; }
+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; }
-#home-page table.module tr:hover { background:#EDF3FE; }
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; }
-table#change-history { width:100%; }
-table#change-history tbody th { width:16em; }
-/* TABLE SORTING */
+/* 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; }
-/* 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 h2, .module caption { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; background:#7CA0C7 url(../img/admin/default-bg.gif) left top repeat-x; color:white; font-weight:bold; }
-.module caption { border:1px solid #ccc; border-bottom:none; }
-.module h3 { margin-top:.6em; }
-#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
-#content-main .verbose .actionlist { float:right; font-size:10px; width:17em; position:relative; top:-1.6em; margin:0 8px; }
-
-/* DASHBOARD */
-.dashboard .module table th { width:100%; }
-.dashboard .module table td { white-space:nowrap; }
-.dashboard .module table td a { display:block; padding-right:.6em; }
+/* 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; }
-/* RECENT ACTIONS MODULE */
-.module ul.actionlist { margin-left:0; }
-ul.actionlist li { list-style-type:none; }
-
-/* FORM DEFAULTS */
-input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; border:1px solid #ccc; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; }
+/* 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=checkbox], input[type=radio] { border:none; }
+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; }
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; }
-.submit-row { padding:5px 7px; text-align:right; background:#ffc; border:1px solid #ccc; margin:5px 0; }
-.submit-row input { margin:0 0 0 5px; }
-.submit-row .float-left { padding-top:.1em; }
-
-/* FORM ROWS */
-.form-row { clear:both; padding:8px 12px; font-size:11px; }
-html>body .form-row { border-bottom:1px solid #eee; }
-.form-row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
-.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; text-align: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; }
-.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 .collapse-toggle { display: inline !important; }
-fieldset.collapse h2 a.collapse-toggle { color:#ffc; }
-fieldset.collapse h2 a.collapse-toggle:hover { text-decoration:underline; }
-.hidden { display:none; }
-
-/* MONOSPACE TEXTAREAS */
-fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
+/* 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 */
+/* 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; }
@@ -183,17 +112,21 @@ td ul.errorlist li { margin:0 !important; }
.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; }
-/* ACTION ICONS */
+/* 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 .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; margin-bottom:5px; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
-.form-row .object-tools { margin-top:0; margin-bottom:0; }
+/* 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; }
@@ -203,123 +136,6 @@ a.deletelink:hover { color:#993333; }
.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; }
-/* INLINE CONTROLS */
-#inline-controls { font-weight:bold; font-size:12px; }
-#inline-specific-controls { margin-left:6px; padding:0 8px; border-left:6px solid #ccc; }
-
-/* BREADCRUMBS */
-p.breadcrumbs { font-size:11px; color:#ccc;text-align:left; } /* old breadcrumbs style */
-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; }
-
-/* SELECTOR (FILTER INTERFACE) */
-.selector { width:580px; float:left; }
-.selector select { width:270px; height:170px; }
-.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:13% 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 for long items */
-.stacked { float:left; width:500px; }
-.stacked select { width:480px; height:100px; }
-.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; font-weight:normal; color:#ccc; 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; }
-
-/* ORDERING WIDGET */
-ul#orderthese { position:absolute; top:8em; right:0; width:240px; padding:0; margin:0; list-style-type:none; }
-ul#orderthese li { list-style-type:none; display:block; padding:0; margin:6px 0; width:214px; background:#f6f6f6; white-space:nowrap; overflow:hidden; }
-ul#orderthese li span { display:block; border:1px solid #e7e7e7; background:transparent url(../img/admin/nav-bg-grabber.gif) top left repeat-y; font-size:10px !important; padding:4px 6px 4px 12px; }
-ul#orderthese span:hover { background-color:#efefef; }
-
-/* 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; }
-
-/* 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; }
-
-/* CUSTOM FORM FIELDS */
-.vSelectMultipleField { vertical-align:top !important; }
-.vCheckboxField { border:none; }
-.vDateField, .vTimeField { margin-right:2px; }
-.vFileUploadField { border:none; }
-.vURLField { width:380px; }
-.vLargeTextField, .vXMLLargeTextField { width:480px; }
-.colM .vLargeTextField, .colM .vXMLLargeTextField { width:720px; }
-body.core-flatfile #id_content { height: 400px; }
-.module table .vPositiveSmallIntegerField { width: 22px; } \ No newline at end of file
+/* OBJECT HISTORY */
+table#change-history { width:100%; }
+table#change-history tbody th { width:16em; }
diff --git a/django/contrib/admin/media/css/layout.css b/django/contrib/admin/media/css/layout.css
new file mode 100644
index 0000000000..19c9286b85
--- /dev/null
+++ b/django/contrib/admin/media/css/layout.css
@@ -0,0 +1,29 @@
+/* PAGE STRUCTURE */
+#container { position:relative; width:100%; min-width:760px; }
+#content { margin:10px 15px; }
+#header { width:100%; }
+#content-main { float:left; width:100%; }
+#content-related { float:right; width:220px; position:relative; margin-right:-230px; }
+#footer { clear:both; padding:10px; }
+
+/* COLUMN TYPES */
+.colMS { margin-right:245px !important; }
+.colSM { margin-left:245px !important; }
+.colSM #content-related { float:left; margin-right:0; margin-left:-230px; }
+.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/django/contrib/admin/media/css/login.css b/django/contrib/admin/media/css/login.css
new file mode 100644
index 0000000000..041135f31e
--- /dev/null
+++ b/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:7em; 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:16em; }
+.login span.help { font-size:10px; display:block; }
+.login .submit-row { clear:both; padding:1em 0 0 7.4em; } \ No newline at end of file
diff --git a/django/contrib/admin/media/css/patch-iewin.css b/django/contrib/admin/media/css/patch-iewin.css
index 46531325df..b2e6a4c560 100644
--- a/django/contrib/admin/media/css/patch-iewin.css
+++ b/django/contrib/admin/media/css/patch-iewin.css
@@ -1,7 +1,6 @@
* 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 #content { width /**/: 768px; } /* fixed width for IE5 */
-* html #content-main { width /**/: 535px; } /* fixed width for IE5 */ \ No newline at end of file
+* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */ \ No newline at end of file
diff --git a/django/contrib/admin/media/css/widgets.css b/django/contrib/admin/media/css/widgets.css
new file mode 100644
index 0000000000..bf526bfd66
--- /dev/null
+++ b/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/django/contrib/admin/media/img/admin/deleted-overlay.gif b/django/contrib/admin/media/img/admin/deleted-overlay.gif
new file mode 100644
index 0000000000..dc3828fe06
--- /dev/null
+++ b/django/contrib/admin/media/img/admin/deleted-overlay.gif
Binary files differ
diff --git a/django/contrib/admin/media/img/admin/inline-delete-8bit.png b/django/contrib/admin/media/img/admin/inline-delete-8bit.png
new file mode 100644
index 0000000000..95caf59a8d
--- /dev/null
+++ b/django/contrib/admin/media/img/admin/inline-delete-8bit.png
Binary files differ
diff --git a/django/contrib/admin/media/img/admin/inline-delete.png b/django/contrib/admin/media/img/admin/inline-delete.png
new file mode 100644
index 0000000000..d59bcd2444
--- /dev/null
+++ b/django/contrib/admin/media/img/admin/inline-delete.png
Binary files differ
diff --git a/django/contrib/admin/media/img/admin/inline-restore-8bit.png b/django/contrib/admin/media/img/admin/inline-restore-8bit.png
new file mode 100644
index 0000000000..e087c8ead3
--- /dev/null
+++ b/django/contrib/admin/media/img/admin/inline-restore-8bit.png
Binary files differ
diff --git a/django/contrib/admin/media/img/admin/inline-restore.png b/django/contrib/admin/media/img/admin/inline-restore.png
new file mode 100644
index 0000000000..efdd92ac39
--- /dev/null
+++ b/django/contrib/admin/media/img/admin/inline-restore.png
Binary files differ
diff --git a/django/contrib/admin/media/img/admin/inline-splitter-bg.gif b/django/contrib/admin/media/img/admin/inline-splitter-bg.gif
new file mode 100644
index 0000000000..32ac5b3498
--- /dev/null
+++ b/django/contrib/admin/media/img/admin/inline-splitter-bg.gif
Binary files differ
diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
index 72c4e95eca..cb84790f44 100644
--- a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
+++ b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
@@ -39,7 +39,7 @@ function dismissAddAnotherPopup(win, newId, newRepr) {
if (elem.nodeName == 'SELECT') {
var o = new Option(newRepr, newId);
elem.options[elem.options.length] = o;
- elem.selectedIndex = elem.length - 1;
+ o.selected = true;
} else if (elem.nodeName == 'INPUT') {
elem.value = newId;
}
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
new file mode 100644
index 0000000000..022d20bed9
--- /dev/null
+++ b/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/django/contrib/admin/models/__init__.py b/django/contrib/admin/models/__init__.py
deleted file mode 100644
index e11e2df093..0000000000
--- a/django/contrib/admin/models/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['admin']
diff --git a/django/contrib/admin/models/admin.py b/django/contrib/admin/models/admin.py
deleted file mode 100644
index b7bcda192b..0000000000
--- a/django/contrib/admin/models/admin.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from django.core import meta
-from django.models import auth, core
-from django.utils.translation import gettext_lazy as _
-
-class LogEntry(meta.Model):
- action_time = meta.DateTimeField(_('action time'), auto_now=True)
- user = meta.ForeignKey(auth.User)
- content_type = meta.ForeignKey(core.ContentType, blank=True, null=True)
- object_id = meta.TextField(_('object id'), blank=True, null=True)
- object_repr = meta.CharField(_('object repr'), maxlength=200)
- action_flag = meta.PositiveSmallIntegerField(_('action flag'))
- change_message = meta.TextField(_('change message'), blank=True)
- class META:
- module_name = 'log'
- verbose_name = _('log entry')
- verbose_name_plural = _('log entries')
- db_table = 'django_admin_log'
- ordering = ('-action_time',)
- module_constants = {
- 'ADDITION': 1,
- 'CHANGE': 2,
- 'DELETION': 3,
- }
-
- 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.get_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.get_content_type().get_package(), self.get_content_type().python_module_name, self.object_id)
-
- def _module_log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
- e = LogEntry(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
- e.save()
diff --git a/django/contrib/admin/templates/admin/404.html b/django/contrib/admin/templates/admin/404.html
index d791f565ba..9bf4293e76 100644
--- a/django/contrib/admin/templates/admin/404.html
+++ b/django/contrib/admin/templates/admin/404.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block title %}{% trans 'Page not found' %}{% endblock %}
diff --git a/django/contrib/admin/templates/admin/500.html b/django/contrib/admin/templates/admin/500.html
index 9d3e3de32c..b30e43170d 100644
--- a/django/contrib/admin/templates/admin/500.html
+++ b/django/contrib/admin/templates/admin/500.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> &rsaquo; {% trans "Server error" %}</div>{% endblock %}
diff --git a/django/contrib/admin/templates/admin/base_site.html b/django/contrib/admin/templates/admin/base_site.html
index b4d285b779..b867bd29bd 100644
--- a/django/contrib/admin/templates/admin/base_site.html
+++ b/django/contrib/admin/templates/admin/base_site.html
@@ -1,4 +1,4 @@
-{% extends "admin/base" %}
+{% extends "admin/base.html" %}
{% load i18n %}
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 02d5760509..a667087f5f 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -1,36 +1,39 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../jsi18n/"></script>
-{% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %}
+{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
-{% block coltype %}{{ bound_manipulator.coltype }}{% endblock %}
-{% block bodyclass %}{{ app_label }}-{{ bound_manipulator.object_name.lower }} change-form{% 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="../">{{ bound_manipulator.verbose_name_plural|capfirst }}</a> &rsaquo;
- {% if add %}{% trans "Add" %} {{ bound_manipulator.verbose_name }}{% else %}{{ bound_manipulator.original|striptags|truncatewords:"18" }}{% endif %}
+ <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
+ {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|striptags|truncatewords:"18" }}{% endif %}
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
{% if change %}{% if not is_popup %}
<ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
- {% if bound_manipulator.has_absolute_url %}<li><a href="/r/{{ bound_manipulator.content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
+ {% 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 %}
-<form {{ bound_manipulator.form_enc_attrib }} action="{{ form_url }}" method="post">{% block form_top %}{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post">{% block form_top %}{% endblock %}
+<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
-{% if bound_manipulator.save_on_top %}{% submit_row bound_manipulator %}{% 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_manipulator.bound_field_sets %}
+{% 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 %}
@@ -41,7 +44,7 @@
{% endfor %}
{% block after_field_sets %}{% endblock %}
{% if change %}
- {% if bound_manipulator.ordered_objects %}
+ {% 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 %}
@@ -49,27 +52,17 @@
</div></fieldset>
{% endif %}
{% endif %}
-{% for related_object in bound_manipulator.inline_related_objects %}{% edit_inline related_object %}{% endfor %}
+{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% block after_related_objects %}{% endblock %}
-{% submit_row bound_manipulator %}
+{% submit_row %}
{% if add %}
- <script type="text/javascript">document.getElementById("{{ bound_manipulator.first_form_field_id }}").focus();</script>
+ <script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
{% endif %}
-{% if bound_manipulator.auto_populated_fields %}
+{% if auto_populated_fields %}
<script type="text/javascript">
- {% auto_populated_field_script bound_manipulator.auto_populated_fields change %}
+ {% auto_populated_field_script auto_populated_fields change %}
</script>
{% endif %}
-{% if change %}
- {% if bound_manipulator.ordered_objects %}
- {% if form.order_objects %}<ul id="orderthese">
- {% for object in form.order_objects %}
- <li id="p{% object_pk bound_manipulator object %}">
- <span id="handlep{% object_pk bound_manipulator object %}">{{ object|truncatewords:"5" }}</span>
- </li>
- {% endfor %}
- </ul>{% endif %}
- {% endif %}
-{% endif %}
+</div>
</form></div>
{% endblock %}
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
index 17b388b699..5b54bfb8cc 100644
--- a/django/contrib/admin/templates/admin/change_list.html
+++ b/django/contrib/admin/templates/admin/change_list.html
@@ -1,8 +1,9 @@
-{% extends "admin/base_site" %}
+{% 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 }} </div>{% endblock %}{% endif %}
+{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html
index 7fba4cebd7..f907c18a16 100644
--- a/django/contrib/admin/templates/admin/delete_confirmation.html
+++ b/django/contrib/admin/templates/admin/delete_confirmation.html
@@ -1,6 +1,14 @@
-{% extends "admin/base_site" %}
+{% 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 }}</a> &rsaquo;
+ <a href="../">{{ object|striptags|truncatewords:"18" }}</a> &rsaquo;
+ {% trans 'Delete' %}
+</div>
+{% endblock %}
{% block content %}
{% if perms_lacking %}
<p>{% blocktrans %}Deleting the {{ object_name }} '{{ object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
@@ -13,8 +21,10 @@
<p>{% blocktrans %}Are you sure you want to delete the {{ object_name }} "{{ 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/django/contrib/admin/templates/admin/edit_inline_stacked.html b/django/contrib/admin/templates/admin/edit_inline_stacked.html
index 62549ef82d..45aa0a4f58 100644
--- a/django/contrib/admin/templates/admin/edit_inline_stacked.html
+++ b/django/contrib/admin/templates/admin/edit_inline_stacked.html
@@ -13,4 +13,4 @@
{% endif %}
{% endfor %}
{% endfor %}
-</fieldset> \ No newline at end of file
+</fieldset>
diff --git a/django/contrib/admin/templates/admin/field_line.html b/django/contrib/admin/templates/admin/field_line.html
index 10f37d5dc9..b7e2fc2ae0 100644
--- a/django/contrib/admin/templates/admin/field_line.html
+++ b/django/contrib/admin/templates/admin/field_line.html
@@ -9,14 +9,6 @@
{% if not bound_field.has_label_first %}
{% field_label bound_field %}
{% 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" }}</strong>{% endif %}
- {% endif %}
- {% endif %}
{% if bound_field.field.help_text %}<p class="help">{{ bound_field.field.help_text }}</p>{% endif %}
{% endfor %}
</div>
diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html
index aabd4eecca..246086861b 100644
--- a/django/contrib/admin/templates/admin/index.html
+++ b/django/contrib/admin/templates/admin/index.html
@@ -1,6 +1,7 @@
-{% extends "admin/base_site" %}
+{% 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 %}
@@ -13,14 +14,14 @@
{% if app_list %}
{% for app in app_list %}
<div class="module">
- <h2>{{ app.name }}</h2>
- <table>
+ <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
+ <caption>{{ app.name }}</caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
- <th><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
+ <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
- <th>{{ model.name }}</th>
+ <th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
@@ -57,7 +58,7 @@
{% 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.get_content_type.name|capfirst }}</span></li>
+ <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 }}</span></li>
{% endfor %}
</ul>
{% endif %}
diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html
index ea823e1020..5f338f703e 100644
--- a/django/contrib/admin/templates/admin/login.html
+++ b/django/contrib/admin/templates/admin/login.html
@@ -1,6 +1,9 @@
-{% extends "admin/base_site" %}
+{% 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 %}
@@ -9,20 +12,18 @@
<p class="errornote">{{ error_message }}</p>
{% endif %}
<div id="content-main">
-<form action="{{ app_path }}" method="post">
-
-<p class="aligned">
-<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
-</p>
-<p class="aligned">
-<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" />
-<input type="hidden" name="post_data" value="{{ post_data }}" />{% comment %} <span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
-</p>
-
-<div class="aligned ">
-<label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
-</div>
+<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" />
+ <input type="hidden" name="post_data" value="{{ post_data }}" /> {% comment %}<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
+ </div>
+ <div class="submit-row">
+ <label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
+ </div>
</form>
<script type="text/javascript">
diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html
index b182f40acb..0dbe7af743 100644
--- a/django/contrib/admin/templates/admin/object_history.html
+++ b/django/contrib/admin/templates/admin/object_history.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
@@ -15,16 +15,16 @@
<table id="change-history">
<thead>
<tr>
- <th>{% trans 'Date/time' %}</th>
- <th>{% trans 'User' %}</th>
- <th>{% trans 'Action' %}</th>
+ <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>{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
- <td>{{ action.get_user.username }}{% if action.get_user.first_name %} ({{ action.get_user.first_name }} {{ action.get_user.last_name }}){% endif %}</td>
+ <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 }} {{ action.user.last_name }}){% endif %}</td>
<td>{{ action.change_message}}</td>
</tr>
{% endfor %}
diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html
index c2d6cf59c5..24eced9ef9 100644
--- a/django/contrib/admin/templates/admin/search_form.html
+++ b/django/contrib/admin/templates/admin/search_form.html
@@ -3,7 +3,7 @@
{% if cl.lookup_opts.admin.search_fields %}
<div id="toolbar"><form id="changelist-search" action="" method="get">
<div><!-- DIV needed for valid HTML -->
-<label><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
+<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 %}
diff --git a/django/contrib/admin/templates/admin/template_validator.html b/django/contrib/admin/templates/admin/template_validator.html
index f9ac09a77d..422e90261f 100644
--- a/django/contrib/admin/templates/admin/template_validator.html
+++ b/django/contrib/admin/templates/admin/template_validator.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% block content %}
diff --git a/django/contrib/admin/templates/admin_doc/bookmarklets.html b/django/contrib/admin/templates/admin_doc/bookmarklets.html
index 708473750a..fa5942926f 100644
--- a/django/contrib/admin/templates/admin_doc/bookmarklets.html
+++ b/django/contrib/admin/templates/admin_doc/bookmarklets.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/admin_doc/index.html b/django/contrib/admin/templates/admin_doc/index.html
index 6665f4bb42..331774d2b3 100644
--- a/django/contrib/admin/templates/admin_doc/index.html
+++ b/django/contrib/admin/templates/admin_doc/index.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/admin_doc/missing_docutils.html b/django/contrib/admin/templates/admin_doc/missing_docutils.html
index a7cf7e95d7..d0b571f957 100644
--- a/django/contrib/admin/templates/admin_doc/missing_docutils.html
+++ b/django/contrib/admin/templates/admin_doc/missing_docutils.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/admin_doc/model_detail.html b/django/contrib/admin/templates/admin_doc/model_detail.html
index 0eb2971048..953bb4bf54 100644
--- a/django/contrib/admin/templates/admin_doc/model_detail.html
+++ b/django/contrib/admin/templates/admin_doc/model_detail.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
@@ -41,6 +41,6 @@
</table>
</div>
-<p class="small"><a href="../">&lsaquo; Back to Models Documentation</p>
+<p class="small"><a href="../">&lsaquo; Back to Models Documentation</a></p>
</div>
{% endblock %}
diff --git a/django/contrib/admin/templates/admin_doc/model_index.html b/django/contrib/admin/templates/admin_doc/model_index.html
index 3183b577e1..c681da75c9 100644
--- a/django/contrib/admin/templates/admin_doc/model_index.html
+++ b/django/contrib/admin/templates/admin_doc/model_index.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
@@ -8,18 +8,19 @@
{% block content %}
-<h1>Models Documentation</h1>
+<h1>Model documentation</h1>
+
+{% regroup models by app_label as grouped_models %}
<div id="content-main">
-{% regroup models|dictsort:"module" by module as grouped_models %}
{% for group in grouped_models %}
<div class="module">
-<h2 id='{{ group.grouper }}'>{{ group.grouper }}</h2>
+<h2 id="{{ group.grouper }}">{{ group.grouper|capfirst }}</h2>
<table class="xfull">
{% for model in group.list %}
<tr>
-<th><a href="{{ model.name }}/">{{ model.class }}</a></th>
+<th><a href="{{ model.app_label }}.{{ model.object_name.lower }}/">{{ model.object_name }}</a></th>
</tr>
{% endfor %}
</table>
@@ -32,11 +33,11 @@
{% block sidebar %}
<div id="content-related" class="sidebar">
<div class="module">
-<h2>Model Groups Quick List</h2>
+<h2>Model groups</h2>
<ul>
-{% regroup models|dictsort:"module" by module as grouped_models %}
+{% regroup models by app_label as grouped_models %}
{% for group in grouped_models %}
- <li><a href="#{{ group.grouper }}">{{ group.grouper }}</a></li>
+ <li><a href="#{{ group.grouper }}">{{ group.grouper|capfirst }}</a></li>
{% endfor %}
</ul>
</div>
diff --git a/django/contrib/admin/templates/admin_doc/template_detail.html b/django/contrib/admin/templates/admin_doc/template_detail.html
index d2a2831098..df67f1856b 100644
--- a/django/contrib/admin/templates/admin_doc/template_detail.html
+++ b/django/contrib/admin/templates/admin_doc/template_detail.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 }}</div>{% endblock %}
{% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %}
diff --git a/django/contrib/admin/templates/admin_doc/template_filter_index.html b/django/contrib/admin/templates/admin_doc/template_filter_index.html
index 30ddee9d64..72344c16cb 100644
--- a/django/contrib/admin/templates/admin_doc/template_filter_index.html
+++ b/django/contrib/admin/templates/admin_doc/template_filter_index.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/admin_doc/template_tag_index.html b/django/contrib/admin/templates/admin_doc/template_tag_index.html
index 9051ae5dde..287475ab09 100644
--- a/django/contrib/admin/templates/admin_doc/template_tag_index.html
+++ b/django/contrib/admin/templates/admin_doc/template_tag_index.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/admin_doc/view_detail.html b/django/contrib/admin/templates/admin_doc/view_detail.html
index a7e920e0d6..ba90399358 100644
--- a/django/contrib/admin/templates/admin_doc/view_detail.html
+++ b/django/contrib/admin/templates/admin_doc/view_detail.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/admin_doc/view_index.html b/django/contrib/admin/templates/admin_doc/view_index.html
index a054ceb66b..caab8a2e71 100644
--- a/django/contrib/admin/templates/admin_doc/view_index.html
+++ b/django/contrib/admin/templates/admin_doc/view_index.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/registration/logged_out.html b/django/contrib/admin/templates/registration/logged_out.html
index 756f82aadc..d339ef0a49 100644
--- a/django/contrib/admin/templates/registration/logged_out.html
+++ b/django/contrib/admin/templates/registration/logged_out.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_change_done.html b/django/contrib/admin/templates/registration/password_change_done.html
index f163e55a68..85e1bf216c 100644
--- a/django/contrib/admin/templates/registration/password_change_done.html
+++ b/django/contrib/admin/templates/registration/password_change_done.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html
index 096e66ce13..036d56212c 100644
--- a/django/contrib/admin/templates/registration/password_change_form.html
+++ b/django/contrib/admin/templates/registration/password_change_form.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% 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 %}
diff --git a/django/contrib/admin/templates/registration/password_reset_done.html b/django/contrib/admin/templates/registration/password_reset_done.html
index dff293c931..f97b5688c2 100644
--- a/django/contrib/admin/templates/registration/password_reset_done.html
+++ b/django/contrib/admin/templates/registration/password_reset_done.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html
index 1b6a2c9a17..423821ba60 100644
--- a/django/contrib/admin/templates/registration/password_reset_form.html
+++ b/django/contrib/admin/templates/registration/password_reset_form.html
@@ -1,4 +1,4 @@
-{% extends "admin/base_site" %}
+{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
diff --git a/django/contrib/admin/templates/widget/foreign.html b/django/contrib/admin/templates/widget/foreign.html
index 582d65df7f..6b43d044bd 100644
--- a/django/contrib/admin/templates/widget/foreign.html
+++ b/django/contrib/admin/templates/widget/foreign.html
@@ -1,12 +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.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}{{"&"|escape}}{% 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.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/" 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 %}
+ {% 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.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/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>
+ <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" }}</strong>{% endif %}
+ {% endif %}
+{% endif %}
diff --git a/django/contrib/admin/templates/widget/many_to_many.html b/django/contrib/admin/templates/widget/many_to_many.html
index 151fe04f30..a93aa65f73 100644
--- a/django/contrib/admin/templates/widget/many_to_many.html
+++ b/django/contrib/admin/templates/widget/many_to_many.html
@@ -1 +1 @@
-{% include "widget/foreign" %}
+{% include "widget/foreign.html" %}
diff --git a/django/contrib/admin/templates/widget/one_to_one.html b/django/contrib/admin/templates/widget/one_to_one.html
index 151fe04f30..a79a12314f 100644
--- a/django/contrib/admin/templates/widget/one_to_one.html
+++ b/django/contrib/admin/templates/widget/one_to_one.html
@@ -1 +1,2 @@
-{% include "widget/foreign" %}
+{% if add %}{% include "widget/foreign.html" %}{% endif %}
+{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index b87fd15e60..5a6b6a24e9 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -1,14 +1,15 @@
-from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR
+from django import template
+from django.conf import settings
+from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, ALL_VAR
from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS
-from django.core import meta, template
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
-from django.conf.settings import ADMIN_MEDIA_PREFIX
-from django.core.template import Library
+from django.template import Library
register = Library()
@@ -16,11 +17,11 @@ DOT = '.'
def paginator_number(cl,i):
if i == DOT:
- return '... '
+ return '... '
elif i == cl.page_num:
- return '<span class="this-page">%d</span> ' % (i+1)
+ 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)
+ 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):
@@ -64,7 +65,7 @@ def pagination(cl):
'ALL_VAR': ALL_VAR,
'1': 1,
}
-pagination = register.inclusion_tag('admin/pagination')(pagination)
+pagination = register.inclusion_tag('admin/pagination.html')(pagination)
def result_headers(cl):
lookup_opts = cl.lookup_opts
@@ -72,22 +73,22 @@ def result_headers(cl):
for i, field_name in enumerate(lookup_opts.admin.list_display):
try:
f = lookup_opts.get_field(field_name)
- except meta.FieldDoesNotExist:
+ 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 __repr__ is a special-case.
- if field_name == '__repr__':
+ # back to the method name. And __str__ is a special-case.
+ if field_name == '__str__':
header = lookup_opts.verbose_name
else:
- func = getattr(cl.mod.Klass, field_name) # Let AttributeErrors propagate.
+ attr = getattr(cl.model, field_name) # Let AttributeErrors propagate.
try:
- header = func.short_description
+ header = attr.short_description
except AttributeError:
- header = func.__name__.replace('_', ' ')
+ header = field_name.replace('_', ' ')
# Non-field list_display values don't get ordering capability.
yield {"text": header}
else:
- if isinstance(f.rel, meta.ManyToOneRel) and f.null:
+ if isinstance(f.rel, models.ManyToOneRel) and f.null:
yield {"text": f.verbose_name}
else:
th_classes = []
@@ -108,34 +109,37 @@ def items_for_result(cl, result):
row_class = ''
try:
f = cl.lookup_opts.get_field(field_name)
- except meta.FieldDoesNotExist:
- # For non-field list_display values, the value is a method
- # name. Execute the method.
+ except models.FieldDoesNotExist:
+ # For non-field list_display values, the value is either a method
+ # or a property.
try:
- func = getattr(result, field_name)
- result_repr = str(func())
+ attr = getattr(result, field_name)
+ allow_tags = getattr(attr, 'allow_tags', False)
+ if callable(attr):
+ attr = attr()
+ 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 getattr(func, 'allow_tags', False):
+ if not allow_tags:
result_repr = escape(result_repr)
else:
field_val = getattr(result, f.attname)
- if isinstance(f.rel, meta.ManyToOneRel):
+ if isinstance(f.rel, models.ManyToOneRel):
if field_val is not None:
- result_repr = getattr(result, 'get_%s' % f.name)()
+ result_repr = 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, meta.DateField) or isinstance(f, meta.TimeField):
+ 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, meta.DateTimeField):
+ if isinstance(f, models.DateTimeField):
result_repr = capfirst(dateformat.format(field_val, datetime_format))
- elif isinstance(f, meta.TimeField):
+ 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))
@@ -143,15 +147,15 @@ def items_for_result(cl, result):
result_repr = EMPTY_CHANGELIST_VALUE
row_class = ' class="nowrap"'
# Booleans are special: We use images.
- elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField):
+ elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
- result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
+ result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
# ImageFields are special: Use a thumbnail.
- elif isinstance(f, meta.ImageField):
+ elif isinstance(f, models.ImageField):
from django.parts.media.photos import get_thumbnail_url
result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
# FloatFields are special: Zero-pad the decimals.
- elif isinstance(f, meta.FloatField):
+ elif isinstance(f, models.FloatField):
if field_val is not None:
result_repr = ('%%.%sf' % f.decimal_places) % field_val
else:
@@ -163,7 +167,7 @@ def items_for_result(cl, result):
else:
result_repr = escape(str(field_val))
if result_repr == '':
- result_repr = '&nbsp;'
+ result_repr = '&nbsp;'
if first: # First column is a special case
first = False
url = cl.url_for_result(result)
@@ -181,28 +185,20 @@ 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")(result_list)
+result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
def date_hierarchy(cl):
- lookup_opts, params, lookup_params, lookup_mod = \
- cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod
-
- if lookup_opts.admin.date_hierarchy:
- field_name = lookup_opts.admin.date_hierarchy
-
+ 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 = params.get(year_field)
- month_lookup = params.get(month_field)
- day_lookup = params.get(day_field)
-
- def link(d):
- return cl.get_query_string(d, [field_generic])
+ year_lookup = cl.params.get(year_field)
+ month_lookup = cl.params.get(month_field)
+ day_lookup = cl.params.get(day_field)
- def get_dates(unit, params):
- return getattr(lookup_mod, 'get_%s_list' % field_name)(unit, **params)
+ link = lambda d: cl.get_query_string(d, [field_generic])
if year_lookup and month_lookup and day_lookup:
month_name = MONTHS[int(month_lookup)]
@@ -215,9 +211,7 @@ def date_hierarchy(cl):
'choices': [{'title': "%s %s" % (month_name, day_lookup)}]
}
elif year_lookup and month_lookup:
- date_lookup_params = lookup_params.copy()
- date_lookup_params.update({year_field: year_lookup, month_field: month_lookup})
- days = get_dates('day', date_lookup_params)
+ days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day')
return {
'show': True,
'back': {
@@ -230,9 +224,7 @@ def date_hierarchy(cl):
} for day in days]
}
elif year_lookup:
- date_lookup_params = lookup_params.copy()
- date_lookup_params.update({year_field: year_lookup})
- months = get_dates('month', date_lookup_params)
+ months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month')
return {
'show' : True,
'back': {
@@ -240,20 +232,20 @@ def date_hierarchy(cl):
'title': _('All dates')
},
'choices': [{
- 'link': link( {year_field: year_lookup, month_field: month.month}),
- 'title': "%s %s" % (month.strftime('%B') , month.year)
+ 'link': link({year_field: year_lookup, month_field: month.month}),
+ 'title': "%s %s" % (month.strftime('%B'), month.year)
} for month in months]
}
else:
- years = get_dates('year', lookup_params)
+ 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 ]
+ } for year in years]
}
-date_hierarchy = register.inclusion_tag('admin/date_hierarchy')(date_hierarchy)
+date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy)
def search_form(cl):
return {
@@ -261,12 +253,12 @@ def search_form(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')(search_form)
+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')(filter)
+filter = register.inclusion_tag('admin/filter.html')(filter)
def filters(cl):
return {'cl': cl}
-filters = register.inclusion_tag('admin/filters')(filters)
+filters = register.inclusion_tag('admin/filters.html')(filters)
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index 6135e74eb4..9f646214ce 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -1,11 +1,13 @@
-from django.core import template, template_loader, meta
+from django import template
+from django.contrib.admin.views.main import AdminBoundField
+from django.template import loader
from django.utils.html import escape
from django.utils.text import capfirst
from django.utils.functional import curry
-from django.contrib.admin.views.main import AdminBoundField
-from django.core.meta.fields import BoundField, Field
-from django.core.meta import BoundRelatedObject, TABULAR, STACKED
-from django.conf.settings import ADMIN_MEDIA_PREFIX
+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()
@@ -16,30 +18,28 @@ def class_name_to_underscored(name):
return '_'.join([s.lower() for s in word_re.findall(name)[:-1]])
def include_admin_script(script_path):
- return '<script type="text/javascript" src="%s%s"></script>' % (ADMIN_MEDIA_PREFIX, script_path)
+ return '<script type="text/javascript" src="%s%s"></script>' % (settings.ADMIN_MEDIA_PREFIX, script_path)
include_admin_script = register.simple_tag(include_admin_script)
-def submit_row(context, bound_manipulator):
+def submit_row(context):
+ opts = context['opts']
change = context['change']
- add = context['add']
- show_delete = context['show_delete']
- has_delete_permission = context['has_delete_permission']
is_popup = context['is_popup']
return {
- 'onclick_attrib': (bound_manipulator.ordered_objects and change
+ 'onclick_attrib': (opts.get_ordered_objects() and change
and 'onclick="submitOrderForm();"' or ''),
- 'show_delete_link': (not is_popup and has_delete_permission
- and (change or show_delete)),
- 'show_save_as_new': not is_popup and change and bound_manipulator.save_as,
- 'show_save_and_add_another': not is_popup and (not bound_manipulator.save_as or add),
- 'show_save_and_continue': not is_popup,
+ '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', takes_context=True)(submit_row)
+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, meta.BooleanField):
+ if isinstance(bound_field.field, models.BooleanField):
class_names.append("vCheckboxLabel")
colon = ""
else:
@@ -64,16 +64,15 @@ class FieldWidgetNode(template.Node):
if not cls.nodelists.has_key(klass):
try:
field_class_name = klass.__name__
- template_name = "widget/%s" % \
- class_name_to_underscored(field_class_name)
- nodelist = template_loader.get_template(template_name).nodelist
+ 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 = template_loader.get_template("widget/default").nodelist
+ cls.default = loader.get_template("widget/default.html").nodelist
nodelist = cls.default
cls.nodelists[klass] = nodelist
@@ -97,21 +96,22 @@ class FieldWrapper(object):
self.field = field
def needs_header(self):
- return not isinstance(self.field, meta.AutoField)
+ 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, (meta.ManyToOneRel, meta.ManyToManyRel)) \
+ 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):
+ 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):
@@ -120,29 +120,25 @@ class TabularBoundRelatedObject(BoundRelatedObject):
fields = self.relation.editable_fields()
- self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields)
- for field_mapping in self.field_mappings]
+ 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"
+ 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.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields)
- for field_mapping in self.field_mappings]
+ 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"
-
-bound_related_object_overrides = {
- TABULAR: TabularBoundRelatedObject,
- STACKED: StackedBoundRelatedObject,
-}
+ return "admin/edit_inline_stacked.html"
class EditInlineNode(template.Node):
def __init__(self, rel_var):
@@ -150,21 +146,16 @@ class EditInlineNode(template.Node):
def render(self, context):
relation = template.resolve_variable(self.rel_var, context)
-
context.push()
-
- klass = relation.field.rel.edit_inline
- bound_related_object_class = bound_related_object_overrides.get(klass, klass)
-
+ if relation.field.rel.edit_inline == models.TABULAR:
+ bound_related_object_class = TabularBoundRelatedObject
+ else:
+ bound_related_object_class = StackedBoundRelatedObject
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 = template_loader.get_template(bound_related_object.template_name())
-
+ t = loader.get_template(bound_related_object.template_name())
output = t.render(context)
-
context.pop()
return output
@@ -191,30 +182,30 @@ 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, meta.ManyToManyRel) and f.rel.filter_interface:
- return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
+ 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, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX)
+ f.name, f.verbose_name, f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
else:
return ''
filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)
-def do_one_arg_tag(node_factory, parser,token):
- tokens = token.contents.split()
- if len(tokens) != 2:
- raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
- return node_factory(tokens[1])
-
-def register_one_arg_tag(node):
- tag_name = class_name_to_underscored(node.__name__)
- parse_func = curry(do_one_arg_tag, node)
- register.tag(tag_name, parse_func)
+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)
-register_one_arg_tag(FieldWidgetNode)
-register_one_arg_tag(EditInlineNode)
+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, BoundField)):
+ if isinstance(argument_val, AdminBoundField):
bound_fields = [argument_val]
else:
bound_fields = [bf for bf in argument_val]
@@ -229,7 +220,7 @@ def admin_field_line(context, argument_val):
break
# Assumes BooleanFields won't be stacked next to each other!
- if isinstance(bound_fields[0].field, meta.BooleanField):
+ if isinstance(bound_fields[0].field, models.BooleanField):
class_names.append('checkbox-row')
return {
@@ -238,8 +229,4 @@ def admin_field_line(context, argument_val):
'bound_fields': bound_fields,
'class_names': " ".join(class_names),
}
-admin_field_line = register.inclusion_tag('admin/field_line', takes_context=True)(admin_field_line)
-
-def object_pk(bound_manip, ordered_obj):
- return bound_manip.get_ordered_object_pk(ordered_obj)
-object_pk = register.simple_tag(object_pk)
+admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line)
diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py
index 7a91516ebc..c328ddf203 100644
--- a/django/contrib/admin/templatetags/adminapplist.py
+++ b/django/contrib/admin/templatetags/adminapplist.py
@@ -1,4 +1,5 @@
-from django.core import template
+from django import template
+from django.db.models import get_models
register = template.Library()
@@ -7,20 +8,24 @@ class AdminApplistNode(template.Node):
self.varname = varname
def render(self, context):
- from django.core import meta
+ from django.db import models
from django.utils.text import capfirst
app_list = []
user = context['user']
- for app in meta.get_installed_model_modules():
- app_label = app.__name__[app.__name__.rindex('.')+1:]
+ 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:
+ for m in app_models:
if m._meta.admin:
- module_name = m._meta.module_name
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())),
@@ -32,7 +37,7 @@ class AdminApplistNode(template.Node):
if True in perms.values():
model_list.append({
'name': capfirst(m._meta.verbose_name_plural),
- 'admin_url': '%s/%s/' % (app_label, m._meta.module_name),
+ 'admin_url': '%s/%s/' % (app_label, m.__name__.lower()),
'perms': perms,
})
diff --git a/django/contrib/admin/templatetags/adminmedia.py b/django/contrib/admin/templatetags/adminmedia.py
index cd513f6c81..266c017e3d 100644
--- a/django/contrib/admin/templatetags/adminmedia.py
+++ b/django/contrib/admin/templatetags/adminmedia.py
@@ -1,10 +1,11 @@
-from django.core.template import Library
+from django.template import Library
+
register = Library()
def admin_media_prefix():
try:
- from django.conf.settings import ADMIN_MEDIA_PREFIX
+ from django.conf import settings
except ImportError:
return ''
- return ADMIN_MEDIA_PREFIX
+ return settings.ADMIN_MEDIA_PREFIX
admin_media_prefix = register.simple_tag(admin_media_prefix)
diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py
index 013e07c80f..5caba2b795 100644
--- a/django/contrib/admin/templatetags/log.py
+++ b/django/contrib/admin/templatetags/log.py
@@ -1,5 +1,5 @@
-from django.models.admin import log
-from django.core import template
+from django import template
+from django.contrib.admin.models import LogEntry
register = template.Library()
@@ -13,7 +13,7 @@ class AdminLogNode(template.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] = log.get_list(user__id__exact=self.user, limit=self.limit, select_related=True)
+ context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
return ''
class DoGetAdminLog:
diff --git a/django/contrib/admin/urls.py b/django/contrib/admin/urls.py
new file mode 100644
index 0000000000..dde848d766
--- /dev/null
+++ b/django/contrib/admin/urls.py
@@ -0,0 +1,31 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ ('^$', 'django.contrib.admin.views.main.index'),
+ ('^r/(\d+)/(\d+)/$', 'django.views.defaults.shortcut'),
+ ('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'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/jump/$', 'django.contrib.admin.views.doc.jump_to_view'),
+ ('^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/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'),
+)
diff --git a/django/contrib/admin/urls/admin.py b/django/contrib/admin/urls/admin.py
deleted file mode 100644
index 3f4dbab419..0000000000
--- a/django/contrib/admin/urls/admin.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from django.conf.urls.defaults import *
-from django.conf.settings import INSTALLED_APPS
-
-urlpatterns = (
- ('^$', 'django.contrib.admin.views.main.index'),
- ('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}),
- ('^logout/$', 'django.views.auth.login.logout'),
- ('^password_change/$', 'django.views.registration.passwords.password_change'),
- ('^password_change/done/$', 'django.views.registration.passwords.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/jump/$', 'django.contrib.admin.views.doc.jump_to_view'),
- ('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
- ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
- ('^doc/models/(?P<model>[^/]+)/$', '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'),
-)
-
-if 'ellington.events' in INSTALLED_APPS:
- urlpatterns += (
- ("^events/usersubmittedevents/(?P<object_id>\d+)/$", 'ellington.events.views.admin.user_submitted_event_change_stage'),
- ("^events/usersubmittedevents/(?P<object_id>\d+)/delete/$", 'ellington.events.views.admin.user_submitted_event_delete_stage'),
- )
-
-if 'ellington.news' in INSTALLED_APPS:
- urlpatterns += (
- ("^stories/preview/$", 'ellington.news.views.admin.story_preview'),
- ("^stories/js/inlinecontrols/$", 'ellington.news.views.admin.inlinecontrols_js'),
- ("^stories/js/inlinecontrols/(?P<label>[-\w]+)/$", 'ellington.news.views.admin.inlinecontrols_js_specific'),
- )
-
-if 'ellington.alerts' in INSTALLED_APPS:
- urlpatterns += (
- ("^alerts/send/$", 'ellington.alerts.views.admin.send_alert_form'),
- ("^alerts/send/do/$", 'ellington.alerts.views.admin.send_alert_action'),
- )
-
-if 'ellington.media' in INSTALLED_APPS:
- urlpatterns += (
- ('^media/photos/caption/(?P<photo_id>\d+)/$', 'ellington.media.views.admin.get_exif_caption'),
- )
-
-urlpatterns += (
- # Metasystem admin pages
- ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.contrib.admin.views.main.change_list'),
- ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
- ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/history/$', 'django.contrib.admin.views.main.history'),
- ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
- ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.contrib.admin.views.main.change_stage'),
-)
-urlpatterns = patterns('', *urlpatterns)
diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py
index 80a8f2a773..c6c794c75e 100644
--- a/django/contrib/admin/utils.py
+++ b/django/contrib/admin/utils.py
@@ -82,18 +82,18 @@ ROLES = {
def create_reference_role(rolename, urlbase):
def _role(name, rawtext, text, lineno, inliner, options={}, content=[]):
- node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text)), **options)
+ 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={}, content=[]):
context = inliner.document.settings.default_reference_context
- node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text)), **options)
+ 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():
+ for name, urlbase in ROLES.items():
create_reference_role(name, urlbase)
diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py
index 5ddc17fa85..d984077dfb 100644
--- a/django/contrib/admin/views/decorators.py
+++ b/django/contrib/admin/views/decorators.py
@@ -1,7 +1,7 @@
-from django.core.extensions import DjangoContext, render_to_response
-from django.conf.settings import SECRET_KEY
-from django.models.auth import users
-from django.utils import httpwrappers
+from django import http, template
+from django.conf import settings
+from django.contrib.auth.models import User, SESSION_KEY
+from django.shortcuts import render_to_response
from django.utils.translation import gettext_lazy
import base64, datetime, md5
import cPickle as pickle
@@ -19,22 +19,22 @@ def _display_login_form(request, error_message=''):
post_data = _encode_post_data(request.POST)
else:
post_data = _encode_post_data({})
- return render_to_response('admin/login', {
+ return render_to_response('admin/login.html', {
'title': _('Log in'),
'app_path': request.path,
'post_data': post_data,
'error_message': error_message
- }, context_instance=DjangoContext(request))
+ }, context_instance=template.RequestContext(request))
def _encode_post_data(post_data):
pickled = pickle.dumps(post_data)
- pickled_md5 = md5.new(pickled + SECRET_KEY).hexdigest()
+ pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def _decode_post_data(encoded_data):
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
- if md5.new(pickled + SECRET_KEY).hexdigest() != tamper_check:
+ if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)
@@ -53,7 +53,7 @@ def staff_member_required(view_func):
request.POST = _decode_post_data(request.POST['post_data'])
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.middleware.sessions.SessionMiddleware'."
+ 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):
@@ -71,14 +71,14 @@ def staff_member_required(view_func):
# Check the password.
username = request.POST.get('username', '')
try:
- user = users.get_object(username__exact=username, is_staff__exact=True)
- except users.UserDoesNotExist:
+ user = User.objects.get(username=username, is_staff=True)
+ except User.DoesNotExist:
message = ERROR_MESSAGE
if '@' in username:
# Mistakenly entered e-mail address instead of username? Look it up.
try:
- user = users.get_object(email__exact=username)
- except users.UserDoesNotExist:
+ 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
@@ -87,7 +87,7 @@ def staff_member_required(view_func):
# The user data is correct; log in the user in and continue.
else:
if user.check_password(request.POST.get('password', '')):
- request.session[users.SESSION_KEY] = user.id
+ request.session[SESSION_KEY] = user.id
user.last_login = datetime.datetime.now()
user.save()
if request.POST.has_key('post_data'):
@@ -99,7 +99,7 @@ def staff_member_required(view_func):
return view_func(request, *args, **kwargs)
else:
request.session.delete_test_cookie()
- return httpwrappers.HttpResponseRedirect(request.path)
+ return http.HttpResponseRedirect(request.path)
else:
return _display_login_form(request, ERROR_MESSAGE)
diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py
index 9a6d3ec400..f3675b6adf 100644
--- a/django/contrib/admin/views/doc.py
+++ b/django/contrib/admin/views/doc.py
@@ -1,12 +1,14 @@
-from django.core import meta
-from django import templatetags
+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.models.core import sites
-from django.core.extensions import DjangoContext, render_to_response
-from django.core.exceptions import Http404, ViewDoesNotExist
-from django.core import template, urlresolvers
+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
@@ -15,15 +17,15 @@ MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
def doc_index(request):
if not utils.docutils_is_available:
return missing_docutils_page(request)
- return render_to_response('admin_doc/index', context_instance=DjangoContext(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', {
- 'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', request.META['HTTP_HOST'], admin_root),
- }, context_instance=DjangoContext(request))
+ return render_to_response('admin_doc/bookmarklets.html', {
+ 'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', get_host(request), admin_root),
+ }, context_instance=RequestContext(request))
bookmarklets = staff_member_required(bookmarklets)
def template_tag_index(request):
@@ -54,7 +56,7 @@ def template_tag_index(request):
'library': tag_library,
})
- return render_to_response('admin_doc/template_tag_index', {'tags': tags}, context_instance=DjangoContext(request))
+ 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):
@@ -84,16 +86,20 @@ def template_filter_index(request):
'meta': metadata,
'library': tag_library,
})
- return render_to_response('admin_doc/template_filter_index', {'filters': filters}, context_instance=DjangoContext(request))
+ 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 site_settings_module in settings.ADMIN_FOR:
- settings_mod = __import__(site_settings_module, '', '', [''])
+ for settings_mod in settings_modules:
urlconf = __import__(settings_mod.ROOT_URLCONF, '', '', [''])
view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
for (func, regex) in view_functions:
@@ -101,10 +107,10 @@ def view_index(request):
'name': func.__name__,
'module': func.__module__,
'site_id': settings_mod.SITE_ID,
- 'site': sites.get_object(pk=settings_mod.SITE_ID),
+ 'site': Site.objects.get(pk=settings_mod.SITE_ID),
'url': simplify_regex(regex),
})
- return render_to_response('admin_doc/view_index', {'views': views}, context_instance=DjangoContext(request))
+ 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):
@@ -123,51 +129,63 @@ def view_detail(request, view):
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', {
+ return render_to_response('admin_doc/view_detail.html', {
'name': view,
'summary': title,
'body': body,
'meta': metadata,
- }, context_instance=DjangoContext(request))
+ }, 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)
- models = []
- for app in meta.get_installed_model_modules():
- for model in app._MODELS:
- opts = model._meta
- models.append({
- 'name': '%s.%s' % (opts.app_label, opts.module_name),
- 'module': opts.app_label,
- 'class': opts.module_name,
- })
- return render_to_response('admin_doc/model_index', {'models': models}, context_instance=DjangoContext(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, model):
+def model_detail(request, app_label, model_name):
if not utils.docutils_is_available:
return missing_docutils_page(request)
+ # Get the model class.
try:
- model = meta.get_app(model)
- except ImportError:
- raise Http404
- opts = model.Klass._meta
+ 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 %r not found in app %r" % (model_name, app_label)
- # Gather fields/field descriptions
+ 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 `%s.%s` object" % (app_label, 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': get_readable_field_data_type(field),
- 'verbose': field.verbose_name,
+ 'data_type': data_type,
+ 'verbose': verbose,
'help': field.help_text,
})
- for func_name, func in model.Klass.__dict__.items():
- if callable(func) and len(inspect.getargspec(func)[0]) == 0:
+
+ # 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):
@@ -182,12 +200,26 @@ def model_detail(request, model):
'data_type': get_return_data_type(func_name),
'verbose': verbose,
})
- return render_to_response('admin_doc/model_detail', {
- 'name': '%s.%s' % (opts.app_label, opts.module_name),
- 'summary': "Fields on %s objects" % opts.verbose_name,
+
+ # Gather related objects
+ for rel in opts.get_all_related_objects():
+ verbose = "related `%s.%s` objects" % (rel.opts.app_label, rel.opts.object_name)
+ accessor = rel.get_accessor_name()
+ fields.append({
+ 'name' : "%s.all" % accessor,
+ 'verbose' : utils.parse_rst("all " + verbose , 'model', 'model:' + opts.module_name),
+ })
+ fields.append({
+ 'name' : "%s.count" % accessor,
+ 'verbose' : utils.parse_rst("number of " + 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=DjangoContext(request))
+ }, context_instance=RequestContext(request))
model_detail = staff_member_required(model_detail)
def template_detail(request, template):
@@ -201,13 +233,13 @@ def template_detail(request, template):
'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': sites.get_object(pk=settings_mod.SITE_ID),
+ 'site': Site.objects.get(pk=settings_mod.SITE_ID),
'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
})
- return render_to_response('admin_doc/template_detail', {
+ return render_to_response('admin_doc/template_detail.html', {
'name': template,
'templates': templates,
- }, context_instance=DjangoContext(request))
+ }, context_instance=RequestContext(request))
template_detail = staff_member_required(template_detail)
####################
@@ -216,7 +248,7 @@ template_detail = staff_member_required(template_detail)
def missing_docutils_page(request):
"""Display an error message for people without docutils"""
- return render_to_response('admin_doc/missing_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.
@@ -271,9 +303,6 @@ DATA_TYPE_MAPPING = {
}
def get_readable_field_data_type(field):
- # ForeignKey is a special case. Use the field type of the relation.
- if field.get_internal_type() == 'ForeignKey':
- field = field.rel.get_related_field()
return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
def extract_views_from_urlpatterns(urlpatterns, base=''):
@@ -295,15 +324,23 @@ def extract_views_from_urlpatterns(urlpatterns, base=''):
raise TypeError, "%s does not appear to be a urlpattern object" % p
return views
-# 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>/"
-
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)
- pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/')
+
+ # 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/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 3d92a7d949..9913914858 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -1,32 +1,34 @@
-# Generic admin views.
-from django.contrib.admin.views.decorators import staff_member_required
+from django import forms, template
+from django.conf import settings
from django.contrib.admin.filterspecs import FilterSpec
-from django.core import formfields, meta, template
-from django.core.template import loader
-from django.core.meta.fields import BoundField, BoundFieldLine, BoundFieldSet
-from django.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
-from django.core.extensions import DjangoContext as Context
-from django.core.extensions import get_object_or_404, render_to_response
+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.conf.settings import ADMIN_MEDIA_PREFIX
-try:
- from django.models.admin import log
-except ImportError:
- raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS."
-from django.utils.html import escape
-from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
-from django.utils.text import capfirst, get_text_list
+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.template import loader
from django.utils import dateformat
from django.utils.dates import MONTHS
from django.utils.html import escape
+from django.utils.text import capfirst, get_text_list
import operator
-# The system will display a "Show all" link only if the total result count
-# is less than or equal to this setting.
-MAX_SHOW_ALL_ALLOWED = 200
+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."
-DEFAULT_RESULTS_PER_PAGE = 100
+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'
@@ -34,229 +36,59 @@ PAGE_VAR = 'p'
SEARCH_VAR = 'q'
IS_POPUP_VAR = 'pop'
-# Text to display within changelist table cells if the value is blank.
+# Text to display within change-list table cells if the value is blank.
EMPTY_CHANGELIST_VALUE = '(None)'
-def _get_mod_opts(app_label, module_name):
- "Helper function that returns a tuple of (module, opts), raising Http404 if necessary."
- try:
- mod = meta.get_module(app_label, module_name)
- except ImportError:
- raise Http404 # Invalid app or module name. Maybe it's not in INSTALLED_APPS.
- opts = mod.Klass._meta
- if not opts.admin:
- raise Http404 # This object is valid but has no admin interface.
- return mod, opts
-
-def index(request):
- return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request))
-index = staff_member_required(index)
+use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin
class IncorrectLookupParameters(Exception):
pass
-class ChangeList(object):
- def __init__(self, request, app_label, module_name):
- self.get_modules_and_options(app_label, module_name, request)
- self.get_search_parameters(request)
- self.get_ordering()
- self.query = request.GET.get(SEARCH_VAR, '')
- self.get_lookup_params()
- 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.get_filters(request)
- self.pk_attname = self.lookup_opts.pk.attname
-
- def get_filters(self, request):
- self.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)
- if spec and spec.has_output():
- self.filter_specs.append(spec)
- self.has_filters = bool(self.filter_specs)
+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 autoamtically
+ 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 get_query_string(self, new_params={}, 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_modules_and_options(self, app_label, module_name, request):
- self.mod, self.opts = _get_mod_opts(app_label, module_name)
- if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()):
- raise PermissionDenied
-
- self.lookup_mod, self.lookup_opts = self.mod, self.opts
-
- def get_search_parameters(self, request):
- # 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]
-
- def get_results(self, request):
- lookup_mod, lookup_params, show_all, page_num = \
- self.lookup_mod, self.lookup_params, self.show_all, self.page_num
- # Get the results.
- try:
- paginator = ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE)
- # 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 filters applied.
- real_lookup_params = lookup_params.copy()
- del real_lookup_params['order_by']
- if real_lookup_params:
- full_result_count = lookup_mod.get_count()
- else:
- full_result_count = paginator.hits
- del real_lookup_params
- result_count = paginator.hits
- can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
- multi_page = result_count > DEFAULT_RESULTS_PER_PAGE
-
- # Get the list of objects to display on this page.
- if (show_all and can_show_all) or not multi_page:
- result_list = lookup_mod.get_list(**lookup_params)
- else:
+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:
- result_list = paginator.get_page(page_num)
- except InvalidPage:
- result_list = []
- (self.result_count, self.full_result_count, self.result_list,
- self.can_show_all, self.multi_page, self.paginator) = (result_count,
- full_result_count, result_list, can_show_all, multi_page, paginator )
-
- def url_for_result(self, result):
- return "%s/" % getattr(result, self.pk_attname)
-
- 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 = meta.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:
- try:
- f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
- except meta.FieldDoesNotExist:
- pass
- else:
- if not isinstance(f.rel, meta.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]
- self.order_field, self.order_type = order_field, order_type
-
- def get_lookup_params(self):
- # Prepare the lookup parameters for the API lookup.
- (params, order_field, lookup_opts, order_type, opts, query) = \
- (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query)
-
- lookup_params = params.copy()
- 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]
- # If the order-by field is a field with a relationship, order by the value
- # in the related table.
- lookup_order_field = order_field
- try:
- f = lookup_opts.get_field(order_field)
- except meta.FieldDoesNotExist:
- pass
- else:
- if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOneRel):
- f = lookup_opts.get_field(order_field)
- rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column
- lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering)
- # Use select_related if one of the list_display options is a field with a
- # relationship.
- if lookup_opts.admin.list_select_related:
- lookup_params['select_related'] = True
+ myappend(mychr(myatoi(item[:2], 16))
+ + item[2:])
+ except ValueError:
+ myappend('_' + item)
else:
- for field_name in lookup_opts.admin.list_display:
- try:
- f = lookup_opts.get_field(field_name)
- except meta.FieldDoesNotExist:
- pass
- else:
- if isinstance(f.rel, meta.ManyToOneRel):
- lookup_params['select_related'] = True
- break
- lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,)
- if lookup_opts.admin.search_fields and query:
- complex_queries = []
- for bit in query.split():
- or_queries = []
- for field_name in lookup_opts.admin.search_fields:
- or_queries.append(meta.Q(**{'%s__icontains' % field_name: bit}))
- complex_queries.append(reduce(operator.or_, or_queries))
- lookup_params['complex'] = reduce(operator.and_, complex_queries)
- if opts.one_to_one_field:
- lookup_params.update(opts.one_to_one_field.rel.limit_choices_to)
- self.lookup_params = lookup_params
-
-def change_list(request, app_label, module_name):
- try:
- cl = ChangeList(request, app_label, module_name)
- except IncorrectLookupParameters:
- return HttpResponseRedirect(request.path)
-
- c = Context(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' % (app_label, cl.opts.object_name.lower()),
- 'admin/%s/change_list' % app_label,
- 'admin/change_list'], context_instance=c)
-change_list = staff_member_required(change_list)
-
-use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOneRel, meta.ManyToManyRel)) and field.rel.raw_id_admin
+ myappend('_' + item)
+ return "".join(res)
-def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_sets):
+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(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
+ 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 ordered_objects:
+ 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)
@@ -264,29 +96,30 @@ def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_se
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' )
+ js.append('js/admin/CollapsedFieldsets.js')
for field_line in field_set:
try:
for f in field_line:
- if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
+ 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(BoundField):
+class AdminBoundField(object):
def __init__(self, field, field_mapping, original):
- super(AdminBoundField, self).__init__(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, meta.BooleanField)
+ 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, meta.DateTimeField)
- self.is_file_field = isinstance(field, meta.FileField)
- self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOneRel) or isinstance(field.rel, meta.ManyToManyRel) and field.rel.to.admin
- self.hidden = isinstance(self.field, meta.AutoField)
+ 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 = []
@@ -298,26 +131,22 @@ class AdminBoundField(BoundField):
self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
self._repr_filled = False
- def _fetch_existing_display(self, func_name):
- class_dict = self.original.__class__.__dict__
- func = class_dict.get(func_name)
- return func(self.original)
+ if field.rel:
+ self.related_url = '../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
- def _fill_existing_display(self):
- if getattr(self, '_display_filled', False):
- return
- # HACK
- if isinstance(self.field.rel, meta.ManyToOneRel):
- func_name = 'get_%s' % self.field.name
- self._display = self._fetch_existing_display(func_name)
- elif isinstance(self.field.rel, meta.ManyToManyRel):
- func_name = 'get_%s_list' % self.field.rel.singular
- self._display = ", ".join([str(obj) for obj in self._fetch_existing_display(func_name)])
- self._display_filled = True
+ def original_value(self):
+ if self.original:
+ return self.original.__dict__[self.field.column]
def existing_display(self):
- self._fill_existing_display()
- return self._display
+ try:
+ return self._display
+ except AttributeError:
+ if isinstance(self.field.rel, models.ManyToOneRel):
+ self._display = getattr(self.original, 'get_%s' % self.field.name)()
+ elif isinstance(self.field.rel, models.ManyToManyRel):
+ self._display = ", ".join([str(obj) for obj in getattr(self.original, 'get_%s_list' % self.field.rel.singular)()])
+ return self._display
def __repr__(self):
return repr(self.__dict__)
@@ -325,94 +154,114 @@ class AdminBoundField(BoundField):
def html_error_list(self):
return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])
-class AdminBoundFieldLine(BoundFieldLine):
+ 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):
- super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField)
+ self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line]
for bound_field in self:
bound_field.first = True
break
-class AdminBoundFieldSet(BoundFieldSet):
- def __init__(self, field_set, field_mapping, original):
- super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine)
-
-class BoundManipulator(object):
- def __init__(self, opts, manipulator, field_mapping):
- self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
- self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None
- self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet)
- for field_set in opts.admin.get_field_sets(opts)]
- self.ordered_objects = opts.get_ordered_objects()[:]
-
-class AdminBoundManipulator(BoundManipulator):
- def __init__(self, opts, manipulator, field_mapping):
- super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping)
- field_sets = opts.admin.get_field_sets(opts)
-
- self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
- self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets);
-
- self.coltype = self.ordered_objects and 'colMS' or 'colM'
- self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
- self.form_enc_attrib = opts.has_field_type(meta.FileField) and \
- 'enctype="multipart/form-data" ' or ''
+ def __iter__(self):
+ for bound_field in self.bound_fields:
+ yield bound_field
- self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
- self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects]
+ def __len__(self):
+ return len(self.bound_fields)
- self.save_on_top = opts.admin.save_on_top
- self.save_as = opts.admin.save_as
+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]
- self.content_type_id = opts.get_content_type_id()
- self.verbose_name_plural = opts.verbose_name_plural
- self.verbose_name = opts.verbose_name
- self.object_name = opts.object_name
+ def __iter__(self):
+ for bound_field_line in self.bound_field_lines:
+ yield bound_field_line
- def get_ordered_object_pk(self, ordered_obj):
- for name in self.ordered_object_pk_names:
- if hasattr(ordered_obj, name):
- return str(getattr(ordered_obj, name))
- return ""
+ def __len__(self):
+ return len(self.bound_field_lines)
-def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''):
+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,
- 'bound_manipulator': AdminBoundManipulator(opts, manipulator, context['form']),
'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,
- 'app_label': app_label,
+ '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" % (app_label, opts.object_name.lower() ),
- "admin/%s/change_form" % app_label ,
- "admin/change_form"], context_instance=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 log_add_message(user, opts,manipulator,new_object):
- pk_value = getattr(new_object, opts.pk.attname)
- log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.ADDITION)
+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
-def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
- mod, opts = _get_mod_opts(app_label, module_name)
if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
raise PermissionDenied
- manipulator = mod.AddManipulator()
+
+ 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(meta.FileField):
+
+ 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 and not request.POST.has_key("_preview"):
+ if not errors:
new_object = manipulator.save(new_data)
- log_add_message(request.user, opts,manipulator,new_object)
- msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':opts.verbose_name, 'obj':new_object}
- pk_value = getattr(new_object,opts.pk.attname)
+ 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.add_message(msg + ' ' + _("You may edit it again below."))
+ 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)
@@ -420,10 +269,10 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
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.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect(request.path)
else:
- request.user.add_message(msg)
+ request.user.message_set.create(message=msg)
return HttpResponseRedirect(post_url)
else:
# Add default data.
@@ -435,73 +284,80 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
errors = {}
# Populate the FormWrapper.
- form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True)
+ form = forms.FormWrapper(manipulator, new_data, errors)
- c = Context(request, {
+ 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(opts, manipulator, app_label, c, add=True)
-add_stage = staff_member_required(add_stage)
+ return render_change_form(model, manipulator, c, add=True)
+add_stage = staff_member_required(never_cache(add_stage))
-def log_change_message(user, opts,manipulator,new_object):
- pk_value = getattr(new_object, opts.pk.column)
- # 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.')
- log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.CHANGE, change_message)
+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
-def change_stage(request, app_label, module_name, object_id):
- mod, opts = _get_mod_opts(app_label, module_name)
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, module_name, form_url='../add/')
+ return add_stage(request, app_label, model_name, form_url='../../add/')
+
try:
- manipulator = mod.ChangeManipulator(object_id)
+ manipulator = model.ChangeManipulator(object_id)
except ObjectDoesNotExist:
raise Http404
if request.POST:
new_data = request.POST.copy()
- if opts.has_field_type(meta.FileField):
+
+ 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 and not request.POST.has_key("_preview"):
+
+ if not errors:
new_object = manipulator.save(new_data)
- log_change_message(request.user,opts,manipulator,new_object)
- msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj':new_object}
- pk_value = getattr(new_object,opts.pk.attname)
+ 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.add_message(msg + ' ' + _("You may edit it again below."))
+ 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.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
+ 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.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect("../add/")
else:
- request.user.add_message(msg)
+ request.user.message_set.create(message=msg)
return HttpResponseRedirect("../")
else:
# Populate new_data with a "flattened" version of the current data.
@@ -519,7 +375,7 @@ def change_stage(request, app_label, module_name, object_id):
errors = {}
# Populate the FormWrapper.
- form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
+ form = forms.FormWrapper(manipulator, new_data, errors)
form.original = manipulator.original_object
form.order_objects = []
@@ -528,20 +384,19 @@ def change_stage(request, app_label, module_name, object_id):
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_method_name_part())
+ related.get_accessor_name())
orig_list = func()
form.order_objects.extend(orig_list)
- c = Context(request, {
+ 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')
+ 'is_popup': request.REQUEST.has_key('_popup'),
})
-
- return render_change_form(opts,manipulator, app_label, c, change=True)
-change_stage = staff_member_required(change_stage)
+ 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
@@ -559,10 +414,10 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if related.opts in opts_seen:
continue
opts_seen.append(related.opts)
- rel_opts_name = related.get_method_name_part()
- if isinstance(related.field.rel, meta.OneToOneRel):
+ rel_opts_name = related.get_accessor_name()
+ if isinstance(related.field.rel, models.OneToOneRel):
try:
- sub_obj = getattr(obj, 'get_%s' % rel_opts_name)()
+ sub_obj = getattr(obj, rel_opts_name)
except ObjectDoesNotExist:
pass
else:
@@ -579,12 +434,12 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
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.module_name,
- getattr(sub_obj, related.opts.pk.attname), sub_obj), []])
+ (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, 'get_%s_list' % rel_opts_name)():
+ 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
@@ -593,7 +448,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
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.module_name, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj))), []])
+ (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.
@@ -605,21 +460,21 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if related.opts in opts_seen:
continue
opts_seen.append(related.opts)
- rel_opts_name = related.get_method_name_part()
+ rel_opts_name = related.get_accessor_name()
has_related_objs = False
- for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
+ 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, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
- {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []])
+ {'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.name, 'name':related.opts.verbose_name}) + \
+ (_('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, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj)))), []])
+ (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:
@@ -627,12 +482,16 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)
-def delete_stage(request, app_label, module_name, object_id):
+def delete_stage(request, app_label, model_name, object_id):
import sets
- mod, opts = _get_mod_opts(app_label, module_name)
+ 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(mod, pk=object_id)
+ obj = get_object_or_404(model, pk=object_id)
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
@@ -645,28 +504,240 @@ def delete_stage(request, app_label, module_name, object_id):
raise PermissionDenied
obj_display = str(obj)
obj.delete()
- log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, log.DELETION)
- request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display})
+ 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("../../")
- return render_to_response('admin/delete_confirmation', {
+ extra_context = {
"title": _("Are you sure?"),
"object_name": opts.verbose_name,
"object": obj,
"deleted_objects": deleted_objects,
"perms_lacking": perms_needed,
- }, context_instance=Context(request))
-delete_stage = staff_member_required(delete_stage)
+ "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, module_name, object_id):
- mod, opts = _get_mod_opts(app_label, module_name)
- action_list = log.get_list(object_id__exact=object_id, content_type__id__exact=opts.get_content_type_id(),
- order_by=("action_time",), select_related=True)
+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(mod, pk=object_id)
- return render_to_response('admin/object_history', {
+ obj = get_object_or_404(model, pk=object_id)
+ extra_context = {
'title': _('Change history: %s') % obj,
'action_list': action_list,
- 'module_name': capfirst(opts.verbose_name_plural),
+ 'module_name': capfirst(model._meta.verbose_name_plural),
'object': obj,
- }, context_instance=Context(request))
-history = staff_member_required(history)
+ }
+ 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]
+
+ 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)
+ if spec and spec.has_output():
+ filter_specs.append(spec)
+ return filter_specs, bool(filter_specs)
+
+ def get_query_string(self, new_params={}, 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:
+ try:
+ f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
+ except models.FieldDoesNotExist:
+ 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.
+ if self.lookup_opts.admin.search_fields and self.query:
+ for bit in self.query.split():
+ or_queries = [models.Q(**{'%s__icontains' % field_name: bit}) for field_name in self.lookup_opts.admin.search_fields]
+ other_qs = QuerySet(self.model)
+ other_qs = other_qs.filter(reduce(operator.or_, or_queries))
+ qs = qs & other_qs
+
+ if self.opts.one_to_one_field:
+ qs = qs.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:
+ return HttpResponseRedirect(request.path)
+ 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/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py
index 3effd57c10..f73b9e4218 100644
--- a/django/contrib/admin/views/template.py
+++ b/django/contrib/admin/views/template.py
@@ -1,9 +1,9 @@
from django.contrib.admin.views.decorators import staff_member_required
-from django.core import formfields, validators
-from django.core import template
-from django.core.template import loader
-from django.core.extensions import DjangoContext, render_to_response
-from django.models.core import sites
+from django.core import validators
+from django import template, forms
+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):
@@ -23,19 +23,19 @@ def template_validator(request):
errors = manipulator.get_validation_errors(new_data)
if not errors:
request.user.add_message('The template is valid.')
- return render_to_response('admin/template_validator', {
+ return render_to_response('admin/template_validator.html', {
'title': 'Template validator',
- 'form': formfields.FormWrapper(manipulator, new_data, errors),
- }, context_instance=DjangoContext(request))
+ 'form': forms.FormWrapper(manipulator, new_data, errors),
+ }, context_instance=template.RequestContext(request))
template_validator = staff_member_required(template_validator)
-class TemplateValidator(formfields.Manipulator):
+class TemplateValidator(forms.Manipulator):
def __init__(self, settings_modules):
self.settings_modules = settings_modules
- site_list = sites.get_in_bulk(settings_modules.keys()).values()
+ site_list = Site.objects.get_in_bulk(settings_modules.keys()).values()
self.fields = (
- formfields.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
- formfields.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
+ forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]),
+ forms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]),
)
def isValidTemplate(self, field_data, all_data):
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index e69de29bb2..ac7b40aca6 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -0,0 +1,2 @@
+LOGIN_URL = '/accounts/login/'
+REDIRECT_FIELD_NAME = 'next'
diff --git a/django/contrib/auth/create_superuser.py b/django/contrib/auth/create_superuser.py
new file mode 100644
index 0000000000..ab5ca36f50
--- /dev/null
+++ b/django/contrib/auth/create_superuser.py
@@ -0,0 +1,84 @@
+"""
+Helper function for creating superusers in the authentication system.
+"""
+
+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
+ 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."
diff --git a/django/views/decorators/auth.py b/django/contrib/auth/decorators.py
index 478f0ac84d..4b264cf815 100644
--- a/django/views/decorators/auth.py
+++ b/django/contrib/auth/decorators.py
@@ -1,6 +1,7 @@
-from django.views.auth import login
+from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
+from django.http import HttpResponseRedirect
-def user_passes_test(test_func, login_url=login.LOGIN_URL):
+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
@@ -10,7 +11,8 @@ def user_passes_test(test_func, login_url=login.LOGIN_URL):
def _checklogin(request, *args, **kwargs):
if test_func(request.user):
return view_func(request, *args, **kwargs)
- return login.redirect_to_login(request.path, login_url)
+ return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.path))
+
return _checklogin
return _dec
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
new file mode 100644
index 0000000000..6c0c8abe97
--- /dev/null
+++ b/django/contrib/auth/forms.py
@@ -0,0 +1,108 @@
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.template import Context, loader
+from django.core import validators
+from django import forms
+
+class AuthenticationForm(forms.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 = [
+ forms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
+ validator_list=[self.isValidUser, self.hasCookiesEnabled]),
+ forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True,
+ validator_list=[self.isValidPasswordForUser]),
+ ]
+ 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):
+ try:
+ self.user_cache = User.objects.get(username=field_data)
+ except User.DoesNotExist:
+ raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
+
+ def isValidPasswordForUser(self, field_data, all_data):
+ if self.user_cache is not None and not self.user_cache.check_password(field_data):
+ self.user_cache = None
+ raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
+
+ 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(forms.Manipulator):
+ "A form that lets a user request a password reset"
+ def __init__(self):
+ self.fields = (
+ forms.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 acount. Are you sure you've registered?"
+
+ def save(self, domain_override=None):
+ "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('registration/password_reset_email.html')
+ 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(forms.Manipulator):
+ "A form that lets a user change his password."
+ def __init__(self, user):
+ self.user = user
+ self.fields = (
+ forms.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
+ validator_list=[self.isValidOldPassword]),
+ forms.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.")]),
+ forms.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()
diff --git a/django/contrib/auth/handlers/modpython.py b/django/contrib/auth/handlers/modpython.py
index d538c9ccc8..b1d7680a33 100644
--- a/django/contrib/auth/handlers/modpython.py
+++ b/django/contrib/auth/handlers/modpython.py
@@ -10,7 +10,7 @@ def authenhandler(req, **kwargs):
# that so that the following import works
os.environ.update(req.subprocess_env)
- from django.models.auth import users
+ from django.contrib.auth.models import User
# check for PythonOptions
_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
@@ -21,14 +21,14 @@ def authenhandler(req, **kwargs):
superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
# check that the username is valid
- kwargs = {'username__exact': req.user, 'is_active__exact': True}
+ kwargs = {'username': req.user, 'is_active': True}
if staff_only:
- kwargs['is_staff__exact'] = True
+ kwargs['is_staff'] = True
if superuser_only:
- kwargs['is_superuser__exact'] = True
+ kwargs['is_superuser'] = True
try:
- user = users.get_object(**kwargs)
- except users.UserDoesNotExist:
+ user = User.objects.get(**kwargs)
+ except User.DoesNotExist:
return apache.HTTP_UNAUTHORIZED
# check the password and any permission given
diff --git a/django/contrib/auth/management.py b/django/contrib/auth/management.py
new file mode 100644
index 0000000000..fe3399edbb
--- /dev/null
+++ b/django/contrib/auth/management.py
@@ -0,0 +1,53 @@
+"""
+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):
+ 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:
+ if not klass._meta.admin:
+ continue
+ ctype = ContentType.objects.get_for_model(klass)
+ for codename, name in _get_all_permissions(klass._meta):
+ try:
+ Permission.objects.get(name=name, codename=codename, content_type__pk=ctype.id)
+ except Permission.DoesNotExist:
+ p = Permission(name=name, codename=codename, content_type=ctype)
+ p.save()
+ print "Adding permission '%s'" % p
+
+def create_superuser(app, created_models):
+ from django.contrib.auth.models import User
+ from django.contrib.auth.create_superuser import createsuperuser as do_create
+ if User in created_models:
+ 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/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py
new file mode 100644
index 0000000000..4b3ed54960
--- /dev/null
+++ b/django/contrib/auth/middleware.py
@@ -0,0 +1,19 @@
+class LazyUser(object):
+ def __init__(self):
+ self._user = None
+
+ def __get__(self, request, obj_type=None):
+ if self._user is None:
+ from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY
+ try:
+ user_id = request.session[SESSION_KEY]
+ self._user = User.objects.get(pk=user_id)
+ except (KeyError, User.DoesNotExist):
+ self._user = AnonymousUser()
+ return self._user
+
+class AuthenticationMiddleware:
+ 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/django/contrib/auth/models.py b/django/contrib/auth/models.py
new file mode 100644
index 0000000000..cea7a694c9
--- /dev/null
+++ b/django/contrib/auth/models.py
@@ -0,0 +1,264 @@
+from django.core import validators
+from django.db import backend, connection, models
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import gettext_lazy as _
+import datetime
+
+SESSION_KEY = '_auth_user_id'
+
+class SiteProfileNotAvailable(Exception):
+ pass
+
+class Permission(models.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 "%r | %s" % (self.content_type, self.name)
+
+class Group(models.Model):
+ 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):
+ username = models.CharField(_('username'), maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])
+ 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]'"))
+ is_staff = models.BooleanField(_('staff status'), help_text=_("Designates whether the user can log into this admin site."))
+ is_active = models.BooleanField(_('active'), default=True)
+ is_superuser = models.BooleanField(_('superuser status'))
+ 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):
+ return False
+
+ def get_full_name(self):
+ 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
+ algo, salt, hsh = self.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."
+
+ 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.all()])
+ 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 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):
+ 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 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
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
new file mode 100644
index 0000000000..f919f82419
--- /dev/null
+++ b/django/contrib/auth/views.py
@@ -0,0 +1,84 @@
+from django.contrib.auth.forms import AuthenticationForm
+from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm
+from django import forms
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.auth.models import SESSION_KEY
+from django.contrib.sites.models import Site
+from django.http import HttpResponse, HttpResponseRedirect
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
+
+def login(request):
+ "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/'
+ request.session[SESSION_KEY] = manipulator.get_user_id()
+ request.session.delete_test_cookie()
+ return HttpResponseRedirect(redirect_to)
+ else:
+ errors = {}
+ request.session.set_test_cookie()
+ return render_to_response('registration/login.html', {
+ 'form': forms.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):
+ "Logs out the user and displays 'You are logged out' message."
+ try:
+ del request.session[SESSION_KEY]
+ except KeyError:
+ return render_to_response('registration/logged_out.html', {'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):
+ 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(request.META['HTTP_HOST'])
+ else:
+ form.save()
+ return HttpResponseRedirect('%sdone/' % request.path)
+ return render_to_response('registration/password_reset_form.html', {'form': forms.FormWrapper(form, new_data, errors)},
+ context_instance=RequestContext(request))
+
+def password_reset_done(request):
+ return render_to_response('registration/password_reset_done.html', context_instance=RequestContext(request))
+
+def password_change(request):
+ 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('registration/password_change_form.html', {'form': forms.FormWrapper(form, new_data, errors)},
+ context_instance=RequestContext(request))
+password_change = login_required(password_change)
+
+def password_change_done(request):
+ return render_to_response('registration/password_change_done.html', context_instance=RequestContext(request))
diff --git a/django/contrib/comments/feeds.py b/django/contrib/comments/feeds.py
index dd6c6ecf15..1f30a3ada2 100644
--- a/django/contrib/comments/feeds.py
+++ b/django/contrib/comments/feeds.py
@@ -1,48 +1,48 @@
from django.conf import settings
+from django.contrib.comments.models import Comment, FreeComment
from django.contrib.syndication.feeds import Feed
from django.core.exceptions import ObjectDoesNotExist
-from django.models.core import sites
-from django.models.comments import comments, freecomments
+from django.contrib.sites.models import Site
class LatestFreeCommentsFeed(Feed):
"""Feed of latest comments on the current site"""
-
- comments_module = freecomments
-
+
+ comments_class = FreeComment
+
def title(self):
if not hasattr(self, '_site'):
- self._site = sites.get_current()
+ self._site = Site.objects.get_current()
return "%s comments" % self._site.name
-
+
def link(self):
if not hasattr(self, '_site'):
- self._site = sites.get_current()
+ self._site = Site.objects.get_current()
return "http://%s/" % (self._site.domain)
-
+
def description(self):
if not hasattr(self, '_site'):
- self._site = sites.get_current()
+ self._site = Site.objects.get_current()
return "Latest comments on %s" % self._site.name
def items(self):
- return self.comments_module.get_list(**self._get_lookup_kwargs())
+ return self.comments_class.objects.filter(**self._get_lookup_kwargs())
def _get_lookup_kwargs(self):
return {
- 'site__pk' : settings.SITE_ID,
- 'is_public__exact' : True,
- 'limit' : 40,
+ 'site__pk': settings.SITE_ID,
+ 'is_public__exact': True,
+ 'limit': 40,
}
class LatestCommentsFeed(LatestFreeCommentsFeed):
"""Feed of latest free comments on the current site"""
-
- comments_module = comments
-
+
+ comments_class = Comment
+
def _get_lookup_kwargs(self):
kwargs = LatestFreeCommentsFeed._get_lookup_kwargs(self)
kwargs['is_removed__exact'] = False
if settings.COMMENTS_BANNED_USERS_GROUP:
kwargs['where'] = ['user_id NOT IN (SELECT user_id FROM auth_users_group WHERE group_id = %s)']
kwargs['params'] = [COMMENTS_BANNED_USERS_GROUP]
- return kwargs \ No newline at end of file
+ return kwargs
diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py
new file mode 100644
index 0000000000..f9122b07d5
--- /dev/null
+++ b/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"
+ >>> 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.get_group_list():
+ 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.get_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.get_karmascore_list():
+ 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.get_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.get_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__id__exact=comment_id, user__id__exact=user_id)
+ except self.model.DoesNotExist:
+ karma = self.model(None, user_id, comment_id, score, 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__id__exact=user.id, comment__id__exact=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/django/contrib/comments/models/__init__.py b/django/contrib/comments/models/__init__.py
deleted file mode 100644
index 2bf3cc96cc..0000000000
--- a/django/contrib/comments/models/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['comments']
diff --git a/django/contrib/comments/models/comments.py b/django/contrib/comments/models/comments.py
deleted file mode 100644
index 3062af62de..0000000000
--- a/django/contrib/comments/models/comments.py
+++ /dev/null
@@ -1,287 +0,0 @@
-from django.core import meta
-from django.models import auth, core
-from django.utils.translation import gettext_lazy as _
-
-class Comment(meta.Model):
- user = meta.ForeignKey(auth.User, raw_id_admin=True)
- content_type = meta.ForeignKey(core.ContentType)
- object_id = meta.IntegerField(_('object ID'))
- headline = meta.CharField(_('headline'), maxlength=255, blank=True)
- comment = meta.TextField(_('comment'), maxlength=3000)
- rating1 = meta.PositiveSmallIntegerField(_('rating #1'), blank=True, null=True)
- rating2 = meta.PositiveSmallIntegerField(_('rating #2'), blank=True, null=True)
- rating3 = meta.PositiveSmallIntegerField(_('rating #3'), blank=True, null=True)
- rating4 = meta.PositiveSmallIntegerField(_('rating #4'), blank=True, null=True)
- rating5 = meta.PositiveSmallIntegerField(_('rating #5'), blank=True, null=True)
- rating6 = meta.PositiveSmallIntegerField(_('rating #6'), blank=True, null=True)
- rating7 = meta.PositiveSmallIntegerField(_('rating #7'), blank=True, null=True)
- rating8 = meta.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 = meta.BooleanField(_('is valid rating'))
- submit_date = meta.DateTimeField(_('date/time submitted'), auto_now_add=True)
- is_public = meta.BooleanField(_('is public'))
- ip_address = meta.IPAddressField(_('IP address'), blank=True, null=True)
- is_removed = meta.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 = meta.ForeignKey(core.Site)
- class META:
- db_table = 'comments'
- verbose_name = _('Comment')
- verbose_name_plural = _('Comments')
- module_constants = {
- # min. and max. allowed dimensions for photo resizing (in pixels)
- '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',
- }
- ordering = ('-submit_date',)
- admin = meta.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.get_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.get_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.get_karmascore_list():
- 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.get_user().username, 'date': self.submit_date,
- 'comment': self.comment, 'domain': self.get_site().domain, 'url': self.get_absolute_url()}
-
- def _module_get_security_hash(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.
- """
- from django.conf.settings import SECRET_KEY
- import md5
- return md5.new(options + photo_options + rating_options + target + SECRET_KEY).hexdigest()
-
- def _module_get_rating_options(rating_string):
- """
- Given a rating_string, this returns a tuple of (rating_range, options).
- >>> s = "scale:1-10|First_category|Second_category"
- >>> 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 _module_get_list_with_karma(**kwargs):
- """
- Returns a list of Comment objects matching the given lookup terms, with
- _karma_total_good and _karma_total_bad filled.
- """
- kwargs.setdefault('select', {})
- kwargs['select']['_karma_total_good'] = 'SELECT COUNT(*) FROM comments_karma WHERE comments_karma.comment_id=comments.id AND score=1'
- kwargs['select']['_karma_total_bad'] = 'SELECT COUNT(*) FROM comments_karma WHERE comments_karma.comment_id=comments.id AND score=-1'
- return get_list(**kwargs)
-
- def _module_user_is_moderator(user):
- from django.conf.settings import COMMENTS_MODERATORS_GROUP
- if user.is_superuser:
- return True
- for g in user.get_group_list():
- if g.id == COMMENTS_MODERATORS_GROUP:
- return True
- return False
-
-class FreeComment(meta.Model):
- # A FreeComment is a comment by a non-registered user.
- content_type = meta.ForeignKey(core.ContentType)
- object_id = meta.IntegerField(_('object ID'))
- comment = meta.TextField(_('comment'), maxlength=3000)
- person_name = meta.CharField(_("person's name"), maxlength=50)
- submit_date = meta.DateTimeField(_('date/time submitted'), auto_now_add=True)
- is_public = meta.BooleanField(_('is public'))
- ip_address = meta.IPAddressField(_('ip address'))
- # TODO: Change this to is_removed, like Comment
- approved = meta.BooleanField(_('approved by staff'))
- site = meta.ForeignKey(core.Site)
- class META:
- db_table = 'comments_free'
- verbose_name = _('Free comment')
- verbose_name_plural = _('Free comments')
- ordering = ('-submit_date',)
- admin = meta.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.get_content_type().get_object_for_this_type(pk=self.object_id)
- except ObjectDoesNotExist:
- return None
-
- get_content_object.short_description = _('Content object')
-
-class KarmaScore(meta.Model):
- user = meta.ForeignKey(auth.User)
- comment = meta.ForeignKey(Comment)
- score = meta.SmallIntegerField(_('score'), db_index=True)
- scored_date = meta.DateTimeField(_('score date'), auto_now=True)
- class META:
- module_name = 'karma'
- verbose_name = _('Karma score')
- verbose_name_plural = _('Karma scores')
- unique_together = (('user', 'comment'),)
- module_constants = {
- # what users get if they don't have any karma
- 'DEFAULT_KARMA': 5,
- 'KARMA_NEEDED_BEFORE_DISPLAYED': 3,
- }
-
- def __repr__(self):
- return _("%(score)d rating by %(user)s") % {'score': self.score, 'user': self.get_user()}
-
- def _module_vote(user_id, comment_id, score):
- try:
- karma = get_object(comment__id__exact=comment_id, user__id__exact=user_id)
- except KarmaScoreDoesNotExist:
- karma = KarmaScore(None, user_id, comment_id, score, datetime.datetime.now())
- karma.save()
- else:
- karma.score = score
- karma.scored_date = datetime.datetime.now()
- karma.save()
-
- def _module_get_pretty_score(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 UserFlag(meta.Model):
- user = meta.ForeignKey(auth.User)
- comment = meta.ForeignKey(Comment)
- flag_date = meta.DateTimeField(_('flag date'), auto_now_add=True)
- class META:
- db_table = 'comments_user_flags'
- verbose_name = _('User flag')
- verbose_name_plural = _('User flags')
- unique_together = (('user', 'comment'),)
-
- def __repr__(self):
- return _("Flag by %r") % self.get_user()
-
- def _module_flag(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 = get_object(user__id__exact=user.id, comment__id__exact=comment.id)
- except UserFlagDoesNotExist:
- from django.core.mail import mail_managers
- f = UserFlag(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 ModeratorDeletion(meta.Model):
- user = meta.ForeignKey(auth.User, verbose_name='moderator')
- comment = meta.ForeignKey(Comment)
- deletion_date = meta.DateTimeField(_('deletion date'), auto_now_add=True)
- class META:
- db_table = 'comments_moderator_deletions'
- verbose_name = _('Moderator deletion')
- verbose_name_plural = _('Moderator deletions')
- unique_together = (('user', 'comment'),)
-
- def __repr__(self):
- return _("Moderator deletion by %r") % self.get_user()
-
diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py
index d71565321f..a3893fdf61 100644
--- a/django/contrib/comments/templatetags/comments.py
+++ b/django/contrib/comments/templatetags/comments.py
@@ -1,16 +1,16 @@
-"Custom template tags for user comments"
-
-from django.core import template
-from django.core.template import loader
+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.models.comments import comments, freecomments
-from django.models.core import contenttypes
+from django.contrib.contenttypes.models import ContentType
import re
register = template.Library()
-COMMENT_FORM = 'comments/form'
-FREE_COMMENT_FORM = 'comments/freeform'
+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,
@@ -46,24 +46,24 @@ class CommentFormNode(template.Node):
context['display_form'] = True
context['target'] = '%s:%s' % (self.content_type.id, self.obj_id)
options = []
- for var, abbr in (('photos_required', comments.PHOTOS_REQUIRED),
- ('photos_optional', comments.PHOTOS_OPTIONAL),
- ('ratings_required', comments.RATINGS_REQUIRED),
- ('ratings_optional', comments.RATINGS_OPTIONAL),
- ('is_public', comments.IS_PUBLIC)):
+ 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'] = comments.get_security_hash(context['options'], '', '', context['target'])
+ 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'] = comments.get_rating_options(self.rating_options)
- context['hash'] = comments.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
+ 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()
@@ -76,13 +76,13 @@ class CommentCountNode(template.Node):
self.var_name, self.free = var_name, free
def render(self, context):
- from django.conf.settings import SITE_ID
- get_count_function = self.free and freecomments.get_count or comments.get_count
+ 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 = get_count_function(object_id__exact=self.obj_id,
- content_type__package__label__exact=self.package,
- content_type__python_module_name__exact=self.module, site__id__exact=SITE_ID)
+ 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 ''
@@ -95,8 +95,8 @@ class CommentListNode(template.Node):
self.extra_kwargs = extra_kwargs or {}
def render(self, context):
- from django.conf.settings import COMMENTS_BANNED_USERS_GROUP, SITE_ID
- get_list_function = self.free and freecomments.get_list or comments.get_list_with_karma
+ 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)
@@ -104,26 +104,24 @@ class CommentListNode(template.Node):
return ''
kwargs = {
'object_id__exact': self.obj_id,
- 'content_type__package__label__exact': self.package,
- 'content_type__python_module_name__exact': self.module,
- 'site__id__exact': SITE_ID,
- 'select_related': True,
- 'order_by': (self.ordering + 'submit_date',),
+ '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 COMMENTS_BANNED_USERS_GROUP:
- kwargs['select'] = {'is_hidden': 'user_id IN (SELECT user_id FROM auth_users_groups WHERE group_id = %s)' % COMMENTS_BANNED_USERS_GROUP}
- comment_list = get_list_function(**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 not context['user'].is_anonymous():
user_id = context['user'].id
- context['user_can_moderate_comments'] = comments.user_is_moderator(context['user'])
+ 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 COMMENTS_BANNED_USERS_GROUP:
+ 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
@@ -157,8 +155,8 @@ class DoCommentForm:
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 = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
- except contenttypes.ContentTypeDoesNotExist:
+ 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():
@@ -183,8 +181,8 @@ class DoCommentForm:
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 (comments.MIN_PHOTO_DIMENSION <= int(opt) <= comments.MAX_PHOTO_DIMENSION):
- raise template.TemplateSyntaxError, "Invalid photo dimension in %r tag: '%s'. Only values between %s and %s are allowed." % (tokens[0], opt, comments.MIN_PHOTO_DIMENSION, comments.MAX_PHOTO_DIMENSION)
+ 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
@@ -237,8 +235,8 @@ class DoCommentCount:
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 = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
- except contenttypes.ContentTypeDoesNotExist:
+ 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():
@@ -292,8 +290,8 @@ class DoGetCommentList:
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 = contenttypes.get_object(package__label__exact=package, python_module_name__exact=module)
- except contenttypes.ContentTypeDoesNotExist:
+ 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():
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index 5918db7dc8..ecacc1f1c5 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -1,14 +1,17 @@
-from django.core import formfields, validators
+from django.core import validators
+from django import forms
from django.core.mail import mail_admins, mail_managers
-from django.core.exceptions import Http404, ObjectDoesNotExist
-from django.core.extensions import DjangoContext, render_to_response
-from django.models.auth import users
-from django.models.comments import comments, freecomments
-from django.models.core import contenttypes
-from django.parts.auth.formfields import AuthenticationForm
-from django.utils.httpwrappers import HttpResponseRedirect
+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.auth.models import SESSION_KEY
+from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, 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.settings import BANNED_IPS, COMMENTS_ALLOW_PROFANITIES, COMMENTS_SKETCHY_USERS_GROUP, COMMENTS_FIRST_FEW, SITE_ID
+from django.conf import settings
from django.utils.translation import ngettext
import base64, datetime
@@ -26,37 +29,37 @@ class PublicCommentManipulator(AuthenticationForm):
else:
return []
self.fields.extend([
- formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+ forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
- formfields.RadioSelectField(field_name="rating1", choices=choices,
+ forms.RadioSelectField(field_name="rating1", choices=choices,
is_required=ratings_required and num_rating_choices > 0,
validator_list=get_validator_list(1),
),
- formfields.RadioSelectField(field_name="rating2", choices=choices,
+ forms.RadioSelectField(field_name="rating2", choices=choices,
is_required=ratings_required and num_rating_choices > 1,
validator_list=get_validator_list(2),
),
- formfields.RadioSelectField(field_name="rating3", choices=choices,
+ forms.RadioSelectField(field_name="rating3", choices=choices,
is_required=ratings_required and num_rating_choices > 2,
validator_list=get_validator_list(3),
),
- formfields.RadioSelectField(field_name="rating4", choices=choices,
+ forms.RadioSelectField(field_name="rating4", choices=choices,
is_required=ratings_required and num_rating_choices > 3,
validator_list=get_validator_list(4),
),
- formfields.RadioSelectField(field_name="rating5", choices=choices,
+ forms.RadioSelectField(field_name="rating5", choices=choices,
is_required=ratings_required and num_rating_choices > 4,
validator_list=get_validator_list(5),
),
- formfields.RadioSelectField(field_name="rating6", choices=choices,
+ forms.RadioSelectField(field_name="rating6", choices=choices,
is_required=ratings_required and num_rating_choices > 5,
validator_list=get_validator_list(6),
),
- formfields.RadioSelectField(field_name="rating7", choices=choices,
+ forms.RadioSelectField(field_name="rating7", choices=choices,
is_required=ratings_required and num_rating_choices > 6,
validator_list=get_validator_list(7),
),
- formfields.RadioSelectField(field_name="rating8", choices=choices,
+ forms.RadioSelectField(field_name="rating8", choices=choices,
is_required=ratings_required and num_rating_choices > 7,
validator_list=get_validator_list(8),
),
@@ -69,25 +72,25 @@ class PublicCommentManipulator(AuthenticationForm):
self.user_cache = user
def hasNoProfanities(self, field_data, all_data):
- if COMMENTS_ALLOW_PROFANITIES:
+ if settings.COMMENTS_ALLOW_PROFANITIES:
return
return validators.hasNoProfanities(field_data, all_data)
def get_comment(self, new_data):
"Helper function"
- return comments.Comment(None, self.get_user_id(), new_data["content_type_id"],
+ 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, SITE_ID)
+ 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 comments.get_list(content_type__id__exact=new_data["content_type_id"],
+ 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
@@ -105,37 +108,37 @@ class PublicCommentManipulator(AuthenticationForm):
c.save()
# If the commentor has posted fewer than COMMENTS_FIRST_FEW comments,
# send the comment to the managers.
- if self.user_cache.get_comments_comment_count() <= COMMENTS_FIRST_FEW:
+ 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') % \
- {'count': COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
+ {'count': settings.COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
mail_managers("Comment posted by rookie user", message)
- if COMMENTS_SKETCHY_USERS_GROUP and COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.get_group_list()]:
+ 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(formfields.Manipulator):
+class PublicFreeCommentManipulator(forms.Manipulator):
"Manipulator that handles public free (unregistered) comments"
def __init__(self):
self.fields = (
- formfields.TextField(field_name="person_name", maxlength=50, is_required=True,
+ forms.TextField(field_name="person_name", maxlength=50, is_required=True,
validator_list=[self.hasNoProfanities]),
- formfields.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
+ forms.LargeTextField(field_name="comment", maxlength=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
)
def hasNoProfanities(self, field_data, all_data):
- if COMMENTS_ALLOW_PROFANITIES:
+ if settings.COMMENTS_ALLOW_PROFANITIES:
return
return validators.hasNoProfanities(field_data, all_data)
def get_comment(self, new_data):
"Helper function"
- return freecomments.FreeComment(None, new_data["content_type_id"],
+ 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, SITE_ID)
+ new_data["ip_address"], False, settings.SITE_ID)
def save(self, new_data):
today = datetime.date.today()
@@ -143,7 +146,7 @@ class PublicFreeCommentManipulator(formfields.Manipulator):
# 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 freecomments.get_list(content_type__id__exact=new_data["content_type_id"],
+ 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):
@@ -190,16 +193,16 @@ def post_comment(request):
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 comments.get_security_hash(options, photo_options, rating_options, target) != security_hash:
+ 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 = comments.get_rating_options(base64.decodestring(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 = contenttypes.get_object(pk=content_type_id).get_object_for_this_type(pk=object_id)
+ 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'
@@ -207,20 +210,20 @@ def post_comment(request):
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'] = comments.IS_PUBLIC in option_list
+ new_data['is_public'] = IS_PUBLIC in option_list
manipulator = PublicCommentManipulator(request.user,
- ratings_required=comments.RATINGS_REQUIRED in option_list,
+ 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 new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']):
- request.session[users.SESSION_KEY] = manipulator.get_user_id()
+ request.session[SESSION_KEY] = manipulator.get_user_id()
if errors or request.POST.has_key('preview'):
- class CommentFormWrapper(formfields.FormWrapper):
+ class CommentFormWrapper(forms.FormWrapper):
def __init__(self, manipulator, new_data, errors, rating_choices):
- formfields.FormWrapper.__init__(self, manipulator, new_data, errors)
+ forms.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))]
@@ -229,22 +232,22 @@ def post_comment(request):
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', {
+ 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': comments.RATINGS_OPTIONAL in option_list,
- 'ratings_required': comments.RATINGS_REQUIRED in option_list,
+ 'ratings_optional': RATINGS_OPTIONAL in option_list,
+ 'ratings_required': RATINGS_REQUIRED in option_list,
'rating_range': rating_range,
'rating_choices': rating_choices,
- }, context_instance=DjangoContext(request))
+ }, 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 BANNED_IPS:
+ 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)
@@ -279,10 +282,10 @@ def post_free_comment(request):
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 comments.get_security_hash(options, '', '', target) != security_hash:
+ 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 = contenttypes.get_object(pk=content_type_id)
+ content_type = ContentType.objects.get(pk=content_type_id)
try:
obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist:
@@ -292,22 +295,22 @@ def post_free_comment(request):
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'] = comments.IS_PUBLIC in option_list
+ 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', {
+ return render_to_response('comments/free_preview.html', {
'comment': comment,
- 'comment_form': formfields.FormWrapper(manipulator, new_data, errors),
+ 'comment_form': forms.FormWrapper(manipulator, new_data, errors),
'options': options,
'target': target,
'hash': security_hash,
- }, context_instance=DjangoContext(request))
+ }, 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 BANNED_IPS:
+ 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:
@@ -330,8 +333,8 @@ def comment_was_posted(request):
if request.GET.has_key('c'):
content_type_id, object_id = request.GET['c'].split(':')
try:
- content_type = contenttypes.get_object(pk=content_type_id)
+ 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', {'object': obj}, context_instance=DjangoContext(request))
+ return render_to_response('comments/posted.html', {'object': obj}, context_instance=RequestContext(request))
diff --git a/django/contrib/comments/views/karma.py b/django/contrib/comments/views/karma.py
index 9db68d69f1..becb02e128 100644
--- a/django/contrib/comments/views/karma.py
+++ b/django/contrib/comments/views/karma.py
@@ -1,6 +1,7 @@
-from django.core.exceptions import Http404
-from django.core.extensions import DjangoContext, render_to_response
-from django.models.comments import comments, karma
+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):
"""
@@ -17,12 +18,12 @@ def vote(request, comment_id, vote):
if request.user.is_anonymous():
raise Http404, _("Anonymous users cannot vote")
try:
- comment = comments.get_object(pk=comment_id)
- except comments.CommentDoesNotExist:
+ comment = Comment.objects.get(pk=comment_id)
+ except Comment.DoesNotExist:
raise Http404, _("Invalid comment ID")
- if comment.user_id == request.user.id:
+ if comment.user.id == request.user.id:
raise Http404, _("No voting for yourself")
- karma.vote(request.user.id, comment_id, rating)
+ KarmaScore.objects.vote(request.user.id, comment_id, rating)
# Reload comment to ensure we have up to date karma count
- comment = comments.get_object(pk=comment_id)
- return render_to_response('comments/karma_vote_accepted', {'comment': comment}, context_instance=DjangoContext(request))
+ 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/django/contrib/comments/views/userflags.py b/django/contrib/comments/views/userflags.py
index 3bcc5b2122..76f14ef19c 100644
--- a/django/contrib/comments/views/userflags.py
+++ b/django/contrib/comments/views/userflags.py
@@ -1,9 +1,10 @@
-from django.core.extensions import DjangoContext, render_to_response
-from django.core.exceptions import Http404
-from django.models.comments import comments, moderatordeletions, userflags
-from django.views.decorators.auth import login_required
-from django.utils.httpwrappers import HttpResponseRedirect
-from django.conf.settings import SITE_ID
+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):
"""
@@ -14,22 +15,16 @@ def flag(request, comment_id):
comment
the flagged `comments.comments` object
"""
- try:
- comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
- except comments.CommentDoesNotExist:
- raise Http404
+ comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
if request.POST:
- userflags.flag(comment, request.user)
+ UserFlag.objects.flag(comment, request.user)
return HttpResponseRedirect('%sdone/' % request.path)
- return render_to_response('comments/flag_verify', {'comment': comment}, context_instance=DjangoContext(request))
+ return render_to_response('comments/flag_verify.html', {'comment': comment}, context_instance=RequestContext(request))
flag = login_required(flag)
def flag_done(request, comment_id):
- try:
- comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
- except comments.CommentDoesNotExist:
- raise Http404
- return render_to_response('comments/flag_done', {'comment': comment}, context_instance=DjangoContext(request))
+ 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):
"""
@@ -40,26 +35,20 @@ def delete(request, comment_id):
comment
the flagged `comments.comments` object
"""
- try:
- comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
- except comments.CommentDoesNotExist:
- raise Http404
- if not comments.user_is_moderator(request.user):
+ 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 = moderatordeletions.ModeratorDeletion(None, request.user.id, comment.id, None)
+ m = ModeratorDeletion(None, request.user.id, comment.id, None)
m.save()
return HttpResponseRedirect('%sdone/' % request.path)
- return render_to_response('comments/delete_verify', {'comment': comment}, context_instance=DjangoContext(request))
+ return render_to_response('comments/delete_verify.html', {'comment': comment}, context_instance=RequestContext(request))
delete = login_required(delete)
def delete_done(request, comment_id):
- try:
- comment = comments.get_object(pk=comment_id, site__id__exact=SITE_ID)
- except comments.CommentDoesNotExist:
- raise Http404
- return render_to_response('comments/delete_done', {'comment': comment}, context_instance=DjangoContext(request))
+ 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/django/contrib/admin/urls/__init__.py b/django/contrib/contenttypes/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/contrib/admin/urls/__init__.py
+++ b/django/contrib/contenttypes/__init__.py
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
new file mode 100644
index 0000000000..eb6029e7d4
--- /dev/null
+++ b/django/contrib/contenttypes/models.py
@@ -0,0 +1,49 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+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
+ try:
+ return self.model._default_manager.get(app_label=opts.app_label,
+ model=opts.object_name.lower())
+ except self.model.DoesNotExist:
+ # The str() is needed around opts.verbose_name because it's a
+ # django.utils.functional.__proxy__ object.
+ ct = self.model(name=str(opts.verbose_name),
+ app_label=opts.app_label, model=opts.object_name.lower())
+ ct.save()
+ return ct
+
+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 __repr__(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/django/contrib/flatpages/middleware.py b/django/contrib/flatpages/middleware.py
index c6f286563d..074e4ea735 100644
--- a/django/contrib/flatpages/middleware.py
+++ b/django/contrib/flatpages/middleware.py
@@ -1,6 +1,6 @@
from django.contrib.flatpages.views import flatpage
-from django.core.extensions import Http404
-from django.conf.settings import DEBUG
+from django.http import Http404
+from django.conf import settings
class FlatpageFallbackMiddleware:
def process_response(self, request, response):
@@ -13,6 +13,6 @@ class FlatpageFallbackMiddleware:
except Http404:
return response
except:
- if DEBUG:
+ if settings.DEBUG:
raise
return response
diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
new file mode 100644
index 0000000000..a60a536923
--- /dev/null
+++ b/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'. If this isn't provided, the system will use 'flatpages/default'."))
+ 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 __repr__(self):
+ return "%s -- %s" % (self.url, self.title)
+
+ def get_absolute_url(self):
+ return self.url
diff --git a/django/contrib/flatpages/models/__init__.py b/django/contrib/flatpages/models/__init__.py
deleted file mode 100644
index bb46349b5a..0000000000
--- a/django/contrib/flatpages/models/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['flatpages']
diff --git a/django/contrib/flatpages/models/flatpages.py b/django/contrib/flatpages/models/flatpages.py
deleted file mode 100644
index cfb2741d34..0000000000
--- a/django/contrib/flatpages/models/flatpages.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from django.core import meta, validators
-from django.models.core import Site
-from django.utils.translation import gettext_lazy as _
-
-class FlatPage(meta.Model):
- url = meta.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
- help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
- title = meta.CharField(_('title'), maxlength=200)
- content = meta.TextField(_('content'))
- enable_comments = meta.BooleanField(_('enable comments'))
- template_name = meta.CharField(_('template name'), maxlength=70, blank=True,
- help_text=_("Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."))
- registration_required = meta.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
- sites = meta.ManyToManyField(Site)
- class META:
- db_table = 'django_flatpages'
- verbose_name = _('flat page')
- verbose_name_plural = _('flat pages')
- ordering = ('url',)
- admin = meta.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 __repr__(self):
- return "%s -- %s" % (self.url, self.title)
-
- def get_absolute_url(self):
- return self.url
diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py
index 48cd4526ab..1441f6f3a3 100644
--- a/django/contrib/flatpages/views.py
+++ b/django/contrib/flatpages/views.py
@@ -1,10 +1,10 @@
-from django.core import template_loader
-from django.core.extensions import get_object_or_404, DjangoContext
-from django.models.flatpages import flatpages
-from django.utils.httpwrappers import HttpResponse
-from django.conf.settings import SITE_ID
+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
-DEFAULT_TEMPLATE = 'flatpages/default'
+DEFAULT_TEMPLATE = 'flatpages/default.html'
def flatpage(request, url):
"""
@@ -12,24 +12,24 @@ def flatpage(request, url):
Models: `flatpages.flatpages`
Templates: Uses the template defined by the ``template_name`` field,
- or `flatpages/default` if template_name is not defined.
+ 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(flatpages, url__exact=url, sites__id__exact=SITE_ID)
+ 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 request.user.is_anonymous():
- from django.views.auth.login import redirect_to_login
+ from django.contrib.auth.views import redirect_to_login
return redirect_to_login(request.path)
if f.template_name:
- t = template_loader.select_template((f.template_name, DEFAULT_TEMPLATE))
+ t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
else:
- t = template_loader.get_template(DEFAULT_TEMPLATE)
- c = DjangoContext(request, {
+ t = loader.get_template(DEFAULT_TEMPLATE)
+ c = RequestContext(request, {
'flatpage': f,
})
return HttpResponse(t.render(c))
diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py
index 5124889b89..dc8a9da031 100644
--- a/django/contrib/markup/templatetags/markup.py
+++ b/django/contrib/markup/templatetags/markup.py
@@ -14,7 +14,8 @@ In each case, if the required library is not installed, the filter will
silently fail and return the un-marked-up text.
"""
-from django.core import template
+from django import template
+from django.conf import settings
register = template.Library()
@@ -22,6 +23,8 @@ 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)
@@ -30,6 +33,8 @@ 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)
@@ -38,6 +43,8 @@ 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:
parts = publish_parts(source=value, writer_name="html4css1")
diff --git a/django/contrib/redirects/middleware.py b/django/contrib/redirects/middleware.py
index c005eba9ac..1960bffa12 100644
--- a/django/contrib/redirects/middleware.py
+++ b/django/contrib/redirects/middleware.py
@@ -1,6 +1,6 @@
-from django.models.redirects import redirects
-from django.utils import httpwrappers
-from django.conf.settings import APPEND_SLASH, SITE_ID
+from django.contrib.redirects.models import Redirect
+from django import http
+from django.conf import settings
class RedirectFallbackMiddleware:
def process_response(self, request, response):
@@ -8,20 +8,20 @@ class RedirectFallbackMiddleware:
return response # No need to check for a redirect for non-404 responses.
path = request.get_full_path()
try:
- r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path)
- except redirects.RedirectDoesNotExist:
+ r = Redirect.objects.get(site__id__exact=settings.SITE_ID, old_path=path)
+ except Redirect.DoesNotExist:
r = None
- if r is None and APPEND_SLASH:
+ if r is None and settings.APPEND_SLASH:
# Try removing the trailing slash.
try:
- r = redirects.get_object(site__id__exact=SITE_ID,
- old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
- except redirects.RedirectDoesNotExist:
+ 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 httpwrappers.HttpResponseGone()
- return httpwrappers.HttpResponsePermanentRedirect(r.new_path)
+ return http.HttpResponseGone()
+ return http.HttpResponsePermanentRedirect(r.new_path)
# No redirect was found. Return the response.
return response
diff --git a/django/contrib/redirects/models/redirects.py b/django/contrib/redirects/models.py
index 06dcdb38ac..3f01996b9c 100644
--- a/django/contrib/redirects/models/redirects.py
+++ b/django/contrib/redirects/models.py
@@ -1,23 +1,22 @@
-from django.core import meta
-from django.models.core import Site
+from django.db import models
+from django.contrib.sites.models import Site
from django.utils.translation import gettext_lazy as _
-class Redirect(meta.Model):
- site = meta.ForeignKey(Site, radio_admin=meta.VERTICAL)
- old_path = meta.CharField(_('redirect from'), maxlength=200, db_index=True,
+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 = meta.CharField(_('redirect to'), maxlength=200, blank=True,
+ 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:
+ class Meta:
verbose_name = _('redirect')
verbose_name_plural = _('redirects')
- db_table = 'django_redirects'
+ db_table = 'django_redirect'
unique_together=(('site', 'old_path'),)
ordering = ('old_path',)
- admin = meta.Admin(
- list_filter = ('site',),
- search_fields = ('old_path', 'new_path'),
- )
+ class Admin:
+ list_filter = ('site',)
+ search_fields = ('old_path', 'new_path')
def __repr__(self):
return "%s ---> %s" % (self.old_path, self.new_path)
diff --git a/django/contrib/redirects/models/__init__.py b/django/contrib/redirects/models/__init__.py
deleted file mode 100644
index 05063b2146..0000000000
--- a/django/contrib/redirects/models/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['redirects']
diff --git a/django/core/db/backends/__init__.py b/django/contrib/sessions/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/core/db/backends/__init__.py
+++ b/django/contrib/sessions/__init__.py
diff --git a/django/middleware/sessions.py b/django/contrib/sessions/middleware.py
index e6fa7d742d..dae8a11554 100644
--- a/django/middleware/sessions.py
+++ b/django/contrib/sessions/middleware.py
@@ -1,5 +1,5 @@
-from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN, SESSION_SAVE_EVERY_REQUEST
-from django.models.core import sessions
+from django.conf import settings
+from django.contrib.sessions.models import Session
from django.utils.cache import patch_vary_headers
import datetime
@@ -52,10 +52,10 @@ class SessionWrapper(object):
self._session_cache = {}
else:
try:
- s = sessions.get_object(session_key__exact=self.session_key,
+ s = Session.objects.get(session_key=self.session_key,
expire_date__gt=datetime.datetime.now())
self._session_cache = s.get_decoded()
- except sessions.SessionDoesNotExist:
+ except Session.DoesNotExist:
self._session_cache = {}
# Set the session_key to None to force creation of a new
# key, for extra security.
@@ -66,7 +66,7 @@ class SessionWrapper(object):
class SessionMiddleware:
def process_request(self, request):
- request.session = SessionWrapper(request.COOKIES.get(SESSION_COOKIE_NAME, None))
+ 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
@@ -77,11 +77,11 @@ class SessionMiddleware:
except AttributeError:
pass
else:
- if modified or SESSION_SAVE_EVERY_REQUEST:
- session_key = request.session.session_key or sessions.get_new_session_key()
- new_session = sessions.save(session_key, request.session._session,
- datetime.datetime.now() + datetime.timedelta(seconds=SESSION_COOKIE_AGE))
- expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
- response.set_cookie(SESSION_COOKIE_NAME, session_key,
- max_age=SESSION_COOKIE_AGE, expires=expires, domain=SESSION_COOKIE_DOMAIN)
+ if modified or settings.SESSION_SAVE_EVERY_REQUEST:
+ session_key = request.session.session_key or Session.objects.get_new_session_key()
+ new_session = Session.objects.save(session_key, request.session._session,
+ datetime.datetime.now() + datetime.timedelta(seconds=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")
+ response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
+ max_age=settings.SESSION_COOKIE_AGE, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN)
return response
diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py
new file mode 100644
index 0000000000..8466495c86
--- /dev/null
+++ b/django/contrib/sessions/models.py
@@ -0,0 +1,55 @@
+import base64, md5, random, sys
+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 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):
+ 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/django/core/template/loaders/__init__.py b/django/contrib/sites/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/core/template/loaders/__init__.py
+++ b/django/contrib/sites/__init__.py
diff --git a/django/contrib/sites/management.py b/django/contrib/sites/management.py
new file mode 100644
index 0000000000..0e1a50227a
--- /dev/null
+++ b/django/contrib/sites/management.py
@@ -0,0 +1,16 @@
+"""
+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):
+ if Site in created_models:
+ 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/django/contrib/sites/models.py b/django/contrib/sites/models.py
new file mode 100644
index 0000000000..861c3b26eb
--- /dev/null
+++ b/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 __repr__(self):
+ return self.domain
diff --git a/django/contrib/syndication/feeds.py b/django/contrib/syndication/feeds.py
index 8e3ccdb6c3..367af4f9ff 100644
--- a/django/contrib/syndication/feeds.py
+++ b/django/contrib/syndication/feeds.py
@@ -1,8 +1,8 @@
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
-from django.core.template import Context, loader, Template, TemplateDoesNotExist
-from django.models.core import sites
+from django.template import Context, loader, Template, TemplateDoesNotExist
+from django.contrib.sites.models import Site
from django.utils import feedgenerator
-from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE
+from django.conf import settings
def add_domain(domain, url):
if not url.startswith('http://'):
@@ -60,7 +60,7 @@ class Feed:
else:
obj = None
- current_site = sites.get_current()
+ current_site = Site.objects.get_current()
link = self.__get_dynamic_attr('link', obj)
link = add_domain(current_site.domain, link)
@@ -68,7 +68,7 @@ class Feed:
title = self.__get_dynamic_attr('title', obj),
link = link,
description = self.__get_dynamic_attr('description', obj),
- language = LANGUAGE_CODE.decode(),
+ language = settings.LANGUAGE_CODE.decode(),
feed_url = add_domain(current_site, self.feed_url),
author_name = self.__get_dynamic_attr('author_name', obj),
author_link = self.__get_dynamic_attr('author_link', obj),
@@ -76,11 +76,11 @@ class Feed:
)
try:
- title_template = loader.get_template('feeds/%s_title' % self.slug)
+ title_template = loader.get_template('feeds/%s_title.html' % self.slug)
except TemplateDoesNotExist:
title_template = Template('{{ obj }}')
try:
- description_template = loader.get_template('feeds/%s_description' % self.slug)
+ description_template = loader.get_template('feeds/%s_description.html' % self.slug)
except TemplateDoesNotExist:
description_template = Template('{{ obj }}')
diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py
index 5117746f19..621665d4d4 100644
--- a/django/contrib/syndication/views.py
+++ b/django/contrib/syndication/views.py
@@ -1,6 +1,5 @@
from django.contrib.syndication import feeds
-from django.core.exceptions import Http404
-from django.utils.httpwrappers import HttpResponse
+from django.http import HttpResponse, Http404
def feed(request, url, feed_dict=None):
if not feed_dict:
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index 4fa79d8d79..1b54addded 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -1,7 +1,7 @@
"Database cache backend."
from django.core.cache.backends.base import BaseCache
-from django.core.db import db, DatabaseError
+from django.db import connection, transaction
import base64, time
from datetime import datetime
try:
@@ -25,7 +25,7 @@ class CacheClass(BaseCache):
self._cull_frequency = 3
def get(self, key, default=None):
- cursor = db.cursor()
+ 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:
@@ -33,14 +33,14 @@ class CacheClass(BaseCache):
now = datetime.now()
if row[2] < now:
cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
- db.commit()
+ 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 = db.cursor()
+ cursor = connection.cursor()
cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
num = cursor.fetchone()[0]
now = datetime.now().replace(microsecond=0)
@@ -58,15 +58,15 @@ class CacheClass(BaseCache):
# To be threadsafe, updates/inserts are allowed to fail silently
pass
else:
- db.commit()
+ transaction.commit_unless_managed()
def delete(self, key):
- cursor = db.cursor()
+ cursor = connection.cursor()
cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
- db.commit()
+ transaction.commit_unless_managed()
def has_key(self, key):
- cursor = db.cursor()
+ cursor = connection.cursor()
cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
return cursor.fetchone() is not None
diff --git a/django/core/context_processors.py b/django/core/context_processors.py
index fc72aca6c8..1ab0768776 100644
--- a/django/core/context_processors.py
+++ b/django/core/context_processors.py
@@ -4,10 +4,10 @@ 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
-DjangoContext.
+RequestContext.
"""
-from django.conf.settings import DEBUG, INTERNAL_IPS, LANGUAGES, LANGUAGE_CODE
+from django.conf import settings
def auth(request):
"""
@@ -23,19 +23,19 @@ def auth(request):
def debug(request):
"Returns context variables helpful for debugging."
context_extras = {}
- if DEBUG and request.META.get('REMOTE_ADDR') in INTERNAL_IPS:
+ if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
context_extras['debug'] = True
- from django.core import db
- context_extras['sql_queries'] = db.db.queries
+ from django.db import connection
+ context_extras['sql_queries'] = connection.queries
return context_extras
def i18n(request):
context_extras = {}
- context_extras['LANGUAGES'] = LANGUAGES
+ context_extras['LANGUAGES'] = settings.LANGUAGES
if hasattr(request, 'LANGUAGE_CODE'):
context_extras['LANGUAGE_CODE'] = request.LANGUAGE_CODE
else:
- context_extras['LANGUAGE_CODE'] = LANGUAGE_CODE
+ context_extras['LANGUAGE_CODE'] = settings.LANGUAGE_CODE
return context_extras
def request(request):
diff --git a/django/core/db/__init__.py b/django/core/db/__init__.py
deleted file mode 100644
index 1d2f04b367..0000000000
--- a/django/core/db/__init__.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-This is the core database connection.
-
-All Django code assumes database SELECT statements cast the resulting values as such:
- * booleans are mapped to Python booleans
- * dates are mapped to Python datetime.date objects
- * times are mapped to Python datetime.time objects
- * timestamps are mapped to Python datetime.datetime objects
-"""
-
-from django.conf.settings import DATABASE_ENGINE
-
-try:
- dbmod = __import__('django.core.db.backends.%s' % DATABASE_ENGINE, '', '', [''])
-except ImportError, exc:
- # 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[:-3] for f in os.listdir(backend_dir) if f.endswith('.py') and not f.startswith('__init__')]
- available_backends.sort()
- raise ImproperlyConfigured, "Could not load database backend: %s. Is your DATABASE_ENGINE setting (currently, %r) spelled correctly? Available options are: %s" % \
- (exc, DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
-
-DatabaseError = dbmod.DatabaseError
-db = dbmod.DatabaseWrapper()
-dictfetchone = dbmod.dictfetchone
-dictfetchmany = dbmod.dictfetchmany
-dictfetchall = dbmod.dictfetchall
-dictfetchall = dbmod.dictfetchall
-get_last_insert_id = dbmod.get_last_insert_id
-get_date_extract_sql = dbmod.get_date_extract_sql
-get_date_trunc_sql = dbmod.get_date_trunc_sql
-get_limit_offset_sql = dbmod.get_limit_offset_sql
-get_random_function_sql = dbmod.get_random_function_sql
-get_table_list = dbmod.get_table_list
-get_table_description = dbmod.get_table_description
-get_relations = dbmod.get_relations
-get_indexes = dbmod.get_indexes
-OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING
-DATA_TYPES = dbmod.DATA_TYPES
-DATA_TYPES_REVERSE = dbmod.DATA_TYPES_REVERSE
diff --git a/django/core/db/backends/mysql.py b/django/core/db/backends/mysql.py
deleted file mode 100644
index d0e27e6f44..0000000000
--- a/django/core/db/backends/mysql.py
+++ /dev/null
@@ -1,235 +0,0 @@
-"""
-MySQL database backend for Django.
-
-Requires MySQLdb: http://sourceforge.net/projects/mysql-python
-"""
-
-from django.core.db import base, typecasts
-from django.core.db.dicthelpers import *
-import MySQLdb as Database
-from MySQLdb.converters import conversions
-from MySQLdb.constants import FIELD_TYPE
-import types
-
-DatabaseError = Database.DatabaseError
-
-django_conversions = conversions.copy()
-django_conversions.update({
- types.BooleanType: typecasts.rev_typecast_boolean,
- FIELD_TYPE.DATETIME: typecasts.typecast_timestamp,
- FIELD_TYPE.DATE: typecasts.typecast_date,
- FIELD_TYPE.TIME: typecasts.typecast_time,
-})
-
-# 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:
- 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):
- self.connection = None
- self.queries = []
-
- 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.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG
- if not self._valid_connection():
- kwargs = {
- 'user': DATABASE_USER,
- 'db': DATABASE_NAME,
- 'passwd': DATABASE_PASSWORD,
- 'host': DATABASE_HOST,
- 'conv': django_conversions,
- }
- if DATABASE_PORT:
- kwargs['port'] = DATABASE_PORT
- self.connection = Database.connect(**kwargs)
- cursor = self.connection.cursor()
- if self.connection.get_server_info() >= '4.1':
- cursor.execute("SET NAMES utf8")
- if DEBUG:
- return base.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
- return cursor
-
- def commit(self):
- self.connection.commit()
-
- def rollback(self):
- if self.connection:
- try:
- self.connection.rollback()
- except Database.NotSupportedError:
- pass
-
- def close(self):
- if self.connection is not None:
- self.connection.close()
- self.connection = None
-
- def quote_name(self, name):
- if name.startswith("`") and name.endswith("`"):
- return name # Quoting once is enough.
- return "`%s`" % name
-
-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'
- # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
- # MySQL doesn't support DATE_TRUNC, so we fake it by subtracting intervals.
- # If you know of a better way to do this, please file a Django ticket.
- # Note that we can't use DATE_FORMAT directly because that causes the output
- # to be a string rather than a datetime object, and we need MySQL to return
- # a date so that it's typecasted properly into a Python datetime object.
- subtractions = ["interval (DATE_FORMAT(%s, '%%%%s')) second - interval (DATE_FORMAT(%s, '%%%%i')) minute - interval (DATE_FORMAT(%s, '%%%%H')) hour" % (field_name, field_name, field_name)]
- if lookup_type in ('year', 'month'):
- subtractions.append(" - interval (DATE_FORMAT(%s, '%%%%e')-1) day" % field_name)
- if lookup_type == 'year':
- subtractions.append(" - interval (DATE_FORMAT(%s, '%%%%m')-1) month" % field_name)
- return "(%s - %s)" % (field_name, ''.join(subtractions))
-
-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_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" % DatabaseWrapper().quote_name(table_name))
- return cursor.description
-
-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}
- """
- cursor.execute("SHOW INDEX FROM %s" % DatabaseWrapper().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
-
-OPERATOR_MAPPING = {
- 'exact': '= %s',
- 'iexact': 'LIKE %s',
- 'contains': 'LIKE BINARY %s',
- 'icontains': 'LIKE %s',
- 'ne': '!= %s',
- 'gt': '> %s',
- 'gte': '>= %s',
- 'lt': '< %s',
- 'lte': '<= %s',
- 'startswith': 'LIKE BINARY %s',
- 'endswith': 'LIKE BINARY %s',
- 'istartswith': 'LIKE %s',
- 'iendswith': 'LIKE %s',
-}
-
-# 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',
- 'URLField': 'varchar(200)',
- 'USStateField': 'varchar(2)',
-}
-
-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_BLOB: 'TextField',
- FIELD_TYPE.MEDIUM_BLOB: 'TextField',
- FIELD_TYPE.LONG_BLOB: 'TextField',
- FIELD_TYPE.VAR_STRING: 'CharField',
-}
diff --git a/django/core/db/backends/postgresql.py b/django/core/db/backends/postgresql.py
deleted file mode 100644
index 0bc799c247..0000000000
--- a/django/core/db/backends/postgresql.py
+++ /dev/null
@@ -1,238 +0,0 @@
-"""
-PostgreSQL database backend for Django.
-
-Requires psycopg 1: http://initd.org/projects/psycopg1
-"""
-
-from django.core.db import base, typecasts
-import psycopg as Database
-
-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
-
-class DatabaseWrapper(local):
- def __init__(self):
- self.connection = None
- self.queries = []
-
- def cursor(self):
- from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG, TIME_ZONE
- if self.connection is None:
- if 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" % DATABASE_NAME
- if DATABASE_USER:
- conn_string = "user=%s %s" % (DATABASE_USER, conn_string)
- if DATABASE_PASSWORD:
- conn_string += " password='%s'" % DATABASE_PASSWORD
- if DATABASE_HOST:
- conn_string += " host=%s" % DATABASE_HOST
- if DATABASE_PORT:
- conn_string += " port=%s" % DATABASE_PORT
- self.connection = Database.connect(conn_string)
- self.connection.set_isolation_level(1) # make transactions transparent to all cursors
- cursor = self.connection.cursor()
- cursor.execute("SET TIME ZONE %s", [TIME_ZONE])
- if DEBUG:
- return base.CursorDebugWrapper(cursor, self)
- return cursor
-
- def commit(self):
- return self.connection.commit()
-
- def rollback(self):
- if self.connection:
- return self.connection.rollback()
-
- def close(self):
- if self.connection is not None:
- self.connection.close()
- self.connection = None
-
- def quote_name(self, 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_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" % DatabaseWrapper().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}
- """
- # Get the table description because we only have the column indexes, and we
- # need the column names.
- desc = get_table_description(cursor, table_name)
- # This query retrieves each index on the given table.
- cursor.execute("""
- SELECT idx.indkey, idx.indisunique, idx.indisprimary
- FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
- pg_catalog.pg_index idx
- WHERE c.oid = idx.indrelid
- AND idx.indexrelid = c2.oid
- AND c.relname = %s""", [table_name])
- indexes = {}
- for row in cursor.fetchall():
- # row[0] (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[0]:
- continue
- col_name = desc[int(row[0])-1][0]
- indexes[col_name] = {'primary_key': row[2], 'unique': row[1]}
- return indexes
-
-# 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", typecasts.typecast_date))
-except AttributeError:
- raise Exception, "You appear to be using psycopg version 2, which isn't supported yet, because it's still in beta. Use psycopg version 1 instead: http://initd.org/projects/psycopg1"
-Database.register_type(Database.new_type((1083,1266), "TIME", typecasts.typecast_time))
-Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", typecasts.typecast_timestamp))
-Database.register_type(Database.new_type((16,), "BOOLEAN", typecasts.typecast_boolean))
-
-OPERATOR_MAPPING = {
- 'exact': '= %s',
- 'iexact': 'ILIKE %s',
- 'contains': 'LIKE %s',
- 'icontains': 'ILIKE %s',
- 'ne': '!= %s',
- 'gt': '> %s',
- 'gte': '>= %s',
- 'lt': '< %s',
- 'lte': '<= %s',
- 'startswith': 'LIKE %s',
- 'endswith': 'LIKE %s',
- 'istartswith': 'ILIKE %s',
- 'iendswith': 'ILIKE %s',
-}
-
-# 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',
- 'URLField': 'varchar(200)',
- 'USStateField': 'varchar(2)',
-}
-
-# 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/django/core/db/backends/sqlite3.py b/django/core/db/backends/sqlite3.py
deleted file mode 100644
index 1b2ae8cd84..0000000000
--- a/django/core/db/backends/sqlite3.py
+++ /dev/null
@@ -1,230 +0,0 @@
-"""
-SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/).
-"""
-
-from django.core.db import base, typecasts
-from django.core.db.dicthelpers import *
-from pysqlite2 import dbapi2 as Database
-DatabaseError = Database.DatabaseError
-
-# Register adaptors ###########################################################
-
-Database.register_converter("bool", lambda s: str(s) == '1')
-Database.register_converter("time", typecasts.typecast_time)
-Database.register_converter("date", typecasts.typecast_date)
-Database.register_converter("datetime", typecasts.typecast_timestamp)
-
-# Database wrapper ############################################################
-
-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):
- self.connection = None
- self.queries = []
-
- def cursor(self):
- from django.conf.settings import DATABASE_NAME, DEBUG
- if self.connection is None:
- self.connection = Database.connect(DATABASE_NAME, detect_types=Database.PARSE_DECLTYPES)
- # register extract and date_trun 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 DEBUG:
- return base.CursorDebugWrapper(cursor, self)
- else:
- return cursor
-
- def commit(self):
- self.connection.commit()
-
- def rollback(self):
- if self.connection:
- self.connection.rollback()
-
- def close(self):
- if self.connection is not None:
- self.connection.close()
- self.connection = None
-
- def quote_name(self, name):
- if name.startswith('"') and name.endswith('"'):
- return name # Quoting once is enough.
- return '"%s"' % name
-
-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" (which I belive is true of other wrappers as well).
- """
-
- def execute(self, query, params=[]):
- query = self.convert_query(query, len(params))
- return Database.Cursor.execute(self, query, params)
-
- def executemany(self, query, params=[]):
- query = self.convert_query(query, len(params[0]))
- return Database.Cursor.executemany(self, query, params)
-
- def convert_query(self, query, num_params):
- # XXX this seems too simple to be correct... is this right?
- return query % tuple("?" * num_params)
-
-# Helper functions ############################################################
-
-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 = typecasts.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 _sqlite_date_trunc(lookup_type, dt):
- try:
- dt = typecasts.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)
-
-def get_table_list(cursor):
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
- return [row[0] for row in cursor.fetchall()]
-
-def get_table_description(cursor, table_name):
- cursor.execute("PRAGMA table_info(%s)" % DatabaseWrapper().quote_name(table_name))
- return [(row[1], row[2], None, None) for row in cursor.fetchall()]
-
-def get_relations(cursor, table_name):
- raise NotImplementedError
-
-def get_indexes(cursor, table_name):
- raise NotImplementedError
-
-# Operators and fields ########################################################
-
-# 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 '\\'",
- 'ne': '!= %s',
- 'gt': '> %s',
- 'gte': '>= %s',
- 'lt': '< %s',
- 'lte': '<= %s',
- 'startswith': "LIKE %s ESCAPE '\\'",
- 'endswith': "LIKE %s ESCAPE '\\'",
- 'istartswith': "LIKE %s ESCAPE '\\'",
- 'iendswith': "LIKE %s ESCAPE '\\'",
-}
-
-# 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',
- 'URLField': 'varchar(200)',
- 'USStateField': 'varchar(2)',
-}
-
-# 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/django/core/db/base.py b/django/core/db/base.py
deleted file mode 100644
index 62cca9d0be..0000000000
--- a/django/core/db/base.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from time import time
-
-class CursorDebugWrapper:
- def __init__(self, cursor, db):
- self.cursor = cursor
- self.db = db
-
- def execute(self, sql, params=[]):
- start = time()
- result = self.cursor.execute(sql, params)
- stop = time()
- self.db.queries.append({
- 'sql': sql % tuple(params),
- 'time': "%.3f" % (stop - start),
- })
- return result
-
- def executemany(self, sql, param_list):
- start = time()
- result = self.cursor.executemany(sql, param_list)
- stop = time()
- self.db.queries.append({
- 'sql': 'MANY: ' + sql + ' ' + str(tuple(param_list)),
- 'time': "%.3f" % (stop - start),
- })
- return result
-
- def __getattr__(self, attr):
- if self.__dict__.has_key(attr):
- return self.__dict__[attr]
- else:
- return getattr(self.cursor, attr)
diff --git a/django/core/db/dicthelpers.py b/django/core/db/dicthelpers.py
deleted file mode 100644
index 5aedc51aed..0000000000
--- a/django/core/db/dicthelpers.py
+++ /dev/null
@@ -1,24 +0,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([(desc[col[0]][0], col[1]) for col in enumerate(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
- return [_dict_helper(desc, row) for row in cursor.fetchmany(number)]
-
-def dictfetchall(cursor):
- "Returns all rows from a cursor as a dict"
- desc = cursor.description
- return [_dict_helper(desc, row) for row in cursor.fetchall()]
diff --git a/django/core/db/typecasts.py b/django/core/db/typecasts.py
deleted file mode 100644
index 9be9062626..0000000000
--- a/django/core/db/typecasts.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import datetime
-
-###############################################
-# 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'
diff --git a/django/core/exceptions.py b/django/core/exceptions.py
index 9d1aab665f..f22f67c261 100644
--- a/django/core/exceptions.py
+++ b/django/core/exceptions.py
@@ -1,13 +1,8 @@
"Global Django exceptions"
-from django.core.template import SilentVariableFailure
-
-class Http404(Exception):
- pass
-
-class ObjectDoesNotExist(SilentVariableFailure):
+class ObjectDoesNotExist(Exception):
"The requested object does not exist"
- pass
+ silent_variable_failure = True
class SuspiciousOperation(Exception):
"The user did something suspicious"
diff --git a/django/core/extensions.py b/django/core/extensions.py
deleted file mode 100644
index c03f63addc..0000000000
--- a/django/core/extensions.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# 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.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist
-from django.core.template import Context, loader
-from django.conf.settings import TEMPLATE_CONTEXT_PROCESSORS
-from django.utils.httpwrappers import HttpResponse
-
-_standard_context_processors = None
-
-# This is a function rather than module-level procedural code because we only
-# want it to execute if somebody uses DjangoContext.
-def get_standard_processors():
- global _standard_context_processors
- if _standard_context_processors is None:
- processors = []
- for path in 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
-
-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(mod, **kwargs):
- try:
- return mod.get_object(**kwargs)
- except ObjectDoesNotExist:
- raise Http404
-
-def get_list_or_404(mod, **kwargs):
- obj_list = mod.get_list(**kwargs)
- if not obj_list:
- raise Http404
- return obj_list
-
-class DjangoContext(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))
-
-# PermWrapper and PermLookupDict proxy the permissions system into objects that
-# the template system can understand.
-
-class PermLookupDict:
- def __init__(self, user, module_name):
- self.user, self.module_name = user, module_name
- def __repr__(self):
- return str(self.user.get_permission_list())
- 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:
- def __init__(self, user):
- self.user = user
- def __getitem__(self, module_name):
- return PermLookupDict(self.user, module_name)
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index fd3a3ccae1..fce724c31f 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -1,4 +1,6 @@
-from django.utils import httpwrappers
+from django.core import signals
+from django.dispatch import dispatcher
+from django import http
import sys
class BaseHandler:
@@ -48,12 +50,9 @@ class BaseHandler:
def get_response(self, path, request):
"Returns an HttpResponse object for the given HttpRequest"
- from django.core import db, exceptions, urlresolvers
+ from django.core import exceptions, urlresolvers
from django.core.mail import mail_admins
- from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF
-
- # Reset query list per request.
- db.db.queries = []
+ from django.conf import settings
# Apply request middleware
for middleware_method in self._request_middleware:
@@ -61,7 +60,7 @@ class BaseHandler:
if response:
return response
- resolver = urlresolvers.RegexURLResolver(r'^/', ROOT_URLCONF)
+ resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF)
try:
callback, callback_args, callback_kwargs = resolver.resolve(path)
@@ -88,30 +87,23 @@ class BaseHandler:
raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, callback.func_name)
return response
- except exceptions.Http404, e:
- if DEBUG:
+ except http.Http404, e:
+ if settings.DEBUG:
return self.get_technical_error_response(request, is404=True, exception=e)
else:
callback, param_dict = resolver.resolve404()
return callback(request, **param_dict)
- except db.DatabaseError:
- db.db.rollback()
- if DEBUG:
- return self.get_technical_error_response(request)
- else:
- subject = 'Database error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', ''))
- message = "%s\n\n%s" % (self._get_traceback(), request)
- mail_admins(subject, message, fail_silently=True)
- return self.get_friendly_error_response(request, resolver)
except exceptions.PermissionDenied:
- return httpwrappers.HttpResponseForbidden('<h1>Permission denied</h1>')
+ return http.HttpResponseForbidden('<h1>Permission denied</h1>')
except: # Handle everything else, including SuspiciousOperation, etc.
- if DEBUG:
+ if settings.DEBUG:
return self.get_technical_error_response(request)
else:
# Get the exception info now, in case another exception is thrown later.
exc_info = sys.exc_info()
- subject = 'Coding error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', ''))
+ 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'), getattr(request, 'path', ''))
try:
request_repr = repr(request)
except:
@@ -123,7 +115,7 @@ class BaseHandler:
def get_friendly_error_response(self, request, resolver):
"""
Returns an HttpResponse that displays a PUBLIC error message for a
- fundamental database or coding error.
+ fundamental error.
"""
from django.core import urlresolvers
callback, param_dict = resolver.resolve500()
@@ -132,7 +124,7 @@ class BaseHandler:
def get_technical_error_response(self, request, is404=False, exception=None):
"""
Returns an HttpResponse that displays a TECHNICAL error message for a
- fundamental database or coding error.
+ fundamental error.
"""
from django.views import debug
if is404:
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index 37499c6f06..ce7f6f1ad2 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -1,5 +1,8 @@
from django.core.handlers.base import BaseHandler
-from django.utils import datastructures, httpwrappers
+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
@@ -7,15 +10,15 @@ import os
# settings) until after ModPythonHandler has been called; otherwise os.environ
# won't be set up correctly (with respect to settings).
-class ModPythonRequest(httpwrappers.HttpRequest):
+class ModPythonRequest(http.HttpRequest):
def __init__(self, req):
self._req = req
self.path = req.uri
def __repr__(self):
- return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s,\nuser:%s>' % \
+ return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
(self.path, pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
- pformat(self.META), pformat(self.user))
+ pformat(self.META))
def get_full_path(self):
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
@@ -23,18 +26,18 @@ class ModPythonRequest(httpwrappers.HttpRequest):
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 = httpwrappers.parse_file_upload(self._req.headers_in, self.raw_post_data)
+ self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
else:
- self._post, self._files = httpwrappers.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
+ 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)
+ self._request = datastructures.MergeDict(self.POST, self.GET)
return self._request
def _get_get(self):
if not hasattr(self, '_get'):
- self._get = httpwrappers.QueryDict(self._req.args)
+ self._get = http.QueryDict(self._req.args)
return self._get
def _set_get(self, get):
@@ -50,7 +53,7 @@ class ModPythonRequest(httpwrappers.HttpRequest):
def _get_cookies(self):
if not hasattr(self, '_cookies'):
- self._cookies = httpwrappers.parse_cookie(self._req.headers_in.get('cookie', ''))
+ self._cookies = http.parse_cookie(self._req.headers_in.get('cookie', ''))
return self._cookies
def _set_cookies(self, cookies):
@@ -95,22 +98,6 @@ class ModPythonRequest(httpwrappers.HttpRequest):
self._raw_post_data = self._req.read()
return self._raw_post_data
- def _get_user(self):
- if not hasattr(self, '_user'):
- from django.models.auth import users
- try:
- user_id = self.session[users.SESSION_KEY]
- if not user_id:
- raise ValueError
- self._user = users.get_object(pk=user_id)
- except (AttributeError, KeyError, ValueError, users.UserDoesNotExist):
- from django.parts.auth import anonymoususers
- self._user = anonymoususers.AnonymousUser()
- return self._user
-
- def _set_user(self, user):
- self._user = user
-
GET = property(_get_get, _set_get)
POST = property(_get_post, _set_post)
COOKIES = property(_get_cookies, _set_cookies)
@@ -118,7 +105,6 @@ class ModPythonRequest(httpwrappers.HttpRequest):
META = property(_get_meta)
REQUEST = property(_get_request)
raw_post_data = property(_get_raw_post_data)
- user = property(_get_user, _set_user)
class ModPythonHandler(BaseHandler):
def __call__(self, req):
@@ -128,7 +114,6 @@ class ModPythonHandler(BaseHandler):
# now that the environ works we can see the correct settings, so imports
# that use settings now can work
from django.conf import settings
- from django.core import db
if settings.ENABLE_PSYCO:
import psyco
@@ -138,15 +123,17 @@ class ModPythonHandler(BaseHandler):
if self._request_middleware is None:
self.load_middleware()
+ dispatcher.send(signal=signals.request_started)
try:
request = ModPythonRequest(req)
response = self.get_response(req.uri, request)
- finally:
- db.db.close()
- # Apply response middleware
- for middleware_method in self._response_middleware:
- response = middleware_method(request, response)
+ # 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.
populate_apache_request(response, req)
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 7541a5fd9d..01dbdf02f1 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -1,5 +1,8 @@
from django.core.handlers.base import BaseHandler
-from django.utils import datastructures, httpwrappers
+from django.core import signals
+from django.dispatch import dispatcher
+from django.utils import datastructures
+from django import http
from pprint import pformat
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
@@ -47,7 +50,7 @@ STATUS_CODE_TEXT = {
505: 'HTTP VERSION NOT SUPPORTED',
}
-class WSGIRequest(httpwrappers.HttpRequest):
+class WSGIRequest(http.HttpRequest):
def __init__(self, environ):
self.environ = environ
self.path = environ['PATH_INFO']
@@ -60,7 +63,7 @@ class WSGIRequest(httpwrappers.HttpRequest):
pformat(self.META))
def get_full_path(self):
- return '%s%s' % (self.path, self.environ['QUERY_STRING'] and ('?' + self.environ['QUERY_STRING']) or '')
+ return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
def _load_post_and_files(self):
# Populates self._post and self._files
@@ -68,21 +71,21 @@ class WSGIRequest(httpwrappers.HttpRequest):
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 = httpwrappers.parse_file_upload(header_dict, self.raw_post_data)
+ self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
else:
- self._post, self._files = httpwrappers.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
+ self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
else:
- self._post, self._files = httpwrappers.QueryDict(''), datastructures.MultiValueDict()
+ 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)
+ 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 = httpwrappers.QueryDict(self.environ.get('QUERY_STRING', ''))
+ self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''))
return self._get
def _set_get(self, get):
@@ -98,7 +101,7 @@ class WSGIRequest(httpwrappers.HttpRequest):
def _get_cookies(self):
if not hasattr(self, '_cookies'):
- self._cookies = httpwrappers.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
+ self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
return self._cookies
def _set_cookies(self, cookies):
@@ -116,34 +119,16 @@ class WSGIRequest(httpwrappers.HttpRequest):
self._raw_post_data = self.environ['wsgi.input'].read(int(self.environ["CONTENT_LENGTH"]))
return self._raw_post_data
- def _get_user(self):
- if not hasattr(self, '_user'):
- from django.models.auth import users
- try:
- user_id = self.session[users.SESSION_KEY]
- if not user_id:
- raise ValueError
- self._user = users.get_object(pk=user_id)
- except (AttributeError, KeyError, ValueError, users.UserDoesNotExist):
- from django.parts.auth import anonymoususers
- self._user = anonymoususers.AnonymousUser()
- return self._user
-
- def _set_user(self, user):
- self._user = user
-
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)
- user = property(_get_user, _set_user)
class WSGIHandler(BaseHandler):
def __call__(self, environ, start_response):
from django.conf import settings
- from django.core import db
if settings.ENABLE_PSYCO:
import psyco
@@ -154,15 +139,17 @@ class WSGIHandler(BaseHandler):
if self._request_middleware is None:
self.load_middleware()
+ dispatcher.send(signal=signals.request_started)
try:
request = WSGIRequest(environ)
response = self.get_response(request.path, request)
- finally:
- db.db.close()
- # Apply response middleware
- for middleware_method in self._response_middleware:
- response = middleware_method(request, response)
+ # 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]
diff --git a/django/core/mail.py b/django/core/mail.py
index 6a3cba141c..3baf191b5c 100644
--- a/django/core/mail.py
+++ b/django/core/mail.py
@@ -46,9 +46,18 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=settings.EMAIL_HOST
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = ', '.join(recipient_list)
- server.sendmail(from_email, recipient_list, msg.as_string())
- num_sent += 1
- server.quit()
+ 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):
diff --git a/django/core/management.py b/django/core/management.py
index 15f570c57b..3838c2abee 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -2,8 +2,10 @@
# development-server initialization.
import django
-import os, re, sys, textwrap
+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'):
@@ -17,7 +19,7 @@ MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(cha
</tr>
{%% endif %%}'''
-APP_ARGS = '[modelmodule ...]'
+APP_ARGS = '[appname ...]'
# Use django.__path__[0] because we don't know which directory django into
# which has been installed.
@@ -25,38 +27,46 @@ PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template')
INVALID_PROJECT_NAMES = ('django', 'test')
-def _get_packages_insert(app_label):
- from django.core.db import db
- return "INSERT INTO %s (%s, %s) VALUES ('%s', '%s');" % \
- (db.quote_name('packages'), db.quote_name('label'), db.quote_name('name'),
- app_label, app_label)
+# 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.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 _get_permission_codename(action, opts):
- return '%s_%s' % (action, opts.object_name.lower())
+def disable_termcolors():
+ class dummy:
+ def __getattr__(self, attr):
+ return lambda x: x
+ global style
+ style = dummy()
-def _get_all_permissions(opts):
- "Returns (codename, name) for all permissions in the given opts."
- perms = []
- if opts.admin:
- 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 _get_permission_insert(name, codename, opts):
- from django.core.db import db
- return "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s');" % \
- (db.quote_name('auth_permissions'), db.quote_name('name'), db.quote_name('package'),
- db.quote_name('codename'), name.replace("'", "''"), opts.app_label, codename)
-
-def _get_contenttype_insert(opts):
- from django.core.db import db
- return "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s');" % \
- (db.quote_name('content_types'), db.quote_name('name'), db.quote_name('package'),
- db.quote_name('python_module_name'), opts.verbose_name, opts.app_label, opts.module_name)
+# Disable terminal coloring if somebody's piping the output.
+if (not sys.stdout.isatty()) or (sys.platform == 'win32'):
+ 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)
+
# 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
@@ -67,304 +77,429 @@ 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[3]:
- v += ' (%s)' % VERSION[3]
+ if VERSION[-1]:
+ v += ' (%s)' % VERSION[-1]
return v
-def get_sql_create(mod):
- "Returns a list of the CREATE TABLE SQL statements for the given module."
- from django.core import db, meta
+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
+ installed_models = _get_installed_models(_get_table_list())
+
final_output = []
- for klass in mod._MODELS:
- opts = klass._meta
- table_output = []
- for f in opts.fields:
- if isinstance(f, meta.ForeignKey):
- 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 = db.DATA_TYPES[data_type]
- if col_type is not None:
- field_output = [db.db.quote_name(f.column), col_type % rel_field.__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.rel:
- field_output.append('REFERENCES %s (%s)' % \
- (db.db.quote_name(f.rel.to.db_table),
- db.db.quote_name(f.rel.to.get_field(f.rel.field_name).column)))
- table_output.append(' '.join(field_output))
- if opts.order_with_respect_to:
- table_output.append('%s %s NULL' % (db.db.quote_name('_order'), db.DATA_TYPES['IntegerField']))
- for field_constraints in opts.unique_together:
- table_output.append('UNIQUE (%s)' % \
- ", ".join([db.db.quote_name(opts.get_field(f).column) for f in field_constraints]))
+ models_output = set(installed_models)
+ pending_references = {}
- full_statement = ['CREATE TABLE %s (' % db.db.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))
+ app_models = models.get_models(app)
+
+ for klass in app_models:
+ output, references = _get_sql_model_create(klass, models_output)
+ final_output.extend(output)
+ pending_references.update(references)
+ final_output.extend(_get_sql_for_pending_references(klass, pending_references))
+ # Keep track of the fact that we've created the table for this model.
+ models_output.add(klass)
+
+ # Create the many-to-many join tables.
+ for klass in app_models:
+ final_output.extend(_get_many_to_many_sql_for_model(klass))
+
+ # 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:
+ final_output.append('-- The following references should be added but depend on non-existant tables:')
+ for klass in not_installed_models:
+ final_output.extend(['-- ' + sql for sql in
+ _get_sql_for_pending_references(klass, pending_references)])
- for klass in mod._MODELS:
- opts = klass._meta
- for f in opts.many_to_many:
- table_output = ['CREATE TABLE %s (' % db.db.quote_name(f.get_m2m_db_table(opts))]
- table_output.append(' %s %s NOT NULL PRIMARY KEY,' % (db.db.quote_name('id'), db.DATA_TYPES['AutoField']))
- table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \
- (db.db.quote_name(opts.object_name.lower() + '_id'),
- db.DATA_TYPES[get_rel_data_type(opts.pk)] % opts.pk.__dict__,
- db.db.quote_name(opts.db_table),
- db.db.quote_name(opts.pk.column)))
- table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \
- (db.db.quote_name(f.rel.to.object_name.lower() + '_id'),
- db.DATA_TYPES[get_rel_data_type(f.rel.to.pk)] % f.rel.to.pk.__dict__,
- db.db.quote_name(f.rel.to.db_table),
- db.db.quote_name(f.rel.to.pk.column)))
- table_output.append(' UNIQUE (%s, %s)' % \
- (db.db.quote_name(opts.object_name.lower() + '_id'),
- db.db.quote_name(f.rel.to.object_name.lower() + '_id')))
- table_output.append(');')
- final_output.append('\n'.join(table_output))
return final_output
-get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given model module name(s)."
+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_delete(mod):
- "Returns a list of the DROP TABLE SQL statements for the given module."
- from django.core import db
+def _get_sql_model_create(klass, models_already_seen=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 = klass._meta
+ final_output = []
+ table_output = []
+ pending_references = {}
+ for f in opts.fields:
+ if isinstance(f, models.ForeignKey):
+ 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 models_already_seen:
+ 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)) + ')'
+ )
+ 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((klass, 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(klass, 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 = klass._meta
+ if klass in pending_references:
+ for rel_class, f in pending_references[klass]:
+ 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
+ final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \
+ (backend.quote_name(r_table),
+ backend.quote_name('%s_referencing_%s_%s' % (r_col, table, col)),
+ backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col)))
+ del pending_references[klass]
+ return final_output
+
+def _get_many_to_many_sql_for_model(klass):
+ from django.db import backend, get_creation_module
+ data_types = get_creation_module().DATA_TYPES
+
+ opts = klass._meta
+ final_output = []
+ for f in opts.many_to_many:
+ 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),' % \
+ (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))))
+ table_output.append(' %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))))
+ 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 connecton isn't available
try:
- cursor = db.db.cursor()
+ cursor = connection.cursor()
except:
cursor = None
- # Determine whether the admin log table exists. It only exists if the
- # person has installed the admin app.
- try:
- if cursor is not None:
- # Check whether the table exists.
- cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name('django_admin_log'))
- except:
- # The table doesn't exist, so it doesn't need to be dropped.
- db.db.rollback()
- admin_log_exists = False
+ # Figure out which tables already exist
+ if cursor:
+ table_names = introspection.get_table_list(cursor)
else:
- admin_log_exists = True
+ table_names = []
output = []
# Output DROP TABLE statements for standard application tables.
- for klass in mod._MODELS:
- try:
- if cursor is not None:
- # Check whether the table exists.
- cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(klass._meta.db_table))
- except:
- # The table doesn't exist, so it doesn't need to be dropped.
- db.db.rollback()
- else:
- output.append("DROP TABLE %s;" % db.db.quote_name(klass._meta.db_table))
+ to_delete = set()
+
+ references_to_delete = {}
+ app_models = models.get_models(app)
+ for klass in app_models:
+ if cursor and klass._meta.db_table in table_names:
+ # The table exists, so it needs to be dropped
+ opts = klass._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( (klass, f) )
+
+ to_delete.add(klass)
+
+ for klass in app_models:
+ if cursor and klass._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(klass._meta.db_table))))
+ if backend.supports_constraints and references_to_delete.has_key(klass):
+ for rel_class, f in references_to_delete[klass]:
+ table = rel_class._meta.db_table
+ col = f.column
+ r_table = klass._meta.db_table
+ r_col = klass._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_referencing_%s_%s" % (col, r_table, r_col)))))
+ del references_to_delete[klass]
# Output DROP TABLE statements for many-to-many tables.
- for klass in mod._MODELS:
+ for klass in app_models:
opts = klass._meta
for f in opts.many_to_many:
- try:
- if cursor is not None:
- cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(f.get_m2m_db_table(opts)))
- except:
- db.db.rollback()
- else:
- output.append("DROP TABLE %s;" % db.db.quote_name(f.get_m2m_db_table(opts)))
+ 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 = mod._MODELS[0]._meta.app_label
-
- # Delete from packages, auth_permissions, content_types.
- output.append("DELETE FROM %s WHERE %s = '%s';" % \
- (db.db.quote_name('packages'), db.db.quote_name('label'), app_label))
- output.append("DELETE FROM %s WHERE %s = '%s';" % \
- (db.db.quote_name('auth_permissions'), db.db.quote_name('package'), app_label))
- output.append("DELETE FROM %s WHERE %s = '%s';" % \
- (db.db.quote_name('content_types'), db.db.quote_name('package'), app_label))
-
- # Delete from the admin log.
- if cursor is not None:
- cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \
- (db.db.quote_name('id'), db.db.quote_name('content_types'),
- db.db.quote_name('package')), [app_label])
- if admin_log_exists:
- for row in cursor.fetchall():
- output.append("DELETE FROM %s WHERE %s = %s;" % \
- (db.db.quote_name('django_admin_log'), db.db.quote_name('content_type_id'), row[0]))
+ 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 is not None:
+ if cursor:
cursor.close()
- db.db.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 model module name(s)."
+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(mod):
+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(mod) + get_sql_all(mod)
-get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given model module name(s)."
+ 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_initial_data(mod):
- "Returns a list of the initial INSERT SQL statements for the given module."
- from django.core import db
+def get_sql_initial_data_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 = []
- app_label = mod._MODELS[0]._meta.app_label
- output.append(_get_packages_insert(app_label))
- app_dir = os.path.normpath(os.path.join(os.path.dirname(mod.__file__), '..', 'sql'))
- for klass in mod._MODELS:
- opts = klass._meta
- # Add custom SQL, if it's available.
- sql_files = [os.path.join(app_dir, opts.module_name + '.' + db.DATABASE_ENGINE + '.sql'),
- os.path.join(app_dir, opts.module_name + '.sql')]
- for sql_file in sql_files:
- if os.path.exists(sql_file):
- fp = open(sql_file)
- output.append(fp.read())
- fp.close()
+ # 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)
+ output.append(fp.read())
+ fp.close()
- # Content types.
- output.append(_get_contenttype_insert(opts))
- # Permissions.
- for codename, name in _get_all_permissions(opts):
- output.append(_get_permission_insert(name, codename, opts))
return output
-get_sql_initial_data.help_doc = "Prints the initial INSERT SQL statements for the given model module name(s)."
+
+def get_sql_initial_data(app):
+ "Returns a list of the initial INSERT 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 klass in app_models:
+ output.extend(get_sql_initial_data_for_model(klass))
+
+ return output
+get_sql_initial_data.help_doc = "Prints the initial INSERT SQL statements for the given app name(s)."
get_sql_initial_data.args = APP_ARGS
-def get_sql_sequence_reset(mod):
- "Returns a list of the SQL statements to reset PostgreSQL sequences for the given module."
- from django.core import db, meta
+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 klass in mod._MODELS:
+ for klass in models.get_models(app):
for f in klass._meta.fields:
- if isinstance(f, meta.AutoField):
- output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % \
- (klass._meta.db_table, f.column, db.db.quote_name(f.column),
- db.db.quote_name(klass._meta.db_table)))
+ 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' % (klass._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(klass._meta.db_table))))
+ break # Only one AutoField is allowed per model, so don't bother continuing.
for f in klass._meta.many_to_many:
- output.append("SELECT setval('%s_id_seq', (SELECT max(%s) FROM %s));" % \
- (f.get_m2m_db_table(klass._meta), db.db.quote_name('id'), f.get_m2m_db_table(klass._meta)))
+ 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 model module name(s)."
+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(mod):
- "Returns a list of the CREATE INDEX SQL statements for the given module."
- from django.core.db import db
+def get_sql_indexes(app):
+ "Returns a list of the CREATE INDEX SQL statements for the given app."
+ from django.db import backend, models
output = []
- for klass in mod._MODELS:
+
+ for klass in models.get_models(app):
for f in klass._meta.fields:
if f.db_index:
- unique = f.unique and "UNIQUE " or ""
- output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
- (unique, klass._meta.db_table, f.column,
- db.quote_name(klass._meta.db_table), db.quote_name(f.column)))
+ unique = f.unique and 'UNIQUE ' or ''
+ output.append(
+ style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
+ style.SQL_TABLE('%s_%s' % (klass._meta.db_table, f.column)) + ' ' + \
+ style.SQL_KEYWORD('ON') + ' ' + \
+ style.SQL_TABLE(backend.quote_name(klass._meta.db_table)) + ' ' + \
+ "(%s);" % style.SQL_FIELD(backend.quote_name(f.column))
+ )
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_all(mod):
- "Returns a list of CREATE TABLE SQL and initial-data insert for the given module."
- return get_sql_create(mod) + get_sql_initial_data(mod)
-get_sql_all.help_doc = "Prints the CREATE TABLE and initial-data SQL statements for the given model module name(s)."
+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_sql_initial_data(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 has_no_records(cursor):
- "Returns True if the cursor, having executed a query, returned no records."
- # This is necessary due to an inconsistency in the DB-API spec.
- # cursor.rowcount can be -1 (undetermined), according to
- # http://www.python.org/peps/pep-0249.html
- if cursor.rowcount < 0:
- return cursor.fetchone() is None
- return cursor.rowcount < 1
-
-def database_check(mod):
- "Checks that everything is properly installed in the database for the given module."
- from django.core import db
- cursor = db.db.cursor()
- app_label = mod._MODELS[0]._meta.app_label
+def syncdb():
+ "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.db.models import signals
+ from django.conf import settings
+ from django.dispatch import dispatcher
- # Check that the package exists in the database.
- cursor.execute("SELECT 1 FROM %s WHERE %s = %%s" % \
- (db.db.quote_name('packages'), db.db.quote_name('label')), [app_label])
- if has_no_records(cursor):
-# sys.stderr.write("The '%s' package isn't installed.\n" % app_label)
- print _get_packages_insert(app_label)
+ disable_termcolors()
- # Check that the permissions and content types are in the database.
- perms_seen = {}
- contenttypes_seen = {}
- for klass in mod._MODELS:
- opts = klass._meta
- perms = _get_all_permissions(opts)
- perms_seen.update(dict(perms))
- contenttypes_seen[opts.module_name] = 1
- for codename, name in perms:
- cursor.execute("SELECT 1 FROM %s WHERE %s = %%s AND %s = %%s" % \
- (db.db.quote_name('auth_permissions'), db.db.quote_name('package'),
- db.db.quote_name('codename')), (app_label, codename))
- if has_no_records(cursor):
-# sys.stderr.write("The '%s.%s' permission doesn't exist.\n" % (app_label, codename))
- print _get_permission_insert(name, codename, opts)
- cursor.execute("SELECT 1 FROM %s WHERE %s = %%s AND %s = %%s" % \
- (db.db.quote_name('content_types'), db.db.quote_name('package'),
- db.db.quote_name('python_module_name')), (app_label, opts.module_name))
- if has_no_records(cursor):
-# sys.stderr.write("The '%s.%s' content type doesn't exist.\n" % (app_label, opts.module_name))
- print _get_contenttype_insert(opts)
+ # First, try validating the models.
+ _check_for_validation_errors()
- # Check that there aren't any *extra* permissions in the DB that the model
- # doesn't know about.
- cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \
- (db.db.quote_name('codename'), db.db.quote_name('auth_permissions'),
- db.db.quote_name('package')), (app_label,))
- for row in cursor.fetchall():
+ # Import the 'management' module within each installed app, to register
+ # dispatcher events.
+ for app_name in settings.INSTALLED_APPS:
try:
- perms_seen[row[0]]
- except KeyError:
-# sys.stderr.write("A permission called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
- print "DELETE FROM %s WHERE %s='%s' AND %s = '%s';" % \
- (db.db.quote_name('auth_permissions'), db.db.quote_name('package'),
- app_label, db.db.quote_name('codename'), row[0])
+ __import__(app_name + '.management', '', '', [''])
+ except ImportError:
+ pass
- # Check that there aren't any *extra* content types in the DB that the
- # model doesn't know about.
- cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \
- (db.db.quote_name('python_module_name'), db.db.quote_name('content_types'),
- db.db.quote_name('package')), (app_label,))
- for row in cursor.fetchall():
- try:
- contenttypes_seen[row[0]]
- except KeyError:
-# sys.stderr.write("A content type called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
- print "DELETE FROM %s WHERE %s='%s' AND %s = '%s';" % \
- (db.db.quote_name('content_types'), db.db.quote_name('package'),
- app_label, db.db.quote_name('python_module_name'), row[0])
-database_check.help_doc = "Checks that everything is installed in the database for the given model module name(s) and prints SQL statements if needed."
-database_check.args = APP_ARGS
+ 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 = {}
+
+ for app in models.get_apps():
+ model_list = models.get_models(app)
+ for model in model_list:
+ # Create the model's database table, if it doesn't already exist.
+ 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)
+ pending_references.update(references)
+ sql.extend(_get_sql_for_pending_references(model, pending_references))
+ print "Creating table %s" % model._meta.db_table
+ for statement in sql:
+ cursor.execute(statement)
+
+ for model in model_list:
+ if model in created_models:
+ sql = _get_many_to_many_sql_for_model(model)
+ if sql:
+ print "Creating many-to-many tables for %s model" % model.__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.
+ for app in models.get_apps():
+ dispatcher.send(signal=signals.post_syncdb, sender=app,
+ app=app, created_models=created_models)
+
+ # Install initial data for the app (but only if this is a model we've
+ # just created)
+ for model in models.get_models(app):
+ if model in created_models:
+ initial_sql = get_sql_initial_data_for_model(model)
+ if initial_sql:
+ print "Installing initial data for %s model" % model._meta.object_name
+ try:
+ for sql in initial_sql:
+ cursor.execute(sql)
+ except Exception, e:
+ sys.stderr.write("Failed to install initial SQL data for %s model: %s" % \
+ (model._meta.object_name, e))
+ transaction.rollback_unless_managed()
+ else:
+ transaction.commit_unless_managed()
+
+syncdb.args = ''
-def get_admin_index(mod):
- "Returns admin-index template snippet (in list form) for the given module."
+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_label = mod._MODELS[0]._meta.app_label
+ 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 klass in mod._MODELS:
+ for klass in app_models:
if klass._meta.admin:
output.append(MODULE_TEMPLATE % {
'app': app_label,
@@ -376,97 +511,114 @@ def get_admin_index(mod):
output.append('</table></div>')
output.append('{% endif %}')
return output
-get_admin_index.help_doc = "Prints the admin-index template snippet for the given model module name(s)."
+get_admin_index.help_doc = "Prints the admin-index template snippet for the given app name(s)."
get_admin_index.args = APP_ARGS
-def init():
- "Initializes the database with auth and core."
- try:
- from django.core import db, meta
- auth = meta.get_app('auth')
- core = meta.get_app('core')
- cursor = db.db.cursor()
- for sql in get_sql_create(core) + get_sql_create(auth) + get_sql_initial_data(core) + get_sql_initial_data(auth):
- cursor.execute(sql)
- cursor.execute("INSERT INTO %s (%s, %s) VALUES ('example.com', 'Example site')" % \
- (db.db.quote_name(core.Site._meta.db_table), db.db.quote_name('domain'),
- db.db.quote_name('name')))
- except Exception, e:
- sys.stderr.write("Error: The database couldn't be initialized.\n%s\n" % e)
- try:
- db.db.rollback()
- except UnboundLocalError:
- pass
- sys.exit(1)
- else:
- db.db.commit()
-init.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)
+ default_settings = _module_to_dict(global_settings)
-def install(mod):
+ 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 install(app):
"Executes the equivalent of 'get_sql_all' in the current database."
- from django.core import db
- from cStringIO import StringIO
- mod_name = mod.__name__[mod.__name__.rindex('.')+1:]
+ from django.db import connection, transaction
+
+ app_name = app.__name__.split('.')[-2]
+
+ disable_termcolors()
# First, try validating the models.
- s = StringIO()
- num_errors = get_validation_errors(s)
- if num_errors:
- sys.stderr.write("Error: %s couldn't be installed, because there were errors in your model:\n" % mod_name)
- s.seek(0)
- sys.stderr.write(s.read())
- sys.exit(1)
- sql_list = get_sql_all(mod)
+ _check_for_validation_errors(app)
+
+ sql_list = get_sql_all(app)
try:
- cursor = db.db.cursor()
+ cursor = connection.cursor()
for sql in sql_list:
cursor.execute(sql)
except Exception, e:
- sys.stderr.write("""Error: %s couldn't be installed. Possible reasons:
+ sys.stderr.write(style.ERROR("""Error: %s couldn't be installed. Possible reasons:
* The database isn't running or isn't configured correctly.
* At least one of the database tables already exists.
* The SQL was invalid.
Hint: Look at the output of 'django-admin.py sqlall %s'. That's the SQL this command wasn't able to run.
-The full error: %s\n""" % \
- (mod_name, mod_name, e))
- db.db.rollback()
+The full error: """ % (app_name, app_name)) + style.ERROR_OUTPUT(str(e)) + '\n')
+ transaction.rollback_unless_managed()
sys.exit(1)
- db.db.commit()
-install.help_doc = "Executes ``sqlall`` for the given model module name(s) in the current database."
+ transaction.commit_unless_managed()
+install.help_doc = "Executes ``sqlall`` for the given app(s) in the current database."
install.args = APP_ARGS
-def installperms(mod):
- "Installs any permissions for the given model, if needed."
- from django.models.auth import permissions
- from django.models.core import packages
- num_added = 0
- package = packages.get_object(pk=mod._MODELS[0]._meta.app_label)
- for klass in mod._MODELS:
- opts = klass._meta
- for codename, name in _get_all_permissions(opts):
- try:
- permissions.get_object(name__exact=name, codename__exact=codename, package__label__exact=package.label)
- except permissions.PermissionDoesNotExist:
- p = permissions.Permission(name=name, package=package, codename=codename)
- p.save()
- print "Added permission '%r'." % p
- num_added += 1
- if not num_added:
- print "No permissions were added, because all necessary permissions were already installed."
-installperms.help_doc = "Installs any permissions for the given model module name(s), if needed."
-installperms.args = APP_ARGS
+def reset(app):
+ "Executes the equivalent of 'get_sql_reset' in the current database."
+ from django.db import connection, transaction
+ from cStringIO import StringIO
+ app_name = app.__name__.split('.')[-2]
+
+ disable_termcolors()
+
+ # First, try validating the models.
+ _check_for_validation_errors(app)
+ sql_list = get_sql_reset(app)
+
+ confirm = raw_input("""
+You have requested a database reset.
+This will IRREVERSIBLY DESTROY any data in your database.
+Are you sure you want to do this?
+
+Type 'yes' to continue, or 'no' to cancel: """)
+ 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 installed. Possible reasons:
+ * The database isn't running or isn't configured correctly.
+ * At least one of the database tables already exists.
+ * 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 = APP_ARGS
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("Error: %r is not a valid %s name. Please use only numbers, letters and underscores.\n" % (name, app_or_project))
+ 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("Error: %s\n" % 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):
@@ -479,17 +631,20 @@ def _start_helper(app_or_project, name, directory, other_name=''):
for f in files:
if f.endswith('.pyc'):
continue
- fp_old = open(os.path.join(d, f), 'r')
- fp_new = open(os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)), 'w')
+ 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()
+ shutil.copymode(path_old, 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("Error: %r isn't a valid project name. Please try another.\n" % project_name)
+ sys.stderr.write(style.ERROR("Error: %r isn't a valid project name. Please try another.\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.
@@ -513,71 +668,19 @@ def startapp(app_name, directory):
startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."
startapp.args = "[appname]"
-def createsuperuser(username=None, email=None, password=None):
- "Creates a superuser account."
- from django.core import validators
- from django.models.auth import users
- import getpass
- try:
- while 1:
- if not username:
- username = raw_input('Username (only letters, digits and underscores): ')
- if not username.isalnum():
- sys.stderr.write("Error: That username is invalid.\n")
- username = None
- try:
- users.get_object(username__exact=username)
- except users.UserDoesNotExist:
- 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 = users.create_user(username, email, password)
- u.is_staff = True
- u.is_active = True
- u.is_superuser = True
- u.save()
- print "User created successfully."
-createsuperuser.args = '[username] [email] [password] (Either all or none)'
-
-def inspectdb(db_name):
+def inspectdb():
"Generator that introspects the tables in the given database name and returns a Django model, one line at a time."
- from django.core import db
+ from django.db import connection, get_introspection_module
from django.conf import settings
import keyword
+ introspection_module = get_introspection_module()
+
def table2model(table_name):
object_name = table_name.title().replace('_', '')
return object_name.endswith('s') and object_name[:-1] or object_name
- settings.DATABASE_NAME = db_name
- cursor = db.db.cursor()
+ 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"
@@ -587,19 +690,19 @@ def inspectdb(db_name):
yield "# Also note: You'll have to insert the output of 'django-admin.py sqlinitialdata [appname]'"
yield "# into your database."
yield ''
- yield 'from django.core import meta'
+ yield 'from django.db import models'
yield ''
- for table_name in db.get_table_list(cursor):
- yield 'class %s(meta.Model):' % table2model(table_name)
+ for table_name in introspection_module.get_table_list(cursor):
+ yield 'class %s(models.Model):' % table2model(table_name)
try:
- relations = db.get_relations(cursor, table_name)
+ relations = introspection_module.get_relations(cursor, table_name)
except NotImplementedError:
relations = {}
try:
- indexes = db.get_indexes(cursor, table_name)
+ indexes = introspection_module.get_indexes(cursor, table_name)
except NotImplementedError:
indexes = {}
- for i, row in enumerate(db.get_table_description(cursor, table_name)):
+ 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'.
@@ -618,7 +721,7 @@ def inspectdb(db_name):
extra_params['db_column'] = att_name
else:
try:
- field_type = db.DATA_TYPES_REVERSE[row[1]]
+ field_type = introspection_module.DATA_TYPES_REVERSE[row[1]]
except KeyError:
field_type = 'TextField'
comment_notes.append('This field type is a guess.')
@@ -652,7 +755,14 @@ def inspectdb(db_name):
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
continue
- field_desc = '%s = meta.%s' % (att_name, field_type)
+ # 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 += ', '
@@ -661,11 +771,11 @@ def inspectdb(db_name):
if comment_notes:
field_desc += ' # ' + ' '.join(comment_notes)
yield ' %s' % field_desc
- yield ' class META:'
+ 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 = "[dbname]"
+inspectdb.args = ""
class ModelErrorCollection:
def __init__(self, outfile=sys.stdout):
@@ -674,123 +784,182 @@ class ModelErrorCollection:
def add(self, opts, error):
self.errors.append((opts, error))
- self.outfile.write("%s.%s: %s\n" % (opts.app_label, opts.module_name, error))
+ self.outfile.write(style.ERROR("%s.%s: %s\n" % (opts.app_label, opts.module_name, 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.db import models
+ from django.db.models.fields.related import RelatedObject
-def get_validation_errors(outfile):
- "Validates all installed models. Writes errors, if any, to outfile. Returns number of errors."
- import django.models
- from django.core import meta
e = ModelErrorCollection(outfile)
- module_list = meta.get_installed_model_modules()
- for module in module_list:
- for mod in module._MODELS:
- opts = mod._meta
+ for cls in models.get_models(app):
+ opts = cls._meta
- # Do field-specific validation.
- for f in opts.fields:
- if isinstance(f, meta.CharField) and f.maxlength in (None, 0):
- e.add(opts, '"%s" field: CharFields require a "maxlength" attribute.' % f.name)
- if isinstance(f, meta.FloatField):
- if f.decimal_places is None:
- e.add(opts, '"%s" field: FloatFields require a "decimal_places" attribute.' % f.name)
- if f.max_digits is None:
- e.add(opts, '"%s" field: FloatFields require a "max_digits" attribute.' % f.name)
- if isinstance(f, meta.FileField) and not f.upload_to:
- e.add(opts, '"%s" field: FileFields require an "upload_to" attribute.' % f.name)
- if isinstance(f, meta.ImageField):
- try:
- from PIL import Image
- except ImportError:
- e.add(opts, '"%s" field: 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" field: prepopulate_from should be a list or tuple.' % f.name)
- if f.choices:
- if not type(f.choices) in (tuple, list):
- e.add(opts, '"%s" field: "choices" should be either 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" field: "choices" should be a sequence of two-tuples.' % f.name)
- if f.db_index not in (None, True, False):
- e.add(opts, '"%s" field: "db_index" should be either None, True or False.' % f.name)
+ # Do field-specific validation.
+ for f in opts.fields:
+ # Check for deprecated args
+ dep_args = getattr(f, 'deprecated_args', None)
+ if dep_args:
+ e.add(opts, "'%s' Initialized with deprecated args:%s" % (f.name, ",".join(dep_args)))
+ 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 type(f.choices) in (tuple, list):
+ e.add(opts, '"%s": "choices" should be either 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 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 uninstalled model %s" % (f.name, rel_opts.object_name))
+
+ rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
+ for r in rel_opts.fields:
+ if r.name == rel_name:
+ e.add(opts, "'%s' accessor name '%s.%s' clashes with another field. 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, "'%s' accessor name '%s.%s' clashes with a m2m field. 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, "'%s' accessor name '%s.%s' clashes with a related m2m field. 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 and r.field is not f:
+ e.add(opts, "'%s' accessor name '%s.%s' clashes with another related field. 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 uninstalled model %s" % (f.name, rel_opts.object_name))
- # Check for multiple ManyToManyFields to the same object, and
- # verify "singular" is set in that case.
- for i, f in enumerate(opts.many_to_many):
- for previous_f in opts.many_to_many[:i]:
- if f.rel.to == previous_f.rel.to and f.rel.singular == previous_f.rel.singular:
- e.add(opts, 'The "%s" field requires a "singular" parameter, because the %s model has more than one ManyToManyField to the same model (%s).' % (f.name, opts.object_name, previous_f.rel.to.object_name))
+ rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
+ for r in rel_opts.fields:
+ if r.name == rel_name:
+ e.add(opts, "'%s' m2m accessor name '%s.%s' clashes with another field. 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, "'%s' m2m accessor name '%s.%s' clashes with a m2m field. 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 and r.field is not f:
+ e.add(opts, "'%s' m2m accessor name '%s.%s' clashes with a related m2m field. 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, "'%s' m2m accessor name '%s.%s' clashes with another related field. 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, meta.Admin):
- e.add(opts, '"admin" attribute, if given, must be set to a meta.Admin() instance.')
+ # 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:
- # 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 meta.FieldDoesNotExist:
- klass = opts.get_model_module().Klass
- if not hasattr(klass, fn) or not callable(getattr(klass, fn)):
- e.add(opts, '"admin.list_display" refers to %r, which isn\'t a field or method.' % fn)
- else:
- if isinstance(f, meta.ManyToManyField):
- e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % 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 meta.FieldDoesNotExist:
- e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn)
+ 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_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)
- # 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
- try:
- opts.get_field(field_name, many_to_many=False)
- except meta.FieldDoesNotExist:
- e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
+ # 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
+ 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():
+ 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 core=True, if needed.
- for related in opts.get_followed_related_objects():
+ # Check unique_together.
+ for ut in opts.unique_together:
+ for field_name in ut:
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
+ 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)
- # 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 meta.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, meta.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):
"Validates all installed models."
- num_errors = get_validation_errors(outfile)
- outfile.write('%s error%s found.\n' % (num_errors, num_errors != 1 and 's' or ''))
+ try:
+ num_errors = get_validation_errors(outfile)
+ 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:
+ sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app))
+ s.seek(0)
+ sys.stderr.write(s.read())
+ sys.exit(1)
+
def runserver(addr, port):
"Starts a lightweight Web server for development."
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
@@ -798,13 +967,13 @@ def runserver(addr, port):
if not addr:
addr = '127.0.0.1'
if not port.isdigit():
- sys.stderr.write("Error: %r is not a valid port number.\n" % port)
+ sys.stderr.write(style.ERROR("Error: %r is not a valid port number.\n" % port))
sys.exit(1)
def inner_run():
- from django.conf.settings import SETTINGS_MODULE
+ from django.conf import settings
print "Validating models..."
validate()
- print "\nDjango version %s, using settings %r" % (get_version(), SETTINGS_MODULE)
+ 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 CONTROL-C (Unix) or CTRL-BREAK (Windows)."
try:
@@ -820,7 +989,7 @@ def runserver(addr, port):
error_text = ERRORS[e.args[0].args[0]]
except (AttributeError, KeyError):
error_text = str(e)
- sys.stderr.write("Error: %s\n" % error_text)
+ sys.stderr.write(style.ERROR("Error: %s" % error_text) + '\n')
sys.exit(1)
except KeyboardInterrupt:
sys.exit(0)
@@ -830,17 +999,18 @@ runserver.args = '[optional port number, or ipaddr:port]'
def createcachetable(tablename):
"Creates the table needed to use the SQL cache backend"
- from django.core import db, meta
+ 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.
- meta.CharField(name='cache_key', maxlength=255, unique=True, primary_key=True),
- meta.TextField(name='value'),
- meta.DateTimeField(name='expires', db_index=True),
+ 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 = [db.db.quote_name(f.column), db.DATA_TYPES[f.get_internal_type()] % f.__dict__]
+ 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")
@@ -849,18 +1019,18 @@ def createcachetable(tablename):
if f.db_index:
unique = f.unique and "UNIQUE " or ""
index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
- (unique, tablename, f.column, db.db.quote_name(tablename),
- db.db.quote_name(f.column)))
+ (unique, tablename, f.name, backend.quote_name(tablename),
+ backend.quote_name(f.name)))
table_output.append(" ".join(field_output))
- full_statement = ["CREATE TABLE %s (" % db.db.quote_name(tablename)]
+ 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 = db.db.cursor()
+ curs = connection.cursor()
curs.execute("\n".join(full_statement))
for statement in index_output:
curs.execute(statement)
- db.db.commit()
+ transaction.commit_unless_managed()
createcachetable.args = "[tablename]"
def run_shell(use_plain=False):
@@ -877,17 +1047,22 @@ def run_shell(use_plain=False):
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 = ""
+
# Utilities for command-line script
DEFAULT_ACTION_MAPPING = {
'adminindex': get_admin_index,
- 'createsuperuser': createsuperuser,
'createcachetable' : createcachetable,
-# 'dbcheck': database_check,
- 'init': init,
+ 'dbshell': dbshell,
+ 'diffsettings': diffsettings,
'inspectdb': inspectdb,
'install': install,
- 'installperms': installperms,
+ 'reset': reset,
'runserver': runserver,
'shell': run_shell,
'sql': get_sql_create,
@@ -899,10 +1074,20 @@ DEFAULT_ACTION_MAPPING = {
'sqlsequencereset': get_sql_sequence_reset,
'startapp': startapp,
'startproject': startproject,
+ 'syncdb': syncdb,
'validate': validate,
}
-NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'installperms', 'sqlindexes')
+NO_SQL_TRANSACTION = (
+ 'adminindex',
+ 'createcachetable',
+ 'dbshell',
+ 'diffsettings',
+ 'install',
+ 'reset',
+ 'sqlindexes',
+ 'syncdb',
+)
class DjangoOptionParser(OptionParser):
def print_usage_and_exit(self):
@@ -914,18 +1099,18 @@ def get_usage(action_mapping):
Returns a usage string. Doesn't do the options stuff, because optparse
takes care of that.
"""
- usage = ["usage: %prog action [options]\nactions:"]
+ 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', func.__doc__), initial_indent=' ', subsequent_indent=' '))
+ 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('Error: %s\nRun "%s --help" for help.\n' % (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):
@@ -961,31 +1146,16 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING):
from django.utils import translation
translation.activate('en-us')
- if action == 'createsuperuser':
- try:
- username, email, password = args[1], args[2], args[3]
- except IndexError:
- if len(args) == 1: # We got no arguments, just the action.
- action_mapping[action]()
- else:
- sys.stderr.write("Error: %r requires arguments of 'username email password' or no argument at all.\n")
- sys.exit(1)
- else:
- action_mapping[action](username, email, password)
- elif action == 'shell':
+ if action == 'shell':
action_mapping[action](options.plain is True)
- elif action in ('init', 'validate'):
+ elif action in ('syncdb', 'validate', 'diffsettings', 'dbshell'):
action_mapping[action]()
elif action == 'inspectdb':
try:
- param = args[1]
- except IndexError:
- parser.print_usage_and_exit()
- try:
- for line in action_mapping[action](param):
+ for line in action_mapping[action]():
print line
except NotImplementedError:
- sys.stderr.write("Error: %r isn't supported for the currently selected database backend.\n" % action)
+ 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:
@@ -1009,25 +1179,22 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING):
addr, port = '', args[1]
action_mapping[action](addr, port)
else:
- from django.core import meta
- if action == 'dbcheck':
- mod_list = meta.get_all_installed_modules()
- else:
- try:
- mod_list = [meta.get_app(app_label) for app_label in args[1:]]
- except ImportError, e:
- sys.stderr.write("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()
+ from django.db import models
+ 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 "BEGIN;"
+ print style.SQL_KEYWORD("BEGIN;")
for mod in mod_list:
output = action_mapping[action](mod)
if output:
print '\n'.join(output)
if action not in NO_SQL_TRANSACTION:
- print "COMMIT;"
+ print style.SQL_KEYWORD("COMMIT;")
def execute_manager(settings_mod):
# Add this project to sys.path so that it's importable in the conventional
@@ -1042,10 +1209,18 @@ def execute_manager(settings_mod):
# Set DJANGO_SETTINGS_MODULE appropriately.
os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name
+ 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.
- action_mapping = DEFAULT_ACTION_MAPPING.copy()
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)
diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py
deleted file mode 100644
index 8121736096..0000000000
--- a/django/core/meta/__init__.py
+++ /dev/null
@@ -1,1983 +0,0 @@
-from django.conf import settings
-from django.core import formfields, validators
-from django.core import db
-from django.core.exceptions import ObjectDoesNotExist
-from django.core.meta.fields import *
-from django.utils.functional import curry
-from django.utils.text import capfirst
-import copy, datetime, os, re, sys, types
-
-# Admin stages.
-ADD, CHANGE, BOTH = 1, 2, 3
-
-# 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
-
-# Prefix (in Python path style) to location of models.
-MODEL_PREFIX = 'django.models'
-
-# Methods on models with the following prefix will be removed and
-# converted to module-level functions.
-MODEL_FUNCTIONS_PREFIX = '_module_'
-
-# Methods on models with the following prefix will be removed and
-# converted to manipulator methods.
-MANIPULATOR_FUNCTIONS_PREFIX = '_manipulator_'
-
-LOOKUP_SEPARATOR = '__'
-
-####################
-# 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 = db.db.quote_name(prefix[:-1]) + '.'
- output = []
- for f in handle_legacy_orderlist(order_list):
- if f.startswith('-'):
- output.append('%s%s DESC' % (prefix, db.db.quote_name(orderfield2column(f[1:], opts))))
- elif f == '?':
- output.append(db.get_random_function_sql())
- else:
- output.append('%s%s ASC' % (prefix, db.db.quote_name(orderfield2column(f, opts))))
- return ', '.join(output)
-
-def get_module(app_label, module_name):
- return __import__('%s.%s.%s' % (MODEL_PREFIX, app_label, module_name), '', '', [''])
-
-def get_app(app_label):
- return __import__('%s.%s' % (MODEL_PREFIX, app_label), '', '', [''])
-
-_installed_models_cache = None
-def get_installed_models():
- """
- Returns a list of installed "models" packages, such as foo.models,
- ellington.news.models, etc. This does NOT include django.models.
- """
- global _installed_models_cache
- if _installed_models_cache is not None:
- return _installed_models_cache
- _installed_models_cache = []
- for a in settings.INSTALLED_APPS:
- try:
- _installed_models_cache.append(__import__(a + '.models', '', '', ['']))
- except ImportError:
- pass
- return _installed_models_cache
-
-_installed_modules_cache = None
-def get_installed_model_modules(core_models=None):
- """
- Returns a list of installed models, such as django.models.core,
- ellington.news.models.news, foo.models.bar, etc.
- """
- global _installed_modules_cache
- if _installed_modules_cache is not None:
- return _installed_modules_cache
- _installed_modules_cache = []
-
- # django.models is a special case.
- for submodule in (core_models or []):
- _installed_modules_cache.append(__import__('django.models.%s' % submodule, '', '', ['']))
- for m in get_installed_models():
- for submodule in getattr(m, '__all__', []):
- mod = __import__('django.models.%s' % submodule, '', '', [''])
- try:
- mod._MODELS
- except AttributeError:
- pass # Skip model modules that don't actually have models in them.
- else:
- _installed_modules_cache.append(mod)
- return _installed_modules_cache
-
-class LazyDate:
- """
- 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' : meta.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
-
-################
-# MAIN CLASSES #
-################
-
-class FieldDoesNotExist(Exception):
- pass
-
-class BadKeywordArguments(Exception):
- pass
-
-class BoundRelatedObject(object):
- def __init__(self, related_object, field_mapping, original):
- self.relation = related_object
- self.field_mappings = field_mapping[related_object.opts.module_name]
-
- def template_name(self):
- raise NotImplementedError
-
- def __repr__(self):
- return repr(self.__dict__)
-
-class RelatedObject(object):
- def __init__(self, parent_opts, opts, field):
- self.parent_opts = parent_opts
- self.opts = opts
- self.field = field
- self.edit_inline = field.rel.edit_inline
- self.name = opts.module_name
- self.var_name = 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 != None:
- func_name = 'get_%s_list' % self.get_method_name_part()
- func = getattr(parent_instance, func_name)
- list = func()
-
- count = len(list) + 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(list)
- if change > 0:
- return list + [None] * change
- if change < 0:
- return list[:change]
- else: # Just right
- return list
- else:
- return [None] * self.field.rel.num_in_admin
-
- 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 __repr__(self):
- return "<RelatedObject: %s related to %s>" % ( self.name, self.field.name)
-
- def get_manipulator_fields(self, opts, manipulator, change, follow):
- # TODO: Remove core fields stuff.
- if change:
- meth_name = 'get_%s_count' % self.get_method_name_part()
- count = getattr(manipulator.original_object, meth_name)()
- count += 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)
- else:
- count = self.field.rel.num_in_admin
-
- 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 bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
- return bound_related_object_class(self, field_mapping, original)
-
- def get_method_name_part(self):
- # This method encapsulates the logic that decides what name to give a
- # method that retrieves related many-to-one or many-to-many objects.
- # Usually it just uses the lower-cased object_name, but if the related
- # object is in another app, the related object's app_label is appended.
- #
- # Examples:
- #
- # # Normal case -- a related object in the same app.
- # # This method returns "choice".
- # Poll.get_choice_list()
- #
- # # A related object in a different app.
- # # This method returns "lcom_bestofaward".
- # Place.get_lcom_bestofaward_list() # "lcom_bestofaward"
- rel_obj_name = self.field.rel.related_name or self.opts.object_name.lower()
- if self.parent_opts.app_label != self.opts.app_label:
- rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name)
- return rel_obj_name
-
-class QBase:
- "Base class for QAnd and QOr"
- def __init__(self, *args):
- self.args = args
-
- def __repr__(self):
- return '(%s)' % self.operator.join([repr(el) for el in self.args])
-
- def get_sql(self, opts, table_count):
- tables, join_where, where, params = [], [], [], []
- for val in self.args:
- tables2, join_where2, where2, params2, table_count = val.get_sql(opts, table_count)
- tables.extend(tables2)
- join_where.extend(join_where2)
- where.extend(where2)
- params.extend(params2)
- return tables, join_where, ['(%s)' % self.operator.join(where)], params, table_count
-
-class QAnd(QBase):
- "Encapsulates a combined query that uses 'AND'."
- operator = ' AND '
- def __or__(self, other):
- if isinstance(other, (QAnd, QOr, Q)):
- return QOr(self, other)
- else:
- raise TypeError, 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(QBase):
- "Encapsulates a combined query that uses 'OR'."
- operator = ' OR '
- def __and__(self, other):
- if isinstance(other, (QAnd, QOr, Q)):
- return QAnd(self, other)
- else:
- raise TypeError, 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:
- "Encapsulates queries for the 'complex' parameter to Django API functions."
- def __init__(self, **kwargs):
- self.kwargs = kwargs
-
- def __repr__(self):
- return 'Q%r' % self.kwargs
-
- def __and__(self, other):
- if isinstance(other, (Q, QAnd, QOr)):
- return QAnd(self, other)
- else:
- raise TypeError, other
-
- def __or__(self, other):
- if isinstance(other, (Q, QAnd, QOr)):
- return QOr(self, other)
- else:
- raise TypeError, other
-
- def get_sql(self, opts, table_count):
- return _parse_lookup(self.kwargs.items(), opts, table_count)
-
-class Options:
- def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
- fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
- where_constraints=None, object_name=None, app_label=None,
- exceptions=None, permissions=None, get_latest_by=None,
- order_with_respect_to=None, module_constants=None):
-
- # Save the original function args, for use by copy(). Note that we're
- # NOT using copy.deepcopy(), because that would create a new copy of
- # everything in memory, and it's better to conserve memory. Of course,
- # this comes with the important gotcha that changing any attribute of
- # this object will change its value in self._orig_init_args, so we
- # need to be careful not to do that. In practice, we can pull this off
- # because Options are generally read-only objects, and __init__() is
- # the only place where its attributes are manipulated.
-
- # locals() is used purely for convenience, so we don't have to do
- # something verbose like this:
- # self._orig_init_args = {
- # 'module_name': module_name,
- # 'verbose_name': verbose_name,
- # ...
- # }
- self._orig_init_args = locals()
- del self._orig_init_args['self'] # because we don't care about it.
-
- # Move many-to-many related fields from self.fields into self.many_to_many.
- self.fields, self.many_to_many = [], []
- for field in (fields or []):
- if field.rel and isinstance(field.rel, ManyToManyRel):
- self.many_to_many.append(field)
- else:
- self.fields.append(field)
- self.module_name, self.verbose_name = module_name, verbose_name
- self.verbose_name_plural = verbose_name_plural or verbose_name + 's'
- self.db_table, self.has_related_links = db_table, has_related_links
- self.ordering = ordering or []
- self.unique_together = unique_together or []
- self.where_constraints = where_constraints or []
- self.exceptions = exceptions or []
- self.permissions = permissions or []
- self.object_name, self.app_label = object_name, app_label
- self.get_latest_by = get_latest_by
- if order_with_respect_to:
- self.order_with_respect_to = self.get_field(order_with_respect_to)
- self.ordering = ('_order',)
- else:
- self.order_with_respect_to = None
- self.module_constants = module_constants or {}
- self.admin = admin
-
- # Calculate one_to_one_field.
- self.one_to_one_field = None
- for f in self.fields:
- if isinstance(f.rel, OneToOneRel):
- self.one_to_one_field = f
- break
- # Cache the primary-key field.
- self.pk = None
- for f in self.fields:
- if f.primary_key:
- self.pk = f
- break
- # If a primary_key field hasn't been specified, add an
- # auto-incrementing primary-key ID field automatically.
- if self.pk is None:
- self.fields.insert(0, AutoField(name='id', verbose_name='ID', primary_key=True))
- self.pk = self.fields[0]
- # Cache whether this has an AutoField.
- self.has_auto_field = False
- for f in self.fields:
- is_auto = isinstance(f, AutoField)
- if is_auto and self.has_auto_field:
- raise AssertionError, "A model can't have more than one AutoField."
- elif is_auto:
- self.has_auto_field = True
-
- def __repr__(self):
- return '<Options for %s>' % self.module_name
-
- def copy(self, **kwargs):
- args = self._orig_init_args.copy()
- args.update(kwargs)
- return self.__class__(**args)
-
- def get_model_module(self):
- return get_module(self.app_label, self.module_name)
-
- def get_content_type_id(self):
- "Returns the content-type ID for this object type."
- if not hasattr(self, '_content_type_id'):
- mod = get_module('core', 'contenttypes')
- self._content_type_id = mod.get_object(python_module_name__exact=self.module_name, package__label__exact=self.app_label).id
- return self._content_type_id
-
- 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, "name=%s" % 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:
- module_list = get_installed_model_modules()
- rel_objs = []
- for mod in module_list:
- for klass in mod._MODELS:
- for f in klass._meta.fields:
- if f.rel and self == f.rel.to:
- rel_objs.append(RelatedObject(self, klass._meta, f))
- if self.has_related_links:
- # Manually add RelatedLink objects, which are a special case.
- relatedlinks = get_module('relatedlinks', 'relatedlinks')
- # Note that the copy() is very important -- otherwise any
- # subsequently loaded object with related links will override this
- # relationship we're adding.
- link_field = copy.copy(relatedlinks.RelatedLink._meta.get_field('object_id'))
- link_field.rel = ManyToOneRel(self.get_model_module().Klass, 'id',
- num_in_admin=3, min_num_in_admin=3, edit_inline=TABULAR,
- lookup_overrides={
- 'content_type__package__label__exact': self.app_label,
- 'content_type__python_module_name__exact': self.module_name,
- })
- rel_objs.append(RelatedObject(self, relatedlinks.RelatedLink._meta, link_field))
- 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:
- follow[f.name] = fol
- return follow
-
- def get_all_related_many_to_many_objects(self):
- module_list = get_installed_model_modules()
- rel_objs = []
- for mod in module_list:
- for klass in mod._MODELS:
- for f in klass._meta.many_to_many:
- if f.rel and self == f.rel.to:
- rel_objs.append(RelatedObject(self, klass._meta, f))
- 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 = []
- for klass in get_app(self.app_label)._MODELS:
- 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:
- 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]
-
-def _reassign_globals(function_dict, extra_globals, namespace):
- new_functions = {}
- for k, v in function_dict.items():
- # Get the code object.
- code = v.func_code
- # Recreate the function, but give it access to extra_globals and the
- # given namespace's globals, too.
- new_globals = {'__builtins__': __builtins__, 'db': db.db, 'datetime': datetime}
- new_globals.update(extra_globals.__dict__)
- func = types.FunctionType(code, globals=new_globals, name=k, argdefs=v.func_defaults)
- func.__dict__.update(v.__dict__)
- setattr(namespace, k, func)
- # For all of the custom functions that have been added so far, give
- # them access to the new function we've just created.
- for new_k, new_v in new_functions.items():
- new_v.func_globals[k] = func
- new_functions[k] = func
-
-# Calculate the module_name using a poor-man's pluralization.
-get_module_name = lambda class_name: class_name.lower() + 's'
-
-# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
-get_verbose_name = lambda class_name: re.sub('([A-Z])', ' \\1', class_name).lower().strip()
-
-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 not bases:
- return type.__new__(cls, name, bases, attrs)
-
- try:
- meta_attrs = attrs.pop('META').__dict__
- del meta_attrs['__module__']
- del meta_attrs['__doc__']
- except KeyError:
- meta_attrs = {}
-
- # Gather all attributes that are Field instances.
- fields = []
- for obj_name, obj in attrs.items():
- if isinstance(obj, Field):
- obj.set_name(obj_name)
- fields.append(obj)
- del attrs[obj_name]
-
- # Sort the fields in the order that they were created. The
- # "creation_counter" is needed because metaclasses don't preserve the
- # attribute order.
- fields.sort(lambda x, y: x.creation_counter - y.creation_counter)
-
- # If this model is a subclass of another model, create an Options
- # object by first copying the base class's _meta and then updating it
- # with the overrides from this class.
- replaces_module = None
- if bases[0] != Model:
- field_names = [f.name for f in fields]
- remove_fields = meta_attrs.pop('remove_fields', [])
- for f in bases[0]._meta._orig_init_args['fields']:
- if f.name not in field_names and f.name not in remove_fields:
- fields.insert(0, f)
- if meta_attrs.has_key('replaces_module'):
- # Set the replaces_module variable for now. We can't actually
- # do anything with it yet, because the module hasn't yet been
- # created.
- replaces_module = meta_attrs.pop('replaces_module').split('.')
- # Pass any Options overrides to the base's Options instance, and
- # simultaneously remove them from attrs. When this is done, attrs
- # will be a dictionary of custom methods, plus __module__.
- meta_overrides = {'fields': fields, 'module_name': get_module_name(name), 'verbose_name': get_verbose_name(name)}
- for k, v in meta_attrs.items():
- if not callable(v) and k != '__module__':
- meta_overrides[k] = meta_attrs.pop(k)
- opts = bases[0]._meta.copy(**meta_overrides)
- opts.object_name = name
- del meta_overrides
- else:
- opts = Options(
- module_name = meta_attrs.pop('module_name', get_module_name(name)),
- # If the verbose_name wasn't given, use the class name,
- # converted from InitialCaps to "lowercase with spaces".
- verbose_name = meta_attrs.pop('verbose_name', get_verbose_name(name)),
- verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''),
- db_table = meta_attrs.pop('db_table', ''),
- fields = fields,
- ordering = meta_attrs.pop('ordering', None),
- unique_together = meta_attrs.pop('unique_together', None),
- admin = meta_attrs.pop('admin', None),
- has_related_links = meta_attrs.pop('has_related_links', False),
- where_constraints = meta_attrs.pop('where_constraints', None),
- object_name = name,
- app_label = meta_attrs.pop('app_label', None),
- exceptions = meta_attrs.pop('exceptions', None),
- permissions = meta_attrs.pop('permissions', None),
- get_latest_by = meta_attrs.pop('get_latest_by', None),
- order_with_respect_to = meta_attrs.pop('order_with_respect_to', None),
- module_constants = meta_attrs.pop('module_constants', None),
- )
-
- if meta_attrs != {}:
- raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
-
- # Dynamically create the module that will contain this class and its
- # associated helper functions.
- if replaces_module is not None:
- new_mod = get_module(*replaces_module)
- else:
- new_mod = types.ModuleType(opts.module_name)
-
- # Collect any/all custom class methods and module functions, and move
- # them to a temporary holding variable. We'll deal with them later.
- if replaces_module is not None:
- # Initialize these values to the base class' custom_methods and
- # custom_functions.
- custom_methods = dict([(k, v) for k, v in new_mod.Klass.__dict__.items() if hasattr(v, 'custom')])
- custom_functions = dict([(k, v) for k, v in new_mod.__dict__.items() if hasattr(v, 'custom')])
- else:
- custom_methods, custom_functions = {}, {}
- manipulator_methods = {}
- for k, v in attrs.items():
- if k in ('__module__', '__init__', '_overrides', '__doc__'):
- continue # Skip the important stuff.
- assert callable(v), "%r is an invalid model parameter." % k
- # Give the function a function attribute "custom" to designate that
- # it's a custom function/method.
- v.custom = True
- if k.startswith(MODEL_FUNCTIONS_PREFIX):
- custom_functions[k[len(MODEL_FUNCTIONS_PREFIX):]] = v
- elif k.startswith(MANIPULATOR_FUNCTIONS_PREFIX):
- manipulator_methods[k[len(MANIPULATOR_FUNCTIONS_PREFIX):]] = v
- else:
- custom_methods[k] = v
- del attrs[k]
-
- # Create the module-level ObjectDoesNotExist exception.
- dne_exc_name = '%sDoesNotExist' % name
- does_not_exist_exception = types.ClassType(dne_exc_name, (ObjectDoesNotExist,), {})
- # Explicitly set its __module__ because it will initially (incorrectly)
- # be set to the module the code is being executed in.
- does_not_exist_exception.__module__ = MODEL_PREFIX + '.' + opts.module_name
- setattr(new_mod, dne_exc_name, does_not_exist_exception)
-
- # Create other exceptions.
- for exception_name in opts.exceptions:
- exc = types.ClassType(exception_name, (Exception,), {})
- exc.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
- setattr(new_mod, exception_name, exc)
-
- # Create any module-level constants, if applicable.
- for k, v in opts.module_constants.items():
- setattr(new_mod, k, v)
-
- # Create the default class methods.
- attrs['__init__'] = curry(method_init, opts)
- attrs['__eq__'] = curry(method_eq, opts)
- attrs['__ne__'] = curry(method_ne, opts)
- attrs['save'] = curry(method_save, opts)
- attrs['save'].alters_data = True
- attrs['delete'] = curry(method_delete, opts)
- attrs['delete'].alters_data = True
-
- if opts.order_with_respect_to:
- attrs['get_next_in_order'] = curry(method_get_next_in_order, opts, opts.order_with_respect_to)
- attrs['get_previous_in_order'] = curry(method_get_previous_in_order, opts, opts.order_with_respect_to)
-
- for f in opts.fields:
- # If the object has a relationship to itself, as designated by
- # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
- if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
- f.rel.to = opts
- f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name)
- f.verbose_name = f.verbose_name or f.rel.to.verbose_name
- f.rel.field_name = f.rel.field_name or f.rel.to.pk.name
- # Add "get_thingie" methods for many-to-one related objects.
- # EXAMPLES: Choice.get_poll(), Story.get_dateline()
- if isinstance(f.rel, ManyToOneRel):
- func = curry(method_get_many_to_one, f)
- func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name)
- attrs['get_%s' % f.name] = func
-
- for f in opts.many_to_many:
- # Add "get_thingie" methods for many-to-many related objects.
- # EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
- func = curry(method_get_many_to_many, f)
- func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name)
- attrs['get_%s_list' % f.rel.singular] = func
- # Add "set_thingie" methods for many-to-many related objects.
- # EXAMPLES: Poll.set_sites(), Story.set_bylines()
- func = curry(method_set_many_to_many, f)
- func.__doc__ = "Resets this object's `%s.%s` list to the given list of IDs. Note that it doesn't check whether the given IDs are valid." % (f.rel.to.app_label, f.rel.to.module_name)
- func.alters_data = True
- attrs['set_%s' % f.name] = func
-
- # Create the class, because we need it to use in currying.
- new_class = type.__new__(cls, name, bases, attrs)
-
- # Give the class a docstring -- its definition.
- if new_class.__doc__ is None:
- new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields]))
-
- # Create the standard, module-level API helper functions such
- # as get_object() and get_list().
- new_mod.get_object = curry(function_get_object, opts, new_class, does_not_exist_exception)
- new_mod.get_object.__doc__ = "Returns the %s object matching the given parameters." % name
-
- new_mod.get_list = curry(function_get_list, opts, new_class)
- new_mod.get_list.__doc__ = "Returns a list of %s objects matching the given parameters." % name
-
- new_mod.get_iterator = curry(function_get_iterator, opts, new_class)
- new_mod.get_iterator.__doc__ = "Returns an iterator of %s objects matching the given parameters." % name
-
- new_mod.get_values = curry(function_get_values, opts, new_class)
- new_mod.get_values.__doc__ = "Returns a list of dictionaries matching the given parameters."
-
- new_mod.get_values_iterator = curry(function_get_values_iterator, opts, new_class)
- new_mod.get_values_iterator.__doc__ = "Returns an iterator of dictionaries matching the given parameters."
-
- new_mod.get_count = curry(function_get_count, opts)
- new_mod.get_count.__doc__ = "Returns the number of %s objects matching the given parameters." % name
-
- new_mod._get_sql_clause = curry(function_get_sql_clause, opts)
-
- new_mod.get_in_bulk = curry(function_get_in_bulk, opts, new_class)
- new_mod.get_in_bulk.__doc__ = "Returns a dictionary of ID -> %s for the %s objects with IDs in the given id_list." % (name, name)
-
- if opts.get_latest_by:
- new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
-
- for f in opts.fields:
- #TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class.
- if f.choices:
- # Add "get_thingie_display" method to get human-readable value.
- func = curry(method_get_display_value, f)
- setattr(new_class, 'get_%s_display' % f.name, func)
- if isinstance(f, DateField) or isinstance(f, DateTimeField):
- # Add "get_next_by_thingie" and "get_previous_by_thingie" methods
- # for all DateFields and DateTimeFields that cannot be null.
- # EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date()
- if not f.null:
- setattr(new_class, 'get_next_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, True))
- setattr(new_class, 'get_previous_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, False))
- # Add "get_thingie_list" for all DateFields and DateTimeFields.
- # EXAMPLE: polls.get_pub_date_list()
- func = curry(function_get_date_list, opts, f)
- func.__doc__ = "Returns a list of days, months or years (as datetime.datetime objects) in which %s objects are available. The first parameter ('kind') must be one of 'year', 'month' or 'day'." % name
- setattr(new_mod, 'get_%s_list' % f.name, func)
-
- elif isinstance(f, FileField):
- setattr(new_class, 'get_%s_filename' % f.name, curry(method_get_file_filename, f))
- setattr(new_class, 'get_%s_url' % f.name, curry(method_get_file_url, f))
- setattr(new_class, 'get_%s_size' % f.name, curry(method_get_file_size, f))
- func = curry(method_save_file, f)
- func.alters_data = True
- setattr(new_class, 'save_%s_file' % f.name, func)
- if isinstance(f, ImageField):
- # 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 f.width_field:
- setattr(new_class, 'get_%s_width' % f.name, curry(method_get_image_width, f))
- if not f.height_field:
- setattr(new_class, 'get_%s_height' % f.name, curry(method_get_image_height, f))
-
- # Add the class itself to the new module we've created.
- new_mod.__dict__[name] = new_class
-
- # Add "Klass" -- a shortcut reference to the class.
- new_mod.__dict__['Klass'] = new_class
-
- # Add the Manipulators.
- new_mod.__dict__['AddManipulator'] = get_manipulator(opts, new_class, manipulator_methods, add=True)
- new_mod.__dict__['ChangeManipulator'] = get_manipulator(opts, new_class, manipulator_methods, change=True)
-
- # Now that we have references to new_mod and new_class, we can add
- # any/all extra class methods to the new class. Note that we could
- # have just left the extra methods in attrs (above), but that would
- # have meant that any code within the extra methods would *not* have
- # access to module-level globals, such as get_list(), db, etc.
- # In order to give these methods access to those globals, we have to
- # deconstruct the method getting its raw "code" object, then recreating
- # the function with a new "globals" dictionary.
- #
- # To complicate matters more, because each method is manually assigned
- # a "globals" value, that "globals" value does NOT include the methods
- # that haven't been created yet. For instance, if there are two custom
- # methods, foo() and bar(), and foo() is created first, it won't have
- # bar() within its globals(). This is a problem because sometimes
- # custom methods/functions refer to other custom methods/functions. To
- # solve this problem, we keep track of the new functions created (in
- # the new_functions variable) and manually append each new function to
- # the func_globals() of all previously-created functions. So, by the
- # end of the loop, all functions will "know" about all the other
- # functions.
- _reassign_globals(custom_methods, new_mod, new_class)
- _reassign_globals(custom_functions, new_mod, new_mod)
- _reassign_globals(manipulator_methods, new_mod, new_mod.__dict__['AddManipulator'])
- _reassign_globals(manipulator_methods, new_mod, new_mod.__dict__['ChangeManipulator'])
-
- if hasattr(new_class, 'get_absolute_url'):
- new_class.get_absolute_url = curry(get_absolute_url, opts, new_class.get_absolute_url)
-
- # Get a reference to the module the class is in, and dynamically add
- # the new module to it.
- app_package = sys.modules.get(new_class.__module__)
- if replaces_module is not None:
- app_label = replaces_module[0]
- else:
- app_package.__dict__[opts.module_name] = new_mod
- app_label = app_package.__name__[app_package.__name__.rfind('.')+1:]
-
- # Populate the _MODELS member on the module the class is in.
- # Example: django.models.polls will have a _MODELS member that will
- # contain this list:
- # [<class 'django.models.polls.Poll'>, <class 'django.models.polls.Choice'>]
- # Don't do this if replaces_module is set.
- app_package.__dict__.setdefault('_MODELS', []).append(new_class)
-
- # Cache the app label.
- opts.app_label = app_label
-
- # If the db_table wasn't provided, use the app_label + module_name.
- if not opts.db_table:
- opts.db_table = "%s_%s" % (app_label, opts.module_name)
- new_class._meta = opts
-
- # Set the __file__ attribute to the __file__ attribute of its package,
- # because they're technically from the same file. Note: if we didn't
- # set this, sys.modules would think this module was built-in.
- try:
- new_mod.__file__ = app_package.__file__
- except AttributeError:
- # 'module' object has no attribute '__file__', which means the
- # class was probably being entered via the interactive interpreter.
- pass
-
- # Add the module's entry to sys.modules -- for instance,
- # "django.models.polls.polls". Note that "django.models.polls" has already
- # been added automatically.
- sys.modules.setdefault('%s.%s.%s' % (MODEL_PREFIX, app_label, opts.module_name), new_mod)
-
- # If this module replaces another one, get a reference to the other
- # module's parent, and replace the other module with the one we've just
- # created.
- if replaces_module is not None:
- old_app = get_app(replaces_module[0])
- setattr(old_app, replaces_module[1], new_mod)
- for i, model in enumerate(old_app._MODELS):
- if model._meta.module_name == replaces_module[1]:
- # Replace the appropriate member of the old app's _MODELS
- # data structure.
- old_app._MODELS[i] = new_class
- # Replace all relationships to the old class with
- # relationships to the new one.
- for related in model._meta.get_all_related_objects() + model._meta.get_all_related_many_to_many_objects():
- related.field.rel.to = opts
- break
- return new_class
-
-class Model:
- __metaclass__ = ModelBase
-
- def __repr__(self):
- return '<%s object>' % self.__class__.__name__
-
-############################################
-# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
-############################################
-
-# CORE METHODS #############################
-
-def method_init(opts, self, *args, **kwargs):
- if kwargs:
- for f in opts.fields:
- if isinstance(f.rel, ManyToOneRel):
- try:
- # Assume object instance was passed in.
- rel_obj = kwargs.pop(f.name)
- except KeyError:
- try:
- # Object instance wasn't passed in -- must be an ID.
- val = kwargs.pop(f.attname)
- except KeyError:
- val = f.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 f.null:
- val = None
- else:
- try:
- val = getattr(rel_obj, f.rel.get_related_field().attname)
- except AttributeError:
- raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
- setattr(self, f.attname, val)
- else:
- val = kwargs.pop(f.attname, f.get_default())
- setattr(self, f.attname, val)
- if kwargs:
- raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
- for i, arg in enumerate(args):
- setattr(self, opts.fields[i].attname, arg)
-
-def method_eq(opts, self, other):
- return isinstance(other, self.__class__) and getattr(self, opts.pk.attname) == getattr(other, opts.pk.attname)
-
-def method_ne(opts, self, other):
- return not method_eq(opts, self, other)
-
-def method_save(opts, self):
- # Run any pre-save hooks.
- if hasattr(self, '_pre_save'):
- self._pre_save()
- non_pks = [f for f in opts.fields if not f.primary_key]
- cursor = db.db.cursor()
-
- # First, try an UPDATE. If that doesn't update anything, do an INSERT.
- pk_val = getattr(self, opts.pk.attname)
- 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" % \
- (db.db.quote_name(opts.db_table), db.db.quote_name(opts.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(getattr(self, f.attname), False)) for f in non_pks]
- cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
- (db.db.quote_name(opts.db_table),
- ','.join(['%s=%%s' % db.db.quote_name(f.column) for f in non_pks]),
- db.db.quote_name(opts.pk.attname)),
- db_values + [pk_val])
- else:
- record_exists = False
- if not pk_set or not record_exists:
- field_names = [db.db.quote_name(f.column) for f in opts.fields if not isinstance(f, AutoField)]
- db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), True)) for f in opts.fields if not isinstance(f, AutoField)]
- # If the PK has been manually set we must respect that
- if pk_set:
- field_names += [f.column for f in opts.fields if isinstance(f, AutoField)]
- db_values += [f.get_db_prep_save(f.pre_save(getattr(self, f.column), True)) for f in opts.fields if isinstance(f, AutoField)]
- placeholders = ['%s'] * len(field_names)
- if opts.order_with_respect_to:
- field_names.append(db.db.quote_name('_order'))
- # TODO: This assumes the database supports subqueries.
- placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
- (db.db.quote_name(opts.db_table), db.db.quote_name(opts.order_with_respect_to.column)))
- db_values.append(getattr(self, opts.order_with_respect_to.attname))
- cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
- (db.db.quote_name(opts.db_table), ','.join(field_names),
- ','.join(placeholders)), db_values)
- if opts.has_auto_field and not pk_set:
- setattr(self, opts.pk.attname, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column))
- db.db.commit()
- # Run any post-save hooks.
- if hasattr(self, '_post_save'):
- self._post_save()
-
-def method_delete(opts, self):
- assert getattr(self, opts.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID."
- # Run any pre-delete hooks.
- if hasattr(self, '_pre_delete'):
- self._pre_delete()
- cursor = db.db.cursor()
- for related in opts.get_all_related_objects():
- rel_opts_name = related.get_method_name_part()
- if isinstance(related.field.rel, OneToOneRel):
- try:
- sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
- except ObjectDoesNotExist:
- pass
- else:
- sub_obj.delete()
- else:
- for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
- sub_obj.delete()
- for related in opts.get_all_related_many_to_many_objects():
- cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
- (db.db.quote_name(related.field.get_m2m_db_table(related.opts)),
- db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)])
- for f in opts.many_to_many:
- cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
- (db.db.quote_name(f.get_m2m_db_table(opts)),
- db.db.quote_name(self._meta.object_name.lower() + '_id')),
- [getattr(self, opts.pk.attname)])
- cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
- (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
- [getattr(self, opts.pk.attname)])
- db.db.commit()
- setattr(self, opts.pk.attname, None)
- for f in opts.fields:
- if isinstance(f, FileField) and getattr(self, f.attname):
- file_name = getattr(self, 'get_%s_filename' % f.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 opts.get_model_module().get_list(**{'%s__exact' % f.name: getattr(self, f.name)}):
- os.remove(file_name)
- # Run any post-delete hooks.
- if hasattr(self, '_post_delete'):
- self._post_delete()
-
-def method_get_next_in_order(opts, order_field, self):
- if not hasattr(self, '_next_in_order_cache'):
- self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',),
- where=['%s > (SELECT %s FROM %s WHERE %s=%%s)' % \
- (db.db.quote_name('_order'), db.db.quote_name('_order'),
- db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
- '%s=%%s' % db.db.quote_name(order_field.column)], limit=1,
- params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
- return self._next_in_order_cache
-
-def method_get_previous_in_order(opts, order_field, self):
- if not hasattr(self, '_previous_in_order_cache'):
- self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',),
- where=['%s < (SELECT %s FROM %s WHERE %s=%%s)' % \
- (db.db.quote_name('_order'), db.db.quote_name('_order'),
- db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
- '%s=%%s' % db.db.quote_name(order_field.column)], limit=1,
- params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
- return self._previous_in_order_cache
-
-# RELATIONSHIP METHODS #####################
-
-# Example: Story.get_dateline()
-def method_get_many_to_one(field_with_rel, self):
- cache_var = field_with_rel.get_cache_name()
- if not hasattr(self, cache_var):
- val = getattr(self, field_with_rel.attname)
- mod = field_with_rel.rel.to.get_model_module()
- if val is None:
- raise getattr(mod, '%sDoesNotExist' % field_with_rel.rel.to.object_name)
- other_field = field_with_rel.rel.get_related_field()
- if other_field.rel:
- params = {'%s__%s__exact' % (field_with_rel.rel.field_name, other_field.rel.field_name): val}
- else:
- params = {'%s__exact'% field_with_rel.rel.field_name: val}
- retrieved_obj = mod.get_object(**params)
- setattr(self, cache_var, retrieved_obj)
- return getattr(self, cache_var)
-
-# Handles getting many-to-many related objects.
-# Example: Poll.get_site_list()
-def method_get_many_to_many(field_with_rel, self):
- rel = field_with_rel.rel.to
- cache_var = '_%s_cache' % field_with_rel.name
- if not hasattr(self, cache_var):
- mod = rel.get_model_module()
- sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \
- (','.join(['a.%s' % db.db.quote_name(f.column) for f in rel.fields]),
- db.db.quote_name(rel.db_table),
- db.db.quote_name(field_with_rel.get_m2m_db_table(self._meta)),
- db.db.quote_name(rel.pk.column),
- db.db.quote_name(rel.object_name.lower() + '_id'),
- db.db.quote_name(self._meta.object_name.lower() + '_id'), rel.get_order_sql('a'))
- cursor = db.db.cursor()
- cursor.execute(sql, [getattr(self, self._meta.pk.attname)])
- setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])
- return getattr(self, cache_var)
-
-# Handles setting many-to-many relationships.
-# Example: Poll.set_sites()
-def method_set_many_to_many(rel_field, self, id_list):
- current_ids = [getattr(obj, obj._meta.pk.attname) for obj in method_get_many_to_many(rel_field, self)]
- ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []
- for current_id in current_ids:
- if current_id in id_list:
- del ids_to_add[current_id]
- else:
- ids_to_delete.append(current_id)
- ids_to_add = ids_to_add.keys()
- # Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete.
- if not ids_to_delete and not ids_to_add:
- return False # No change
- rel = rel_field.rel.to
- m2m_table = rel_field.get_m2m_db_table(self._meta)
- cursor = db.db.cursor()
- this_id = getattr(self, self._meta.pk.attname)
- if ids_to_delete:
- sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
- (db.db.quote_name(m2m_table),
- db.db.quote_name(self._meta.object_name.lower() + '_id'),
- db.db.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete)))
- cursor.execute(sql, [this_id])
- if ids_to_add:
- sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
- (db.db.quote_name(m2m_table),
- db.db.quote_name(self._meta.object_name.lower() + '_id'),
- db.db.quote_name(rel.object_name.lower() + '_id'))
- cursor.executemany(sql, [(this_id, i) for i in ids_to_add])
- db.db.commit()
- try:
- delattr(self, '_%s_cache' % rel_field.name) # clear cache, if it exists
- except AttributeError:
- pass
- return True
-
-# Handles related-object retrieval.
-# Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count()
-def method_get_related(method_name, rel_mod, rel_field, self, **kwargs):
- if self._meta.has_related_links and rel_mod.Klass._meta.module_name == 'relatedlinks':
- kwargs['object_id__exact'] = getattr(self, rel_field.rel.field_name)
- else:
- kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to.pk.name)] = getattr(self, rel_field.rel.get_related_field().attname)
- kwargs.update(rel_field.rel.lookup_overrides)
- related = getattr(rel_mod, method_name)(**kwargs)
-
- # Cache the 'self' object for backward links.
- # Example: Each choice in Poll.get_choice_list() will have its poll cache filled.
- # Pre-cache the self object, for following links back.
- if method_name == 'get_list':
- cache_name = rel_field.get_cache_name()
- for obj in related:
- setattr(obj, cache_name, self)
- elif method_name == 'get_object':
- setattr(related, rel_field.get_cache_name(), self)
- return related
-
-# Handles adding related objects.
-# Example: Poll.add_choice()
-def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs):
- init_kwargs = dict(zip([f.attname for f in rel_obj.fields if f != rel_field and not isinstance(f, AutoField)], args))
- init_kwargs.update(kwargs)
- for f in rel_obj.fields:
- if isinstance(f, AutoField):
- init_kwargs[f.attname] = None
- init_kwargs[rel_field.name] = self
- obj = rel_mod.Klass(**init_kwargs)
- obj.save()
- return obj
-
-# Handles related many-to-many object retrieval.
-# Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count()
-def method_get_related_many_to_many(method_name, opts, rel_mod, rel_field, self, **kwargs):
- kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.attname)
- return getattr(rel_mod, method_name)(**kwargs)
-
-# Handles setting many-to-many related objects.
-# Example: Album.set_songs()
-def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
- id_list = map(int, id_list) # normalize to integers
- rel = rel_field.rel.to
- m2m_table = rel_field.get_m2m_db_table(rel_opts)
- this_id = getattr(self, self._meta.pk.attname)
- cursor = db.db.cursor()
- cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
- (db.db.quote_name(m2m_table),
- db.db.quote_name(rel.object_name.lower() + '_id')), [this_id])
- sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
- (db.db.quote_name(m2m_table),
- db.db.quote_name(rel.object_name.lower() + '_id'),
- db.db.quote_name(rel_opts.object_name.lower() + '_id'))
- cursor.executemany(sql, [(this_id, i) for i in id_list])
- db.db.commit()
-
-# ORDERING METHODS #########################
-
-def method_set_order(ordered_obj, self, id_list):
- cursor = db.db.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" % \
- (db.db.quote_name(ordered_obj.db_table), db.db.quote_name('_order'),
- db.db.quote_name(ordered_obj.order_with_respect_to.column),
- db.db.quote_name(ordered_obj.pk.column))
- rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
- cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
- db.db.commit()
-
-def method_get_order(ordered_obj, self):
- cursor = db.db.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" % \
- (db.db.quote_name(ordered_obj.pk.column),
- db.db.quote_name(ordered_obj.db_table),
- db.db.quote_name(ordered_obj.order_with_respect_to.column),
- db.db.quote_name('_order'))
- rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
- cursor.execute(sql, [rel_val])
- return [r[0] for r in cursor.fetchall()]
-
-# DATE-RELATED METHODS #####################
-
-def method_get_next_or_previous(get_object_func, opts, field, is_next, self, **kwargs):
- op = is_next and '>' or '<'
- kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
- (db.db.quote_name(field.column), op, db.db.quote_name(field.column),
- db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), op))
- param = str(getattr(self, field.attname))
- kwargs.setdefault('params', []).extend([param, param, getattr(self, opts.pk.attname)])
- kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + opts.pk.name]
- kwargs['limit'] = 1
- return get_object_func(**kwargs)
-
-# CHOICE-RELATED METHODS ###################
-
-def method_get_display_value(field, self):
- value = getattr(self, field.attname)
- return dict(field.choices).get(value, value)
-
-# FILE-RELATED METHODS #####################
-
-def method_get_file_filename(field, self):
- return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
-
-def method_get_file_url(field, self):
- if getattr(self, field.attname): # value is not blank
- import urlparse
- return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
- return ''
-
-def method_get_file_size(field, self):
- return os.path.getsize(method_get_file_filename(field, self))
-
-def method_save_file(field, self, filename, raw_contents):
- 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)
- fp = open(getattr(self, 'get_%s_filename' % field.name)(), '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(getattr(self, 'get_%s_filename' % field.name)())
- 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.
- self.save()
-
-# IMAGE FIELD METHODS ######################
-
-def method_get_image_width(field, self):
- return _get_image_dimensions(field, self)[0]
-
-def method_get_image_height(field, self):
- return _get_image_dimensions(field, self)[1]
-
-def _get_image_dimensions(field, self):
- cachename = "__%s_dimensions_cache" % field.name
- if not hasattr(self, cachename):
- from django.utils.images import get_image_dimensions
- fname = getattr(self, "get_%s_filename" % field.name)()
- setattr(self, cachename, get_image_dimensions(fname))
- return getattr(self, cachename)
-
-##############################################
-# 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)
-
-def _get_where_clause(lookup_type, table_prefix, field_name, value):
- if table_prefix.endswith('.'):
- table_prefix = db.db.quote_name(table_prefix[:-1])+'.'
- field_name = db.db.quote_name(field_name)
- try:
- return '%s%s %s' % (table_prefix, field_name, (db.OPERATOR_MAPPING[lookup_type] % '%s'))
- except KeyError:
- pass
- if lookup_type == 'in':
- return '%s%s IN (%s)' % (table_prefix, field_name, ','.join(['%s' for v in value]))
- elif lookup_type == 'range':
- return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name)
- elif lookup_type in ('year', 'month', 'day'):
- return "%s = %%s" % db.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 ''))
- raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
-
-def function_get_object(opts, klass, does_not_exist_exception, **kwargs):
- kwargs['order_by'] = kwargs.get('order_by', ())
- obj_list = function_get_list(opts, klass, **kwargs)
- if len(obj_list) < 1:
- raise does_not_exist_exception, "%s does not exist for %s" % (opts.object_name, kwargs)
- assert len(obj_list) == 1, "get_object() returned more than one %s -- it returned %s! Lookup parameters were %s" % (opts.object_name, len(obj_list), kwargs)
- return obj_list[0]
-
-def _get_cached_row(opts, row, index_start):
- "Helper function that recursively returns an object with cache filled"
- index_end = index_start + len(opts.fields)
- obj = opts.get_model_module().Klass(*row[index_start:index_end])
- for f in opts.fields:
- if f.rel and not f.null:
- rel_obj, index_end = _get_cached_row(f.rel.to, row, index_end)
- setattr(obj, f.get_cache_name(), rel_obj)
- return obj, index_end
-
-def function_get_iterator(opts, klass, **kwargs):
- # kwargs['select'] is a dictionary, and dictionaries' key order is
- # undefined, so we convert it to a list of tuples internally.
- kwargs['select'] = kwargs.get('select', {}).items()
-
- cursor = db.db.cursor()
- select, sql, params = function_get_sql_clause(opts, **kwargs)
- cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
- fill_cache = kwargs.get('select_related')
- index_end = len(opts.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(opts, row, 0)
- else:
- obj = klass(*row[:index_end])
- for i, k in enumerate(kwargs['select']):
- setattr(obj, k[0], row[index_end+i])
- yield obj
-
-def function_get_list(opts, klass, **kwargs):
- return list(function_get_iterator(opts, klass, **kwargs))
-
-def function_get_count(opts, **kwargs):
- kwargs['order_by'] = []
- kwargs['offset'] = None
- kwargs['limit'] = None
- kwargs['select_related'] = False
- _, sql, params = function_get_sql_clause(opts, **kwargs)
- cursor = db.db.cursor()
- cursor.execute("SELECT COUNT(*)" + sql, params)
- return cursor.fetchone()[0]
-
-def function_get_values_iterator(opts, klass, **kwargs):
- # select_related and select aren't supported in get_values().
- kwargs['select_related'] = False
- kwargs['select'] = {}
-
- # 'fields' is a list of field names to fetch.
- try:
- fields = [opts.get_field(f).column for f in kwargs.pop('fields')]
- except KeyError: # Default to all fields.
- fields = [f.column for f in opts.fields]
-
- cursor = db.db.cursor()
- _, sql, params = function_get_sql_clause(opts, **kwargs)
- select = ['%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(f)) for f in fields]
- cursor.execute("SELECT " + (kwargs.get('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(fields, row))
-
-def function_get_values(opts, klass, **kwargs):
- return list(function_get_values_iterator(opts, klass, **kwargs))
-
-def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen):
- """
- Helper function that recursively populates the select, tables and where (in
- place) for fill-cache queries.
- """
- for f in opts.fields:
- if f.rel and not f.null:
- db_table = f.rel.to.db_table
- if db_table not in cache_tables_seen:
- tables.append(db.db.quote_name(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' % (db.db.quote_name(db_table), db.db.quote_name(new_prefix)))
- db_table = new_prefix
- cache_tables_seen.append(db_table)
- where.append('%s.%s = %s.%s' % \
- (db.db.quote_name(old_prefix), db.db.quote_name(f.column),
- db.db.quote_name(db_table), db.db.quote_name(f.rel.get_related_field().column)))
- select.extend(['%s.%s' % (db.db.quote_name(db_table), db.db.quote_name(f2.column)) for f2 in f.rel.to.fields])
- _fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen)
-
-def _throw_bad_kwarg_error(kwarg):
- # Helper function to remove redundancy.
- raise TypeError, "got unexpected keyword argument '%s'" % kwarg
-
-def _parse_lookup(kwarg_items, opts, table_count=0):
- # Helper function that handles converting API kwargs (e.g.
- # "name__exact": "tom") to SQL.
-
- # Note that there is a distinction between where and join_where. The latter
- # is specifically a list of where clauses to use for JOINs. This
- # distinction is necessary because of support for "_or".
-
- # table_count is used to ensure table aliases are unique.
- tables, join_where, where, params = [], [], [], []
- for kwarg, kwarg_value in kwarg_items:
- if kwarg in ('order_by', 'limit', 'offset', 'select_related', 'distinct', 'select', 'tables', 'where', 'params'):
- continue
- if kwarg_value is None:
- continue
- if kwarg == 'complex':
- tables2, join_where2, where2, params2, table_count = kwarg_value.get_sql(opts, table_count)
- tables.extend(tables2)
- join_where.extend(join_where2)
- where.extend(where2)
- params.extend(params2)
- continue
- if kwarg == '_or':
- for val in kwarg_value:
- tables2, join_where2, where2, params2, table_count = _parse_lookup(val, opts, table_count)
- tables.extend(tables2)
- join_where.extend(join_where2)
- where.append('(%s)' % ' OR '.join(where2))
- params.extend(params2)
- continue
- lookup_list = kwarg.split(LOOKUP_SEPARATOR)
- # pk="value" is shorthand for (primary key)__exact="value"
- if lookup_list[-1] == 'pk':
- if opts.pk.rel:
- lookup_list = lookup_list[:-1] + [opts.pk.name, opts.pk.rel.field_name, 'exact']
- else:
- lookup_list = lookup_list[:-1] + [opts.pk.name, 'exact']
- if len(lookup_list) == 1:
- _throw_bad_kwarg_error(kwarg)
- lookup_type = lookup_list.pop()
- current_opts = opts # We'll be overwriting this, so keep a reference to the original opts.
- current_table_alias = current_opts.db_table
- param_required = False
- while lookup_list or param_required:
- table_count += 1
- try:
- # "current" is a piece of the lookup list. For example, in
- # choices.get_list(poll__sites__id__exact=5), lookup_list is
- # ["polls", "sites", "id"], and the first current is "polls".
- try:
- current = lookup_list.pop(0)
- except IndexError:
- # If we're here, lookup_list is empty but param_required
- # is set to True, which means the kwarg was bad.
- # Example: choices.get_list(poll__exact='foo')
- _throw_bad_kwarg_error(kwarg)
- # Try many-to-many relationships first...
- for f in current_opts.many_to_many:
- if f.name == current:
- rel_table_alias = db.db.quote_name('t%s' % table_count)
- table_count += 1
- tables.append('%s %s' % \
- (db.db.quote_name(f.get_m2m_db_table(current_opts)), rel_table_alias))
- join_where.append('%s.%s = %s.%s' % \
- (db.db.quote_name(current_table_alias),
- db.db.quote_name(current_opts.pk.column),
- rel_table_alias,
- db.db.quote_name(current_opts.object_name.lower() + '_id')))
- # Optimization: In the case of primary-key lookups, we
- # don't have to do an extra join.
- if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact':
- where.append(_get_where_clause(lookup_type, rel_table_alias+'.',
- f.rel.to.object_name.lower()+'_id', kwarg_value))
- params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
- lookup_list.pop()
- param_required = False
- else:
- new_table_alias = 't%s' % table_count
- tables.append('%s %s' % (db.db.quote_name(f.rel.to.db_table),
- db.db.quote_name(new_table_alias)))
- join_where.append('%s.%s = %s.%s' % \
- (db.db.quote_name(rel_table_alias),
- db.db.quote_name(f.rel.to.object_name.lower() + '_id'),
- db.db.quote_name(new_table_alias),
- db.db.quote_name(f.rel.to.pk.column)))
- current_table_alias = new_table_alias
- param_required = True
- current_opts = f.rel.to
- raise StopIteration
- for f in current_opts.fields:
- # Try many-to-one relationships...
- if f.rel and f.name == current:
- # Optimization: In the case of primary-key lookups, we
- # don't have to do an extra join.
- if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact':
- where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
- params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
- lookup_list.pop()
- param_required = False
- # 'isnull' lookups in many-to-one relationships are a special case,
- # because we don't want to do a join. We just want to find out
- # whether the foreign key field is NULL.
- elif lookup_type == 'isnull' and not lookup_list:
- where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
- params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
- else:
- new_table_alias = 't%s' % table_count
- tables.append('%s %s' % \
- (db.db.quote_name(f.rel.to.db_table), db.db.quote_name(new_table_alias)))
- join_where.append('%s.%s = %s.%s' % \
- (db.db.quote_name(current_table_alias), db.db.quote_name(f.column),
- db.db.quote_name(new_table_alias), db.db.quote_name(f.rel.to.pk.column)))
- current_table_alias = new_table_alias
- param_required = True
- current_opts = f.rel.to
- raise StopIteration
- # Try direct field-name lookups...
- if f.name == current:
- where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
- params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
- param_required = False
- raise StopIteration
- # If we haven't hit StopIteration at this point, "current" must be
- # an invalid lookup, so raise an exception.
- _throw_bad_kwarg_error(kwarg)
- except StopIteration:
- continue
- return tables, join_where, where, params, table_count
-
-def function_get_sql_clause(opts, **kwargs):
- def quote_only_if_word(word):
- if ' ' in word:
- return word
- else:
- return db.db.quote_name(word)
-
- # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
- select = ["%s.%s" % (db.db.quote_name(opts.db_table), db.db.quote_name(f.column)) for f in opts.fields]
- tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or [])
- tables = [quote_only_if_word(t) for t in tables]
- where = kwargs.get('where') and kwargs['where'][:] or []
- params = kwargs.get('params') and kwargs['params'][:] or []
-
- # Convert the kwargs into SQL.
- tables2, join_where2, where2, params2, _ = _parse_lookup(kwargs.items(), opts)
- tables.extend(tables2)
- where.extend(join_where2 + where2)
- params.extend(params2)
-
- # Add any additional constraints from the "where_constraints" parameter.
- where.extend(opts.where_constraints)
-
- # Add additional tables and WHERE clauses based on select_related.
- if kwargs.get('select_related') is True:
- _fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
-
- # Add any additional SELECTs passed in via kwargs.
- if kwargs.get('select'):
- select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), db.db.quote_name(s[0])) for s in kwargs['select']])
-
- # ORDER BY clause
- order_by = []
- for f in handle_legacy_orderlist(kwargs.get('order_by', opts.ordering)):
- if f == '?': # Special case.
- order_by.append(db.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 = db.db.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 [k[0] for k in kwargs.get('select', [])]:
- table_prefix = db.db.quote_name(opts.db_table) + '.'
- else:
- table_prefix = ''
- order_by.append('%s%s %s' % (table_prefix, db.db.quote_name(orderfield2column(col_name, opts)), order))
- order_by = ", ".join(order_by)
-
- # LIMIT and OFFSET clauses
- if kwargs.get('limit') is not None:
- limit_sql = " %s " % db.get_limit_offset_sql(kwargs['limit'], kwargs.get('offset'))
- else:
- assert kwargs.get('offset') is None, "'offset' is not allowed without 'limit'"
- limit_sql = ""
-
- return select, " FROM " + ",".join(tables) + (where and " WHERE " + " AND ".join(where) or "") + (order_by and " ORDER BY " + order_by or "") + limit_sql, params
-
-def function_get_in_bulk(opts, klass, *args, **kwargs):
- id_list = args and args[0] or kwargs.get('id_list', [])
- assert id_list != [], "get_in_bulk() cannot be passed an empty list."
- kwargs['where'] = ["%s.%s IN (%s)" % (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), ",".join(['%s'] * len(id_list)))]
- kwargs['params'] = id_list
- obj_list = function_get_list(opts, klass, **kwargs)
- return dict([(getattr(o, opts.pk.attname), o) for o in obj_list])
-
-def function_get_latest(opts, klass, does_not_exist_exception, **kwargs):
- kwargs['order_by'] = ('-' + opts.get_latest_by,)
- kwargs['limit'] = 1
- return function_get_object(opts, klass, does_not_exist_exception, **kwargs)
-
-def function_get_date_list(opts, field, *args, **kwargs):
- from django.core.db.typecasts import typecast_timestamp
- kind = args and args[0] or kwargs['kind']
- assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."
- order = kwargs.pop('_order', 'ASC')
- assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
- kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
- if field.null:
- kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \
- (db.db.quote_name(opts.db_table), db.db.quote_name(field.column)))
- select, sql, params = function_get_sql_clause(opts, **kwargs)
- sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))), sql)
- cursor = db.db.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()]
-
-###################################
-# HELPER FUNCTIONS (MANIPULATORS) #
-###################################
-
-def get_manipulator(opts, klass, extra_methods, add=False, change=False):
- "Returns the custom Manipulator (either add or change) for the given opts."
- assert (add == False or change == False) and add != change, "get_manipulator() can be passed add=True or change=True, but not both"
- man = types.ClassType('%sManipulator%s' % (opts.object_name, add and 'Add' or 'Change'), (formfields.Manipulator,), {})
- man.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
- man.__init__ = curry(manipulator_init, opts, add, change)
- man.save = curry(manipulator_save, opts, klass, add, change)
- man.get_related_objects = curry(manipulator_get_related_objects, opts, klass, add, change)
- man.flatten_data = curry(manipulator_flatten_data, opts, klass, add, change)
- for field_name_list in opts.unique_together:
- setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
- for f in opts.fields:
- if f.unique_for_date:
- setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_date), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_date), opts, 'date'))
- if f.unique_for_month:
- setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_month), opts, 'month'))
- if f.unique_for_year:
- setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_year), opts, 'year'))
- for k, v in extra_methods.items():
- setattr(man, k, v)
- return man
-
-def manipulator_init(opts, add, change, self, obj_key=None, follow=None):
- self.follow = opts.get_follow(follow)
-
- if change:
- assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
- self.obj_key = obj_key
- try:
- self.original_object = opts.get_model_module().get_object(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 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.
- lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
- lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
- _ = opts.one_to_one_field.rel.to.get_model_module().get_object(**lookup_kwargs)
- params = dict([(f.attname, f.get_default()) for f in opts.fields])
- params[opts.pk.attname] = obj_key
- self.original_object = opts.get_model_module().Klass(**params)
- else:
- raise
- self.fields = []
-
- for f in opts.fields + opts.many_to_many:
- if self.follow.get(f.name, False):
- self.fields.extend(f.get_manipulator_fields(opts, self, change))
-
- # Add fields for related objects.
- for f in opts.get_all_related_objects():
- if self.follow.get(f.name, False):
- fol = self.follow[f.name]
- self.fields.extend(f.get_manipulator_fields(opts, self, change, fol))
-
- # Add field for ordering.
- if change and opts.get_ordered_objects():
- self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
-
-def manipulator_save(opts, klass, add, change, self, new_data):
- # TODO: big cleanup when core fields go -> use recursive manipulators.
- from django.utils.datastructures import DotExpandedDict
- params = {}
- for f in opts.fields:
- # Fields with auto_now_add should keep their original value in the change stage.
- auto_now_add = 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 change:
- param = getattr(self.original_object, f.attname)
- else:
- param = f.get_default()
- params[f.attname] = param
-
- if change:
- params[opts.pk.attname] = self.obj_key
-
- # First, save the basic object itself.
- new_object = klass(**params)
- new_object.save()
-
- # Now that the object's been saved, save any uploaded files.
- for f in opts.fields:
- if isinstance(f, FileField):
- f.save_file(new_data, new_object, change and self.original_object or None, change, rel=False)
-
- # Calculate which primary fields have changed.
- if change:
- self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
- for f in 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: Poll.set_sites()
- for f in 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)
- was_changed = getattr(new_object, 'set_%s' % f.name)(new_vals)
- if change and was_changed:
- self.fields_changed.append(f.verbose_name)
-
- expanded_data = DotExpandedDict(dict(new_data))
- # Save many-to-one objects. Example: Add the Choice objects for a Poll.
- for related in 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[related.var_name].items()
- 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 change:
- if rel_new_data[related.opts.pk.name][0]:
- try:
- old_rel_obj = getattr(self.original_object, 'get_%s' % related.get_method_name_part() )(**{'%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.field_name)
- elif add and isinstance(f, AutoField):
- param = None
- elif 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
-
- # Related links are a special case, because we have to
- # manually set the "content_type_id" and "object_id" fields.
- if opts.has_related_links and related.opts.module_name == 'relatedlinks':
- contenttypes_mod = get_module('core', 'contenttypes')
- params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=opts.app_label, python_module_name__exact=opts.module_name).id
- params['object_id'] = new_object.id
-
- # Create the related item.
- new_rel_obj = related.opts.get_model_module().Klass(**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, change and old_rel_obj or None, old_rel_obj is not None, rel=True)
-
- # Calculate whether any fields have changed.
- if 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:
- was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname])
- if change and was_changed:
- 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 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 change and opts.get_ordered_objects():
- order = new_data['order_'] and map(int, new_data['order_'].split(',')) or []
- for rel_opts in opts.get_ordered_objects():
- getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
- return new_object
-
-def manipulator_get_related_objects(opts, klass, add, change, self):
- return opts.get_followed_related_objects(self.follow)
-
-def manipulator_flatten_data(opts, klass, add, change, self):
- new_data = {}
- obj = change and self.original_object or None
- for f in opts.get_data_holders(self.follow):
- fol = self.follow.get(f.name)
- new_data.update(f.flatten_data(fol, obj))
- return new_data
-
-def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
- 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.attname, 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
- mod = opts.get_model_module()
- try:
- old_obj = mod.get_object(**kwargs)
- except ObjectDoesNotExist:
- return
- if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname):
- 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(field_name_list[1:], 'and')}
-
-def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data):
- date_str = all_data.get(date_field.get_manipulator_field_names('')[0], None)
- mod = opts.get_model_module()
- date_val = formfields.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 = mod.get_object(**lookup_kwargs)
- except ObjectDoesNotExist:
- return
- else:
- if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname):
- 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/django/core/paginator.py b/django/core/paginator.py
index a4dbfebaae..6e01c1ccec 100644
--- a/django/core/paginator.py
+++ b/django/core/paginator.py
@@ -6,20 +6,17 @@ class InvalidPage(Exception):
class ObjectPaginator:
"""
- This class makes pagination easy. Feed it a module (an object with
- get_count() and get_list() methods) and a dictionary of arguments
- to be passed to those methods, 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
+ This class makes pagination easy. Feed it a QuerySet, 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).
"""
- def __init__(self, module, args, num_per_page, count_method='get_count', list_method='get_list'):
- self.module, self.args = module, args
+ def __init__(self, query_set, num_per_page):
+ self.query_set = query_set
self.num_per_page = num_per_page
- self.count_method, self.list_method = count_method, list_method
self._hits, self._pages = None, None
self._has_next = {} # Caches page_number -> has_next_boolean
@@ -30,14 +27,17 @@ class ObjectPaginator:
raise InvalidPage
if page_number < 0:
raise InvalidPage
- args = copy(self.args)
- args['offset'] = page_number * self.num_per_page
+
# Retrieve one extra record, and check for the existence of that extra
# record to determine whether there's a next page.
- args['limit'] = self.num_per_page + 1
- object_list = getattr(self.module, self.list_method)(**args)
+ limit = self.num_per_page + 1
+ offset = page_number * self.num_per_page
+
+ object_list = list(self.query_set[offset:offset+limit])
+
if not object_list:
raise InvalidPage
+
self._has_next[page_number] = (len(object_list) > self.num_per_page)
return object_list[:self.num_per_page]
@@ -45,11 +45,8 @@ class ObjectPaginator:
"Does page $page_number have a 'next' page?"
if not self._has_next.has_key(page_number):
if self._pages is None:
- args = copy(self.args)
- args['offset'] = (page_number + 1) * self.num_per_page
- args['limit'] = 1
- object_list = getattr(self.module, self.list_method)(**args)
- self._has_next[page_number] = (object_list != [])
+ offset = (page_number + 1) * self.num_per_page
+ self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0
else:
self._has_next[page_number] = page_number < (self.pages - 1)
return self._has_next[page_number]
@@ -59,12 +56,7 @@ class ObjectPaginator:
def _get_hits(self):
if self._hits is None:
- order_args = copy(self.args)
- if order_args.has_key('order_by'):
- del order_args['order_by']
- if order_args.has_key('select_related'):
- del order_args['select_related']
- self._hits = getattr(self.module, self.count_method)(**order_args)
+ self._hits = self.query_set.count()
return self._hits
def _get_pages(self):
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index c792e0a970..5772912031 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -242,7 +242,7 @@ class ServerHandler:
# Error handling (also per-subclass or per-instance)
traceback_limit = None # Print entire traceback to self.get_stderr()
- error_status = "500 Dude, this is whack!"
+ error_status = "500 INTERNAL SERVER ERROR"
error_headers = [('Content-Type','text/plain')]
# State variables (don't mess with these)
@@ -383,7 +383,7 @@ class ServerHandler:
assert type(data) is StringType,"write() argument must be string"
if not self.status:
- raise AssertionError("write() before start_response()")
+ raise AssertionError("write() before start_response()")
elif not self.headers_sent:
# Before the first output, send the stored headers
@@ -532,8 +532,8 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
server_version = "WSGIServer/" + __version__
def __init__(self, *args, **kwargs):
- from django.conf.settings import ADMIN_MEDIA_PREFIX
- self.admin_media_prefix = ADMIN_MEDIA_PREFIX
+ from django.conf import settings
+ self.admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def get_environ(self):
diff --git a/django/core/signals.py b/django/core/signals.py
new file mode 100644
index 0000000000..7a236079a5
--- /dev/null
+++ b/django/core/signals.py
@@ -0,0 +1,3 @@
+request_started = object()
+request_finished = object()
+got_request_exception = object()
diff --git a/django/core/template_loader.py b/django/core/template_loader.py
index e268c390e1..ee86178cc1 100644
--- a/django/core/template_loader.py
+++ b/django/core/template_loader.py
@@ -1,7 +1,7 @@
# This module is DEPRECATED!
#
-# You should no longer be using django.core.template_loader.
+# You should no longer be using django.template_loader.
#
-# Use django.core.template.loader instead.
+# Use django.template.loader instead.
-from django.core.template.loader import *
+from django.template.loader import *
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 9582dd4d81..e11b63e977 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -7,7 +7,8 @@ a string) and returns a tuple in this format:
(view_function, function_args, function_kwargs)
"""
-from django.core.exceptions import Http404, ImproperlyConfigured, ViewDoesNotExist
+from django.http import Http404
+from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
import re
class Resolver404(Http404):
diff --git a/django/core/validators.py b/django/core/validators.py
index 88cf6db4e1..91d72033de 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -8,6 +8,9 @@ validator will *always* be run, regardless of whether its associated
form field is required.
"""
+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'(19|2\d)\d{2}-((?:0?[1-9])|(?:1[0-2]))-((?:0?[1-9])|(?:[12][0-9])|(?:3[0-1]))'
@@ -24,10 +27,6 @@ phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNO
slug_re = re.compile(r'^[-\w]+$')
url_re = re.compile(r'^https?://\S+$')
-from django.conf.settings import JING_PATH
-from django.utils.translation import gettext_lazy, ngettext
-from django.utils.functional import Promise, lazy
-
lazy_inter = lazy(lambda a,b: str(a) % b, str)
class ValidationError(Exception):
@@ -58,11 +57,11 @@ class CriticalValidationError(Exception):
def isAlphaNumeric(field_data, all_data):
if not alnum_re.search(field_data):
- raise ValidationError, _("This value must contain only letters, numbers and underscores.")
+ 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, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
+ 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):
@@ -70,18 +69,18 @@ def isSlug(field_data, all_data):
def isLowerCase(field_data, all_data):
if field_data.lower() != field_data:
- raise ValidationError, _("Uppercase letters are not allowed here.")
+ raise ValidationError, gettext("Uppercase letters are not allowed here.")
def isUpperCase(field_data, all_data):
if field_data.upper() != field_data:
- raise ValidationError, _("Lowercase letters are not allowed here.")
+ 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, _("Enter only digits separated by commas.")
+ raise ValidationError, gettext("Enter only digits separated by commas.")
def isCommaSeparatedEmailList(field_data, all_data):
"""
@@ -93,48 +92,48 @@ def isCommaSeparatedEmailList(field_data, all_data):
try:
isValidEmail(supposed_email.strip(), '')
except ValidationError:
- raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
+ 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, _("Please enter a valid IP address.")
+ raise ValidationError, gettext("Please enter a valid IP address.")
def isNotEmpty(field_data, all_data):
if field_data.strip() == '':
- raise ValidationError, _("Empty values are not allowed here.")
+ raise ValidationError, gettext("Empty values are not allowed here.")
def isOnlyDigits(field_data, all_data):
if not field_data.isdigit():
- raise ValidationError, _("Non-numeric characters aren't allowed here.")
+ raise ValidationError, gettext("Non-numeric characters aren't allowed here.")
def isNotOnlyDigits(field_data, all_data):
if field_data.isdigit():
- raise ValidationError, _("This value can't be comprised solely of digits.")
+ 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, _("Enter a whole number.")
+ raise ValidationError, gettext("Enter a whole number.")
def isOnlyLetters(field_data, all_data):
if not field_data.isalpha():
- raise ValidationError, _("Only alphabetical characters are allowed here.")
+ raise ValidationError, gettext("Only alphabetical characters are allowed here.")
def isValidANSIDate(field_data, all_data):
if not ansi_date_re.search(field_data):
- raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
+ raise ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.')
def isValidANSITime(field_data, all_data):
if not ansi_time_re.search(field_data):
- raise ValidationError, _('Enter a valid time in HH:MM format.')
+ 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, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+ raise ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
def isValidEmail(field_data, all_data):
if not email_re.search(field_data):
- raise ValidationError, _('Enter a valid e-mail address.')
+ raise ValidationError, gettext('Enter a valid e-mail address.')
def isValidImage(field_data, all_data):
"""
@@ -146,18 +145,18 @@ def isValidImage(field_data, all_data):
try:
Image.open(StringIO(field_data['content']))
except IOError: # Python Imaging Library doesn't recognize it as an image
- raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted 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, _("The URL %s does not point to a valid image.") % field_data
+ 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, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % 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)"
@@ -165,11 +164,11 @@ def isValidQuicktimeVideoURL(field_data, all_data):
try:
uc(field_data, all_data)
except URLMimeTypeCheck.InvalidContentType:
- raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
+ 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, _("A valid URL is required.")
+ raise ValidationError, gettext("A valid URL is required.")
def isValidHTML(field_data, all_data):
import urllib, urllib2
@@ -183,14 +182,14 @@ def isValidHTML(field_data, all_data):
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, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
+ 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, _("Badly formed XML: %s") % str(e)
+ raise ValidationError, gettext("Badly formed XML: %s") % str(e)
def isWellFormedXmlFragment(field_data, all_data):
isWellFormedXml('<root>%s</root>' % field_data, all_data)
@@ -200,19 +199,19 @@ def isExistingURL(field_data, all_data):
try:
u = urllib2.urlopen(field_data)
except ValueError:
- raise ValidationError, _("Invalid URL: %s") % field_data
+ raise ValidationError, gettext("Invalid URL: %s") % field_data
except urllib2.HTTPError, e:
# 401s are valid; they just mean authorization is required.
if e.code not in ('401',):
- raise ValidationError, _("The URL %s is a broken link.") % field_data
+ raise ValidationError, gettext("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
+ raise ValidationError, gettext("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, _("Enter a valid U.S. state abbreviation.")
+ raise ValidationError, gettext("Enter a valid U.S. state abbreviation.")
def hasNoProfanities(field_data, all_data):
"""
@@ -334,7 +333,7 @@ class IsAPowerOf:
from math import log
val = log(int(field_data)) / log(self.power_of)
if val != int(val):
- raise ValidationError, _("This value must be a power of %s.") % self.power_of
+ raise ValidationError, gettext("This value must be a power of %s.") % self.power_of
class IsValidFloat:
def __init__(self, max_digits, decimal_places):
@@ -345,9 +344,9 @@ class IsValidFloat:
try:
float(data)
except ValueError:
- raise ValidationError, _("Please enter a valid decimal number.")
+ raise ValidationError, gettext("Please enter a valid decimal number.")
if len(data) > (self.max_digits + 1):
- raise ValidationError, ngettext( "Please enter a valid decimal number with at most %s total digit.",
+ 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 '.' 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.",
@@ -424,10 +423,10 @@ class URLMimeTypeCheck:
try:
info = urllib2.urlopen(field_data).info()
except (urllib2.HTTPError, urllib2.URLError):
- raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
+ 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, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
+ raise URLMimeTypeCheck.InvalidContentType, gettext("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
'url': field_data, 'contenttype': content_type}
class RelaxNGCompact:
@@ -447,9 +446,9 @@ class RelaxNGCompact:
fp = open(filename, 'w')
fp.write(field_data)
fp.close()
- if not os.path.exists(JING_PATH):
- raise Exception, "%s not found!" % JING_PATH
- p = os.popen('%s -c %s %s' % (JING_PATH, self.schema_path, filename))
+ 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)
diff --git a/django/core/xheaders.py b/django/core/xheaders.py
index 98d2586b75..e173bcbca8 100644
--- a/django/core/xheaders.py
+++ b/django/core/xheaders.py
@@ -9,14 +9,13 @@ 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, package, python_module_name, object_id):
+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 package, python_module_name and
- object_id -- but only if the given HttpRequest object has an IP address
- within the INTERNAL_IPS setting.
+ 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.
"""
- from django.conf.settings import INTERNAL_IPS
- if request.META.get('REMOTE_ADDR') in INTERNAL_IPS:
- response['X-Object-Type'] = "%s.%s" % (package, python_module_name)
+ from django.conf import settings
+ if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
+ 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/django/db/__init__.py b/django/db/__init__.py
new file mode 100644
index 0000000000..317d6059bf
--- /dev/null
+++ b/django/db/__init__.py
@@ -0,0 +1,45 @@
+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()
+ raise ImproperlyConfigured, "Could not load database backend: %s. Is your DATABASE_ENGINE setting (currently, %r) spelled correctly? Available options are: %s" % \
+ (e, settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
+
+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()
+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/django/parts/__init__.py b/django/db/backends/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/parts/__init__.py
+++ b/django/db/backends/__init__.py
diff --git a/django/parts/auth/__init__.py b/django/db/backends/ado_mssql/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/parts/auth/__init__.py
+++ b/django/db/backends/ado_mssql/__init__.py
diff --git a/django/core/db/backends/ado_mssql.py b/django/db/backends/ado_mssql/base.py
index 4afe0cef70..b43be1fa7a 100644
--- a/django/core/db/backends/ado_mssql.py
+++ b/django/db/backends/ado_mssql/base.py
@@ -4,8 +4,7 @@ ADO MSSQL database backend for Django.
Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/
"""
-from django.core.db import base
-from django.core.db.dicthelpers import *
+from django.db.backends import util
import adodbapi as Database
import datetime
try:
@@ -45,10 +44,10 @@ def variantToPython(variant, adType):
Database.convertVariantToPython = variantToPython
try:
- # Only exists in python 2.4+
+ # Only exists in Python 2.4+
from threading import local
except ImportError:
- # Import copy of _thread_local.py from python 2.4
+ # Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
class DatabaseWrapper(local):
@@ -57,25 +56,25 @@ class DatabaseWrapper(local):
self.queries = []
def cursor(self):
- from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG
+ from django.conf import settings
if self.connection is None:
- if DATABASE_NAME == '' or DATABASE_USER == '':
+ 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 DATABASE_HOST:
- DATABASE_HOST = "127.0.0.1"
+ 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" % (DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME)
+ 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 DEBUG:
+ if settings.DEBUG:
return base.CursorDebugWrapper(cursor, self)
return cursor
- def commit(self):
+ def _commit(self):
return self.connection.commit()
- def rollback(self):
+ def _rollback(self):
if self.connection:
return self.connection.rollback()
@@ -84,10 +83,16 @@ class DatabaseWrapper(local):
self.connection.close()
self.connection = None
- def quote_name(self, name):
- if name.startswith('[') and name.endswith(']'):
- return name # Quoting once is enough.
- return '[%s]' % name
+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))
@@ -116,24 +121,14 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql():
return "RAND()"
-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
+def get_drop_foreignkey_sql():
+ return "DROP CONSTRAINT"
OPERATOR_MAPPING = {
'exact': '= %s',
'iexact': 'LIKE %s',
'contains': 'LIKE %s',
'icontains': 'LIKE %s',
- 'ne': '!= %s',
'gt': '> %s',
'gte': '>= %s',
'lt': '< %s',
@@ -143,32 +138,3 @@ OPERATOR_MAPPING = {
'istartswith': 'LIKE %s',
'iendswith': 'LIKE %s',
}
-
-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',
- 'URLField': 'varchar(200)',
- 'USStateField': 'varchar(2)',
-}
-
-DATA_TYPES_REVERSE = {}
diff --git a/django/db/backends/ado_mssql/client.py b/django/db/backends/ado_mssql/client.py
new file mode 100644
index 0000000000..5c197cafa4
--- /dev/null
+++ b/django/db/backends/ado_mssql/client.py
@@ -0,0 +1,2 @@
+def runshell():
+ raise NotImplementedError
diff --git a/django/db/backends/ado_mssql/creation.py b/django/db/backends/ado_mssql/creation.py
new file mode 100644
index 0000000000..4d85d27ea5
--- /dev/null
+++ b/django/db/backends/ado_mssql/creation.py
@@ -0,0 +1,26 @@
+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',
+ 'URLField': 'varchar(200)',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/django/db/backends/ado_mssql/introspection.py b/django/db/backends/ado_mssql/introspection.py
new file mode 100644
index 0000000000..b125cc995f
--- /dev/null
+++ b/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/django/parts/media/__init__.py b/django/db/backends/dummy/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/parts/media/__init__.py
+++ b/django/db/backends/dummy/__init__.py
diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py
new file mode 100644
index 0000000000..89fec00c1d
--- /dev/null
+++ b/django/db/backends/dummy/base.py
@@ -0,0 +1,37 @@
+"""
+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 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_drop_foreignkey_sql = complain
+OPERATOR_MAPPING = {}
diff --git a/django/db/backends/dummy/client.py b/django/db/backends/dummy/client.py
new file mode 100644
index 0000000000..e332987aa8
--- /dev/null
+++ b/django/db/backends/dummy/client.py
@@ -0,0 +1,3 @@
+from django.db.backends.dummy.base import complain
+
+runshell = complain
diff --git a/django/db/backends/dummy/creation.py b/django/db/backends/dummy/creation.py
new file mode 100644
index 0000000000..b82c4fe568
--- /dev/null
+++ b/django/db/backends/dummy/creation.py
@@ -0,0 +1 @@
+DATA_TYPES = {}
diff --git a/django/db/backends/dummy/introspection.py b/django/db/backends/dummy/introspection.py
new file mode 100644
index 0000000000..c52a812046
--- /dev/null
+++ b/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/django/views/auth/__init__.py b/django/db/backends/mysql/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/views/auth/__init__.py
+++ b/django/db/backends/mysql/__init__.py
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
new file mode 100644
index 0000000000..e2d6cbfc5e
--- /dev/null
+++ b/django/db/backends/mysql/base.py
@@ -0,0 +1,167 @@
+"""
+MySQL database backend for Django.
+
+Requires MySQLdb: http://sourceforge.net/projects/mysql-python
+"""
+
+from django.db.backends import util
+import MySQLdb as Database
+from MySQLdb.converters import conversions
+from MySQLdb.constants import FIELD_TYPE
+import types
+
+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 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:
+ 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):
+ self.connection = None
+ self.queries = []
+
+ 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)
+ self.connection = Database.connect(**kwargs)
+ cursor = self.connection.cursor()
+ if self.connection.get_server_info() >= '4.1':
+ cursor.execute("SET NAMES 'utf8'")
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
+ return cursor
+
+ def _commit(self):
+ self.connection.commit()
+
+ def _rollback(self):
+ if self.connection:
+ 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
+
+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_drop_foreignkey_sql():
+ return "DROP FOREIGN KEY"
+
+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/django/db/backends/mysql/client.py b/django/db/backends/mysql/client.py
new file mode 100644
index 0000000000..f9d6297b8e
--- /dev/null
+++ b/django/db/backends/mysql/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/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
new file mode 100644
index 0000000000..116b490124
--- /dev/null
+++ b/django/db/backends/mysql/creation.py
@@ -0,0 +1,30 @@
+# 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',
+ 'URLField': 'varchar(200)',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
new file mode 100644
index 0000000000..a2eeb6de7b
--- /dev/null
+++ b/django/db/backends/mysql/introspection.py
@@ -0,0 +1,94 @@
+from django.db import transaction
+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 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" % 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_BLOB: 'TextField',
+ FIELD_TYPE.MEDIUM_BLOB: 'TextField',
+ FIELD_TYPE.LONG_BLOB: 'TextField',
+ FIELD_TYPE.VAR_STRING: 'CharField',
+}
diff --git a/django/views/registration/__init__.py b/django/db/backends/postgresql/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/views/registration/__init__.py
+++ b/django/db/backends/postgresql/__init__.py
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
new file mode 100644
index 0000000000..3c807f2120
--- /dev/null
+++ b/django/db/backends/postgresql/base.py
@@ -0,0 +1,128 @@
+"""
+PostgreSQL database backend for Django.
+
+Requires psycopg 1: http://initd.org/projects/psycopg1
+"""
+
+from django.db.backends import util
+import psycopg as Database
+
+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
+
+class DatabaseWrapper(local):
+ def __init__(self):
+ self.connection = None
+ self.queries = []
+
+ def cursor(self):
+ from django.conf import settings
+ if self.connection is None:
+ 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.connection.set_isolation_level(1) # make transactions transparent to all cursors
+ cursor = self.connection.cursor()
+ cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(cursor, self)
+ return cursor
+
+ def _commit(self):
+ return self.connection.commit()
+
+ def _rollback(self):
+ if self.connection:
+ 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_drop_foreignkey_sql():
+ return "DROP CONSTRAINT"
+
+# 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, which isn't supported yet, because it's still in beta. Use psycopg version 1 instead: http://initd.org/projects/psycopg1"
+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/django/db/backends/postgresql/client.py b/django/db/backends/postgresql/client.py
new file mode 100644
index 0000000000..3d0d7a0d2a
--- /dev/null
+++ b/django/db/backends/postgresql/client.py
@@ -0,0 +1,14 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ['']
+ args += ["-U%s" % settings.DATABASE_USER]
+ if settings.DATABASE_PASSWORD:
+ args += ["-W"]
+ if settings.DATABASE_HOST:
+ args += ["-h %s" % settings.DATABASE_HOST]
+ if settings.DATABASE_PORT:
+ args += ["-p %s" % settings.DATABASE_PORT]
+ args += [settings.DATABASE_NAME]
+ os.execvp('psql', args)
diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
new file mode 100644
index 0000000000..65a804ec40
--- /dev/null
+++ b/django/db/backends/postgresql/creation.py
@@ -0,0 +1,30 @@
+# 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',
+ 'URLField': 'varchar(200)',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
new file mode 100644
index 0000000000..c4f759da10
--- /dev/null
+++ b/django/db/backends/postgresql/introspection.py
@@ -0,0 +1,85 @@
+from django.db import transaction
+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}
+ """
+ # Get the table description because we only have the column indexes, and we
+ # need the column names.
+ desc = get_table_description(cursor, table_name)
+ # This query retrieves each index on the given table.
+ cursor.execute("""
+ SELECT idx.indkey, idx.indisunique, idx.indisprimary
+ FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
+ pg_catalog.pg_index idx
+ WHERE c.oid = idx.indrelid
+ AND idx.indexrelid = c2.oid
+ AND c.relname = %s""", [table_name])
+ indexes = {}
+ for row in cursor.fetchall():
+ # row[0] (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[0]:
+ continue
+ col_name = desc[int(row[0])-1][0]
+ indexes[col_name] = {'primary_key': row[2], 'unique': row[1]}
+ 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/tests/testapp/__init__.py b/django/db/backends/sqlite3/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/testapp/__init__.py
+++ b/django/db/backends/sqlite3/__init__.py
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
new file mode 100644
index 0000000000..ecaf9b0c0d
--- /dev/null
+++ b/django/db/backends/sqlite3/base.py
@@ -0,0 +1,150 @@
+"""
+SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/).
+"""
+
+from django.db.backends import util
+from pysqlite2 import dbapi2 as Database
+
+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)
+
+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):
+ self.connection = None
+ self.queries = []
+
+ def cursor(self):
+ from django.conf import settings
+ if self.connection is None:
+ self.connection = Database.connect(settings.DATABASE_NAME, detect_types=Database.PARSE_DECLTYPES)
+ # register extract and date_trun 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):
+ self.connection.commit()
+
+ def _rollback(self):
+ if self.connection:
+ self.connection.rollback()
+
+ def close(self):
+ if self.connection is not None:
+ 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_drop_foreignkey_sql():
+ return ""
+
+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/django/db/backends/sqlite3/client.py b/django/db/backends/sqlite3/client.py
new file mode 100644
index 0000000000..097218341f
--- /dev/null
+++ b/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/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
new file mode 100644
index 0000000000..e845179e64
--- /dev/null
+++ b/django/db/backends/sqlite3/creation.py
@@ -0,0 +1,29 @@
+# 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',
+ 'URLField': 'varchar(200)',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
new file mode 100644
index 0000000000..c5fa738249
--- /dev/null
+++ b/django/db/backends/sqlite3/introspection.py
@@ -0,0 +1,50 @@
+from django.db import transaction
+from django.db.backends.sqlite3.base import quote_name
+
+def get_table_list(cursor):
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ cursor.execute("PRAGMA table_info(%s)" % quote_name(table_name))
+ return [(row[1], row[2], None, None) for row in cursor.fetchall()]
+
+def get_relations(cursor, table_name):
+ raise NotImplementedError
+
+def get_indexes(cursor, table_name):
+ raise NotImplementedError
+
+# 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/django/db/backends/util.py b/django/db/backends/util.py
new file mode 100644
index 0000000000..b9c6f573c9
--- /dev/null
+++ b/django/db/backends/util.py
@@ -0,0 +1,114 @@
+import datetime
+from time import time
+
+class CursorDebugWrapper:
+ 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()
+ self.db.queries.append({
+ 'sql': sql % tuple(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([(desc[col[0]][0], col[1]) for col in enumerate(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
+ return [_dict_helper(desc, row) for row in cursor.fetchmany(number)]
+
+def dictfetchall(cursor):
+ "Returns all rows from a cursor as a dict"
+ desc = cursor.description
+ return [_dict_helper(desc, row) for row in cursor.fetchall()]
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
new file mode 100644
index 0000000000..d708fa60bc
--- /dev/null
+++ b/django/db/models/__init__.py
@@ -0,0 +1,40 @@
+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 import signals
+from django.utils.functional import curry
+from django.utils.text import capfirst
+
+# Admin stages.
+ADD, CHANGE, BOTH = 1, 2, 3
+
+class LazyDate:
+ """
+ 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
+
+ def __getattr__(self, attr):
+ return getattr(self.__get_value__(), attr)
diff --git a/django/db/models/base.py b/django/db/models/base.py
new file mode 100644
index 0000000000..2185471e2b
--- /dev/null
+++ b/django/db/models/base.py
@@ -0,0 +1,401 @@
+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.related import RelatedObject
+from django.db.models.query import orderlist2sql, 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
+from django.dispatch import dispatcher
+from django.utils.datastructures import SortedDict
+from django.utils.functional import curry
+from django.conf import settings
+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 not bases or bases == (object,):
+ return type.__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]
+
+ # 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)
+ return new_class
+
+class Model(object):
+ __metaclass__ = ModelBase
+
+ def _get_pk_val(self):
+ return getattr(self, self._meta.pk.attname)
+
+ def __repr__(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)
+ for f in self._meta.fields:
+ if isinstance(f.rel, ManyToOneRel):
+ try:
+ # Assume object instance was passed in.
+ rel_obj = kwargs.pop(f.name)
+ except KeyError:
+ try:
+ # Object instance wasn't passed in -- must be an ID.
+ val = kwargs.pop(f.attname)
+ except KeyError:
+ val = f.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 f.null:
+ val = None
+ else:
+ try:
+ val = getattr(rel_obj, f.rel.get_related_field().attname)
+ except AttributeError:
+ raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
+ setattr(self, f.attname, val)
+ else:
+ val = kwargs.pop(f.attname, f.get_default())
+ setattr(self, f.attname, val)
+ if kwargs:
+ raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
+ for i, arg in enumerate(args):
+ setattr(self, self._meta.fields[i].attname, arg)
+ 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(getattr(self, f.attname), False)) for f in non_pks]
+ 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.attname)),
+ 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(getattr(self, f.attname), 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(getattr(self, f.column), 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))
+ cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
+ (backend.quote_name(self._meta.db_table), ','.join(field_names),
+ ','.join(placeholders)), db_values)
+ 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):
+ 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.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)])
+ return q[0]
+
+ 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(opts.db_table), backend.quote_name(opts.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):
+ return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
+
+ 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):
+ 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.
+ 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)
+
+ # Handles setting many-to-many related objects.
+ # Example: Album.set_songs()
+ def _set_related_many_to_many(self, rel_class, rel_field, id_list):
+ id_list = map(int, id_list) # normalize to integers
+ rel = rel_field.rel.to
+ m2m_table = rel_field.m2m_db_table()
+ this_id = self._get_pk_val()
+ cursor = connection.cursor()
+ cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
+ (backend.quote_name(m2m_table),
+ backend.quote_name(rel_field.m2m_column_name())), [this_id])
+ sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
+ (backend.quote_name(m2m_table),
+ backend.quote_name(rel_field.m2m_column_name()),
+ backend.quote_name(rel_field.m2m_reverse_name()))
+ cursor.executemany(sql, [(this_id, i) for i in id_list])
+ transaction.commit_unless_managed()
+
+############################################
+# 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.db_table), backend.quote_name('_order'),
+ backend.quote_name(ordered_obj.order_with_respect_to.column),
+ backend.quote_name(ordered_obj.pk.column))
+ rel_val = getattr(self, ordered_obj.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/django/core/meta/fields.py b/django/db/models/fields/__init__.py
index 8eeb9550c6..8cc17079a9 100644
--- a/django/core/meta/fields.py
+++ b/django/db/models/fields/__init__.py
@@ -1,10 +1,13 @@
+from django.db.models import signals
+from django.dispatch import dispatcher
from django.conf import settings
-from django.core import formfields, validators
+from django.core import validators
+from django import forms
from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import curry, lazy
from django.utils.text import capfirst
-from django.utils.translation import gettext_lazy, ngettext
-import datetime, os
+from django.utils.translation import gettext, gettext_lazy, ngettext
+import datetime, os, time
class NOT_PROVIDED:
pass
@@ -16,71 +19,29 @@ HORIZONTAL, VERTICAL = 1, 2
BLANK_CHOICE_DASH = [("", "---------")]
BLANK_CHOICE_NONE = [("", "None")]
-# Values for Relation.edit_inline.
-TABULAR, STACKED = 1, 2
-
-RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
-
# prepares a value for use in a LIKE query
prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_")
# returns the <ul> class for a given radio_admin value
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
-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 manipulator_valid_rel_key(f, self, field_data, all_data):
- "Validates that the value is a valid foreign key"
- mod = f.rel.to.get_model_module()
- try:
- mod.get_object(pk=field_data)
- except ObjectDoesNotExist:
- raise validators.ValidationError, _("Please enter a valid %s.") % f.verbose_name
+class FieldDoesNotExist(Exception):
+ pass
def manipulator_validator_unique(f, opts, self, field_data, all_data):
"Validates that the value is unique for this field."
- if f.rel and isinstance(f.rel, ManyToOneRel):
- lookup_type = '%s__%s__exact' % (f.name, f.rel.get_related_field().name)
- else:
- lookup_type = '%s__exact' % f.name
+ lookup_type = f.get_validator_unique_lookup_type()
try:
- old_obj = opts.get_model_module().get_object(**{lookup_type: field_data})
+ old_obj = self.manager.get(**{lookup_type: field_data})
except ObjectDoesNotExist:
return
- if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname):
+ if getattr(self, 'original_object', None) and self.original_object._get_pk_val() == old_obj._get_pk_val():
return
- raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
-
-class BoundField(object):
- def __init__(self, field, field_mapping, original):
- self.field = field
- self.original = original
- self.form_fields = self.resolve_form_fields(field_mapping)
-
- def resolve_form_fields(self, field_mapping):
- return [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
-
- def as_field_list(self):
- return [self.field]
-
- def original_value(self):
- if self.original:
- return self.original.__dict__[self.field.column]
-
- def __repr__(self):
- return "BoundField:(%s, %s)" % (self.field.name, self.form_fields)
+ 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 specified in the model.
+# * 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.
@@ -103,13 +64,13 @@ class Field(object):
creation_counter = 0
def __init__(self, verbose_name=None, name=None, primary_key=False,
- maxlength=None, unique=False, blank=False, null=False, db_index=None,
+ maxlength=None, unique=False, blank=False, null=False, db_index=False,
core=False, rel=None, default=NOT_PROVIDED, editable=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 or (name and name.replace('_', ' '))
+ self.verbose_name = verbose_name
self.primary_key = primary_key
self.maxlength, self.unique = maxlength, unique
self.blank, self.null = blank, null
@@ -123,39 +84,70 @@ class Field(object):
self.radio_admin = radio_admin
self.help_text = help_text
self.db_column = db_column
- if rel and isinstance(rel, ManyToManyRel):
- if rel.raw_id_admin:
- self.help_text = string_concat(self.help_text,
- gettext_lazy(' Separate multiple IDs with commas.'))
- else:
- self.help_text = string_concat(self.help_text,
- gettext_lazy(' Hold down "Control", or "Command" on a Mac, to select more than one.'))
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
- if db_index is None:
- if isinstance(rel, OneToOneRel) or isinstance(rel, ManyToOneRel):
- self.db_index = True
- else:
- self.db_index = False
- else:
- self.db_index = 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 set_name(self, name):
self.name = name
self.verbose_name = self.verbose_name or name.replace('_', ' ')
self.attname, self.column = self.get_attname_column()
+ def get_attname(self):
+ return self.name
+
def get_attname_column(self):
- if isinstance(self.rel, ManyToOneRel):
- attname = '%s_id' % self.name
- else:
- attname = self.name
+ attname = self.get_attname()
column = self.db_column or attname
return attname, column
@@ -198,8 +190,8 @@ class Field(object):
def get_default(self):
"Returns the default value for this field."
if self.default is not NOT_PROVIDED:
- if hasattr(self.default, '__get_value__'):
- return self.default.__get_value__()
+ if callable(self.default):
+ return self.default()
return self.default
if not self.empty_strings_allowed or self.null:
return None
@@ -211,42 +203,32 @@ class Field(object):
"""
return [name_prefix + self.name]
- def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
- """
- Returns a list of formfields.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.
- """
+ 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 isinstance(self.rel, ManyToOneRel):
- params['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 = [formfields.RadioSelectField]
- params['ul_class'] = get_ul_class(self.radio_admin)
- else:
- if self.null:
- field_objs = [formfields.NullSelectField]
- else:
- field_objs = [formfields.SelectField]
- params['choices'] = self.get_choices_default()
- elif self.choices:
+
+ if self.choices:
if self.radio_admin:
- field_objs = [formfields.RadioSelectField]
+ field_objs = [forms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
- field_objs = [formfields.SelectField]
+ field_objs = [forms.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 forms.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:
@@ -269,6 +251,11 @@ class Field(object):
# 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 or validator_list.
+ if isinstance(self, BooleanField):
+ del params['validator_list'], 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.
@@ -282,15 +269,13 @@ class Field(object):
if core_field_names:
params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required.")))
- # BooleanFields (CheckboxFields) are a special case. They don't take
- # is_required or validator_list.
- if isinstance(self, BooleanField):
- del params['validator_list'], params['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
@@ -298,32 +283,31 @@ class Field(object):
"""
if rel:
return new_data.get(self.name, [self.get_default()])[0]
- else:
- val = new_data.get(self.name, self.get_default())
- if not self.empty_strings_allowed and val == '' and self.null:
- val = None
- return val
+ 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_obj = self.rel.to
- return first_choice + [(getattr(x, rel_obj.pk.attname), str(x))
- for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
+ rel_model = self.rel.to
+ return first_choice + [(x._get_pk_val(), str(x))
+ for x in rel_model._default_manager.filter(**self.rel.limit_choices_to)]
def get_choices_default(self):
- if(self.radio_admin):
+ 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)
+ return getattr(obj, self.attname)
else:
- return self.get_default()
+ return self.get_default()
def flatten_data(self, follow, obj=None):
"""
@@ -339,57 +323,101 @@ class Field(object):
else:
return self.editable
- def bind(self, fieldmapping, original, bound_field_class=BoundField):
+ def bind(self, fieldmapping, original, bound_field_class):
return bound_field_class(self, fieldmapping, original)
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 get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
+ 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)
+ return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
def get_manipulator_field_objs(self):
- return [formfields.HiddenField]
+ return [forms.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
+
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 is 't': return True
+ if value is 'f': return False
+ raise validators.ValidationError, gettext("This value must be either True or False.")
+
def get_manipulator_field_objs(self):
- return [formfields.CheckboxField]
+ return [forms.CheckboxField]
class CharField(Field):
def get_manipulator_field_objs(self):
- return [formfields.TextField]
+ return [forms.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)
+
+# TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField):
def get_manipulator_field_objs(self):
- return [formfields.CommaSeparatedIntegerField]
+ return [forms.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...
+ #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 isinstance(value, datetime.datetime):
+ return value.date()
+ if isinstance(value, datetime.date):
+ return value
+ validators.isValidANSIDate(value, None)
+ return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
+
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', 'ne'):
+ value = value.strftime('%Y-%m-%d')
else:
value = str(value)
return Field.get_db_prep_lookup(self, lookup_type, value)
@@ -399,6 +427,14 @@ class DateField(Field):
return datetime.datetime.now()
return value
+ 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:
@@ -413,13 +449,29 @@ class DateField(Field):
return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self):
- return [formfields.DateField]
+ return [forms.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 '')}
class DateTimeField(DateField):
+ def to_python(self, 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:
@@ -430,8 +482,15 @@ class DateTimeField(DateField):
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 [formfields.DateField, formfields.TimeField]
+ return [forms.DateField, forms.TimeField]
def get_manipulator_field_names(self, name_prefix):
return [name_prefix + self.name + '_date', name_prefix + self.name + '_time']
@@ -454,25 +513,27 @@ class DateTimeField(DateField):
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 '')}
-class EmailField(Field):
+class EmailField(CharField):
def __init__(self, *args, **kwargs):
kwargs['maxlength'] = 75
- Field.__init__(self, *args, **kwargs)
+ CharField.__init__(self, *args, **kwargs)
def get_internal_type(self):
return "CharField"
def get_manipulator_field_objs(self):
- return [formfields.EmailField]
+ return [forms.EmailField]
+
+ def validate(self, field_data, all_data):
+ validators.isValidEmail(field_data, all_data)
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):
- field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
-
+ 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.
@@ -507,8 +568,25 @@ class FileField(Field):
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: instance._save_FIELD_file(self, filename, raw_contents))
+ 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 [formfields.FileUploadField, formfields.HiddenField]
+ return [forms.FileUploadField, forms.HiddenField]
def get_manipulator_field_names(self, name_prefix):
return [name_prefix + self.name + '_file', name_prefix + self.name]
@@ -536,7 +614,7 @@ class FilePathField(Field):
Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [curry(formfields.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
+ return [curry(forms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
class FloatField(Field):
empty_strings_allowed = False
@@ -545,7 +623,7 @@ class FloatField(Field):
Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [curry(formfields.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
+ return [curry(forms.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):
@@ -553,7 +631,16 @@ class ImageField(FileField):
FileField.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [formfields.ImageUploadField, formfields.HiddenField]
+ return [forms.ImageUploadField, forms.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):
FileField.save_file(self, new_data, new_object, original_object, change, rel)
@@ -570,7 +657,7 @@ class ImageField(FileField):
class IntegerField(Field):
empty_strings_allowed = False
def get_manipulator_field_objs(self):
- return [formfields.IntegerField]
+ return [forms.IntegerField]
class IPAddressField(Field):
def __init__(self, *args, **kwargs):
@@ -578,7 +665,10 @@ class IPAddressField(Field):
Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self):
- return [formfields.IPAddressField]
+ return [forms.IPAddressField]
+
+ def validate(self, field_data, all_data):
+ validators.isValidIPAddress4(field_data, None)
class NullBooleanField(Field):
def __init__(self, *args, **kwargs):
@@ -586,23 +676,25 @@ class NullBooleanField(Field):
Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self):
- return [formfields.NullBooleanField]
+ return [forms.NullBooleanField]
class PhoneNumberField(IntegerField):
def get_manipulator_field_objs(self):
- return [formfields.PhoneNumberField]
+ return [forms.PhoneNumberField]
+
+ def validate(self, field_data, all_data):
+ validators.isValidPhone(field_data, all_data)
class PositiveIntegerField(IntegerField):
def get_manipulator_field_objs(self):
- return [formfields.PositiveIntegerField]
+ return [forms.PositiveIntegerField]
class PositiveSmallIntegerField(IntegerField):
def get_manipulator_field_objs(self):
- return [formfields.PositiveSmallIntegerField]
+ return [forms.PositiveSmallIntegerField]
class SlugField(Field):
def __init__(self, *args, **kwargs):
- # Default to a maxlength of 50 but allow overrides.
kwargs['maxlength'] = kwargs.get('maxlength', 50)
kwargs.setdefault('validator_list', []).append(validators.isSlug)
# Set db_index=True unless it's been set manually.
@@ -611,20 +703,20 @@ class SlugField(Field):
Field.__init__(self, *args, **kwargs)
def get_manipulator_field_objs(self):
- return [formfields.TextField]
+ return [forms.TextField]
class SmallIntegerField(IntegerField):
def get_manipulator_field_objs(self):
- return [formfields.SmallIntegerField]
+ return [forms.SmallIntegerField]
class TextField(Field):
def get_manipulator_field_objs(self):
- return [formfields.LargeTextField]
+ return [forms.LargeTextField]
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
+ 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)
@@ -652,7 +744,7 @@ class TimeField(Field):
return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self):
- return [formfields.TimeField]
+ return [forms.TimeField]
def flatten_data(self,follow, obj = None):
val = self._get_val_from_obj(obj)
@@ -665,11 +757,11 @@ class URLField(Field):
Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self):
- return [formfields.URLField]
+ return [forms.URLField]
class USStateField(Field):
def get_manipulator_field_objs(self):
- return [formfields.USStateField]
+ return [forms.USStateField]
class XMLField(TextField):
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
@@ -680,277 +772,17 @@ class XMLField(TextField):
return "TextField"
def get_manipulator_field_objs(self):
- return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)]
-
-class ForeignKey(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 to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model 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)
-
- 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 [formfields.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)
-
-class ManyToManyField(Field):
- def __init__(self, to, **kwargs):
- kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
- kwargs['rel'] = ManyToManyRel(to, kwargs.pop('singular', None),
- 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))
- if kwargs["rel"].raw_id_admin:
- kwargs.setdefault("validator_list", []).append(self.isValidIDList)
- Field.__init__(self, **kwargs)
-
- def get_manipulator_field_objs(self):
- if self.rel.raw_id_admin:
- return [formfields.RawIdAdminField]
- else:
- choices = self.get_choices_default()
- return [curry(formfields.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, original_opts):
- "Returns the name of the many-to-many 'join' table."
- return '%s_%s' % (original_opts.db_table, self.name)
-
- def isValidIDList(self, field_data, all_data):
- "Validates that the value is a valid list of foreign keys"
- mod = self.rel.to.get_model_module()
- try:
- pks = map(int, field_data.split(','))
- except ValueError:
- # the CommaSeparatedIntegerField validator will catch this error
- return
- objects = mod.get_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:
- get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular)
- instance_ids = [getattr(instance, self.rel.to.pk.attname) for instance in get_list_func()]
- 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
-
-class OneToOneField(IntegerField):
- def __init__(self, to, to_field=None, **kwargs):
- kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
- to_field = to_field or to._meta.pk.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)
-
-class ManyToOneRel:
- 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:
- self.to = to._meta
- except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
- assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT
- self.to = to
- self.field_name = 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
- self.limit_choices_to = limit_choices_to or {}
- self.lookup_overrides = lookup_overrides or {}
- self.raw_id_admin = raw_id_admin
-
- def get_related_field(self):
- "Returns the Field in the 'to' object to which this relationship is tied."
- return self.to.get_field(self.field_name)
-
-class ManyToManyRel:
- def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
- filter_interface=None, limit_choices_to=None, raw_id_admin=False):
- self.to = to._meta
- self.singular = singular or to._meta.object_name.lower()
- self.num_in_admin = num_in_admin
- self.related_name = related_name
- self.filter_interface = filter_interface
- self.limit_choices_to = limit_choices_to or {}
- self.edit_inline = False
- self.raw_id_admin = raw_id_admin
- assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
-
-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._meta, field_name
- self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
- self.related_name = related_name
- self.limit_choices_to = limit_choices_to or {}
- self.lookup_overrides = lookup_overrides or {}
- self.raw_id_admin = raw_id_admin
-
-class BoundFieldLine(object):
- def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField):
- self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line]
+ return [curry(forms.XMLLargeTextField, schema_path=self.schema_path)]
- def __iter__(self):
- for bound_field in self.bound_fields:
- yield bound_field
-
- def __len__(self):
- return len(self.bound_fields)
-
-class FieldLine(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=BoundFieldLine):
- 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)
-
-class BoundFieldSet(object):
- def __init__(self, field_set, field_mapping, original, bound_field_line_class=BoundFieldLine):
- self.name = field_set.name
- self.classes = field_set.classes
- self.bound_field_lines = [field_line.bind(field_mapping,original, bound_field_line_class) 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)
-
-class FieldSet(object):
- def __init__(self, name, classes, field_locator_func, line_specs):
- self.name = name
- self.field_lines = [FieldLine(field_locator_func, line_spec) for line_spec in line_specs]
- self.classes = classes
-
- def __repr__(self):
- return "FieldSet:(%s,%s)" % (self.name, self.field_lines)
-
- def bind(self, field_mapping, original, bound_field_set_class=BoundFieldSet):
- 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 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 )
-class Admin:
- def __init__(self, fields=None, js=None, list_display=None, list_filter=None, date_hierarchy=None,
- save_as=False, ordering=None, search_fields=None, save_on_top=False, list_select_related=False):
- self.fields = fields
- self.js = js or []
- self.list_display = list_display or ['__repr__']
- 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
+ def get_internal_type(self):
+ return "IntegerField"
- def get_field_sets(self, opts):
- 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:
- name = fieldset[0]
- fs_options = fieldset[1]
- classes = fs_options.get('classes', ())
- line_specs = fs_options['fields']
- new_fieldset_list.append(FieldSet(name, classes, opts.get_field, line_specs))
- return new_fieldset_list
+ def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
+ return [forms.HiddenField(name_prefix + self.name)]
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
new file mode 100644
index 0000000000..908aa75207
--- /dev/null
+++ b/django/db/models/fields/related.py
@@ -0,0 +1,718 @@
+from django.db import backend, connection, transaction
+from django.db.models import signals
+from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
+from django.db.models.related import RelatedObject
+from django.utils.translation import gettext_lazy, string_concat
+from django.utils.functional import curry
+from django.core import validators
+from django import 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)
+ 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(pk=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_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, "'%s' is not related to '%s'." % (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()
+ for obj in value:
+ manager.add(obj)
+
+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
+ if instance:
+ self._pk_val = self.instance._get_pk_val()
+
+ 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
+ from django.db import connection
+
+ # 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
+ new_ids = set([obj._get_pk_val() for obj in objs])
+ 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
+
+ for obj in objs:
+ if not isinstance(obj, self.model):
+ raise ValueError, "objects to remove() must be %s instances" % self.model._meta.object_name
+ # Remove the specified objects from the join table
+ cursor = connection.cursor()
+ for obj in objs:
+ cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \
+ (self.join_table, source_col_name, target_col_name),
+ [self._pk_val, obj._get_pk_val()])
+ 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()
+ for obj in value:
+ manager.add(obj)
+
+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()
+ for obj in value:
+ manager.add(obj)
+
+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 = [forms.RadioSelectField]
+ params['ul_class'] = get_ul_class(self.radio_admin)
+ else:
+ if self.null:
+ field_objs = [forms.NullSelectField]
+ else:
+ field_objs = [forms.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 [forms.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))
+
+class OneToOneField(RelatedField, IntegerField):
+ def __init__(self, to, to_field=None, **kwargs):
+ kwargs['verbose_name'] = kwargs.get('verbose_name', '')
+ to_field = to_field or to._meta.pk.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 = [forms.RadioSelectField]
+ params['ul_class'] = get_ul_class(self.radio_admin)
+ else:
+ if self.null:
+ field_objs = [forms.NullSelectField]
+ else:
+ field_objs = [forms.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
+
+class ManyToManyField(RelatedField, Field):
+ def __init__(self, to, **kwargs):
+ kwargs['verbose_name'] = kwargs.get('verbose_name', None)
+ kwargs['rel'] = ManyToManyRel(to, kwargs.pop('singular', None),
+ 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))
+ 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 [forms.RawIdAdminField]
+ else:
+ choices = self.get_choices_default()
+ return [curry(forms.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"
+ 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))
+
+ self.rel.singular = self.rel.singular or self.rel.to._meta.object_name.lower()
+
+ # 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
+
+class ManyToOneRel:
+ 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
+ self.limit_choices_to = limit_choices_to or {}
+ 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
+ self.limit_choices_to = limit_choices_to or {}
+ self.lookup_overrides = lookup_overrides or {}
+ self.raw_id_admin = raw_id_admin
+ self.multiple = False
+
+class ManyToManyRel:
+ def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
+ filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
+ self.to = to
+ self.singular = singular or None
+ self.num_in_admin = num_in_admin
+ self.related_name = related_name
+ self.filter_interface = filter_interface
+ self.limit_choices_to = limit_choices_to or {}
+ 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/django/db/models/loading.py b/django/db/models/loading.py
new file mode 100644
index 0000000000..a9e0348f8e
--- /dev/null
+++ b/django/db/models/loading.py
@@ -0,0 +1,71 @@
+"Utilities for loading models and the modules that contain them."
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+
+__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models')
+
+_app_list = None # Cache of installed apps.
+_app_models = {} # Dictionary of models against app label
+ # Each value is a dictionary of model name: model class
+
+def get_apps():
+ "Returns a list of all installed modules that contain models."
+ global _app_list
+ if _app_list is not None:
+ return _app_list
+ _app_list = []
+ for app_name in settings.INSTALLED_APPS:
+ try:
+ _app_list.append(__import__(app_name, '', '', ['models']).models)
+ except (ImportError, AttributeError), e:
+ pass
+ return _app_list
+
+def get_app(app_label):
+ "Returns the module containing the models for the given app_label."
+ for app_name in settings.INSTALLED_APPS:
+ if app_label == app_name.split('.')[-1]:
+ return __import__(app_name, '', '', ['models']).models
+ raise ImproperlyConfigured, "App with label %s could not be found" % app_label
+
+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):
+ """
+ Returns the model matching the given app_label and case-insensitive model_name.
+ Returns None if no model is found.
+ """
+ get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
+ 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, {})
+ model_dict[model_name] = model
diff --git a/django/db/models/manager.py b/django/db/models/manager.py
new file mode 100644
index 0000000000..d847631c82
--- /dev/null
+++ b/django/db/models/manager.py
@@ -0,0 +1,101 @@
+from django.utils.functional import curry
+from django.db import backend, connection
+from django.db.models.query import QuerySet
+from django.dispatch import dispatcher
+from django.db.models import signals
+from django.utils.datastructures import SortedDict
+
+# 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.
+ if hasattr(cls, 'objects'):
+ raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name
+ 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_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 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 filter(self, *args, **kwargs):
+ return self.get_query_set().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/django/db/models/manipulators.py b/django/db/models/manipulators.py
new file mode 100644
index 0000000000..fc553bc90c
--- /dev/null
+++ b/django/db/models/manipulators.py
@@ -0,0 +1,330 @@
+from django.core.exceptions import ObjectDoesNotExist
+from django import forms
+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, MultiValueDict
+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(forms.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(formfields.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, save the basic object itself.
+ new_object = self.model(**params)
+ new_object.save()
+
+ # Now that the object's been saved, 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)
+
+ # 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[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.field_name)
+ 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:
+ was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname])
+ if self.change and was_changed:
+ 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.
+ lookup_kwargs = 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().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.attname, 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(field_name_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 = forms.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/django/db/models/options.py b/django/db/models/options.py
new file mode 100644
index 0000000000..d1f5eeb756
--- /dev/null
+++ b/django/db/models/options.py
@@ -0,0 +1,269 @@
+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])', ' \\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:
+ 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
+
+ def __repr__(self):
+ return '<Options for %s>' % self.object_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, "name=%s" % 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:
+ def __init__(self, fields=None, js=None, list_display=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_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/django/db/models/query.py b/django/db/models/query.py
new file mode 100644
index 0000000000..365ead2a3a
--- /dev/null
+++ b/django/db/models/query.py
@@ -0,0 +1,888 @@
+from django.db import backend, connection, transaction
+from django.db.models.fields import DateField, FieldDoesNotExist
+from django.db.models import signals
+from django.dispatch import dispatcher
+from django.utils.datastructures import SortedDict
+
+import operator
+
+# For Python 2.3
+if not hasattr(__builtins__, 'set'):
+ from sets import Set as set
+
+LOOKUP_SEPARATOR = '__'
+
+# 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
+
+####################
+# 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 ' ' in word:
+ 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._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 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:
+ return self._clone(_offset=k, _limit=1).get()
+ 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."
+ # 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()
+ select, sql, params = self._get_sql_clause()
+ 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(self.model, row, 0)
+ 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."
+ counter = self._clone()
+ counter._order_by = ()
+ counter._offset = None
+ counter._limit = None
+ counter._select_related = False
+ select, sql, params = counter._get_sql_clause()
+ cursor = connection.cursor()
+ cursor.execute("SELECT COUNT(*)" + sql, params)
+ return cursor.fetchone()[0]
+
+ def get(self, *args, **kwargs):
+ "Performs the SELECT and returns a single object matching the given keyword arguments."
+ clone = self.filter(*args, **kwargs)
+ if not clone._order_by:
+ clone._order_by = ()
+ obj_list = list(clone)
+ if len(obj_list) < 1:
+ raise self.model.DoesNotExist, "%s does not exist for %s" % (self.model._meta.object_name, kwargs)
+ 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 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(Q, *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, qtype, *args, **kwargs):
+ 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 & qtype(**kwargs)
+ if len(args) > 0:
+ clone._filters = clone._filters & reduce(operator.and_, args)
+ return clone
+
+ def select_related(self, true_or_false=True):
+ "Returns a new QuerySet instance with '_select_related' modified."
+ return self._clone(_select_related=true_or_false)
+
+ 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._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' 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.
+ tables2, joins2, where2, params2 = self._filters.get_sql(opts)
+ tables.extend(tables2)
+ 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, opts.db_table, [opts.db_table])
+
+ # 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 iterator(self):
+ # select_related and select aren't supported in values().
+ self._select_related = False
+ self._select = {}
+
+ # 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]
+
+ cursor = connection.cursor()
+ select, sql, params = self._get_sql_clause()
+ select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
+ 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:
+ date_query._where.append('%s.%s IS NOT NULL' % \
+ (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
+ select, sql, params = self._get_sql_clause()
+ 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 QOperator:
+ "Base class for QAnd and QOr"
+ def __init__(self, *args):
+ self.args = args
+
+ def get_sql(self, opts):
+ tables, joins, where, params = [], SortedDict(), [], []
+ for val in self.args:
+ tables2, joins2, where2, params2 = val.get_sql(opts)
+ tables.extend(tables2)
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ if where:
+ return tables, joins, ['(%s)' % self.operator.join(where)], params
+ return tables, 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 get_sql(self, opts):
+ tables, joins, where, params = super(QNot, self).get_sql(opts)
+ where2 = ['(NOT (%s))' % " AND ".join(where)]
+ return tables, 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':
+ return '%s%s IN (%s)' % (table_prefix, field_name, ','.join(['%s' for v in value]))
+ elif lookup_type == 'range':
+ return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name)
+ elif lookup_type in ('year', '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 ''))
+ raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
+
+def get_cached_row(klass, row, index_start):
+ "Helper function that recursively returns an object with cache filled"
+ 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:
+ rel_obj, index_end = get_cached_row(f.rel.to, row, index_end)
+ 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):
+ """
+ Helper function that recursively populates the select, tables and where (in
+ place) for fill-cache queries.
+ """
+ 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(backend.quote_name(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' % (backend.quote_name(db_table), backend.quote_name(new_prefix)))
+ db_table = new_prefix
+ cache_tables_seen.append(db_table)
+ where.append('%s.%s = %s.%s' % \
+ (backend.quote_name(old_prefix), backend.quote_name(f.column),
+ backend.quote_name(db_table), backend.quote_name(f.rel.get_related_field().column)))
+ select.extend(['%s.%s' % (backend.quote_name(db_table), backend.quote_name(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)
+
+def parse_lookup(kwarg_items, opts):
+ # Helper function that handles converting API kwargs
+ # (e.g. "name__exact": "tom") to SQL.
+
+ # '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.
+ tables, joins, where, params = [], SortedDict(), [], []
+
+ for kwarg, value in kwarg_items:
+ if value is not None:
+ path = kwarg.split(LOOKUP_SEPARATOR)
+ # Extract the last elements of the kwarg.
+ # The very-last is the clause (equals, like, etc).
+ # The second-last is the table column on which the clause is
+ # to be performed.
+ # The exceptions to this are:
+ # 1) "pk", which is an implicit id__exact;
+ # if we find "pk", make the clause "exact', and insert
+ # a dummy name of None, which we will replace when
+ # we know which table column to grab as the primary key.
+ # 2) If there is only one part, assume it to be an __exact
+ clause = path.pop()
+ if clause == 'pk':
+ clause = 'exact'
+ path.append(None)
+ elif len(path) == 0:
+ path.append(clause)
+ clause = 'exact'
+
+ if len(path) < 1:
+ raise TypeError, "Cannot parse keyword query %r" % kwarg
+
+ tables2, joins2, where2, params2 = lookup_inner(path, clause, value, opts, opts.db_table, None)
+ tables.extend(tables2)
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ return tables, 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, clause, value, opts, table, column):
+ tables, 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:
+ 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 + LOOKUP_SEPARATOR + 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 + LOOKUP_SEPARATOR + 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 + LOOKUP_SEPARATOR + 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 + LOOKUP_SEPARATOR + name
+ new_opts = field.rel.to._meta
+ new_column = new_opts.pk.column
+ join_column = field.column
+
+ raise FieldFound
+
+ except FieldFound: # Match found, loop has been shortcut.
+ pass
+ except: # Any other exception; rethrow
+ raise
+ else: # No match found.
+ raise TypeError, "Cannot resolve keyword '%s' into field" % name
+
+ # Check to see if an intermediate join is required between current_table
+ # and new_table.
+ if intermediate_table:
+ joins[backend.quote_name(current_table)] = (
+ backend.quote_name(intermediate_table),
+ "LEFT OUTER JOIN",
+ "%s.%s = %s.%s" % \
+ (backend.quote_name(table),
+ backend.quote_name(current_opts.pk.column),
+ backend.quote_name(current_table),
+ backend.quote_name(intermediate_column))
+ )
+
+ if path:
+ if len(path) == 1 and path[0] in (new_opts.pk.name, None) \
+ and clause in ('exact', 'isnull') and not join_required:
+ # If the last name query is for a 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[backend.quote_name(new_table)] = (
+ backend.quote_name(new_opts.db_table),
+ "INNER JOIN",
+ "%s.%s = %s.%s" %
+ (backend.quote_name(current_table),
+ backend.quote_name(join_column),
+ backend.quote_name(new_table),
+ backend.quote_name(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.
+ tables2, joins2, where2, params2 = lookup_inner(path, clause, value, new_opts, new_table, join_column)
+
+ tables.extend(tables2)
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ else:
+ # Evaluate clause on current table.
+ if name in (current_opts.pk.name, None) and clause in ('exact', 'isnull') and current_column:
+ # If this is an exact/isnull key search, and the last pass
+ # found/introduced a current/intermediate table that we can use to
+ # optimize the query, then use that column name.
+ column = current_column
+ else:
+ column = field.column
+
+ where.append(get_where_clause(clause, current_table + '.', column, value))
+ params.extend(field.get_db_prep_lookup(clause, value))
+
+ return tables, joins, where, params
+
+def delete_objects(seen_objs):
+ "Iterate through a list of seen classes, and remove any instances that are referred to"
+ 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():
+ for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
+ cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
+ (backend.quote_name(related.field.m2m_db_table()),
+ backend.quote_name(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:
+ for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
+ cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
+ (backend.quote_name(f.m2m_db_table()),
+ backend.quote_name(f.m2m_column_name()),
+ ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
+ pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
+ 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)" % \
+ (backend.quote_name(cls._meta.db_table),
+ backend.quote_name(field.column),
+ backend.quote_name(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)" % \
+ (backend.quote_name(cls._meta.db_table),
+ backend.quote_name(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/django/db/models/related.py b/django/db/models/related.py
new file mode 100644
index 0000000000..4ab8cde5e7
--- /dev/null
+++ b/django/db/models/related.py
@@ -0,0 +1,132 @@
+class BoundRelatedObject(object):
+ def __init__(self, related_object, field_mapping, original):
+ self.relation = related_object
+ self.field_mappings = field_mapping[related_object.opts.module_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 = 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:
+ return [None] * self.field.rel.num_in_admin
+
+ 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
+ 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 = self.field.rel.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:
+ 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/django/db/models/signals.py b/django/db/models/signals.py
new file mode 100644
index 0000000000..2171cb1bf3
--- /dev/null
+++ b/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/django/db/transaction.py b/django/db/transaction.py
new file mode 100644
index 0000000000..906995ca02
--- /dev/null
+++ b/django/db/transaction.py
@@ -0,0 +1,219 @@
+"""
+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.
+"""
+
+import 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 explicitely 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/django/dispatch/__init__.py b/django/dispatch/__init__.py
new file mode 100644
index 0000000000..bccae2a2da
--- /dev/null
+++ b/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/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
new file mode 100644
index 0000000000..d93f696685
--- /dev/null
+++ b/django/dispatch/dispatcher.py
@@ -0,0 +1,497 @@
+"""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.)
+"""
+from __future__ import generators
+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]
+
+try:
+ True
+except NameError:
+ True = 1==1
+ False = 1==0
+
+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)
+ if connections.has_key(senderkey):
+ signals = connections[senderkey]
+ else:
+ connections[senderkey] = signals = {}
+ # 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.
+ """
+ try:
+ return connections[id(sender)][signal]
+ except KeyError:
+ 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 receivers which should receive
+ the given signal from sender, each receiver should
+ be produced only once by the resulting generator
+ """
+ receivers = {}
+ for set in (
+ # Get receivers that receive *this* signal from *this* sender.
+ getReceivers( sender, signal ),
+ # Add receivers that receive *any* signal from *this* sender.
+ getReceivers( sender, Any ),
+ # Add receivers that receive *this* signal from *any* sender.
+ getReceivers( Any, signal ),
+ # Add receivers that receive *any* signal from *any* sender.
+ getReceivers( Any, Any ),
+ ):
+ for receiver in set:
+ if receiver: # filter out dead instance-method weakrefs
+ try:
+ if not receivers.has_key( receiver ):
+ 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 liveReceivers(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)
+ try:
+ del connections[senderkey]
+ except KeyError:
+ pass
+ # Senderkey will only be in senders dictionary if sender
+ # could be weakly referenced.
+ try:
+ del senders[senderkey]
+ except:
+ pass
+
+
+def _removeBackrefs( senderkey):
+ """Remove all back-references to this senderkey"""
+ try:
+ signals = connections[senderkey]
+ except KeyError:
+ signals = None
+ else:
+ items = signals.items()
+ def allReceivers( ):
+ for signal,set in items:
+ for item in set:
+ yield item
+ for receiver in allReceivers():
+ _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)
+ set = sendersBack.get( receiverkey, () )
+ while senderkey in set:
+ try:
+ set.remove( senderkey )
+ except:
+ break
+ if not set:
+ try:
+ del sendersBack[ receiverkey ]
+ except KeyError:
+ pass
+ return True
diff --git a/django/dispatch/errors.py b/django/dispatch/errors.py
new file mode 100644
index 0000000000..a2eb32ed75
--- /dev/null
+++ b/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/django/dispatch/license.txt b/django/dispatch/license.txt
new file mode 100644
index 0000000000..2f0b6b5ef2
--- /dev/null
+++ b/django/dispatch/license.txt
@@ -0,0 +1,34 @@
+PyDispatcher License
+
+ Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
+ 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.
+
+ The name of Patrick K. O'Brien, or the name of any Contributor,
+ may not 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 HOLDERS AND 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.
+
diff --git a/django/dispatch/robust.py b/django/dispatch/robust.py
new file mode 100644
index 0000000000..ba19934d43
--- /dev/null
+++ b/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/django/dispatch/robustapply.py b/django/dispatch/robustapply.py
new file mode 100644
index 0000000000..0350e60cfc
--- /dev/null
+++ b/django/dispatch/robustapply.py
@@ -0,0 +1,49 @@
+"""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)
+
+ \ No newline at end of file
diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py
new file mode 100644
index 0000000000..6b3eda1d38
--- /dev/null
+++ b/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/django/core/formfields.py b/django/forms/__init__.py
index 167439cc07..b0c1c2004f 100644
--- a/django/core/formfields.py
+++ b/django/forms/__init__.py
@@ -1,8 +1,8 @@
from django.core import validators
from django.core.exceptions import PermissionDenied
from django.utils.html import escape
-from django.conf.settings import DEFAULT_CHARSET
-from django.utils.translation import gettext_lazy, ngettext
+from django.conf import settings
+from django.utils.translation import gettext, gettext_lazy, ngettext
FORM_FIELD_ID_PREFIX = 'id_'
@@ -10,7 +10,7 @@ class EmptyValue(Exception):
"This is raised when empty data is provided"
pass
-class Manipulator:
+class Manipulator(object):
# List of permission strings. User must have at least one to manipulate.
# None means everybody has permission.
required_permission = ''
@@ -55,26 +55,35 @@ class Manipulator:
"Returns dictionary mapping field_names to error-message lists"
errors = {}
for field in self.fields:
- 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)
+ 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):
@@ -203,7 +212,7 @@ class FormFieldCollection(FormFieldWrapper):
return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
class InlineObjectCollection:
- "An object that acts like a list of form field collections."
+ "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
@@ -230,27 +239,35 @@ class InlineObjectCollection:
def __iter__(self):
self.fill()
- return self._collections.__iter__()
+ 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()
- wrapper = []
- orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object or None
+ 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)
- wrapper.append(FormFieldCollection(collection))
- self._collections = wrapper
+ 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:
"""Abstract class representing a form field.
@@ -310,10 +327,35 @@ class FormField:
new_data.setlist(name, converted_data)
else:
try:
- # individual fields deal with None values themselves
- new_data.setlist(name, [self.__class__.html2python(None)])
+ #individual fields deal with None values themselves
+ new_data.setlist(name, [self.__class__.html2python(None)])
except EmptyValue:
- new_data.setlist(name, [])
+ 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_lazy('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."
@@ -334,13 +376,13 @@ class TextField(FormField):
self.member_name = member_name
def isValidLength(self, data, form):
- if data and self.maxlength and len(data.decode(DEFAULT_CHARSET)) > self.maxlength:
+ 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, _("Line breaks are not allowed here.")
+ raise validators.ValidationError, gettext("Line breaks are not allowed here.")
def render(self, data):
if data is None:
@@ -349,7 +391,7 @@ class TextField(FormField):
if self.maxlength:
maxlength = 'maxlength="%s" ' % self.maxlength
if isinstance(data, unicode):
- data = data.encode(DEFAULT_CHARSET)
+ 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)
@@ -374,7 +416,7 @@ class LargeTextField(TextField):
if data is None:
data = ''
if isinstance(data, unicode):
- data = data.encode(DEFAULT_CHARSET)
+ 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))
@@ -435,7 +477,7 @@ class SelectField(FormField):
str_data = str(data)
str_choices = [str(item[0]) for item in self.choices]
if str_data not in str_choices:
- raise validators.ValidationError, _("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': 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"
@@ -506,7 +548,7 @@ class RadioSelectField(FormField):
str_data = str(data)
str_choices = [str(item[0]) for item in self.choices]
if str_data not in str_choices:
- raise validators.ValidationError, _("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':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"
@@ -544,7 +586,7 @@ class SelectMultipleField(SelectField):
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, _("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':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:
@@ -600,7 +642,7 @@ class FileUploadField(FormField):
def isNonEmptyFile(self, field_data, all_data):
if not field_data['content']:
- raise validators.CriticalValidationError, _("The submitted file is empty.")
+ raise validators.CriticalValidationError, gettext("The submitted file is empty.")
def render(self, data):
return '<input type="file" id="%s" class="v%s" name="%s" />' % \
@@ -654,7 +696,7 @@ class SmallIntegerField(IntegerField):
def isSmallInteger(self, field_data, all_data):
if not -32768 <= int(field_data) <= 32767:
- raise validators.CriticalValidationError, _("Enter a whole number between -32,768 and 32,767.")
+ 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=[]):
@@ -663,7 +705,7 @@ class PositiveIntegerField(IntegerField):
def isPositive(self, field_data, all_data):
if int(field_data) < 0:
- raise validators.CriticalValidationError, _("Enter a positive number.")
+ 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=[]):
@@ -672,7 +714,7 @@ class PositiveSmallIntegerField(IntegerField):
def isPositiveSmall(self, field_data, all_data):
if not 0 <= int(field_data) <= 32767:
- raise validators.CriticalValidationError, _("Enter a whole number between 0 and 32,767.")
+ 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=[]):
@@ -741,7 +783,7 @@ class DateField(TextField):
time_tuple = time.strptime(data, '%Y-%m-%d')
return datetime.date(*time_tuple[0:3])
except (ValueError, TypeError):
- return None
+ return data
html2python = staticmethod(html2python)
class TimeField(TextField):
@@ -837,7 +879,7 @@ class FilePathField(SelectField):
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(path, f), f))
+ choices.append((os.path.join(root, f), f))
else:
try:
for f in os.listdir(path):
diff --git a/django/utils/httpwrappers.py b/django/http/__init__.py
index c059ff60a8..bb03ab8ea5 100644
--- a/django/utils/httpwrappers.py
+++ b/django/http/__init__.py
@@ -9,6 +9,9 @@ try:
except ImportError:
from cgi import parse_qsl
+class Http404(Exception):
+ pass
+
class HttpRequest(object): # needs to be new-style class because subclasses define "property"s
"A basic HTTP request"
def __init__(self):
@@ -271,3 +274,10 @@ 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/django/middleware/cache.py b/django/middleware/cache.py
index bb3396c849..b5e142a383 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -1,7 +1,7 @@
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
-from django.utils.httpwrappers import HttpResponseNotModified
+from django.http import HttpResponseNotModified
class CacheMiddleware:
"""
diff --git a/django/middleware/common.py b/django/middleware/common.py
index 3643fce553..763918878a 100644
--- a/django/middleware/common.py
+++ b/django/middleware/common.py
@@ -1,5 +1,5 @@
from django.conf import settings
-from django.utils import httpwrappers
+from django import http
from django.core.mail import mail_managers
import md5, os
@@ -27,10 +27,11 @@ class CommonMiddleware:
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 httpwrappers.HttpResponseForbidden('<h1>Forbidden</h1>')
+ return http.HttpResponseForbidden('<h1>Forbidden</h1>')
# Check for a redirect based on settings.APPEND_SLASH and settings.PREPEND_WWW
- old_url = [request.META.get('HTTP_HOST', ''), request.path]
+ 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]
@@ -46,7 +47,7 @@ class CommonMiddleware:
newurl = new_url[1]
if request.GET:
newurl += '?' + request.GET.urlencode()
- return httpwrappers.HttpResponsePermanentRedirect(newurl)
+ return http.HttpResponsePermanentRedirect(newurl)
return None
@@ -56,7 +57,7 @@ class CommonMiddleware:
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 = request.META['HTTP_HOST']
+ domain = http.get_host(request)
referer = request.META.get('HTTP_REFERER', None)
is_internal = referer and (domain in referer)
path = request.get_full_path()
@@ -69,7 +70,7 @@ class CommonMiddleware:
if settings.USE_ETAGS:
etag = md5.new(response.content).hexdigest()
if request.META.get('HTTP_IF_NONE_MATCH') == etag:
- response = httpwrappers.HttpResponseNotModified()
+ response = http.HttpResponseNotModified()
else:
response['ETag'] = etag
diff --git a/django/middleware/doc.py b/django/middleware/doc.py
index c96be2c0f6..6376fe4d5c 100644
--- a/django/middleware/doc.py
+++ b/django/middleware/doc.py
@@ -1,5 +1,5 @@
from django.conf import settings
-from django.utils import httpwrappers
+from django import http
class XViewMiddleware:
"""
@@ -12,6 +12,6 @@ class XViewMiddleware:
documentation module to lookup the view function for an arbitrary page.
"""
if request.META['REQUEST_METHOD'] == 'HEAD' and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
- response = httpwrappers.HttpResponse()
+ response = http.HttpResponse()
response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__)
return response
diff --git a/django/middleware/transaction.py b/django/middleware/transaction.py
new file mode 100644
index 0000000000..da218ac31a
--- /dev/null
+++ b/django/middleware/transaction.py
@@ -0,0 +1,28 @@
+from django.conf import settings
+from django.db import transaction
+
+class TransactionMiddleware:
+ """
+ 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/django/models/__init__.py b/django/models/__init__.py
deleted file mode 100644
index ea0c66f2ae..0000000000
--- a/django/models/__init__.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from django.core import meta
-from django.utils.functional import curry
-
-__all__ = ['auth', 'core']
-
-# Alter this package's __path__ variable so that calling code can import models
-# from "django.models" even though the model code doesn't physically live
-# within django.models.
-for mod in meta.get_installed_models():
- __path__.extend(mod.__path__)
-
-# First, import all models so the metaclasses run.
-modules = meta.get_installed_model_modules(__all__)
-
-# Now, create the extra methods that we couldn't create earlier because
-# relationships hadn't been known until now.
-for mod in modules:
- for klass in mod._MODELS:
-
- # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods
- # for all related objects.
- for related in klass._meta.get_all_related_objects():
- # Determine whether this related object is in another app.
- # If it's in another app, the method names will have the app
- # label prepended, and the add_BLAH() method will not be
- # generated.
- rel_mod = related.opts.get_model_module()
- rel_obj_name = related.get_method_name_part()
- if isinstance(related.field.rel, meta.OneToOneRel):
- # Add "get_thingie" methods for one-to-one related objects.
- # EXAMPLE: Place.get_restaurants_restaurant()
- func = curry(meta.method_get_related, 'get_object', rel_mod, related.field)
- func.__doc__ = "Returns the associated `%s.%s` object." % (related.opts.app_label, related.opts.module_name)
- setattr(klass, 'get_%s' % rel_obj_name, func)
- elif isinstance(related.field.rel, meta.ManyToOneRel):
- # Add "get_thingie" methods for many-to-one related objects.
- # EXAMPLE: Poll.get_choice()
- func = curry(meta.method_get_related, 'get_object', rel_mod, related.field)
- func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % \
- (related.opts.app_label, related.opts.module_name)
- setattr(klass, 'get_%s' % rel_obj_name, func)
- # Add "get_thingie_count" methods for many-to-one related objects.
- # EXAMPLE: Poll.get_choice_count()
- func = curry(meta.method_get_related, 'get_count', rel_mod, related.field)
- func.__doc__ = "Returns the number of associated `%s.%s` objects." % \
- (related.opts.app_label, related.opts.module_name)
- setattr(klass, 'get_%s_count' % rel_obj_name, func)
- # Add "get_thingie_list" methods for many-to-one related objects.
- # EXAMPLE: Poll.get_choice_list()
- func = curry(meta.method_get_related, 'get_list', rel_mod, related.field)
- func.__doc__ = "Returns a list of associated `%s.%s` objects." % \
- (related.opts.app_label, related.opts.module_name)
- setattr(klass, 'get_%s_list' % rel_obj_name, func)
- # Add "add_thingie" methods for many-to-one related objects,
- # but only for related objects that are in the same app.
- # EXAMPLE: Poll.add_choice()
- if related.opts.app_label == klass._meta.app_label:
- func = curry(meta.method_add_related, related.opts, rel_mod, related.field)
- func.alters_data = True
- setattr(klass, 'add_%s' % rel_obj_name, func)
- del func
- del rel_obj_name, rel_mod, related # clean up
-
- # Do the same for all related many-to-many objects.
- for related in klass._meta.get_all_related_many_to_many_objects():
- rel_mod = related.opts.get_model_module()
- rel_obj_name = related.get_method_name_part()
- setattr(klass, 'get_%s' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, related.field))
- setattr(klass, 'get_%s_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, related.field))
- setattr(klass, 'get_%s_list' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, related.field))
- if related.opts.app_label == klass._meta.app_label:
- func = curry(meta.method_set_related_many_to_many, related.opts, related.field)
- func.alters_data = True
- setattr(klass, 'set_%s' % related.opts.module_name, func)
- del func
- del rel_obj_name, rel_mod, related # clean up
-
- # Add "set_thingie_order" and "get_thingie_order" methods for objects
- # that are ordered with respect to this.
- for obj in klass._meta.get_ordered_objects():
- func = curry(meta.method_set_order, obj)
- func.__doc__ = "Sets the order of associated `%s.%s` objects to the given ID list." % (obj.app_label, obj.module_name)
- func.alters_data = True
- setattr(klass, 'set_%s_order' % obj.object_name.lower(), func)
-
- func = curry(meta.method_get_order, obj)
- func.__doc__ = "Returns the order of associated `%s.%s` objects as a list of IDs." % (obj.app_label, obj.module_name)
- setattr(klass, 'get_%s_order' % obj.object_name.lower(), func)
- del func, obj # clean up
- del klass # clean up
- del mod
-del modules
-
-# Expose get_app and get_module.
-from django.core.meta import get_app, get_module
diff --git a/django/models/auth.py b/django/models/auth.py
deleted file mode 100644
index 2595727ad0..0000000000
--- a/django/models/auth.py
+++ /dev/null
@@ -1,219 +0,0 @@
-from django.core import meta, validators
-from django.models import core
-from django.utils.translation import gettext_lazy as _
-
-class Permission(meta.Model):
- name = meta.CharField(_('name'), maxlength=50)
- package = meta.ForeignKey(core.Package, db_column='package')
- codename = meta.CharField(_('codename'), maxlength=100)
- class META:
- verbose_name = _('Permission')
- verbose_name_plural = _('Permissions')
- unique_together = (('package', 'codename'),)
- ordering = ('package', 'codename')
-
- def __repr__(self):
- return "%s | %s" % (self.package_id, self.name)
-
-class Group(meta.Model):
- name = meta.CharField(_('name'), maxlength=80, unique=True)
- permissions = meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL)
- class META:
- verbose_name = _('Group')
- verbose_name_plural = _('Groups')
- ordering = ('name',)
- admin = meta.Admin(
- search_fields = ('name',),
- )
-
- def __repr__(self):
- return self.name
-
-class User(meta.Model):
- username = meta.CharField(_('username'), maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])
- first_name = meta.CharField(_('first name'), maxlength=30, blank=True)
- last_name = meta.CharField(_('last name'), maxlength=30, blank=True)
- email = meta.EmailField(_('e-mail address'), blank=True)
- password = meta.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]'"))
- is_staff = meta.BooleanField(_('staff status'), help_text=_("Designates whether the user can log into this admin site."))
- is_active = meta.BooleanField(_('active'), default=True)
- is_superuser = meta.BooleanField(_('superuser status'))
- last_login = meta.DateTimeField(_('last login'), default=meta.LazyDate())
- date_joined = meta.DateTimeField(_('date joined'), default=meta.LazyDate())
- groups = meta.ManyToManyField(Group, 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 = meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL)
- class META:
- verbose_name = _('User')
- verbose_name_plural = _('Users')
- module_constants = {
- 'SESSION_KEY': '_auth_user_id',
- }
- ordering = ('username',)
- exceptions = ('SiteProfileNotAvailable',)
- admin = meta.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 __repr__(self):
- return self.username
-
- def get_absolute_url(self):
- return "/users/%s/" % self.username
-
- def is_anonymous(self):
- return False
-
- def get_full_name(self):
- 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
- algo, salt, hsh = self.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."
-
- 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 = db.cursor()
- # The SQL below works out to the following, after DB quoting:
- # cursor.execute("""
- # SELECT p.package, p.codename
- # FROM auth_permissions p, auth_groups_permissions gp, auth_users_groups ug
- # WHERE p.id = gp.permission_id
- # AND gp.group_id = ug.group_id
- # AND ug.user_id = %s""", [self.id])
- sql = """
- SELECT p.%s, p.%s
- FROM %s p, %s gp, %s ug
- WHERE p.%s = gp.%s
- AND gp.%s = ug.%s
- AND ug.%s = %%s""" % (
- db.quote_name('package'), db.quote_name('codename'),
- db.quote_name('auth_permissions'), db.quote_name('auth_groups_permissions'),
- db.quote_name('auth_users_groups'), db.quote_name('id'),
- db.quote_name('permission_id'), db.quote_name('group_id'),
- db.quote_name('group_id'), db.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.package_id, p.codename) for p in self.get_permission_list()])
- 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, package_name):
- "Returns True if the user has any permissions in the given package."
- if self.is_superuser:
- return True
- return bool(len([p for p in self.get_all_permissions() if p[:p.index('.')] == package_name]))
-
- def get_and_delete_messages(self):
- messages = []
- for m in self.get_message_list():
- 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.settings import AUTH_PROFILE_MODULE
- if not AUTH_PROFILE_MODULE:
- raise SiteProfileNotAvailable
- try:
- app, mod = AUTH_PROFILE_MODULE.split('.')
- module = __import__('ellington.%s.apps.%s' % (app, mod), [], [], [''])
- self._profile_cache = module.get_object(user_id=self.id)
- except ImportError:
- try:
- module = __import__('django.models.%s' % AUTH_PROFILE_MODULE, [], [], [''])
- self._profile_cache = module.get_object(user__id__exact=self.id)
- except ImportError:
- raise SiteProfileNotAvailable
- return self._profile_cache
-
- def _module_create_user(username, email, password):
- "Creates and saves a User with the given username, e-mail and password."
- now = datetime.datetime.now()
- user = User(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
- user.set_password(password)
- user.save()
- return user
-
- def _module_make_random_password(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 Message(meta.Model):
- user = meta.ForeignKey(User)
- message = meta.TextField(_('Message'))
-
- def __repr__(self):
- return self.message
diff --git a/django/models/core.py b/django/models/core.py
deleted file mode 100644
index f78f23f265..0000000000
--- a/django/models/core.py
+++ /dev/null
@@ -1,121 +0,0 @@
-import base64, md5, random, sys
-import cPickle as pickle
-from django.core import meta
-from django.utils.translation import gettext_lazy as _
-
-class Site(meta.Model):
- domain = meta.CharField(_('domain name'), maxlength=100)
- name = meta.CharField(_('display name'), maxlength=50)
- class META:
- verbose_name = _('site')
- verbose_name_plural = _('sites')
- db_table = 'sites'
- ordering = ('domain',)
- admin = meta.Admin(
- list_display = ('domain', 'name'),
- search_fields = ('domain', 'name'),
- )
-
- def __repr__(self):
- return self.domain
-
- def _module_get_current():
- "Returns the current site, according to the SITE_ID constant."
- from django.conf.settings import SITE_ID
- return get_object(pk=SITE_ID)
-
-class Package(meta.Model):
- label = meta.CharField(_('label'), maxlength=20, primary_key=True)
- name = meta.CharField(_('name'), maxlength=30, unique=True)
- class META:
- verbose_name = _('package')
- verbose_name_plural = _('packages')
- db_table = 'packages'
- ordering = ('name',)
-
- def __repr__(self):
- return self.name
-
-class ContentType(meta.Model):
- name = meta.CharField(_('name'), maxlength=100)
- package = meta.ForeignKey(Package, db_column='package')
- python_module_name = meta.CharField(_('python module name'), maxlength=50)
- class META:
- verbose_name = _('content type')
- verbose_name_plural = _('content types')
- db_table = 'content_types'
- ordering = ('package', 'name')
- unique_together = (('package', 'python_module_name'),)
-
- def __repr__(self):
- return "%s | %s" % (self.package_id, self.name)
-
- def get_model_module(self):
- "Returns the Python model module for accessing this type of content."
- return __import__('django.models.%s.%s' % (self.package_id, self.python_module_name), '', '', [''])
-
- 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.get_model_module().get_object(**kwargs)
-
-class Session(meta.Model):
- session_key = meta.CharField(_('session key'), maxlength=40, primary_key=True)
- session_data = meta.TextField(_('session data'))
- expire_date = meta.DateTimeField(_('expire date'))
- class META:
- verbose_name = _('session')
- verbose_name_plural = _('sessions')
- module_constants = {
- 'base64': base64,
- 'md5': md5,
- 'pickle': pickle,
- 'random': random,
- 'sys': sys,
- }
-
- def get_decoded(self):
- from django.conf.settings import SECRET_KEY
- encoded_data = base64.decodestring(self.session_data)
- pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
- if md5.new(pickled + 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 {}
-
- def _module_encode(session_dict):
- "Returns the given session dictionary pickled and encoded as a string."
- from django.conf.settings import SECRET_KEY
- pickled = pickle.dumps(session_dict)
- pickled_md5 = md5.new(pickled + SECRET_KEY).hexdigest()
- return base64.encodestring(pickled + pickled_md5)
-
- def _module_get_new_session_key():
- "Returns session key that isn't being used."
- from django.conf.settings import SECRET_KEY
- # 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)) + SECRET_KEY).hexdigest()
- try:
- get_object(session_key__exact=session_key)
- except SessionDoesNotExist:
- break
- return session_key
-
- def _module_save(session_key, session_dict, expire_date):
- s = Session(session_key, encode(session_dict), expire_date)
- if session_dict:
- s.save()
- else:
- s.delete() # Clear sessions with no data.
- return s
diff --git a/django/parts/auth/anonymoususers.py b/django/parts/auth/anonymoususers.py
deleted file mode 100644
index fea93e2f5a..0000000000
--- a/django/parts/auth/anonymoususers.py
+++ /dev/null
@@ -1,44 +0,0 @@
-class AnonymousUser:
- id = None
-
- def __init__(self):
- pass
-
- def __repr__(self):
- return 'AnonymousUser'
-
- 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_group_list(self):
- return []
-
- def set_groups(self, group_id_list):
- raise NotImplementedError
-
- def get_permission_list(self):
- return []
-
- def set_permissions(self, permission_id_list):
- raise NotImplementedError
-
- 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
diff --git a/django/parts/auth/formfields.py b/django/parts/auth/formfields.py
deleted file mode 100644
index cfbad248da..0000000000
--- a/django/parts/auth/formfields.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from django.models.auth import users
-from django.core import formfields, validators
-
-class AuthenticationForm(formfields.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 = [
- formfields.TextField(field_name="username", length=15, maxlength=30, is_required=True,
- validator_list=[self.isValidUser, self.hasCookiesEnabled]),
- formfields.PasswordField(field_name="password", length=15, maxlength=30, is_required=True,
- validator_list=[self.isValidPasswordForUser]),
- ]
- 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):
- try:
- self.user_cache = users.get_object(username__exact=field_data)
- except users.UserDoesNotExist:
- raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
-
- def isValidPasswordForUser(self, field_data, all_data):
- if self.user_cache is not None and not self.user_cache.check_password(field_data):
- self.user_cache = None
- raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
-
- def get_user_id(self):
- if self.user_cache:
- return self.user_cache.id
- return None
-
- def get_user(self):
- return self.user_cache
diff --git a/django/parts/media/photos.py b/django/parts/media/photos.py
deleted file mode 100644
index a14b3de19b..0000000000
--- a/django/parts/media/photos.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import re
-
-def get_thumbnail_url(photo_url, width):
- bits = photo_url.split('/')
- bits[-1] = re.sub(r'(?i)\.(gif|jpg)$', '_t%s.\\1' % width, bits[-1])
- return '/'.join(bits)
diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
new file mode 100644
index 0000000000..b42ede0339
--- /dev/null
+++ b/django/shortcuts/__init__.py
@@ -0,0 +1,23 @@
+# 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
+
+
+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, **kwargs):
+ try:
+ return klass._default_manager.get(**kwargs)
+ except klass.DoesNotExist:
+ raise Http404
+
+def get_list_or_404(klass, **kwargs):
+ obj_list = list(klass._default_manager.filter(**kwargs))
+ if not obj_list:
+ raise Http404
+ return obj_list
diff --git a/django/core/template/__init__.py b/django/template/__init__.py
index fe1a9cb338..e32200cccb 100644
--- a/django/core/template/__init__.py
+++ b/django/template/__init__.py
@@ -57,9 +57,10 @@ times with multiple contexts)
import re
from inspect import getargspec
from django.utils.functional import curry
-from django.conf.settings import DEFAULT_CHARSET, TEMPLATE_DEBUG, TEMPLATE_STRING_IF_INVALID
+from django.conf import settings
+from django.template.context import Context, RequestContext, ContextPopException
-__all__ = ('Template','Context','compile_string')
+__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
TOKEN_TEXT = 0
TOKEN_VAR = 1
@@ -92,20 +93,12 @@ builtins = []
class TemplateSyntaxError(Exception):
pass
-class ContextPopException(Exception):
- "pop() has been called more times than push()"
- pass
-
class TemplateDoesNotExist(Exception):
pass
class VariableDoesNotExist(Exception):
pass
-class SilentVariableFailure(Exception):
- "Any function raising this exception will be ignored by resolve_variable"
- pass
-
class InvalidTemplateLibrary(Exception):
pass
@@ -130,7 +123,7 @@ class StringOrigin(Origin):
class Template:
def __init__(self, template_string, origin=None):
"Compilation stage"
- if TEMPLATE_DEBUG and origin == None:
+ 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...
@@ -151,58 +144,6 @@ def compile_string(template_string, origin):
parser = parser_factory(lexer.tokenize())
return parser.parse()
-class Context:
- "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 dict in self.dicts:
- if dict.has_key(key):
- return dict[key]
- return TEMPLATE_STRING_IF_INVALID
-
- def __delitem__(self, key):
- "Delete a variable from the current context"
- del self.dicts[0][key]
-
- def has_key(self, key):
- for dict in self.dicts:
- if dict.has_key(key):
- return True
- return False
-
- def get(self, key, otherwise):
- for dict in self.dicts:
- if dict.has_key(key):
- return dict[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
-
class Token:
def __init__(self, token_type, contents):
"The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
@@ -257,7 +198,7 @@ class DebugLexer(Lexer):
upto = end
last_bit = self.template_string[upto:]
if last_bit:
- token_tups.append( (last_bit, (upto, upto + len(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):
@@ -304,7 +245,7 @@ class Parser(object):
compiled_result = compile_func(self, token)
except TemplateSyntaxError, e:
if not self.compile_function_error(token, e):
- raise
+ raise
self.extend_nodelist(nodelist, compiled_result, token)
self.exit_command()
if parse_until:
@@ -412,12 +353,19 @@ class DebugParser(Parser):
if not hasattr(e, 'source'):
e.source = token.source
-if TEMPLATE_DEBUG:
- lexer_factory = DebugLexer
- parser_factory = DebugParser
-else:
- lexer_factory = Lexer
- parser_factory = Parser
+
+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:
"""
@@ -571,9 +519,9 @@ class FilterExpression(object):
args = []
constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
if i18n_arg:
- args.append((False, _(i18n_arg.replace('\\', ''))))
+ args.append((False, _(i18n_arg.replace(r'\"', '"'))))
elif constant_arg:
- args.append((False, constant_arg.replace('\\', '')))
+ args.append((False, constant_arg.replace(r'\"', '"')))
elif var_arg:
args.append((True, var_arg))
filter_func = parser.find_filter(filter_name)
@@ -588,7 +536,7 @@ class FilterExpression(object):
try:
obj = resolve_variable(self.var, context)
except VariableDoesNotExist:
- obj = TEMPLATE_STRING_IF_INVALID
+ obj = settings.TEMPLATE_STRING_IF_INVALID
for func, args in self.filters:
arg_vals = []
for lookup, arg in args:
@@ -636,7 +584,7 @@ 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), or an integer or float literal.
+ and ends with single or double quote marks).
>>> c = {'article': {'section':'News'}}
>>> resolve_variable('article.section', c)
@@ -655,9 +603,9 @@ def resolve_variable(path, context):
if path[0] in '0123456789':
number_type = '.' in path and float or int
try:
- current = number_type(path)
+ current = number_type(path)
except ValueError:
- current = TEMPLATE_STRING_IF_INVALID
+ current = settings.TEMPLATE_STRING_IF_INVALID
elif path[0] in ('"', "'") and path[0] == path[-1]:
current = path[1:-1]
else:
@@ -671,21 +619,29 @@ def resolve_variable(path, context):
current = getattr(current, bits[0])
if callable(current):
if getattr(current, 'alters_data', False):
- current = TEMPLATE_STRING_IF_INVALID
+ current = settings.TEMPLATE_STRING_IF_INVALID
else:
try: # method call (assuming no args required)
current = current()
- except SilentVariableFailure:
- current = TEMPLATE_STRING_IF_INVALID
except TypeError: # arguments *were* required
# GOTCHA: This will also catch any TypeError
# raised in the function itself.
- current = TEMPLATE_STRING_IF_INVALID # invalid method call
+ 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, ValueError, KeyError):
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
@@ -764,7 +720,7 @@ class VariableNode(Node):
if not isinstance(output, basestring):
return str(output)
elif isinstance(output, unicode):
- return output.encode(DEFAULT_CHARSET)
+ return output.encode(settings.DEFAULT_CHARSET)
else:
return output
@@ -775,7 +731,7 @@ class VariableNode(Node):
class DebugVariableNode(VariableNode):
def render(self, context):
try:
- output = self.filter_expression.resolve(context)
+ output = self.filter_expression.resolve(context)
except TemplateSyntaxError, e:
if not hasattr(e, 'source'):
e.source = self.source
@@ -888,7 +844,7 @@ class Library(object):
dict = func(*args)
if not getattr(self, 'nodelist', False):
- from django.core.template_loader import get_template
+ from django.template.loader import get_template
t = get_template(file_name)
self.nodelist = t.nodelist
return self.nodelist.render(context_class(dict))
@@ -916,5 +872,5 @@ def get_library(module_name):
def add_to_builtins(module_name):
builtins.append(get_library(module_name))
-add_to_builtins('django.core.template.defaulttags')
-add_to_builtins('django.core.template.defaultfilters')
+add_to_builtins('django.template.defaulttags')
+add_to_builtins('django.template.defaultfilters')
diff --git a/django/template/context.py b/django/template/context.py
new file mode 100644
index 0000000000..f50fb07598
--- /dev/null
+++ b/django/template/context.py
@@ -0,0 +1,97 @@
+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:
+ "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]
+ return settings.TEMPLATE_STRING_IF_INVALID
+
+ 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 get(self, key, otherwise):
+ 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/django/core/template/defaultfilters.py b/django/template/defaultfilters.py
index b82d54a31c..fa4975e643 100644
--- a/django/core/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -1,7 +1,7 @@
"Default variable filters"
-from django.core.template import resolve_variable, Library
-from django.conf.settings import DATE_FORMAT, TIME_FORMAT
+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
@@ -133,7 +133,7 @@ def wordwrap(value, arg):
"""
Wraps words at specified line length
- Argument: number of characters at which to wrap the text
+ Argument: number of words to wrap the text at.
"""
from django.utils.text import wrap
return wrap(str(value), int(arg))
@@ -327,12 +327,12 @@ def get_digit(value, arg):
# DATES #
###################
-def date(value, arg=DATE_FORMAT):
+def date(value, arg=settings.DATE_FORMAT):
"Formats a date according to the given format"
from django.utils.dateformat import format
return format(value, arg)
-def time(value, arg=TIME_FORMAT):
+def time(value, arg=settings.TIME_FORMAT):
"Formats a time according to the given format"
from django.utils.dateformat import time_format
return time_format(value, arg)
@@ -431,8 +431,11 @@ def phone2numeric(value):
def pprint(value):
"A wrapper around pprint.pprint -- for debugging, really"
from pprint import pformat
- return pformat(value)
-
+ 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)
diff --git a/django/core/template/defaulttags.py b/django/template/defaulttags.py
index 1438b9f74a..18f1b9ab30 100644
--- a/django/core/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -1,8 +1,9 @@
"Default tags used by the template system, available to all templates."
-from django.core.template import Node, NodeList, Template, Context, resolve_variable
-from django.core.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END
-from django.core.template import get_library, Library, InvalidTemplateLibrary
+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
+from django.template import get_library, Library, InvalidTemplateLibrary
+from django.conf import settings
import sys
register = Library()
@@ -121,7 +122,12 @@ class IfChangedNode(Node):
def render(self, context):
content = self.nodelist.render(context)
if content != self._last_seen:
+ firstloop = (self._last_seen == None)
self._last_seen = content
+ context.push()
+ context['ifchanged'] = {'firstloop': firstloop}
+ content = self.nodelist.render(context)
+ context.pop()
return content
else:
return ''
@@ -196,8 +202,7 @@ class RegroupNode(Node):
return ''
def include_is_allowed(filepath):
- from django.conf.settings import ALLOWED_INCLUDE_ROOTS
- for root in ALLOWED_INCLUDE_ROOTS:
+ for root in settings.ALLOWED_INCLUDE_ROOTS:
if filepath.startswith(root):
return True
return False
@@ -207,9 +212,8 @@ class SsiNode(Node):
self.filepath, self.parsed = filepath, parsed
def render(self, context):
- from django.conf.settings import DEBUG
if not include_is_allowed(self.filepath):
- if DEBUG:
+ if settings.DEBUG:
return "[Didn't have permission to include file]"
else:
return '' # Fail silently for invalid includes.
@@ -224,7 +228,7 @@ class SsiNode(Node):
t = Template(output)
return t.render(context)
except TemplateSyntaxError, e:
- if DEBUG:
+ if settings.DEBUG:
return "[Included template had syntax error: %s]" % e
else:
return '' # Fail silently for invalid included templates.
@@ -340,6 +344,8 @@ def cycle(parser, token):
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]
diff --git a/django/core/template/loader.py b/django/template/loader.py
index 3d83a1494f..ebca582ef9 100644
--- a/django/core/template/loader.py
+++ b/django/template/loader.py
@@ -21,8 +21,8 @@
# installed, because pkg_resources is necessary to read eggs.
from django.core.exceptions import ImproperlyConfigured
-from django.core.template import Origin, StringOrigin, Template, Context, TemplateDoesNotExist, add_to_builtins
-from django.conf.settings import TEMPLATE_LOADERS, TEMPLATE_DEBUG
+from django.template import Origin, StringOrigin, Template, Context, TemplateDoesNotExist, add_to_builtins
+from django.conf import settings
template_source_loaders = None
@@ -35,7 +35,7 @@ class LoaderOrigin(Origin):
return self.loader(self.loadname, self.dirs)[0]
def make_origin(display_name, loader, name, dirs):
- if TEMPLATE_DEBUG:
+ if settings.TEMPLATE_DEBUG:
return LoaderOrigin(display_name, loader, name, dirs)
else:
return None
@@ -47,7 +47,7 @@ def find_template_source(name, dirs=None):
global template_source_loaders
if template_source_loaders is None:
template_source_loaders = []
- for path in TEMPLATE_LOADERS:
+ for path in settings.TEMPLATE_LOADERS:
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
try:
@@ -113,4 +113,4 @@ def select_template(template_name_list):
# If we get here, none of the templates could be loaded
raise TemplateDoesNotExist, ', '.join(template_name_list)
-add_to_builtins('django.core.template.loader_tags')
+add_to_builtins('django.template.loader_tags')
diff --git a/django/core/template/loader_tags.py b/django/template/loader_tags.py
index 238ece348c..e0d9a70a98 100644
--- a/django/core/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -1,7 +1,8 @@
-from django.core.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable
-from django.core.template import Library, Context, Node
-from django.core.template.loader import get_template, get_template_from_string, find_template_source
-from django.conf.settings import TEMPLATE_DEBUG
+from django.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable
+from django.template import Library, Context, 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):
@@ -84,8 +85,8 @@ class ConstantIncludeNode(Node):
t = get_template(template_path)
self.template = t
except:
- if TEMPLATE_DEBUG:
- raise
+ if settings.TEMPLATE_DEBUG:
+ raise
self.template = None
def render(self, context):
@@ -99,16 +100,16 @@ class IncludeNode(Node):
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 TEMPLATE_DEBUG:
+ 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.
+ return ''
+ except:
+ return '' # Fail silently for invalid included templates.
def do_block(parser, token):
"""
diff --git a/django/template/loaders/__init__.py b/django/template/loaders/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/template/loaders/__init__.py
diff --git a/django/core/template/loaders/app_directories.py b/django/template/loaders/app_directories.py
index 390e47852e..8a9bfef4b6 100644
--- a/django/core/template/loaders/app_directories.py
+++ b/django/template/loaders/app_directories.py
@@ -1,13 +1,13 @@
# Wrapper for loading templates from "template" directories in installed app packages.
-from django.conf.settings import INSTALLED_APPS, TEMPLATE_FILE_EXTENSION
+from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
-from django.core.template import TemplateDoesNotExist
+from django.template import TemplateDoesNotExist
import os
# At compile time, cache the directories to search.
app_template_dirs = []
-for app in INSTALLED_APPS:
+for app in settings.INSTALLED_APPS:
i = app.rfind('.')
if i == -1:
m, a = app, None
@@ -29,7 +29,7 @@ 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) + TEMPLATE_FILE_EXTENSION
+ 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):
diff --git a/django/core/template/loaders/eggs.py b/django/template/loaders/eggs.py
index 5d48326dce..6184aeaccf 100644
--- a/django/core/template/loaders/eggs.py
+++ b/django/template/loaders/eggs.py
@@ -5,8 +5,8 @@ try:
except ImportError:
resource_string = None
-from django.core.template import TemplateDoesNotExist
-from django.conf.settings import INSTALLED_APPS, TEMPLATE_FILE_EXTENSION
+from django.template import TemplateDoesNotExist
+from django.conf import settings
def load_template_source(template_name, template_dirs=None):
"""
@@ -15,8 +15,8 @@ def load_template_source(template_name, template_dirs=None):
For every installed app, it tries to get the resource (app, template_name).
"""
if resource_string is not None:
- pkg_name = 'templates/' + template_name + TEMPLATE_FILE_EXTENSION
- for app in INSTALLED_APPS:
+ 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:
diff --git a/django/core/template/loaders/filesystem.py b/django/template/loaders/filesystem.py
index 23ce6cd9e4..0c94021fb8 100644
--- a/django/core/template/loaders/filesystem.py
+++ b/django/template/loaders/filesystem.py
@@ -1,14 +1,14 @@
# Wrapper for loading templates from the filesystem.
-from django.conf.settings import TEMPLATE_DIRS, TEMPLATE_FILE_EXTENSION
-from django.core.template import TemplateDoesNotExist
+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 = TEMPLATE_DIRS
+ template_dirs = settings.TEMPLATE_DIRS
for template_dir in template_dirs:
- yield os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
+ yield os.path.join(template_dir, template_name)
def load_template_source(template_name, template_dirs=None):
tried = []
diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py
index 538da5b354..62374577ea 100644
--- a/django/templatetags/__init__.py
+++ b/django/templatetags/__init__.py
@@ -1,6 +1,6 @@
-from django.conf.settings import INSTALLED_APPS
+from django.conf import settings
-for a in INSTALLED_APPS:
+for a in settings.INSTALLED_APPS:
try:
__path__.extend(__import__(a + '.templatetags', '', '', ['']).__path__)
except ImportError:
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index 7c2019cac0..0c601535af 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -1,6 +1,6 @@
-from django.core.template import Node, NodeList, Template, Context, resolve_variable
-from django.core.template import TemplateSyntaxError, TokenParser, Library
-from django.core.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR
+from django.template import Node, NodeList, Template, Context, resolve_variable
+from django.template import TemplateSyntaxError, TokenParser, Library
+from django.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR
from django.utils import translation
import re, sys
@@ -11,8 +11,8 @@ class GetAvailableLanguagesNode(Node):
self.variable = variable
def render(self, context):
- from django.conf.settings import LANGUAGES
- context[self.variable] = LANGUAGES
+ from django.conf import settings
+ context[self.variable] = settings.LANGUAGES
return ''
class GetCurrentLanguageNode(Node):
diff --git a/django/utils/cache.py b/django/utils/cache.py
index b888cef179..5eba302ebe 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -80,8 +80,17 @@ def patch_response_headers(response, cache_timeout=None):
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.
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index 20aa30bcff..bc8fb07ef5 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -40,6 +40,43 @@ class MergeDict:
return True
return False
+class SortedDict(dict):
+ "A dictionary that keeps its keys in the order in which they're inserted."
+ def __init__(self, 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)
+
class MultiValueDictKeyError(KeyError):
pass
@@ -193,4 +230,4 @@ class DotExpandedDict(dict):
try:
current[bits[-1]] = v
except TypeError: # Special-case if current isn't a dict.
- current = {bits[-1]: v}
+ current = {bits[-1] : v}
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index 22db5abe7f..f7c25f2933 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -21,8 +21,6 @@ http://diveintomark.org/archives/2004/02/04/incompatible-rss
from django.utils.xmlutils import SimplerXMLGenerator
import datetime, re, time
import email.Utils
-from xml.dom import minidom
-from xml.parsers.expat import ExpatError
def rfc2822_date(date):
return email.Utils.formatdate(time.mktime(date.timetuple()))
@@ -158,9 +156,11 @@ class Rss201rev2Feed(RssFeed):
handler.addQuickElement(u"description", item['description'])
# Author information.
- if item['author_email'] is not None and item['author_name'] is not None:
- handler.addQuickElement(u"author", u"%s (%s)" % \
+ 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'))
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 69aeb81850..d1514d5728 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -24,14 +24,14 @@ def lazy(func, *resultclasses):
# 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))
+ 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
diff --git a/django/utils/html.py b/django/utils/html.py
index 6c9779a156..a0d1e82dcf 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -25,7 +25,7 @@ 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;')
+ return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
def linebreaks(value):
"Converts newlines into <p> and <br />s"
diff --git a/django/utils/termcolors.py b/django/utils/termcolors.py
new file mode 100644
index 0000000000..3ce1d5bb6b
--- /dev/null
+++ b/django/utils/termcolors.py
@@ -0,0 +1,70 @@
+"""
+termcolors.py
+"""
+
+import types
+
+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/django/utils/text.py b/django/utils/text.py
index ce1225b83c..7b6e1182ab 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -1,6 +1,6 @@
import re
-from django.conf.settings import DEFAULT_CHARSET
+from django.conf import settings
# Capitalizes the first letter of a string.
capfirst = lambda x: x and x[0].upper() + x[1:]
@@ -100,7 +100,7 @@ def javascript_quote(s):
return r"\u%04x" % ord(match.group(1))
if type(s) == str:
- s = s.decode(DEFAULT_CHARSET)
+ s = s.decode(settings.DEFAULT_CHARSET)
elif type(s) != unicode:
raise TypeError, s
s = s.replace('\\', '\\\\')
diff --git a/django/utils/timesince.py b/django/utils/timesince.py
index a16616584b..bc4f969dc4 100644
--- a/django/utils/timesince.py
+++ b/django/utils/timesince.py
@@ -11,6 +11,7 @@ def timesince(d, now=None):
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))
diff --git a/django/utils/translation.py b/django/utils/translation.py
index 56cd5426f0..a877f60009 100644
--- a/django/utils/translation.py
+++ b/django/utils/translation.py
@@ -115,7 +115,7 @@ def translation(language):
if sys.version_info < (2, 4):
klass = DjangoTranslation23
- globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+ globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale')
parts = settings.SETTINGS_MODULE.split('.')
project = __import__(parts[0], {}, {}, [])
@@ -209,8 +209,8 @@ def get_language():
except AttributeError:
pass
# If we don't have a real translation object, assume it's the default language.
- from django.conf.settings import LANGUAGE_CODE
- return LANGUAGE_CODE
+ from django.conf import settings
+ return settings.LANGUAGE_CODE
def catalog():
"""
@@ -275,7 +275,7 @@ def check_for_language(lang_code):
only used for language codes from either the cookies or session.
"""
from django.conf import settings
- globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+ 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:
@@ -289,7 +289,7 @@ def get_language_from_request(request):
"""
global _accepted
from django.conf import settings
- globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+ globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale')
supported = dict(settings.LANGUAGES)
if hasattr(request, 'session'):
@@ -346,16 +346,16 @@ def get_date_formats():
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.settings import DATE_FORMAT, DATETIME_FORMAT, TIME_FORMAT
+ from django.conf import settings
date_format = _('DATE_FORMAT')
datetime_format = _('DATETIME_FORMAT')
time_format = _('TIME_FORMAT')
if date_format == 'DATE_FORMAT':
- date_format = DATE_FORMAT
+ date_format = settings.DATE_FORMAT
if datetime_format == 'DATETIME_FORMAT':
- datetime_format = DATETIME_FORMAT
+ datetime_format = settings.DATETIME_FORMAT
if time_format == 'TIME_FORMAT':
- time_format = TIME_FORMAT
+ time_format = settings.TIME_FORMAT
return (date_format, datetime_format, time_format)
def install():
@@ -384,7 +384,7 @@ def templatize(src):
does so by translating the Django translation tags into standard gettext
function invocations.
"""
- from django.core.template import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK
+ from django.template import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK
out = StringIO()
intrans = False
inplural = False
@@ -457,3 +457,13 @@ def templatize(src):
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)
diff --git a/django/views/auth/login.py b/django/views/auth/login.py
deleted file mode 100644
index 3f2bd43015..0000000000
--- a/django/views/auth/login.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from django.parts.auth.formfields import AuthenticationForm
-from django.core import formfields
-from django.core.extensions import DjangoContext, render_to_response
-from django.models.auth import users
-from django.models.core import sites
-from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
-
-REDIRECT_FIELD_NAME = 'next'
-LOGIN_URL = '/accounts/login/'
-
-def login(request):
- "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/'
- request.session[users.SESSION_KEY] = manipulator.get_user_id()
- request.session.delete_test_cookie()
- return HttpResponseRedirect(redirect_to)
- else:
- errors = {}
- request.session.set_test_cookie()
- return render_to_response('registration/login', {
- 'form': formfields.FormWrapper(manipulator, request.POST, errors),
- REDIRECT_FIELD_NAME: redirect_to,
- 'site_name': sites.get_current().name,
- }, context_instance=DjangoContext(request))
-
-def logout(request, next_page=None):
- "Logs out the user and displays 'You are logged out' message."
- try:
- del request.session[users.SESSION_KEY]
- except KeyError:
- return render_to_response('registration/logged_out', context_instance=DjangoContext(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))
diff --git a/django/views/debug.py b/django/views/debug.py
index b08a56a524..aa0a93b863 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -1,7 +1,7 @@
from django.conf import settings
-from django.core.template import Template, Context, TemplateDoesNotExist
+from django.template import Template, Context, TemplateDoesNotExist
from django.utils.html import escape
-from django.utils.httpwrappers import HttpResponseServerError, HttpResponseNotFound
+from django.http import HttpResponseServerError, HttpResponseNotFound
import os, re
from itertools import count, izip
from os.path import dirname, join as pathjoin
@@ -72,7 +72,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
template_does_not_exist = False
loader_debug_info = None
if issubclass(exc_type, TemplateDoesNotExist):
- from django.core.template.loader import template_source_loaders
+ from django.template.loader import template_source_loaders
template_does_not_exist = True
loader_debug_info = []
for loader in template_source_loaders:
@@ -641,8 +641,8 @@ EMPTY_URLCONF_TEMPLATE = """
<div id="instructions">
<p>Of course, you haven't actually done any work yet. Here's what to do next:</p>
<ul>
- <li>Edit the <code>DATABASE_*</code> settings in <code>{{ project_name }}/settings.py</code>.</li>
- <li>Start your first app by running <code>{{ project_name }}/manage.py startapp [appname]</code>.</li>
+ <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>
diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
index f86372cf4e..5467ff501e 100644
--- a/django/views/decorators/cache.py
+++ b/django/views/decorators/cache.py
@@ -13,7 +13,7 @@ account on caching -- just like the middleware does.
import re
from django.utils.decorators import decorator_from_middleware
-from django.utils.cache import patch_cache_control
+from django.utils.cache import patch_cache_control, add_never_cache_headers
from django.middleware.cache import CacheMiddleware
cache_page = decorator_from_middleware(CacheMiddleware)
@@ -31,3 +31,13 @@ def cache_control(**kwargs):
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/django/views/decorators/http.py b/django/views/decorators/http.py
index b9b6bac757..a15e82fcc7 100644
--- a/django/views/decorators/http.py
+++ b/django/views/decorators/http.py
@@ -4,7 +4,7 @@ Decorators for views based on HTTP headers.
from django.utils.decorators import decorator_from_middleware
from django.middleware.http import ConditionalGetMiddleware
-from django.utils.httpwrappers import HttpResponseForbidden
+from django.http import HttpResponseForbidden
conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
diff --git a/django/views/defaults.py b/django/views/defaults.py
index 95c18b4263..d5460a7495 100644
--- a/django/views/defaults.py
+++ b/django/views/defaults.py
@@ -1,69 +1,89 @@
-from django.core.exceptions import Http404, ObjectDoesNotExist
-from django.core.template import Context, loader
-from django.models.core import sites, contenttypes
-from django.utils import httpwrappers
+from django.core.exceptions import ObjectDoesNotExist
+from django.template import Context, 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 = contenttypes.get_object(pk=content_type_id)
+ content_type = ContentType.objects.get(pk=content_type_id)
obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist:
- raise Http404, "Content type %s object %s doesn't exist" % (content_type_id, object_id)
+ 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 Http404, "%s objects don't have get_absolute_url() methods" % content_type.name
+ 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 httpwrappers.HttpResponseRedirect(absurl)
+ return http.HttpResponseRedirect(absurl)
object_domain = None
- # Next, look for an many-to-many relationship to sites
- if hasattr(obj, 'get_site_list'):
- site_list = obj.get_site_list()
- if site_list:
- object_domain = site_list[0].domain
+ # Otherwise, we need to introspect the object's relationships for a
+ # relation to the Site object
+ opts = obj._meta
- # Next, look for a many-to-one relationship to sites
- elif hasattr(obj, 'get_site'):
+ # 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 Site.DoesNotExist:
+ 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 = obj.get_site().domain
- except sites.SiteDoesNotExist:
+ object_domain = Site.objects.get_current().domain
+ except Site.DoesNotExist:
pass
- # Then, fall back to the current site (if possible)
+ # 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:
- try:
- object_domain = sites.get_current().domain
- except sites.SiteDoesNotExist:
- # Finally, give up and use a URL without the domain name
- return httpwrappers.HttpResponseRedirect(obj.get_absolute_url())
- return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, obj.get_absolute_url()))
+ return http.HttpResponseRedirect(absurl)
-def page_not_found(request, template_name='404'):
+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`
- Context: None
+ Templates: `404.html`
+ Context:
+ request_path
+ The path of the requested URL (e.g., '/app/pages/bad_page/')
"""
t = loader.get_template(template_name)
- return httpwrappers.HttpResponseNotFound(t.render(Context()))
+ return http.HttpResponseNotFound(t.render(Context({'request_path': request.path})))
-def server_error(request, template_name='500'):
+def server_error(request, template_name='500.html'):
"""
500 error handler.
- Templates: `500`
+ Templates: `500.html`
Context: None
"""
t = loader.get_template(template_name)
- return httpwrappers.HttpResponseServerError(t.render(Context()))
+ return http.HttpResponseServerError(t.render(Context()))
diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
index e9b552df3b..0605744e3d 100644
--- a/django/views/generic/create_update.py
+++ b/django/views/generic/create_update.py
@@ -1,20 +1,20 @@
-from django import models
from django.core.xheaders import populate_xheaders
-from django.core.template import loader
-from django.core import formfields, meta
-from django.views.auth.login import redirect_to_login
-from django.core.extensions import DjangoContext
+from django.template import loader
+from django import forms
+from django.db.models import FileField
+from django.contrib.auth.views import redirect_to_login
+from django.template import RequestContext
from django.core.paginator import ObjectPaginator, InvalidPage
-from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
-from django.core.exceptions import Http404, ObjectDoesNotExist, ImproperlyConfigured
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
-def create_object(request, app_label, module_name, template_name=None,
+def create_object(request, model, template_name=None,
template_loader=loader, extra_context={}, post_save_redirect=None,
login_required=False, follow=None, context_processors=None):
"""
Generic object-creation function.
- Templates: ``<app_label>/<module_name>_form``
+ Templates: ``<app_label>/<model_name>_form.html``
Context:
form
the form wrapper for the object
@@ -22,13 +22,12 @@ def create_object(request, app_label, module_name, template_name=None,
if login_required and request.user.is_anonymous():
return redirect_to_login(request.path)
- mod = models.get_module(app_label, module_name)
- manipulator = mod.AddManipulator(follow=follow)
+ 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 mod.Klass._meta.has_field_type(meta.FileField):
+ if model._meta.has_field_type(FileField):
new_data.update(request.FILES)
# Check for errors
@@ -40,7 +39,7 @@ def create_object(request, app_label, module_name, template_name=None,
new_object = manipulator.save(new_data)
if not request.user.is_anonymous():
- request.user.add_message("The %s was created successfully." % mod.Klass._meta.verbose_name)
+ request.user.message_set.create(message="The %s was created successfully." % 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.
@@ -56,11 +55,11 @@ def create_object(request, app_label, module_name, template_name=None,
new_data = manipulator.flatten_data()
# Create the FormWrapper, template, context, response
- form = formfields.FormWrapper(manipulator, new_data, errors)
+ form = forms.FormWrapper(manipulator, new_data, errors)
if not template_name:
- template_name = "%s/%s_form" % (app_label, module_name)
+ template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
- c = DjangoContext(request, {
+ c = RequestContext(request, {
'form': form,
}, context_processors)
for key, value in extra_context.items():
@@ -70,15 +69,15 @@ def create_object(request, app_label, module_name, template_name=None,
c[key] = value
return HttpResponse(t.render(c))
-def update_object(request, app_label, module_name, object_id=None, slug=None,
+def update_object(request, model, object_id=None, slug=None,
slug_field=None, template_name=None, template_loader=loader,
- extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None,
+ extra_context={}, post_save_redirect=None,
login_required=False, follow=None, context_processors=None,
template_object_name='object'):
"""
Generic object-update function.
- Templates: ``<app_label>/<module_name>_form``
+ Templates: ``<app_label>/<model_name>_form.html``
Context:
form
the form wrapper for the object
@@ -88,23 +87,20 @@ def update_object(request, app_label, module_name, object_id=None, slug=None,
if login_required and request.user.is_anonymous():
return redirect_to_login(request.path)
- mod = models.get_module(app_label, module_name)
-
# Look up the object to be edited
lookup_kwargs = {}
if object_id:
- lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = 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")
- lookup_kwargs.update(extra_lookup_kwargs)
try:
- object = mod.get_object(**lookup_kwargs)
+ object = model.objects.get(**lookup_kwargs)
except ObjectDoesNotExist:
- raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
+ raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
- manipulator = mod.ChangeManipulator(object.id, follow=follow)
+ manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.name), follow=follow)
if request.POST:
new_data = request.POST.copy()
@@ -114,7 +110,7 @@ def update_object(request, app_label, module_name, object_id=None, slug=None,
manipulator.save(new_data)
if not request.user.is_anonymous():
- request.user.add_message("The %s was updated successfully." % mod.Klass._meta.verbose_name)
+ request.user.message_set.create(message="The %s was updated successfully." % model._meta.verbose_name)
# Do a post-after-redirect so that reload works, etc.
if post_save_redirect:
@@ -128,11 +124,11 @@ def update_object(request, app_label, module_name, object_id=None, slug=None,
# This makes sure the form acurate represents the fields of the place.
new_data = manipulator.flatten_data()
- form = formfields.FormWrapper(manipulator, new_data, errors)
+ form = forms.FormWrapper(manipulator, new_data, errors)
if not template_name:
- template_name = "%s/%s_form" % (app_label, module_name)
+ template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
- c = DjangoContext(request, {
+ c = RequestContext(request, {
'form': form,
template_object_name: object,
}, context_processors)
@@ -142,12 +138,12 @@ def update_object(request, app_label, module_name, object_id=None, slug=None,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, model, getattr(object, object._meta.pk.name))
return response
-def delete_object(request, app_label, module_name, post_delete_redirect,
+def delete_object(request, model, post_delete_redirect,
object_id=None, slug=None, slug_field=None, template_name=None,
- template_loader=loader, extra_lookup_kwargs={}, extra_context={},
+ template_loader=loader, extra_context={},
login_required=False, context_processors=None, template_object_name='object'):
"""
Generic object-delete function.
@@ -156,7 +152,7 @@ def delete_object(request, app_label, module_name, post_delete_redirect,
fetched using GET; for safty, deletion will only be performed if this
view is POSTed.
- Templates: ``<app_label>/<module_name>_confirm_delete``
+ Templates: ``<app_label>/<model_name>_confirm_delete.html``
Context:
object
the original object being deleted
@@ -164,32 +160,29 @@ def delete_object(request, app_label, module_name, post_delete_redirect,
if login_required and request.user.is_anonymous():
return redirect_to_login(request.path)
- mod = models.get_module(app_label, module_name)
-
# Look up the object to be edited
lookup_kwargs = {}
if object_id:
- lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = 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")
- lookup_kwargs.update(extra_lookup_kwargs)
try:
- object = mod.get_object(**lookup_kwargs)
+ object = model._default_manager.get(**lookup_kwargs)
except ObjectDoesNotExist:
- raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
+ raise Http404, "No %s found for %s" % (model._meta.app_label, lookup_kwargs)
if request.META['REQUEST_METHOD'] == 'POST':
object.delete()
if not request.user.is_anonymous():
- request.user.add_message("The %s was deleted." % mod.Klass._meta.verbose_name)
+ request.user.message_set.create(message="The %s was deleted." % model._meta.verbose_name)
return HttpResponseRedirect(post_delete_redirect)
else:
if not template_name:
- template_name = "%s/%s_confirm_delete" % (app_label, module_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 = DjangoContext(request, {
+ c = RequestContext(request, {
template_object_name: object,
}, context_processors)
for key, value in extra_context.items():
@@ -198,5 +191,5 @@ def delete_object(request, app_label, module_name, post_delete_redirect,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, model, getattr(object, object._meta.pk.name))
return response
diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
index 9b9a3034ba..1a6cbc8369 100644
--- a/django/views/generic/date_based.py
+++ b/django/views/generic/date_based.py
@@ -1,44 +1,37 @@
-from django.core.template import loader
-from django.core.exceptions import Http404, ObjectDoesNotExist
-from django.core.extensions import DjangoContext
+from django.template import loader, RequestContext
+from django.core.exceptions import ObjectDoesNotExist
from django.core.xheaders import populate_xheaders
-from django.models import get_module
-from django.utils.httpwrappers import HttpResponse
+from django.http import Http404, HttpResponse
import datetime, time
-def archive_index(request, app_label, module_name, date_field, num_latest=15,
- template_name=None, template_loader=loader, extra_lookup_kwargs={},
+def archive_index(request, queryset, date_field, num_latest=15,
+ template_name=None, template_loader=loader,
extra_context={}, allow_empty=False, context_processors=None):
"""
Generic top-level archive of date-based objects.
- Templates: ``<app_label>/<module_name>_archive``
+ Templates: ``<app_label>/<model_name>_archive.html``
Context:
date_list
List of years
latest
Latest N (defaults to 15) objects by date
"""
- mod = get_module(app_label, module_name)
- lookup_kwargs = {'%s__lte' % date_field: datetime.datetime.now()}
- lookup_kwargs.update(extra_lookup_kwargs)
- date_list = getattr(mod, "get_%s_list" % date_field)('year', **lookup_kwargs)[::-1]
+ model = queryset.model
+ 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.%s available" % (app_label, module_name))
+ raise Http404, "No %s available" % model._meta.verbose_name
if date_list and num_latest:
- lookup_kwargs.update({
- 'limit': num_latest,
- 'order_by': ('-' + date_field,),
- })
- latest = mod.get_list(**lookup_kwargs)
+ latest = queryset.order_by('-'+date_field)[:num_latest]
else:
latest = None
if not template_name:
- template_name = "%s/%s_archive" % (app_label, module_name)
+ template_name = "%s/%s_archive.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
- c = DjangoContext(request, {
+ c = RequestContext(request, {
'date_list' : date_list,
'latest' : latest,
}, context_processors)
@@ -49,33 +42,34 @@ def archive_index(request, app_label, module_name, date_field, num_latest=15,
c[key] = value
return HttpResponse(t.render(c))
-def archive_year(request, year, app_label, module_name, date_field,
- template_name=None, template_loader=loader, extra_lookup_kwargs={},
- extra_context={}, allow_empty=False, context_processors=None):
+def archive_year(request, year, queryset, date_field, template_name=None,
+ template_loader=loader, extra_context={}, allow_empty=False,
+ context_processors=None):
"""
Generic yearly archive view.
- Templates: ``<app_label>/<module_name>_archive_year``
+ Templates: ``<app_label>/<model_name>_archive_year.html``
Context:
date_list
List of months in this year with objects
year
This year
"""
- mod = get_module(app_label, module_name)
+ 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.
if int(year) >= now.year:
lookup_kwargs['%s__lte' % date_field] = now
- lookup_kwargs.update(extra_lookup_kwargs)
- date_list = getattr(mod, "get_%s_list" % date_field)('month', **lookup_kwargs)
+ date_list = queryset.filter(**lookup_kwargs).dates(date_field, 'month')
if not date_list and not allow_empty:
raise Http404
if not template_name:
- template_name = "%s/%s_archive_year" % (app_label, module_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 = DjangoContext(request, {
+ c = RequestContext(request, {
'date_list': date_list,
'year': year,
}, context_processors)
@@ -86,14 +80,14 @@ def archive_year(request, year, app_label, module_name, date_field,
c[key] = value
return HttpResponse(t.render(c))
-def archive_month(request, year, month, app_label, module_name, date_field,
+def archive_month(request, year, month, queryset, date_field,
month_format='%b', template_name=None, template_loader=loader,
- extra_lookup_kwargs={}, extra_context={}, allow_empty=False,
- context_processors=None, template_object_name='object'):
+ extra_context={}, allow_empty=False, context_processors=None,
+ template_object_name='object'):
"""
Generic monthly archive view.
- Templates: ``<app_label>/<module_name>_archive_month``
+ Templates: ``<app_label>/<model_name>_archive_month.html``
Context:
month:
(date) this month
@@ -109,8 +103,9 @@ def archive_month(request, year, month, app_label, module_name, date_field,
except ValueError:
raise Http404
- mod = get_module(app_label, module_name)
+ 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:
@@ -118,17 +113,17 @@ def archive_month(request, year, month, app_label, module_name, date_field,
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.
if last_day >= now.date():
lookup_kwargs['%s__lte' % date_field] = now
- lookup_kwargs.update(extra_lookup_kwargs)
- object_list = mod.get_list(**lookup_kwargs)
+ 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_month" % (app_label, module_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 = DjangoContext(request, {
+ c = RequestContext(request, {
'%s_list' % template_object_name: object_list,
'month': date,
'next_month': (last_day < datetime.date.today()) and (last_day + datetime.timedelta(days=1)) or None,
@@ -141,14 +136,61 @@ def archive_month(request, year, month, app_label, module_name, date_field,
c[key] = value
return HttpResponse(t.render(c))
-def archive_day(request, year, month, day, app_label, module_name, date_field,
+def archive_week(request, year, week, queryset, date_field,
+ template_name=None, template_loader=loader,
+ extra_context={}, allow_empty=True, context_processors=None,
+ template_object_name='object'):
+ """
+ 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
+ """
+ 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.
+ if last_day >= now.date():
+ 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))
+
+def archive_day(request, year, month, day, queryset, date_field,
month_format='%b', day_format='%d', template_name=None,
- template_loader=loader, extra_lookup_kwargs={}, extra_context={},
- allow_empty=False, context_processors=None, template_object_name='object'):
+ template_loader=loader, extra_context={}, allow_empty=False,
+ context_processors=None, template_object_name='object'):
"""
Generic daily archive view.
- Templates: ``<app_label>/<module_name>_archive_day``
+ Templates: ``<app_label>/<model_name>_archive_day.html``
Context:
object_list:
list of objects published that day
@@ -164,22 +206,23 @@ def archive_day(request, year, month, day, app_label, module_name, date_field,
except ValueError:
raise Http404
- mod = get_module(app_label, module_name)
+ model = queryset.model
now = datetime.datetime.now()
+
lookup_kwargs = {
'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
}
+
# Only bother to check current date if the date isn't in the past.
if date >= now.date():
lookup_kwargs['%s__lte' % date_field] = now
- lookup_kwargs.update(extra_lookup_kwargs)
- object_list = mod.get_list(**lookup_kwargs)
+ object_list = queryset.filter(**lookup_kwargs)
if not allow_empty and not object_list:
raise Http404
if not template_name:
- template_name = "%s/%s_archive_day" % (app_label, module_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 = DjangoContext(request, {
+ c = RequestContext(request, {
'%s_list' % template_object_name: object_list,
'day': date,
'previous_day': date - datetime.timedelta(days=1),
@@ -204,15 +247,15 @@ def archive_today(request, **kwargs):
})
return archive_day(request, **kwargs)
-def object_detail(request, year, month, day, app_label, module_name, date_field,
+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_lookup_kwargs={}, extra_context={},
- context_processors=None, template_object_name='object'):
+ template_loader=loader, extra_context={}, context_processors=None,
+ template_object_name='object'):
"""
Generic detail view from year/month/day/slug or year/month/day/id structure.
- Templates: ``<app_label>/<module_name>_detail``
+ Templates: ``<app_label>/<model_name>_detail.html``
Context:
object:
the object to be detailed
@@ -222,34 +265,35 @@ def object_detail(request, year, month, day, app_label, module_name, date_field,
except ValueError:
raise Http404
- mod = get_module(app_label, module_name)
+ model = queryset.model
now = datetime.datetime.now()
+
lookup_kwargs = {
'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
}
+
# Only bother to check current date if the date isn't in the past.
if date >= now.date():
lookup_kwargs['%s__lte' % date_field] = now
if object_id:
- lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = 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")
- lookup_kwargs.update(extra_lookup_kwargs)
+ raise AttributeError, "Generic detail view must be called with either an object_id or a slug/slugfield"
try:
- object = mod.get_object(**lookup_kwargs)
+ obj = queryset.get(**lookup_kwargs)
except ObjectDoesNotExist:
- raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
+ raise Http404, "No %s found for" % model._meta.verbose_name
if not template_name:
- template_name = "%s/%s_detail" % (app_label, module_name)
+ template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
if template_name_field:
- template_name_list = [getattr(object, template_name_field), template_name]
+ 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 = DjangoContext(request, {
- template_object_name: object,
+ c = RequestContext(request, {
+ template_object_name: obj,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
@@ -257,5 +301,5 @@ def object_detail(request, year, month, day, app_label, module_name, date_field,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
index 5cc496f5ed..68a1e73b07 100644
--- a/django/views/generic/list_detail.py
+++ b/django/views/generic/list_detail.py
@@ -1,18 +1,16 @@
-from django import models
-from django.core.template import loader
-from django.utils.httpwrappers import HttpResponse
+from django.template import loader, RequestContext
+from django.http import Http404, HttpResponse
from django.core.xheaders import populate_xheaders
-from django.core.extensions import DjangoContext
from django.core.paginator import ObjectPaginator, InvalidPage
-from django.core.exceptions import Http404, ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist
-def object_list(request, app_label, module_name, paginate_by=None, allow_empty=False,
- template_name=None, template_loader=loader, extra_lookup_kwargs={},
+def object_list(request, queryset, paginate_by=None, allow_empty=False,
+ template_name=None, template_loader=loader,
extra_context={}, context_processors=None, template_object_name='object'):
"""
Generic list of objects.
- Templates: ``<app_label>/<module_name>_list``
+ Templates: ``<app_label>/<model_name>_list.html``
Context:
object_list
list of objects
@@ -35,10 +33,10 @@ def object_list(request, app_label, module_name, paginate_by=None, allow_empty=F
hits
number of objects, total
"""
- mod = models.get_module(app_label, module_name)
- lookup_kwargs = extra_lookup_kwargs.copy()
+ queryset = queryset._clone()
+ model = queryset.model
if paginate_by:
- paginator = ObjectPaginator(mod, lookup_kwargs, paginate_by)
+ paginator = ObjectPaginator(queryset, paginate_by)
page = request.GET.get('page', 1)
try:
page = int(page)
@@ -48,7 +46,7 @@ def object_list(request, app_label, module_name, paginate_by=None, allow_empty=F
object_list = []
else:
raise Http404
- c = DjangoContext(request, {
+ c = RequestContext(request, {
'%s_list' % template_object_name: object_list,
'is_paginated': paginator.pages > 1,
'results_per_page': paginate_by,
@@ -61,12 +59,11 @@ def object_list(request, app_label, module_name, paginate_by=None, allow_empty=F
'hits' : paginator.hits,
}, context_processors)
else:
- object_list = mod.get_list(**lookup_kwargs)
- c = DjangoContext(request, {
- '%s_list' % template_object_name: object_list,
+ c = RequestContext(request, {
+ '%s_list' % template_object_name: queryset,
'is_paginated': False
}, context_processors)
- if len(object_list) == 0 and not allow_empty:
+ if not allow_empty and len(queryset) == 0:
raise Http404
for key, value in extra_context.items():
if callable(value):
@@ -74,44 +71,42 @@ def object_list(request, app_label, module_name, paginate_by=None, allow_empty=F
else:
c[key] = value
if not template_name:
- template_name = "%s/%s_list" % (app_label, module_name)
+ 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))
-def object_detail(request, app_label, module_name, object_id=None, slug=None,
+def object_detail(request, queryset, object_id=None, slug=None,
slug_field=None, template_name=None, template_name_field=None,
- template_loader=loader, extra_lookup_kwargs={}, extra_context={},
+ template_loader=loader, extra_context={},
context_processors=None, template_object_name='object'):
"""
Generic list of objects.
- Templates: ``<app_label>/<module_name>_detail``
+ Templates: ``<app_label>/<model_name>_detail.html``
Context:
object
the object
"""
- mod = models.get_module(app_label, module_name)
- lookup_kwargs = {}
+ model = queryset.model
if object_id:
- lookup_kwargs['pk'] = object_id
+ queryset = queryset.filter(pk=object_id)
elif slug and slug_field:
- lookup_kwargs['%s__exact' % slug_field] = slug
+ 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")
- lookup_kwargs.update(extra_lookup_kwargs)
+ raise AttributeError, "Generic detail view must be called with either an object_id or a slug/slug_field."
try:
- object = mod.get_object(**lookup_kwargs)
+ obj = queryset.get()
except ObjectDoesNotExist:
- raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
+ raise Http404, "No %s found matching the query" % (model._meta.verbose_name)
if not template_name:
- template_name = "%s/%s_detail" % (app_label, module_name)
+ template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
if template_name_field:
- template_name_list = [getattr(object, template_name_field), template_name]
+ 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 = DjangoContext(request, {
- template_object_name: object,
+ c = RequestContext(request, {
+ template_object_name: obj,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
@@ -119,5 +114,5 @@ def object_detail(request, app_label, module_name, object_id=None, slug=None,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response
diff --git a/django/views/generic/simple.py b/django/views/generic/simple.py
index 086ed42805..4571ef8605 100644
--- a/django/views/generic/simple.py
+++ b/django/views/generic/simple.py
@@ -1,12 +1,13 @@
-from django.core.extensions import DjangoContext, render_to_response
-from django.utils.httpwrappers import HttpResponse, HttpResponsePermanentRedirect, HttpResponseGone
+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, **kwargs):
"""
Render a given template with any extra URL parameters in the context as
``{{ params }}``.
"""
- return render_to_response(template, {'params' : kwargs}, context_instance=DjangoContext(request))
+ return render_to_response(template, {'params' : kwargs}, context_instance=RequestContext(request))
def redirect_to(request, url, **kwargs):
"""
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 2f3b2b2d31..a2bc54c9ed 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -1,4 +1,4 @@
-from django.utils import httpwrappers
+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
@@ -17,7 +17,7 @@ def set_language(request):
next = request.META.get('HTTP_REFERER', None)
if not next:
next = '/'
- response = httpwrappers.HttpResponseRedirect(next)
+ response = http.HttpResponseRedirect(next)
if check_for_language(lang_code):
if hasattr(request, 'session'):
request.session['django_language'] = lang_code
@@ -190,5 +190,5 @@ def javascript_catalog(request, domain='djangojs', packages=None):
src.append(LibFoot)
src.append(InterPolate)
src = ''.join(src)
- return httpwrappers.HttpResponse(src, 'text/javascript')
+ return http.HttpResponse(src, 'text/javascript')
diff --git a/django/views/registration/passwords.py b/django/views/registration/passwords.py
deleted file mode 100644
index 65bacafd98..0000000000
--- a/django/views/registration/passwords.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from django.core import formfields, validators
-from django.core.extensions import DjangoContext, render_to_response
-from django.core.template import Context, loader
-from django.models.auth import users
-from django.views.decorators.auth import login_required
-from django.utils.httpwrappers import HttpResponseRedirect
-
-class PasswordResetForm(formfields.Manipulator):
- "A form that lets a user request a password reset"
- def __init__(self):
- self.fields = (
- formfields.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 = users.get_object(email__iexact=new_data)
- except users.UserDoesNotExist:
- raise validators.ValidationError, "That e-mail address doesn't have an associated user acount. Are you sure you've registered?"
-
- def save(self, domain_override=None):
- "Calculates a new password randomly and sends it to the user"
- from django.core.mail import send_mail
- from django.models.core import sites
- new_pass = users.make_random_password()
- self.user_cache.set_password(new_pass)
- self.user_cache.save()
- if not domain_override:
- current_site = sites.get_current()
- site_name = current_site.name
- domain = current_site.domain
- else:
- site_name = domain = domain_override
- t = loader.get_template('registration/password_reset_email')
- 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(formfields.Manipulator):
- "A form that lets a user change his password."
- def __init__(self, user):
- self.user = user
- self.fields = (
- formfields.PasswordField(field_name="old_password", length=30, maxlength=30, is_required=True,
- validator_list=[self.isValidOldPassword]),
- formfields.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.")]),
- formfields.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()
-
-def password_reset(request, is_admin_site=False):
- 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(request.META['HTTP_HOST'])
- else:
- form.save()
- return HttpResponseRedirect('%sdone/' % request.path)
- return render_to_response('registration/password_reset_form', {'form': formfields.FormWrapper(form, new_data, errors)},
- context_instance=DjangoContext(request))
-
-def password_reset_done(request):
- return render_to_response('registration/password_reset_done', context_instance=DjangoContext(request))
-
-def password_change(request):
- 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('registration/password_change_form', {'form': formfields.FormWrapper(form, new_data, errors)},
- context_instance=DjangoContext(request))
-password_change = login_required(password_change)
-
-def password_change_done(request):
- return render_to_response('registration/password_change_done', context_instance=DjangoContext(request))
diff --git a/django/views/static.py b/django/views/static.py
index 1499dd4847..072a01671e 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -1,15 +1,14 @@
+from django.template import loader
+from django.core.exceptions import ImproperlyConfigured
+from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
+from django.template import Template, Context, TemplateDoesNotExist
+import mimetypes
import os
-import urllib
import posixpath
-import mimetypes
import re
import rfc822
import stat
-from django.core import template_loader
-from django.core.exceptions import Http404, ImproperlyConfigured
-from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect, \
- HttpResponseNotModified
-from django.core.template import Template, Context, TemplateDoesNotExist
+import urllib
def serve(request, path, document_root=None, show_indexes=False):
"""
@@ -81,7 +80,7 @@ DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
def directory_index(path, fullpath):
try:
- t = template_loader.get_template('static/directory_index')
+ t = loader.get_template('static/directory_index')
except TemplateDoesNotExist:
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE)
files = []
diff --git a/docs/add_ons.txt b/docs/add_ons.txt
index 44354d9c82..e602e429ad 100644
--- a/docs/add_ons.txt
+++ b/docs/add_ons.txt
@@ -12,9 +12,9 @@ admin
=====
The automatic Django administrative interface. For more information, see
-`Tutorial 3`_.
+`Tutorial 2`_.
-.. _Tutorial 3: http://www.djangoproject.com/documentation/tutorial2/
+.. _Tutorial 2: http://www.djangoproject.com/documentation/tutorial2/
comments
========
diff --git a/docs/admin_css.txt b/docs/admin_css.txt
index 419e0bcd42..069012a84b 100644
--- a/docs/admin_css.txt
+++ b/docs/admin_css.txt
@@ -28,11 +28,13 @@ Column Types
.. admonition:: Note
- In the Django development version, all admin pages (except the dashboard) are fluid-width. All fixed-width classes have been removed.
+ 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.
+(``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".
@@ -46,39 +48,12 @@ colMS
colSM
Same as above, with the sidebar on the left. The source order of the columns
doesn't matter.
-colM superwide (removed in Django development version)
- This is for ridiculously wide pages. Doesn't really work very well for
- anything but colM. With superwide, you've got 1000px to work with. Don't
- waste them.
-flex (removed in Django development version)
- This is for liquid-width pages, such as changelists. Currently only works
- with single-column pages (does not combine with ``.colMS`` or ``.colSM``).
- Form pages should never use ``.flex``.
-For instance, you could stick this in a template to make a two-column page with the sidebar on the right::
+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 %}
-
-Widths
-======
-
-**Removed in Django development version (see note above).**
-
-There's a whole mess of classes in the stylesheet for custom pixel widths on
-objects. They come in handy for tables and table cells, if you want to avoid
-using the ``width`` attribute. Each class sets the width to the number of pixels
-in the class, except ``.xfull`` which will always be the width of the column
-it's in. (This helps with tables that you want to always fill the horizontal
-width, without using ``width="100%"`` which makes IE 5's box model cry.)
-
-**Note:** Within a ``.flex`` page, the ``.xfull`` class will ``usually`` set
-to 100%, but there are exceptions and still some untested cases.
-
-Available width classes::
-
- .x50 .x75 .x100 .x150 .x200 .x250 .x300 .x400 .x500 .xfull
-
Text Styles
===========
@@ -107,17 +82,18 @@ There are also a few styles for styling text.
.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 withing ``.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.
+ to ``p`` elements withing ``.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.
+ 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.
+ Keeps text and inline objects from wrapping. Comes in handy for table
+ headers you want to stay on one line.
Floats and Clears
-----------------
@@ -173,9 +149,10 @@ 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.
+ 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.
+ Used in combination with ``.aligned`` to widen the space available for the
+ labels.
Form Rows
---------
diff --git a/docs/authentication.txt b/docs/authentication.txt
index 4c45ec6759..8f618f8a20 100644
--- a/docs/authentication.txt
+++ b/docs/authentication.txt
@@ -6,13 +6,8 @@ Django comes with a user authentication system. It handles user accounts,
groups, permissions and cookie-based user sessions. This document explains how
things work.
-The basics
-==========
-
-Django supports authentication out of the box. The ``django-admin.py init``
-command, used to initialize a database with Django's core database tables,
-creates the infrastructure for the auth system. You don't have to do anything
-else to use authentication.
+Overview
+========
The auth system consists of:
@@ -23,13 +18,35 @@ The auth system consists of:
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.
+
+Once you've taken those steps, that's it.
+
Users
=====
Users are represented by a standard Django model, which lives in
-`django/models/auth.py`_.
+`django/contrib/auth/models.py`_.
-.. _django/models/auth.py: http://code.djangoproject.com/browser/django/trunk/django/models/auth.py
+.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py
API reference
-------------
@@ -62,16 +79,20 @@ Methods
~~~~~~~
``User`` objects have two many-to-many fields: ``groups`` and
-``user_permissions``. Because of those relationships, ``User`` objects get
-data-access methods like any other `Django model`_:
+``user_permissions``. ``User`` objects can access their related
+objects in the same way as any other `Django model`_::
- * ``get_group_list(**kwargs)``
- * ``set_groups(id_list)``
- * ``get_permission_list(**kwargs)``
- * ``set_user_permissions(id_list)``
+ ``myuser.objects.groups = [group_list]``
+ ``myuser.objects.groups.add(group, group,...)``
+ ``myuser.objects.groups.remove(group, group,...)``
+ ``myuser.objects.groups.clear()``
+ ``myuser.objects.permissions = [permission_list]``
+ ``myuser.objects.permissions.add(permission, permission, ...)``
+ ``myuser.objects.permissions.remove(permission, permission, ...]``
+ ``myuser.objects.permissions.clear()``
In addition to those automatic API methods, ``User`` objects have the following
-methods:
+custom methods:
* ``is_anonymous()`` -- Always returns ``False``. This is a way of
comparing ``User`` objects to anonymous users.
@@ -80,11 +101,12 @@ methods:
with a space in between.
* ``set_password(raw_password)`` -- Sets the user's password to the given
- raw string, taking care of the MD5 hashing. Doesn't save the ``User``
- object.
+ 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.
+ 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.
@@ -110,23 +132,25 @@ methods:
`DEFAULT_FROM_EMAIL`_ setting.
* ``get_profile()`` -- Returns a site-specific profile for this user.
- Raises ``django.models.auth.SiteProfileNotAvailable`` if the current site
+ Raises ``django.contrib.auth.models.SiteProfileNotAvailable`` if the current site
doesn't allow profiles.
.. _Django model: http://www.djangoproject.com/documentation/model_api/
.. _DEFAULT_FROM_EMAIL: http://www.djangoproject.com/documentation/settings/#default-from-email
-Module functions
-~~~~~~~~~~~~~~~~
+Manager functions
+~~~~~~~~~~~~~~~~~
-The ``django.models.auth.users`` module has the following helper 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
+ 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.
@@ -140,11 +164,12 @@ Creating users
The most basic way to create users is to use the ``create_user`` helper
function that comes with Django::
- >>> from django.models.auth import users
- >>> user = users.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
+ >>> from django.contrib.auth.models import User
+ >>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
- # Now, user is a User object already saved to the database.
- # You can continue to change its attributes if you want to change other fields.
+ # 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()
@@ -153,28 +178,26 @@ Changing passwords
Change a password with ``set_password()``::
- >>> from django.models.auth import users
- >>> u = users.get_object(username__exact='john')
+ >>> 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 field directly unless you know what you're doing. This
-is explained in the next section.
+Don't set the ``password`` attribute directly unless you know what you're
+doing. This is explained in the next section.
Passwords
---------
-Previous versions, such as Django 0.90, used simple MD5 hashes without password
-salts.
-
-The ``password`` field of a ``User`` object is a string in this format::
+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``. Salt is a random string
-used to salt the raw password to create the hash.
+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::
@@ -183,17 +206,22 @@ For example::
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.parts.auth.anonymoususers.AnonymousUser`` is a class that implements
-the ``django.models.auth.users.User`` interface, with these differences:
+``django.contrib.auth.models.AnonymousUser`` is a class that implements
+the ``django.contirb.auth.models.User`` interface, with these differences:
* ``id`` is always ``None``.
* ``is_anonymous()`` returns ``True`` instead of ``False``.
* ``has_perm()`` always returns ``False``.
- * ``set_password()``, ``check_password()``, ``set_groups()`` and
- ``set_permissions()`` raise ``NotImplementedError``.
+ * ``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.
@@ -202,10 +230,15 @@ 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 hooks this
+authentication-related objects. On a higher level, Django can hook this
authentication framework into its system of `request objects`_.
-In any Django view, ``request.user`` will give you a ``User`` object
+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_anonymous()``, like so::
@@ -215,10 +248,6 @@ previous section). You can tell them apart with ``is_anonymous()``, like so::
else:
# Do something for logged-in users.
-If you want to use ``request.user`` in your view code, make sure you have
-``SessionMiddleware`` enabled. See the `session documentation`_ for more
-information.
-
.. _request objects: http://www.djangoproject.com/documentation/request_response/#httprequest-objects
.. _session documentation: http://www.djangoproject.com/documentation/sessions/
@@ -227,8 +256,8 @@ How to log a user in
To log a user in, do the following within a view::
- from django.models.auth import users
- request.session[users.SESSION_KEY] = some_user.id
+ from django.contrib.auth.models import SESSION_KEY
+ request.session[SESSION_KEY] = some_user.id
Because this uses sessions, you'll need to make sure you have
``SessionMiddleware`` enabled. See the `session documentation`_ for more
@@ -246,7 +275,7 @@ The raw way
The simple, raw way to limit access to pages is to check
``request.user.is_anonymous()`` and either redirect to a login page::
- from django.utils.httpwrappers import HttpResponseRedirect
+ from django.http import HttpResponseRedirect
def my_view(request):
if request.user.is_anonymous():
@@ -257,7 +286,7 @@ The simple, raw way to limit access to pages is to check
def my_view(request):
if request.user.is_anonymous():
- return render_to_response('myapp/login_error')
+ return render_to_response('myapp/login_error.html')
# ...
The login_required decorator
@@ -265,15 +294,16 @@ The login_required decorator
As a shortcut, you can use the convenient ``login_required`` decorator::
- from django.views.decorators.auth import login_required
+ from django.contrib.auth.decorators import login_required
def my_view(request):
# ...
my_view = login_required(my_view)
-Here's the same thing, using Python 2.4's decorator syntax::
+Here's an equivalent example, using the more compact decorator syntax
+introduced in Python 2.4::
- from django.views.decorators.auth import login_required
+ from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
@@ -304,7 +334,7 @@ permission ``polls.can_vote``::
As a shortcut, you can use the convenient ``user_passes_test`` decorator::
- from django.views.decorators.auth import user_passes_test
+ from django.contrib.auth.decorators import user_passes_test
def my_view(request):
# ...
@@ -312,7 +342,7 @@ As a shortcut, you can use the convenient ``user_passes_test`` decorator::
Here's the same thing, using Python 2.4's decorator syntax::
- from django.views.decorators.auth import user_passes_test
+ from django.contrib.auth.decorators import user_passes_test
@user_passes_test(lambda u: u.has_perm('polls.can_vote'))
def my_view(request):
@@ -328,7 +358,7 @@ specify the URL for your login page (``/accounts/login/`` by default).
Example in Python 2.3 syntax::
- from django.views.decorators.auth import user_passes_test
+ from django.contrib.auth.decorators import user_passes_test
def my_view(request):
# ...
@@ -336,7 +366,7 @@ Example in Python 2.3 syntax::
Example in Python 2.4 syntax::
- from django.views.decorators.auth import user_passes_test
+ 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):
@@ -380,49 +410,52 @@ 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 or publication date." The latter functionality is something
+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 ``admin`` set. Behind the scenes, these
-permissions are added to the ``auth_permissions`` database table when you run
-``django-admin.py install [app]``. You can view the exact SQL ``INSERT``
-statements by running ``django-admin.py sqlinitialdata [app]``.
+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 ``admin`` set when you run
-``django-admin.py install``, the permissions won't be created. If you
-initialize your database and add ``admin`` to models after the fact, you'll
-need to add the permissions to the database manually. Do this by running
-``django-admin.py installperms [app]``, which creates any missing permissions
-for the given app.
+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
+``django-admin.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`_.
+`model Meta attribute`_.
This example model creates three custom permissions::
- class USCitizen(meta.Model):
+ class USCitizen(models.Model):
# ...
- class META:
+ class Meta:
permissions = (
("can_drive", "Can drive"),
("can_vote", "Can vote in elections"),
("can_drink", "Can drink alcohol"),
)
-.. _model META attribute: http://www.djangoproject.com/documentation/model_api/#meta-options
+The only thing this does is create those extra permissions when you run
+``syncdb``.
+
+.. _model Meta attribute: http://www.djangoproject.com/documentation/model_api/#meta-options
API reference
-------------
Just like users, permissions are implemented in a Django model that lives in
-`django/models/auth.py`_.
+`django/contrib/auth/models.py`_.
+
+.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py
Fields
~~~~~~
@@ -430,8 +463,8 @@ Fields
``Permission`` objects have the following fields:
* ``name`` -- Required. 50 characters or fewer. Example: ``'Can vote'``.
- * ``package`` -- Required. A reference to the ``packages`` database table,
- which contains a record for each installed Django application.
+ * ``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
@@ -444,21 +477,21 @@ Authentication data in templates
================================
The currently logged-in user and his/her permissions are made available in the
-`template context`_ when you use ``DjangoContext``.
+`template context`_ when you use ``RequestContext``.
.. admonition:: Technicality
Technically, these variables are only made available in the template context
- if you use ``DjangoContext`` *and* your ``TEMPLATE_CONTEXT_PROCESSORS``
+ if you use ``RequestContext`` *and* your ``TEMPLATE_CONTEXT_PROCESSORS``
setting contains ``"django.core.context_processors.auth"``, which is default.
- For more, see the `DjangoContext docs`_.
+ For more, see the `RequestContext docs`_.
- .. _DjangoContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
+ .. _RequestContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
Users
-----
-The currently logged-in user, either a ``User`` object or an``AnonymousUser``
+The currently logged-in user, either a ``User`` instance or an``AnonymousUser``
instance, is stored in the template variable ``{{ user }}``::
{% if user.is_anonymous %}
@@ -504,25 +537,25 @@ Thus, you can check permissions in template ``{% if %}`` statements::
Groups
======
-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.
+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 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.
+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
+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,
@@ -530,8 +563,9 @@ Messages are used by the Django admin after successful actions. For example,
The API is simple::
- * To add messages, use ``user.add_message(message_text)``.
- * To retrieve/delete messages, use ``user.get_and_delete_messages()``,
+ * 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.
@@ -541,10 +575,11 @@ a playlist::
def create_playlist(request, songs):
# Create the playlist with the given songs.
# ...
- request.user.add_message("Your playlist was added successfully.")
- return render_to_response("playlists/create", context_instance=DjangoContext(request))
+ 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 ``DjangoContext``, the currently logged-in user and his/her
+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::
@@ -556,7 +591,7 @@ messages are made available in the `template context`_ as the template variable
</ul>
{% endif %}
-Note that ``DjangoContext`` calls ``get_and_delete_messages`` behind the
+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
diff --git a/docs/cache.txt b/docs/cache.txt
index f1f5668137..4fecdc6372 100644
--- a/docs/cache.txt
+++ b/docs/cache.txt
@@ -2,63 +2,180 @@
Django's cache framework
========================
-So, you got slashdotted_. Now what?
+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.
-Django's cache framework gives you three methods of caching dynamic pages in
-memory or in a database. 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.
+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.
-.. _slashdotted: http://en.wikipedia.org/wiki/Slashdot_effect
+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 framework allows for different "backends" -- different methods of
-caching data. There's a simple single-process memory cache (mostly useful as a
-fallback) and a memcached_ backend (the fastest option, by far, if you've got
-the RAM).
+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``.
-Before using the cache, you'll need to tell Django which cache backend you'd
-like to use. Do this by setting the ``CACHE_BACKEND`` in your settings file.
+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.
-The ``CACHE_BACKEND`` setting is a "fake" URI (really an unregistered scheme).
-Examples:
+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``.
- ============================== ===========================================
- CACHE_BACKEND Explanation
- ============================== ===========================================
- memcached://127.0.0.1:11211/ A memcached backend; the server is running
- on localhost port 11211. You can use
- multiple memcached servers by separating
- them with semicolons.
+Local-memory caching
+--------------------
- This backend requires the
- `Python memcached bindings`_.
+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::
- db://tablename/ A database backend in a table named
- "tablename". This table should be created
- with "django-admin createcachetable".
+ CACHE_BACKEND = 'locmem:///'
- file:///var/tmp/django_cache/ A file-based cache stored in the directory
- /var/tmp/django_cache/.
+Simple caching (for development)
+--------------------------------
- simple:/// A simple single-process memory cache; you
- probably don't want to use this except for
- testing. Note that this cache backend is
- NOT thread-safe!
+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::
- locmem:/// A more sophisticated local memory cache;
- this is multi-process- and thread-safe.
+ CACHE_BACKEND = 'simple:///'
- dummy:/// Doesn't actually cache; just implements the
- cache backend interface and doesn't do
- anything. This is an easy way to turn off
- caching for a test environment.
- ============================== ===========================================
+Dummy caching (for development)
+-------------------------------
-All caches may take arguments -- they're given in query-string style. Valid
-arguments are:
+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
@@ -66,7 +183,7 @@ arguments are:
max_entries
For the simple and database backends, the maximum number of entries
- allowed in the cache before it is cleaned. Defaults to 300.
+ 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.
@@ -77,20 +194,21 @@ arguments are:
dumped when max_entries is reached. This makes culling *much* faster
at the expense of more cache misses.
-For example::
+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.
-.. _memcached: http://www.danga.com/memcached/
-.. _Python memcached bindings: ftp://ftp.tummy.com/pub/python-memcached/
-
The per-site cache
==================
-Once the cache is set up, the simplest way to use the cache is to cache your
+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::
@@ -159,52 +277,100 @@ 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 API is simple. The cache module, ``django.core.cache``, exports a
+``cache`` object that's automatically created from the ``CACHE_BACKEND``
+setting::
- # The cache module 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).
+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!'
- # (Wait 30 seconds...)
+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'
+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)::
- # There's also a get_many() interface that only hits the cache once.
- # Also, note that the timeout argument is optional and defaults to what
- # you've given in the settings file.
>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
-
- # get_many() returns a dictionary with all the keys you asked for that
- # actually exist in the cache (and haven't expired).
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
- # There's also a way to delete keys explicitly.
+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.
-Controlling cache: Using Vary headers
-=====================================
+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 site 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.
-The Django cache framework works with `HTTP Vary headers`_ to allow developers
-to instruct caching mechanisms to differ their cache contents depending on
-request HTTP headers.
+ * 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.
-Essentially, the ``Vary`` response HTTP header defines which request headers a
-cache mechanism should take into account when building its cache key.
+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
@@ -241,7 +407,7 @@ 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.
-Note that you can pass multiple headers to ``vary_on_headers()``::
+You can pass multiple headers to ``vary_on_headers()``::
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
@@ -261,7 +427,8 @@ decorator. These two views are equivalent::
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, ``patch_vary_headers()``, directly::
+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):
@@ -273,7 +440,9 @@ You can also use a helper function, ``patch_vary_headers()``, directly::
``patch_vary_headers`` takes an ``HttpResponse`` instance as its first argument
and a list/tuple of header names as its second argument.
-.. _`HTTP Vary headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
+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
======================================
@@ -317,15 +486,19 @@ cache on every access and to store cached versions for, at most, 3600 seconds::
def my_view(request):
...
-Any valid ``Cache-Control`` directive is valid in ``cache_control()``. For a
-full list, see the `Cache-Control spec`_. Just pass the directives as keyword
-arguments to ``cache_control()``, substituting underscores for hyphens. For
-directives that don't take an argument, set the argument to ``True``.
+Any valid ``Cache-Control`` HTTP directive is valid in ``cache_control()``.
+Here's a full list:
-Examples:
+ * ``public=True``
+ * ``private=True``
+ * ``no_cache=True``
+ * ``no_transform=True``
+ * ``must_revalidate=True``
+ * ``proxy_revalidate=True``
+ * ``max_age=num_seconds``
+ * ``s_maxage=num_seconds``
- * ``@cache_control(max_age=3600)`` turns into ``max-age=3600``.
- * ``@cache_control(public=True)`` turns into ``public``.
+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
diff --git a/docs/db-api.txt b/docs/db-api.txt
index e37645e601..8da6ebbac2 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -2,509 +2,1366 @@
Database API reference
======================
-Once you've created your `data models`_, you'll need to retrieve data from the
-database. This document explains the database abstraction API derived from the
-models, and how to create, retrieve and update objects.
+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`: http://www.djangoproject.com/documentation/model_api/
-Throughout this reference, we'll refer to the following Poll application::
+Throughout this reference, we'll refer to the following models, which comprise
+a weblog application::
- class Poll(meta.Model):
- slug = meta.SlugField(unique_for_month='pub_date')
- question = meta.CharField(maxlength=255)
- pub_date = meta.DateTimeField()
- expire_date = meta.DateTimeField()
+ class Blog(models.Model):
+ name = models.CharField(maxlength=100)
+ tagline = models.TextField()
- def __repr__(self):
- return self.question
+ def __str__(self):
+ return self.name
- class Choice(meta.Model):
- poll = meta.ForeignKey(Poll, edit_inline=meta.TABULAR,
- num_in_admin=10, min_num_in_admin=5)
- choice = meta.CharField(maxlength=255, core=True)
- votes = meta.IntegerField(editable=False, default=0)
+ class Author(models.Model):
+ name = models.CharField(maxlength=50)
+ email = models.URLField()
- def __repr__(self):
- return self.choice
+ class __str__(self):
+ return self.name
-Basic lookup functions
-======================
+ 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)
-Each model exposes these module-level functions for lookups:
+ def __str__(self):
+ return self.headline
-get_object(\**kwargs)
----------------------
+Creating objects
+================
-Returns the object matching the given lookup parameters, which should be in
-the format described in "Field lookups" below. Raises a module-level
-``*DoesNotExist`` exception if an object wasn't found for the given parameters.
-Raises ``AssertionError`` if more than one object was found.
+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.
-get_list(\**kwargs)
--------------------
+To create an object, instantiate it using keyword arguments to the model class,
+then call ``save()`` to save it to the database.
-Returns a list of objects matching the given lookup parameters, which should be
-in the format described in "Field lookups" below. If no objects match the given
-parameters, it returns an empty list. ``get_list()`` will always return a list.
+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.)
-get_iterator(\**kwargs)
------------------------
+Assuming models live in a file ``mysite/blog/models.py``, here's an example::
-Just like ``get_list()``, except it returns an iterator instead of a list. This
-is more efficient for large result sets. This example shows the difference::
+ from mysite.blog.models import Blog
+ b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
+ b.save()
- # get_list() loads all objects into memory.
- for obj in foos.get_list():
- print repr(obj)
+This performs an ``INSERT`` SQL statement behind the scenes. Django doesn't hit
+the database until you explicitly call ``save()``.
- # get_iterator() only loads a number of objects into memory at a time.
- for obj in foos.get_iterator():
- print repr(obj)
+The ``save()`` method has no return value.
-get_count(\**kwargs)
---------------------
+Auto-incrementing primary keys
+------------------------------
-Returns an integer representing the number of objects in the database matching
-the given lookup parameters, which should be in the format described in
-"Field lookups" below. ``get_count()`` never raises exceptions
+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()``.
-Depending on which database you're using (e.g. PostgreSQL vs. MySQL), this may
-return a long integer instead of a normal Python integer.
+Example::
-get_values(\**kwargs)
----------------------
+ 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.
-Just like ``get_list()``, except it returns a list of dictionaries instead of
-model-instance objects.
+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.
-It accepts an optional parameter, ``fields``, which should be a list or tuple
-of field names. If you don't specify ``fields``, each dictionary in the list
-returned by ``get_values()`` will have a key and value for each field in the
-database table. If you specify ``fields``, each dictionary will have only the
-field keys/values for the fields you specify. Here's an example, using the
-``Poll`` model defined above::
+(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`_.)
- >>> from datetime import datetime
- >>> p1 = polls.Poll(slug='whatsup', question="What's up?",
- ... pub_date=datetime(2005, 2, 20), expire_date=datetime(2005, 3, 20))
- >>> p1.save()
- >>> p2 = polls.Poll(slug='name', question="What's your name?",
- ... pub_date=datetime(2005, 3, 20), expire_date=datetime(2005, 4, 20))
- >>> p2.save()
- >>> polls.get_list()
- [What's up?, What's your name?]
- >>> polls.get_values()
- [{'id': 1, 'slug': 'whatsup', 'question': "What's up?", 'pub_date': datetime.datetime(2005, 2, 20), 'expire_date': datetime.datetime(2005, 3, 20)},
- {'id': 2, 'slug': 'name', 'question': "What's your name?", 'pub_date': datetime.datetime(2005, 3, 20), 'expire_date': datetime.datetime(2005, 4, 20)}]
- >>> polls.get_values(fields=['id', 'slug'])
- [{'id': 1, 'slug': 'whatsup'}, {'id': 2, 'slug': 'name'}]
+.. _AutoField documentation: TODO: Link
-Use ``get_values()`` when you know you're only going to need a couple of field
-values 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.
+Explicitly specifying auto-primary-key values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-get_values_iterator(\**kwargs)
-------------------------------
+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.
-Just like ``get_values()``, except it returns an iterator instead of a list.
-See the section on ``get_iterator()`` above.
+Example::
-get_in_bulk(id_list, \**kwargs)
--------------------------------
+ b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
+ b3.id # Returns 3.
+ b3.save()
+ b3.id # Returns 3.
-Takes a list of IDs and returns a dictionary mapping each ID to an instance of
-the object with the given ID. Also takes optional keyword lookup arguments,
-which should be in the format described in "Field lookups" below. Here's an
-example, using the ``Poll`` model defined above::
+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.
- >>> from datetime import datetime
- >>> p1 = polls.Poll(slug='whatsup', question="What's up?",
- ... pub_date=datetime(2005, 2, 20), expire_date=datetime(2005, 3, 20))
- >>> p1.save()
- >>> p2 = polls.Poll(slug='name', question="What's your name?",
- ... pub_date=datetime(2005, 3, 20), expire_date=datetime(2005, 4, 20))
- >>> p2.save()
- >>> polls.get_list()
- [What's up?, What's your name?]
- >>> polls.get_in_bulk([1])
- {1: What's up?}
- >>> polls.get_in_bulk([1, 2])
- {1: What's up?, 2: What's your name?}
+Given the above ``'Cheddar Talk'`` blog example, this example would override
+the previous record in the database::
-Field lookups
-=============
+ b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
+ b4.save() # Overrides the previous blog with ID=3!
-Basic field lookups take the form ``field__lookuptype`` (that's a
-double-underscore). For example::
+See _`How Django knows to UPDATE vs. INSERT`, below, for the reason this
+happens.
- polls.get_list(pub_date__lte=datetime.datetime.now())
+Explicitly specifying auto-primary-key values is mostly useful for bulk-saving
+objects, when you're confident you won't have primary-key collision.
-translates (roughly) into the following SQL::
+Saving changes to objects
+=========================
- SELECT * FROM polls_polls WHERE pub_date <= NOW();
+To save changes to an object that's already in the database, use ``save()``.
-.. admonition:: How this is possible
+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::
- Python has the ability to define functions that accept arbitrary name-value
- arguments whose names and values are evaluated at run time. For more
- information, see `Keyword Arguments`_ in the official Python tutorial.
+ b5.name = 'New name'
+ b5.save()
-The DB API supports the following lookup types:
+This performs an ``UPDATE`` SQL statement behind the scenes. Django doesn't hit
+the database until you explicitly call ``save()``.
- =========== ==============================================================
- Type Description
- =========== ==============================================================
- exact Exact match: ``polls.get_object(id__exact=14)``.
- iexact Case-insensitive exact match:
- ``polls.get_list(slug__iexact="foo")`` matches a slug of
- ``foo``, ``FOO``, ``fOo``, etc.
- contains Case-sensitive containment test:
- ``polls.get_list(question__contains="spam")`` returns all polls
- that contain "spam" in the question. (PostgreSQL and MySQL
- only. SQLite doesn't support case-sensitive LIKE statements;
- ``contains`` will act like ``icontains`` for SQLite.)
- icontains Case-insensitive containment test.
- gt Greater than: ``polls.get_list(id__gt=4)``.
- gte Greater than or equal to.
- lt Less than.
- lte Less than or equal to.
- ne Not equal to.
- in In a given list: ``polls.get_list(id__in=[1, 3, 4])`` returns
- a list of polls whose IDs are either 1, 3 or 4.
- startswith Case-sensitive starts-with:
- ``polls.get_list(question__startswith="Would")``. (PostgreSQL
- and MySQL only. SQLite doesn't support case-sensitive LIKE
- statements; ``startswith`` will act like ``istartswith`` for
- SQLite.)
- endswith Case-sensitive ends-with. (PostgreSQL and MySQL only.)
- istartswith Case-insensitive starts-with.
- iendswith Case-insensitive ends-with.
- range Range test:
- ``polls.get_list(pub_date__range=(start_date, end_date))``
- returns all polls with a pub_date between ``start_date``
- and ``end_date`` (inclusive).
- year For date/datetime fields, exact year match:
- ``polls.get_count(pub_date__year=2005)``.
- month For date/datetime fields, exact month match.
- day For date/datetime fields, exact day match.
- isnull True/False; does is IF NULL/IF NOT NULL lookup:
- ``polls.get_list(expire_date__isnull=True)``.
- =========== ==============================================================
+The ``save()`` method has no return value.
-Multiple lookups are allowed, of course, and are translated as "AND"s::
+How Django knows to UPDATE vs. INSERT
+-------------------------------------
- polls.get_list(
- pub_date__year=2005,
- pub_date__month=1,
- question__startswith="Would",
- )
+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:
-...retrieves all polls published in January 2005 that have a question starting with "Would."
+ * If the object's primary key attribute is set to a value that evaluates to
+ ``False`` (such as ``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``.
-For convenience, there's a ``pk`` lookup type, which translates into
-``(primary_key)__exact``. In the polls example, these two statements are
-equivalent::
+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.
- polls.get_object(id__exact=3)
- polls.get_object(pk=3)
+Retrieving objects
+==================
-``pk`` lookups also work across joins. In the polls example, these two
-statements are equivalent::
+To retrieve objects from your database, you construct a ``QuerySet`` via a
+``Manager`` on your model class.
- choices.get_list(poll__id__exact=3)
- choices.get_list(poll__pk=3)
+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``.
-If you pass an invalid keyword argument, the function will raise ``TypeError``.
+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::
-.. _`Keyword Arguments`: http://docs.python.org/tut/node6.html#SECTION006720000000000000000
+ 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."
-OR lookups
-----------
+(``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.
-By default, multiple lookups are "AND"ed together. If you'd like to use ``OR``
-statements in your queries, use the ``complex`` lookup type.
+Retrieving all objects
+----------------------
-``complex`` takes an expression of clauses, each of which is an instance of
-``django.core.meta.Q``. ``Q`` takes an arbitrary number of keyword arguments in
-the standard Django lookup format. And you can use Python's "and" (``&``) and
-"or" (``|``) operators to combine ``Q`` instances. For example::
+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``.
- from django.core.meta import Q
- polls.get_object(complex=(Q(question__startswith='Who') | Q(question__startswith='What')))
+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 ``|`` symbol signifies an "OR", so this (roughly) translates into::
+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.
- SELECT * FROM polls
- WHERE question LIKE 'Who%' OR question LIKE 'What%';
+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.
-You can use ``&`` and ``|`` operators together, and use parenthetical grouping.
Example::
- polls.get_object(complex=(Q(question__startswith='Who') & (Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6))))
+ q1 = Entry.objects.filter(headline__startswith="What")
+ q2 = q1.exclude(pub_date__gte=datetime.now())
+ q3 = q1.filter(pub_date__gte=datetime.now())
-This roughly translates into::
+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.
- SELECT * FROM polls
- WHERE question LIKE 'Who%'
- AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06');
+QuerySets are lazy
+------------------
-See the `OR lookups examples page`_ for more examples.
+``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*.
-.. _OR lookups examples page: http://www.djangoproject.com/documentation/models/or_lookups/
+When QuerySets are evaluated
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Ordering
-========
+You can evaluate a ``QuerySet`` in the following ways:
-The results are automatically ordered by the ordering tuple given by the
-``ordering`` key in the model, but the ordering may be explicitly
-provided by the ``order_by`` argument to a lookup::
+ * **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::
- polls.get_list(
- pub_date__year=2005,
- pub_date__month=1,
- order_by=('-pub_date', 'question'),
- )
+ for e in Entry.objects.all():
+ print e.headline
+
+ * **Slicing.** A ``QuerySet`` can be sliced, using Python's array-slicing
+ syntax, and it executes its database query the first time you slice it.
+ Examples::
+
+ fifth_entry = Entry.objects.all()[4]
+ all_entries_but_the_first_two = Entry.objects.all()[2:]
+ every_second_entry = Entry.objects.all()[::2]
+
+ * **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.
+
+QuerySet methods that return new QuerySets
+------------------------------------------
-The result set above will be ordered by ``pub_date`` descending, then
-by ``question`` ascending. The negative sign in front of "-pub_date" indicates
-descending order. Ascending order is implied. To order randomly, use "?", like
-so::
+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.
- polls.get_list(order_by=['?'])
+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::
- choices.get_list(order_by=('polls.pub_date', 'choice'))
+ 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.
-Relationships (joins)
-=====================
+distinct()
+~~~~~~~~~~
-Joins may implicitly be performed by following relationships:
-``choices.get_list(poll__slug__exact="eggs")`` fetches a list of ``Choice``
-objects where the associated ``Poll`` has a slug of ``eggs``. Multiple levels
-of joins are allowed.
+Returns a new ``QuerySet`` that uses ``SELECT DISTINCT`` in its SQL query. This
+eliminates duplicate rows from the query results.
-Given an instance of an object, related objects can be looked-up directly using
-convenience functions. For example, if ``p`` is a ``Poll`` instance,
-``p.get_choice_list()`` will return a list of all associated choices. Astute
-readers will note that this is the same as
-``choices.get_list(poll__id__exact=p.id)``, except clearer.
+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.
-Each type of relationship creates a set of methods on each object in the
-relationship. These methods are created in both directions, so objects that are
-"related-to" need not explicitly define reverse relationships; that happens
-automatically.
+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()``.
-One-to-one relations
---------------------
+values(*fields)
+~~~~~~~~~~~~~~~
-Each object in a one-to-one relationship will have a ``get_relatedobjectname()``
-method. For example::
+Returns a ``ValuesQuerySet`` -- a ``QuerySet`` that evaluates to a list of
+dictionaries instead of model-instance objects.
- class Place(meta.Model):
- # ...
+Each of those dictionaries represents an object, with the keys corresponding to
+the attribute names of model objects.
- class Restaurant(meta.Model):
- # ...
- the_place = meta.OneToOneField(places.Place)
+This example compares the dictionaries of ``values()`` with the normal model
+objects::
-In the above example, each ``Place`` will have a ``get_restaurant()`` method,
-and each ``Restaurant`` will have a ``get_the_place()`` method.
+ # This list contains a Blog object.
+ >>> Blog.objects.filter(name__startswith='Beatles')
+ [Beatles Blog]
-Many-to-one relations
----------------------
+ # This list contains a dictionary.
+ >>> Blog.objects.filter(name__startswith='Beatles').values()
+ [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]
-In each many-to-one relationship, the related object will have a
-``get_relatedobject()`` method, and the related-to object will have
-``get_relatedobject()``, ``get_relatedobject_list()``, and
-``get_relatedobject_count()`` methods (the same as the module-level
-``get_object()``, ``get_list()``, and ``get_count()`` methods).
+``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.
-In the poll example above, here are the available choice methods on a ``Poll`` object ``p``::
+Example::
- p.get_choice()
- p.get_choice_list()
- p.get_choice_count()
+ >>> Blog.objects.values()
+ [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}],
+ >>> Blog.objects.values('id', 'name')
+ [{'id': 1, 'name': 'Beatles Blog'}]
-And a ``Choice`` object ``c`` has the following method::
+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.
- c.get_poll()
+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::
-Many-to-many relations
-----------------------
+ 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::
-Many-to-many relations result in the same set of methods as `Many-to-one relations`_,
-except that the ``get_relatedobject_list()`` function on the related object will
-return a list of instances instead of a single instance. So, if the relationship
-between ``Poll`` and ``Choice`` was many-to-many, ``choice.get_poll_list()`` would
-return a list.
+ >>> 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)]
-Relationships across applications
----------------------------------
+select_related()
+~~~~~~~~~~~~~~~~
-If a relation spans applications -- if ``Place`` was had a ManyToOne relation to
-a ``geo.City`` object, for example -- the name of the other application will be
-added to the method, i.e. ``place.get_geo_city()`` and
-``city.get_places_place_list()``.
+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.
-Selecting related objects
--------------------------
+The following examples illustrate the difference between plain lookups and
+``select_related()`` lookups. Here's standard lookup::
-Relations are the bread and butter of databases, so there's an option to "follow"
-all relationships and pre-fill them in a simple cache so that later calls to
-objects with a one-to-many relationship don't have to hit the database. Do this by
-passing ``select_related=True`` to a lookup. This results in (sometimes much) larger
-queries, but it means that later use of relationships is much faster.
+ # Hits the database.
+ e = Entry.objects.get(id=5)
-For example, using the Poll and Choice models from above, if you do the following::
+ # Hits the database again to get the related Blog object.
+ b = e.blog
- c = choices.get_object(id__exact=5, select_related=True)
+And here's ``select_related`` lookup::
-Then subsequent calls to ``c.get_poll()`` won't hit the database.
+ # Hits the database.
+ e = Entry.objects.select_related().get(id=5)
-Note that ``select_related`` follows foreign keys as far as possible. If you have the
+ # 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 Poll(meta.Model):
+ class City(models.Model):
# ...
- class Choice(meta.Model):
+ class Person(models.Model):
# ...
- poll = meta.ForeignKey(Poll)
+ hometown = models.ForeignKey(City)
- class SingleVote(meta.Model):
+ class Book(meta.Model):
# ...
- choice = meta.ForeignKey(Choice)
+ author = models.ForeignKey(Person)
-then a call to ``singlevotes.get_object(id__exact=4, select_related=True)`` will
-cache the related choice *and* the related poll::
+...then a call to ``Book.objects.select_related().get(id=4)`` will cache the
+related ``Person`` *and* the related ``City``::
- >>> sv = singlevotes.get_object(id__exact=4, select_related=True)
- >>> c = sv.get_choice() # Doesn't hit the database.
- >>> p = c.get_poll() # Doesn't hit the database.
+ 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 = singlevotes.get_object(id__exact=4) # Note no "select_related".
- >>> c = sv.get_choice() # Hits the database.
- >>> p = c.get_poll() # Hits 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.
-Limiting selected rows
-======================
+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'"})
-The ``limit``, ``offset``, and ``distinct`` keywords can be used to control
-which rows are returned. Both ``limit`` and ``offset`` should be integers which
-will be directly passed to the SQL ``LIMIT``/``OFFSET`` commands.
+ 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.
-If ``distinct`` is True, only distinct rows will be returned. This is equivalent
-to a ``SELECT DISTINCT`` SQL clause. You can use this with ``get_values()`` to
-get distinct values. For example, this returns the distinct first_names::
+ Django inserts the given SQL snippet directly into the ``SELECT``
+ statement, so the resulting SQL of the above example would be::
- >>> people.get_values(fields=['first_name'], distinct=True)
- [{'first_name': 'Adrian'}, {'first_name': 'Jacob'}, {'first_name': 'Simon'}]
+ SELECT blog_entry.*, (pub_date > '2006-01-01')
+ FROM blog_entry;
-Other lookup options
-====================
-There are a few other ways of more directly controlling the generated SQL
-for the lookup. Note that by definition these extra lookups may not be
-portable to different database engines (because you're explicitly writing
-SQL code) and should be avoided if possible.:
+ 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.
-All the extra-SQL params described below may use standard Python string
-formatting codes to indicate parameters that the database engine will
-automatically quote. The ``params`` argument can contain any extra
-parameters to be substituted.
+ Example::
-``select``
-----------
+ 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."
+
+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.
+
+Example::
+
+ Entry.objects.get(id__exact=14)
+
+SQL equivalent::
+
+ SELECT ... WHERE id = 14;
+
+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;
-The ``select`` keyword allows you to select extra fields. This should be a
-dictionary mapping attribute names to a SQL clause to use to calculate that
-attribute. For example::
+gte
+~~~
- polls.get_list(
- select={
- 'choice_count': 'SELECT COUNT(*) FROM choices WHERE poll_id = polls.id'
- }
+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
+~~~~~~
+
+``NULL`` or ``IS NOT NULL`` match. Takes either ``True`` or ``False``, which
+correspond to ``IS NULL`` and ``IS NOT NULL``, respectively.
+
+Example::
+
+ Entry.objects.filter(pub_date__isnull=True)
+
+SQL equivalent::
+
+ SELECT ... WHERE pub_date IS NULL;
+
+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=14)
+ Blog.objects.get(id__exact=14)
+
+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". This is shorthand for "an exact lookup on the primary-key."
+
+In the example ``Blog`` model, the primary key is the ``id`` field, so these
+two statements are equivalent::
+
+ Blog.objects.get(id__exact=14)
+ Blog.objects.get(pk=14)
+
+``pk`` lookups also work across joins. For example, these two statements are
+equivalent::
+
+ Entry.objects.filter(blog__id__exact=3)
+ Entry.objects.filter(blog__pk=3)
+
+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
+
+
+
+
+========================================
+THE REST OF THIS HAS NOT YET BEEN EDITED
+========================================
+
+
+OR lookups
+==========
+
+Keyword argument queries are "AND"ed together. If you have more
+complex query requirements (for example, you need to include an ``OR``
+statement in your query), you need to 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 in
+the same way as keyword arguments to the basic lookup functions like get()
+and filter(). For example::
+
+ Q(question__startswith='What')
+
+is a ``Q`` object encapsulating a single ``LIKE`` query. ``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 the statement::
+
+ Q(question__startswith='Who') | Q(question__startswith='What')
+
+... yields a single ``Q`` object that represents the "OR" of two
+"question__startswith" queries, equivalent to the 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. Parenthetical grouping can also be used.
+
+One or more ``Q`` objects can then provided as arguments to the lookup
+functions. If multiple ``Q`` object arguments are provided to a lookup
+function, they 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))
)
-Each of the resulting ``Poll`` objects will have an extra attribute, ``choice_count``,
-an integer count of associated ``Choice`` objects. Note that the parenthesis required by
-most database engines around sub-selects are not required in Django's ``select``
-clauses.
+... roughly translates into the SQL::
-``where`` / ``tables``
-----------------------
+ SELECT * from polls WHERE question LIKE 'Who%'
+ AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
+
+If necessary, lookup functions can mix the use of ``Q`` objects and keyword
+arguments. All arguments provided to a lookup function (be they keyword
+argument or ``Q`` object) 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.
+
+A ``Q`` objects can also be provided to the ``complex`` keyword argument. For example::
+
+ Poll.objects.get(
+ complex=Q(question__startswith='Who') &
+ (Q(pub_date=date(2005, 5, 2)) |
+ Q(pub_date=date(2005, 5, 6))
+ )
+ )
+
+See the `OR lookups examples page`_ for more examples.
+
+.. _OR lookups examples page: http://www.djangoproject.com/documentation/models/or_lookups/
+
+
+Relationships (joins)
+=====================
+
+When you define a relationship in a model (i.e., a ForeignKey,
+OneToOneField, or ManyToManyField), Django uses the name of the
+relationship to add a descriptor_ on every instance of the model.
+This descriptor behaves just like a normal attribute, providing
+access to the related object or objects. For example,
+``mychoice.poll`` will return the poll object associated with a specific
+instance of ``Choice``.
+
+.. _descriptor: http://users.rcn.com/python/download/Descriptor.htm
+
+Django also adds a descriptor for the 'other' side of the relationship -
+the link from the related model to the model that defines the relationship.
+Since the related model has no explicit reference to the source model,
+Django will automatically derive a name for this descriptor. The name that
+Django chooses depends on the type of relation that is represented. However,
+if the definition of the relation has a `related_name` parameter, Django
+will use this name in preference to deriving a name.
-If you need to explicitly pass extra ``WHERE`` clauses -- perhaps to perform
-non-explicit joins -- use the ``where`` keyword. If you need to
-join other tables into your query, you can pass their names to ``tables``.
+There are two types of descriptor that can be employed: Single Object
+Descriptors and Object Set Descriptors. The following table describes
+when each descriptor type is employed. The local model is the model on
+which the relation is defined; the related model is the model referred
+to by the relation.
-``where`` and ``tables`` both take a list of strings. All ``where`` parameters
-are "AND"ed to any other search criteria.
+ =============== ============= =============
+ Relation Type Local Model Related Model
+ =============== ============= =============
+ OneToOneField Single Object Single Object
+ ForeignKey Single Object Object Set
+
+ ManyToManyField Object Set Object Set
+ =============== ============= =============
+
+Single object descriptor
+------------------------
+
+If the related object is a single object, the descriptor acts
+just as if the related object were an attribute::
+
+ # Obtain the existing poll
+ old_poll = mychoice.poll
+ # Set a new poll
+ mychoice.poll = new_poll
+ # Save the change
+ mychoice.save()
+
+Whenever a change is made to a Single Object Descriptor, save()
+must be called to commit the change to the database.
+
+If no `related_name` parameter is defined, Django will use the
+lower case version of the source model name as the name for the
+related descriptor. For example, if the ``Choice`` model had
+a field::
+
+ coordinator = models.OneToOneField(User)
+
+... instances of the model ``User`` would be able to call:
+
+ old_choice = myuser.choice
+ myuser.choice = new_choice
+
+By default, relations do not allow values of None; if you attempt
+to assign None to a Single Object Descriptor, an AttributeError
+will be thrown. However, if the relation has 'null=True' set
+(i.e., the database will allow NULLs for the relation), None can
+be assigned and returned by the descriptor to represent empty
+relations.
+
+Access to Single Object Descriptors is cached. The first time
+a descriptor on an instance is accessed, the database will be
+queried, and the result stored. Subsequent attempts to access
+the descriptor on the same instance will use the cached value.
+
+Object set descriptor
+---------------------
+
+An Object Set Descriptor acts just like the Manager - as an initial Query
+Set describing the set of objects related to an instance. As such, any
+query refining technique (filter, exclude, etc) can be used on the Object
+Set descriptor. This also means that Object Set Descriptor cannot be evaluated
+directly - the ``all()`` method must be used to produce a Query Set that
+can be evaluated.
+
+If no ``related_name`` parameter is defined, Django will use the lower case
+version of the source model name appended with `_set` as the name for the
+related descriptor. For example, every ``Poll`` object has a ``choice_set``
+descriptor.
+
+The Object Set Descriptor has utility methods to add objects to the
+related object set:
+
+``add(obj1, obj2, ...)``
+ Add the specified objects to the related object set.
+
+``create(\**kwargs)``
+ Create a new object, and put it in the related object set. See
+ _`Creating new objects`
+
+The Object Set Descriptor may also have utility methods to remove objects
+from the related object set:
+
+``remove(obj1, obj2, ...)``
+ Remove the specified objects from the related object set.
+
+``clear()``
+ Remove all objects from the related object set.
+
+These two removal methods will not exist on ForeignKeys where ``Null=False``
+(such as in the Poll example). This is to prevent database inconsistency - if
+the related field cannot be set to None, then an object cannot be removed
+from one relation without adding it to another.
+
+The members of a related object set can be assigned from any iterable object.
For example::
- polls.get_list(question__startswith='Who', where=['id IN (3, 4, 5, 20)'])
+ mypoll.choice_set = [choice1, choice2]
-...translates (roughly) into the following SQL:
+If the ``clear()`` method is available, any pre-existing objects will be removed
+from the Object Set before all objects in the iterable (in this case, a list)
+are added to the choice set. If the ``clear()`` method is not available, all
+objects in the iterable will be added without removing any existing elements.
- SELECT * FROM polls_polls WHERE question LIKE 'Who%' AND id IN (3, 4, 5, 20);
+Each of these operations on the Object Set Descriptor has immediate effect
+on the database - every add, create and remove is immediately and
+automatically saved to the database.
-Changing objects
-================
+Relationships and queries
+=========================
-Once you've retrieved an object from the database using any of the above
-options, changing it is extremely easy. Make changes directly to the
-objects fields, then call the object's ``save()`` method::
+When composing a ``filter`` or ``exclude`` refinement, it may be necessary to
+include conditions that span relationships. Relations can be followed as deep
+as required - just add descriptor names, separated by double underscores, to
+describe the full path to the query attribute. The query::
- >>> p = polls.get_object(id__exact=15)
- >>> p.slug = "new_slug"
- >>> p.pub_date = datetime.datetime.now()
- >>> p.save()
+ Foo.objects.filter(name1__name2__name3__attribute__lookup=value)
-Creating new objects
-====================
+... is interpreted as 'get every Foo that has a name1 that has a name2 that
+has a name3 that has an attribute with lookup matching value'. In the Poll
+example::
-Creating new objects (i.e. ``INSERT``) is done by creating new instances
-of objects then calling save() on them::
+ Choice.objects.filter(poll__slug__startswith="eggs")
- >>> p = polls.Poll(slug="eggs",
- ... question="How do you like your eggs?",
- ... pub_date=datetime.datetime.now(),
- ... expire_date=some_future_date)
- >>> p.save()
+... describes the set of choices for which the related poll has a slug
+attribute that starts with "eggs". Django automatically composes the joins
+and conditions required for the SQL query.
-Calling ``save()`` on an object with a primary key whose value is ``None``
-signifies to Django that the object is new and should be inserted.
+Creating new related objects
+============================
-Related objects (e.g. ``Choices``) are created using convenience functions::
+Related objects are created using the ``create()`` convenience function on
+the descriptor Manager for relation::
- >>> p.add_choice(choice="Over easy", votes=0)
- >>> p.add_choice(choice="Scrambled", votes=0)
- >>> p.add_choice(choice="Fertilized", votes=0)
- >>> p.add_choice(choice="Poached", votes=0)
- >>> p.get_choice_count()
+ >>> p.choice_set.create(choice="Over easy", votes=0)
+ >>> p.choice_set.create(choice="Scrambled", votes=0)
+ >>> p.choice_set.create(choice="Fertilized", votes=0)
+ >>> p.choice_set.create(choice="Poached", votes=0)
+ >>> p.choice_set.count()
4
-Each of those ``add_choice`` methods is equivalent to (but much simpler than)::
+Each of those ``create()`` methods is equivalent to (but much simpler than)::
- >>> c = polls.Choice(poll_id=p.id, choice="Over easy", votes=0)
+ >>> c = Choice(poll_id=p.id, choice="Over easy", votes=0)
>>> c.save()
-Note that when using the `add_foo()`` methods, you do not give any value
+Note that when using the `create()`` method, you do not give any value
for the ``id`` field, nor do you give a value for the field that stores
the relation (``poll_id`` in this case).
-The ``add_FOO()`` method always returns the newly created object.
+The ``create()`` method always returns the newly created object.
Deleting objects
================
@@ -514,31 +1371,27 @@ deletes the object and has no return value. Example::
>>> c.delete()
-Comparing objects
-=================
+Objects can also be deleted in bulk. Every Query Set has a ``delete()`` method
+that will delete all members of the query set. For example::
-To compare two model objects, just use the standard Python comparison operator,
-the double equals sign: ``==``. Behind the scenes, that compares the primary
-key values of two models.
+ >>> Polls.objects.filter(pub_date__year=2005).delete()
-Using the ``Poll`` example above, the following two statements are equivalent::
+would bulk delete all Polls with a year of 2005. Note that ``delete()`` is the
+only Query Set method that is not exposed on the Manager itself.
- some_poll == other_poll
- some_poll.id == other_poll.id
+This is a safety mechanism to prevent you from accidentally requesting
+``Polls.objects.delete()``, and deleting *all* the polls.
-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::
+If you *actually* want to delete all the objects, then you have to explicitly
+request a complete query set::
- some_obj == other_obj
- some_obj.name == other_obj.name
+ Polls.objects.all().delete()
Extra instance methods
======================
-In addition to ``save()``, ``delete()`` and all of the ``add_*`` and ``get_*``
-related-object methods, a model object might get any or all of the following
-methods:
+In addition to ``save()``, ``delete()``, a model object might get any or all
+of the following methods:
get_FOO_display()
-----------------
@@ -572,10 +1425,10 @@ 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.
+``DoesNotExist`` exception when appropriate.
Both methods accept optional keyword arguments, which should be in the format
-described in "Field lookups" above.
+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.
@@ -604,14 +1457,14 @@ returns an empty string.
get_FOO_size()
--------------
-For every ``FileField``, the object will have a ``get_FOO_size()`` method,
+For every ``FileField``, the object will have a ``get_FOO_filename()`` 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,
+For every ``FileField``, the object will have a ``get_FOO_filename()`` 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
@@ -623,48 +1476,3 @@ 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.
-
-Extra module functions
-======================
-
-In addition to every function described in "Basic lookup functions" above, a
-model module might get any or all of the following methods:
-
-get_FOO_list(kind, \**kwargs)
------------------------------
-
-For every ``DateField`` and ``DateTimeField``, the model module will have a
-``get_FOO_list()`` function, where ``FOO`` is the name of the field. This
-returns a list of ``datetime.datetime`` objects representing all available
-dates of the given scope, as defined by the ``kind`` argument. ``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.
-
-Additional, optional keyword arguments, in the format described in
-"Field lookups" above, are also accepted.
-
-Here's an example, using the ``Poll`` model defined above::
-
- >>> from datetime import datetime
- >>> p1 = polls.Poll(slug='whatsup', question="What's up?",
- ... pub_date=datetime(2005, 2, 20), expire_date=datetime(2005, 3, 20))
- >>> p1.save()
- >>> p2 = polls.Poll(slug='name', question="What's your name?",
- ... pub_date=datetime(2005, 3, 20), expire_date=datetime(2005, 4, 20))
- >>> p2.save()
- >>> polls.get_pub_date_list('year')
- [datetime.datetime(2005, 1, 1)]
- >>> polls.get_pub_date_list('month')
- [datetime.datetime(2005, 2, 1), datetime.datetime(2005, 3, 1)]
- >>> polls.get_pub_date_list('day')
- [datetime.datetime(2005, 2, 20), datetime.datetime(2005, 3, 20)]
- >>> polls.get_pub_date_list('day', question__contains='name')
- [datetime.datetime(2005, 3, 20)]
-
-``get_FOO_list()`` also accepts an optional keyword argument ``order``, which
-should be either ``"ASC"`` or ``"DESC"``. This specifies how to order the
-results. Default is ``"ASC"``.
diff --git a/docs/design_philosophies.txt b/docs/design_philosophies.txt
index 89a537da17..17ed3ad6da 100644
--- a/docs/design_philosophies.txt
+++ b/docs/design_philosophies.txt
@@ -20,6 +20,9 @@ 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
@@ -49,7 +52,10 @@ 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 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
@@ -96,8 +102,9 @@ 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`` argument exists. It's an optional
-performance booster for the common case of selecting "every related object."
+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
----------------------
@@ -144,6 +151,8 @@ 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
---------------
@@ -186,6 +195,13 @@ 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
--------------------------
diff --git a/docs/django-admin.txt b/docs/django-admin.txt
index 45cc2363dc..b314366fee 100644
--- a/docs/django-admin.txt
+++ b/docs/django-admin.txt
@@ -1,15 +1,10 @@
-===========================
-The django-admin.py utility
-===========================
+=============================
+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.
-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 to it from some place on your path, such as ``/usr/local/bin``.
-
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``:
@@ -19,6 +14,11 @@ two things for you before delegating to ``django-admin.py``:
* 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 to it from some place on your path, such as ``/usr/local/bin``.
+
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
@@ -38,18 +38,17 @@ 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 "modelmodule"s. A "modelmodule," in this case, is
-the name of a file containing Django models. For example, if you have a model
-module called ``myproject/apps/polls/pollmodels.py``, the "modelmodule" in this
-case would be ``"pollmodels"``.
+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 [modelmodule modelmodule ...]
-----------------------------------------
+adminindex [appname appname ...]
+--------------------------------
-Prints the admin-index template snippet for the given model module(s).
+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.
@@ -64,29 +63,41 @@ backend. See the `cache documentation`_ for more information.
.. _cache documentation: http://www.djangoproject.com/documentation/cache/
-createsuperuser
----------------
+dbshell
+-------
-Creates a superuser account interactively. It asks you for a username, e-mail
-address and password.
+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.
-You can specify ``username email password`` on the command line, for convenient
-use in shell scripts. Example::
+ * 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.
- django-admin.py createsuperuser john john@example.com mypassword
+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.
-init
-----
+diffsettings
+------------
-Initializes the database with the tables and data Django needs by default.
-Specifically, these are the database tables from the ``auth`` and ``core``
-models.
+Displays differences between the current settings file and Django's default
+settings.
-inspectdb [dbname]
-------------------
+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``.
-Introspects the database tables in the given database and outputs a Django
-model module to standard output.
+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.
+
+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
@@ -101,12 +112,12 @@ output:
``'This field type is a guess.'`` next to the field in the generated
model.
- * **New in Django development version.** 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
+ * 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.
@@ -115,25 +126,16 @@ 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.
-If you're using Django 0.90 or 0.91, you'll need to add ``primary_key=True`` to
-one field in each model. In the Django development version, primary keys are
-automatically introspected for PostgreSQL and MySQL, and Django puts in the
-``primary_key=True`` where needed.
+Primary keys are automatically introspected for PostgreSQL and MySQL, 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.
-
-install [modelmodule modelmodule ...]
--------------------------------------
-
-Executes the equivalent of ``sqlall`` for the given model module(s).
+only works in PostgreSQL and with certain types of MySQL tables.
-installperms [modelmodule modelmodule ...]
-------------------------------------------
+install [appname appname ...]
+-----------------------------
-Installs any admin permissions for the given model module(s) that aren't
-already installed in the database. Outputs a message telling how many
-permissions were added, if any.
+Executes the equivalent of ``sqlall`` for the given appnames.
runserver [optional port number, or ipaddr:port]
------------------------------------------------
@@ -144,7 +146,7 @@ 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 superusers (root).
+are reserved for the superuser (root).
DO NOT USE THIS SERVER IN A PRODUCTION SETTING.
@@ -153,7 +155,7 @@ 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" option below.) If the validator finds errors, it will print
+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.
@@ -180,49 +182,49 @@ shell
Starts the Python interactive interpreter.
-**New in Django development version:** Uses 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 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 [modelmodule modelmodule ...]
----------------------------------
+sql [appname appname ...]
+-------------------------
-Prints the CREATE TABLE SQL statements for the given model module(s).
+Prints the CREATE TABLE SQL statements for the given appnames.
-sqlall [modelmodule modelmodule ...]
-------------------------------------
+sqlall [appname appname ...]
+----------------------------
-Prints the CREATE TABLE and initial-data SQL statements for the given model module(s).
+Prints the CREATE TABLE and initial-data SQL statements for the given appnames.
-sqlclear [modelmodule modelmodule ...]
+sqlclear [appname appname ...]
--------------------------------------
-Prints the DROP TABLE SQL statements for the given model module(s).
+Prints the DROP TABLE SQL statements for the given appnames.
-sqlindexes [modelmodule modelmodule ...]
+sqlindexes [appname appname ...]
----------------------------------------
-Prints the CREATE INDEX SQL statements for the given model module(s).
+Prints the CREATE INDEX SQL statements for the given appnames.
-sqlinitialdata [modelmodule modelmodule ...]
+sqlinitialdata [appname appname ...]
--------------------------------------------
-Prints the initial INSERT SQL statements for the given model module(s).
+Prints the initial INSERT SQL statements for the given appnames.
-sqlreset [modelmodule modelmodule ...]
+sqlreset [appname appname ...]
--------------------------------------
-Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given model module(s).
+Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given appnames.
-sqlsequencereset [modelmodule modelmodule ...]
+sqlsequencereset [appname appname ...]
----------------------------------------------
Prints the SQL statements for resetting PostgreSQL sequences for the given
-model module(s).
+appnames.
See http://simon.incutio.com/archive/2004/04/21/postgres for more information.
@@ -252,11 +254,12 @@ Available options
Example usage::
- django-admin.py init --settings=myproject.settings
+ django-admin.py syncdb --settings=mysite.settings
Explicitly specifies the settings module to use. The settings module should be
-in Python path syntax, e.g. "myproject.settings". If this isn't provided,
-``django-admin.py`` will use the DJANGO_SETTINGS_MODULE environment variable.
+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.
@@ -266,7 +269,7 @@ setting ``DJANGO_SETTINGS_MODULE`` for you.
Example usage::
- django-admin.py init --pythonpath='/home/djangoprojects/myproject'
+ 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
@@ -282,3 +285,27 @@ setting the Python path for you.
Displays a help message that includes a terse list of all available actions and
options.
+
+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/docs/email.txt b/docs/email.txt
index ae55a51a14..b38b855cb3 100644
--- a/docs/email.txt
+++ b/docs/email.txt
@@ -20,11 +20,11 @@ In two lines::
send_mail('Subject here', 'Here is the message.', 'from@example.com',
['to@example.com'], fail_silently=False)
-The send_mail function
-======================
+send_mail()
+===========
The simplest way to send e-mail is using the function
-``django.core.mail.send_mail``. Here's its definition::
+``django.core.mail.send_mail()``. Here's its definition::
send_mail(subject, message, from_email, recipient_list,
fail_silently=False, auth_user=EMAIL_HOST_USER,
@@ -42,20 +42,19 @@ are required.
* ``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``: **New in Django development version.** 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``: **New in Django development version.** 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.
+ * ``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
-The send_mass_mail function
-===========================
+send_mass_mail()
+================
-``django.core.mail.send_mass_mail`` is intended to handle mass e-mailing.
+``django.core.mail.send_mass_mail()`` is intended to handle mass e-mailing.
Here's the definition::
send_mass_mail(datatuple, fail_silently=False,
@@ -66,25 +65,24 @@ Here's the definition::
(subject, message, from_email, recipient_list)
``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
-as in ``send_mail()``. Note that ``auth_user`` and ``auth_password`` are only
-available in the Django development version.
+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
-----------------------------
+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.
-The mail_admins function
-========================
+mail_admins()
+=============
-``django.core.mail.mail_admins`` is a shortcut for sending an e-mail to the
+``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)
@@ -94,14 +92,16 @@ site admins, as defined in the `ADMINS setting`_. Here's the definition::
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: http://www.djangoproject.com/documentation/settings/#admins
.. _EMAIL_SUBJECT_PREFIX setting: http://www.djangoproject.com/documentation/settings/#email-subject-prefix
.. _SERVER_EMAIL setting: http://www.djangoproject.com/documentation/settings/#server-email
-The mail_managers function
-==========================
+mail_managers() function
+========================
-``django.core.mail.mail_managers`` is just like ``mail_admins``, except it
+``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::
diff --git a/docs/faq.txt b/docs/faq.txt
index 977a3644fe..e8bf09a7d3 100644
--- a/docs/faq.txt
+++ b/docs/faq.txt
@@ -8,14 +8,20 @@ General questions
Why does this project exist?
----------------------------
-Django grew from a very practical need: in our fast-paced newsroom, we often
-have only a matter of hours to take a complicated Web application from
-concept to public launch. Django was designed to not only allow us to
-build Web applications quickly, but to allow us to build them right.
+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.
+
+Thus, Django was designed not only to allow fast Web development, but
+*best-practice* Web development.
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`_, `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/
@@ -29,25 +35,28 @@ to early 1950s. To this day, he's considered one of the best guitarists of all t
Listen to his music. You'll like it.
-According to Wikipedia_, "Django is pronounced **zhane**-go (with a long 'a')."
+Django is pronounced **JANG**-oh. Rhymes with FANG-oh.
.. _Django Reinhardt: http://en.wikipedia.org/wiki/Django_Reinhardt
-.. _Wikipedia: http://en.wikipedia.org/wiki/Django_Reinhardt
Is Django stable?
-----------------
-We've been using Django for almost two years. Sites built on Django have
-weathered traffic spikes of over one million hits an hour, and at least
-one Slashdotting. Yes, it's quite stable.
+Yes. World Online has been using Django for more than two years. Sites built on
+Django have weathered traffic spikes of over one million hits an hour and at
+least one Slashdotting. 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 ships with clean separation of the database layer from the
-application layer and a simple-yet-powerful `cache framework`_.
+
+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`: http://www.djangoproject.com/documentation/cache/
@@ -60,32 +69,34 @@ 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's editor of
- editorial innovations at washingtonpost.com, and he 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.
+ 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. He's enthusiastic, he's passionate about best
- practices in Web development, and he really likes squirrels. Probably to a
- fault. He went back to university to finish his degree and is poised to
- continue doing big, exciting things on the Web. He lives in England.
+ 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``.
-`Jacob Kaplan-Moss`_
- Jacob is a whipper-snapper from California who spends equal time coding and
- cooking. He does Web development for 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``.
-
`Wilson Miner`_
Wilson's design-fu makes us all look like rock stars. When not sneaking
into apartment complex swimming pools, he's the Commercial Development
@@ -106,18 +117,17 @@ Lawrence, Kansas, USA.
Which sites use Django?
-----------------------
-The Django wiki features a `list of Django-powered sites`_. Feel free to add
-your Django-powered site to the list.
+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?
-----------------------------------------------------------------------------------------------------------------------------------------------------
-That's because Django isn't strictly a MVC framework. We don't really believe in
-any capital-M Methodologies; we do what "feels" right. If you squint the right
-way, you can call Django's ORM the "Model", the view functions the "View", and
-the dynamically-generated API the "Controller" -- but not really.
+That's because Django isn't strictly a MVC framework. If you squint the right
+way, you can call Django's database layer the "Model", the view functions the
+"View", and the URL dispatcher the "Controller" -- but not really.
In fact, you might say that Django is a "MTV" framework -- that is, Model,
Template, and View make much more sense to us.
@@ -183,9 +193,13 @@ begin maintaining backwards compatibility. This should happen in a couple of
months or so, although it's entirely possible that it could happen earlier.
That translates into summer 2006.
+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?
@@ -225,12 +239,12 @@ How do I get started?
How do I fix the "install a later version of setuptools" error?
---------------------------------------------------------------
-Just run the ``ex_setup.py`` script in the Django distribution.
+Just run the ``ez_setup.py`` script in the Django distribution.
What are Django's prerequisites?
--------------------------------
-Django requires Python_ 2.3 or later.
+Django requires Python_ 2.3 or later. No other Python libraries are required.
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
@@ -256,7 +270,7 @@ Not if you just want to play around and develop things on your local computer.
Django comes with its own Web server, and things should Just Work.
For production use, though, we recommend mod_python. The Django developers have
-been running it on mod_python for about two years, and it's quite stable.
+been running it on mod_python for more than two years, and it's quite stable.
However, if you don't want to use mod_python, you can use a different server,
as long as that server has WSGI_ hooks. See the `server arrangements wiki page`_.
@@ -325,6 +339,14 @@ 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?
-----------------------------------
@@ -367,9 +389,8 @@ input.
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 a
-``django-admin.py updatedb`` command, which would output the necessary
-``ALTER TABLE`` statements, if any.
+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?
------------------------------------------------------
@@ -392,19 +413,19 @@ 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.core.db import db
- >>> db.queries
+ >>> 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'}]
-``db.queries`` is only available if ``DEBUG`` is ``True``. It's a list of
-dictionaries in order of query execution. Each dictionary has the following::
+``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.
-``db.queries`` includes all SQL statements -- INSERTs, UPDATES, SELECTs, etc.
-Each time your app hits the database, the query will be recorded.
+``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?
----------------------------------------------
@@ -465,8 +486,8 @@ documentation.
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.
+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,
@@ -477,9 +498,9 @@ 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 ``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.
+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
@@ -492,10 +513,23 @@ next question.
The dynamically-generated admin site is ugly! How can I change it?
------------------------------------------------------------------
-We think it's very purty, 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, so any changes you'd like to make should
-be possible by editing the CSS stylesheet. We've got a `guide to the CSS used in
-the admin`_ to get you started.
+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`: http://www.djangoproject.com/documentation/admin_css/
+
+How do I create users without having to edit password hashes?
+-------------------------------------------------------------
+
+We don't recommend you create users via the admin interface, because at the
+moment it requires you to edit password hashes manually. (Passwords are hashed
+using one-way hash algorithms for security; there's currently no Web interface
+for changing passwords by entering the actual password rather than the hash.)
+
+To create a user, you'll have to use the Python API. See `creating users`_ for
+full info.
+
+.. _creating users: http://www.djangoproject.com/documentation/authentication/#creating-users
diff --git a/docs/flatpages.txt b/docs/flatpages.txt
index 21416079e9..73b5653c6b 100644
--- a/docs/flatpages.txt
+++ b/docs/flatpages.txt
@@ -3,7 +3,8 @@ 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.
+"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
@@ -23,10 +24,10 @@ 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"``
+ 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 ``django-admin.py install flatpages``.
+ 3. Run the command ``manage.py syncdb``.
.. _INSTALLED_APPS: http://www.djangoproject.com/documentation/settings/#installed-apps
.. _MIDDLEWARE_CLASSES: http://www.djangoproject.com/documentation/settings/#middleware-classes
@@ -34,10 +35,10 @@ To install the flatpages app, follow these steps:
How it works
============
-``django-admin.py install flatpages`` creates two tables in your database:
-``django_flatpages`` and ``django_flatpages_sites``. ``django_flatpages`` is a
-simple lookup table that essentially maps a URL to a title and bunch of text
-content. ``django_flatpages_sites`` associates a flatpage with a site.
+``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
@@ -49,7 +50,7 @@ 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 DjangoContext_ in rendering the template.
+ 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.
@@ -63,7 +64,7 @@ resort.
For more on middleware, read the `middleware docs`_.
.. _SITE_ID: http://www.djangoproject.com/documentation/settings/#site-id
-.. _DjangoContext: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
+.. _RequestContext: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
.. _middleware docs: http://www.djangoproject.com/documentation/middleware/
How to add, change and delete flatpages
@@ -80,8 +81,8 @@ Via the Python API
------------------
Flatpages are represented by a standard `Django model`_, which lives in
-`django/contrib/flatpages/models/flatpages.py`_. You can access flatpage
-objects via the `Django database API`_.
+`django/contrib/flatpages/models.py`_. You can access flatpage objects via the
+`Django database API`_.
.. _Django model: http://www.djangoproject.com/documentation/model_api/
.. _django/contrib/flatpages/models/flatpages.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/flatpages/models/flatpages.py
@@ -90,17 +91,17 @@ objects via the `Django database API`_.
Flatpage templates
==================
-By default, flatpages are rendered via the template ``flatpages/default``, but
-you can override that for a particular flatpage.
+By default, flatpages are rendered via the template ``flatpages/default.html``,
+but you can override that for a particular flatpage.
-Creating the ``flatpages/default`` template is your responsibility; in your
-template directory, just create a ``flatpages`` directory containing a file
-``default.html``.
+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`` template::
+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">
diff --git a/docs/forms.txt b/docs/forms.txt
index 5df942c15f..2f8a3106fc 100644
--- a/docs/forms.txt
+++ b/docs/forms.txt
@@ -9,10 +9,7 @@ code. It is, and this document explains how the framework works.
.. admonition:: A note to the lazy
If all you want to do is present forms for a user to create and/or
- update a given object, don't read any further. Instead, click thyself
- to the `generic views`_ documentation. The following exercises are
- for those interested in how Django's form framework works and those
- needing to do more than simple creation/updating.
+ update a given object, you may be able to use `generic views`_.
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
@@ -32,13 +29,14 @@ this document, we'll be working with the following model, a "place" object::
state = meta.USStateField()
zip_code = meta.CharField(maxlength=5, blank=True)
place_type = meta.IntegerField(choices=PLACE_TYPES)
- class META:
- admin = meta.Admin()
- def __repr__(self):
+ class Admin:
+ pass
+
+ def __str__(self):
return self.name
-Defining the above class is enough to create an admin interface to a ``place``,
+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?
Manipulators
@@ -53,11 +51,11 @@ similar, but the former knows how to create new instances of the model, while
the later modifies existing instances. Both types of classes are automatically
created when you define a new class::
- >>> from django.models.places import places
- >>> places.AddManipulator
- <class django.models.places.PlaceManipulatorAdd at 0x4c1540>
- >>> places.ChangeManipulator
- <class django.models.places.PlaceManipulatorChange at 0x4c1630>
+ >>> from mysite.myapp.models import Place
+ >>> Place.AddManipulator
+ <class 'django.models.manipulators.AddManipulator'>
+ >>> Place.ChangeManipulator
+ <class 'django.models.manipulators.ChangeManipulator'>
Using the ``AddManipulator``
----------------------------
@@ -65,16 +63,15 @@ 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.core.exceptions import Http404
- from django.core.extensions import render_to_response
- from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
- from django.models.places import places
- from django.core import formfields
+ 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 = places.AddManipulator()
+ manipulator = Place.AddManipulator()
# Make a copy of the POSTed data so that do_html2python can
# modify it in place (request.POST is immutable).
@@ -110,16 +107,17 @@ view with a form that submits to this flawed creation view::
"""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 = formfields.FormWrapper(places.AddManipulator(), {}, {})
- return render_to_response('places/naive_create_form', {'form': form})
+ 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 ``formfields.FormWrapper`` object is a wrapper that templates can
-easily deal with to create forms. Here's the ``naive_create_form`` template::
+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" %}
+ {% extends "base.html" %}
{% block content %}
<h1>Create a place:</h1>
@@ -156,23 +154,23 @@ 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 = places.AddManipulator()
+ manipulator = Place.AddManipulator()
new_data = request.POST.copy()
# Check for validation errors
errors = manipulator.get_validation_errors(new_data)
if errors:
- return render_to_response('places/errors', {'errors': errors})
+ return render_to_response('places/errors.html', {'errors': errors})
else:
- manipulator.do_html2python(request.POST)
- new_place = manipulator.save(request.POST)
+ manipulator.do_html2python(new_data)
+ 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" %}
+ {% extends "base.html" %}
{% block content %}
@@ -200,7 +198,7 @@ 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::
+.. 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
@@ -209,7 +207,7 @@ context.
Below is the finished view::
def create_place(request):
- manipulator = places.AddManipulator()
+ manipulator = Place.AddManipulator()
if request.POST:
# If data was POSTed, we're trying to create a new Place.
@@ -233,18 +231,18 @@ Below is the finished view::
errors = new_data = {}
# Create the FormWrapper, template, context, response.
- form = formfields.FormWrapper(manipulator, new_data, errors)
- return render_to_response('places/create_form', {'form': form})
+ 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" %}
+ {% extends "base.html" %}
{% block content %}
<h1>Create a place:</h1>
{% if form.has_errors %}
- <h2>Please correct the following error{{ errors|pluralize }}:</h2>
+ <h2>Please correct the following error{{ form.error_dict|pluralize }}:</h2>
{% endif %}
<form method="post" action=".">
@@ -289,7 +287,8 @@ The second argument is the error list retrieved from
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.
+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``
-------------------------------
@@ -301,8 +300,8 @@ about editing an existing one? It's shockingly similar to creating a new one::
# Get the place in question from the database and create a
# ChangeManipulator at the same time.
try:
- manipulator = places.ChangeManipulator(place_id)
- except places.PlaceDoesNotExist:
+ manipulator = Place.ChangeManipulator(place_id)
+ except Place.DoesNotExist:
raise Http404
# Grab the Place object in question for future use.
@@ -322,8 +321,8 @@ about editing an existing one? It's shockingly similar to creating a new one::
# This makes sure the form accurate represents the fields of the place.
new_data = place.__dict__
- form = formfields.FormWrapper(manipulator, new_data, errors)
- return render_to_response('places/edit_form', {'form': form, 'place': place})
+ form = forms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('places/edit_form.html', {'form': form, 'place': place})
The only real differences are:
@@ -362,7 +361,7 @@ 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.core import formfields
+ from django import forms
urgency_choices = (
(1, "Extremely urgent"),
@@ -371,18 +370,18 @@ for a "contact" form on a website::
(4, "Unimportant"),
)
- class ContactManipulator(formfields.Manipulator):
+ class ContactManipulator(forms.Manipulator):
def __init__(self):
self.fields = (
- formfields.EmailField(field_name="from", is_required=True),
- formfields.TextField(field_name="subject", length=30, maxlength=200, is_required=True),
- formfields.SelectField(field_name="urgency", choices=urgency_choices),
- formfields.LargeTextField(field_name="contents", is_required=True),
+ 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.core.formfields`` module for
+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.
@@ -401,8 +400,8 @@ Here's a simple function that might drive the above form::
return HttpResponseRedirect("/contact/thankyou/")
else:
errors = new_data = {}
- form = formfields.FormWrapper(manipulator, new_data, errors)
- return render_to_response('contact_form', {'form': form})
+ form = forms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('contact_form.html', {'form': form})
Validators
==========
@@ -410,16 +409,17 @@ 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, but defining
-your own couldn't be easier::
+``django.core.validators`` defines a host of validator functions (see below),
+but defining your own couldn't be easier::
- from django.core import validators, formfields
+ from django.core import validators
+ from django import forms
- class ContactManipulator(formfields.Manipulator):
+ class ContactManipulator(forms.Manipulator):
def __init__(self):
self.fields = (
# ... snip fields as above ...
- formfields.EmailField(field_name="to", validator_list=[self.isValidToAddress])
+ forms.EmailField(field_name="to", validator_list=[self.isValidToAddress])
)
def isValidToAddress(self, field_data, all_data):
@@ -432,10 +432,153 @@ 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. Note that at the point validators are called all
-data will still be strings (as ``do_html2python`` hasn't been called yet).
+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.
+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_vaue``, 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.
+
+ 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.
+
+``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`: http://www.djangoproject.com/documentation/generic_views/
+.. _`models API`: http://www.djangoproject.com/documentation/model_api/
+.. _settings: http://www.djangoproject.com/documentation/settings/
diff --git a/docs/generic_views.txt b/docs/generic_views.txt
index 3e6d2244d9..5d20f2e41b 100644
--- a/docs/generic_views.txt
+++ b/docs/generic_views.txt
@@ -1,11 +1,11 @@
-===================
-Using generic views
-===================
+=============
+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 views.
+an object without actually needing to write any Python code.
Django's generic views contain the following:
@@ -28,14 +28,14 @@ Django's generic views contain the following:
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 example, here's the URLconf for the simple weblog app that
-drives the blog on djangoproject.com::
+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 = {
- 'app_label': 'blog',
- 'module_name': 'entries',
+ 'queryset': Entry.objects.all(),
'date_field': 'pub_date',
}
@@ -47,388 +47,902 @@ drives the blog on djangoproject.com::
(r'^/?$', 'archive_index', info_dict),
)
-As you can see, this URLconf defines a few options in ``info_dict`` that tell
-the generic view which model to use (``blog.entries`` in this case), as well as
-some extra information.
+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
-``app_label``, ``module_name``, etc.).
+``queryset``, ``date_field``, etc.).
-Most of the generic views that follow require the ``app_label`` and
-``module_name`` keys. These values are easiest to explain through example::
+Most generic views require the ``queryset`` key, which is a ``QuerySet``
+instance; see the `database API docs`_ for more information about ``Queryset``
+objects.
- >>> from django.models.blog import entries
-
-In the above line, ``blog`` is the ``app_label`` (the name of the file that
-holds all your model definitions) and ``entries`` is the ``module_name``
-(either a pluralized, lowercased version of the model class name, or the value
-of the ``module_name`` option of your model). In the docs below, these keys
-will not be repeated, but each generic view requires them.
-
-Using "simple" generic views
-============================
+"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. These views are:
+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.
+
+**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``
+-------------------------------------------
-``direct_to_template``
- Renders a given template, passing it a ``{{ params }}`` template variable,
- which is a dictionary of the parameters captured in the URL. This requires
- the ``template`` argument.
+**Description:**
- For example, given the following URL patterns::
+Redirects to a given URL.
- urlpatterns = patterns('django.views.generic.simple',
- (r'^foo/$', 'direct_to_template', {'template': 'foo_index'}),
- (r'^foo/(?P<id>\d+)/$', 'direct_to_template', {'template': 'foo_detail'}),
- )
+The given URL may contain dictionary-style string formatting, which will be
+interpolated against the parameters captured in the URL.
- ... a request to ``/foo/`` would cause the ``foo_index`` template to be
- rendered, and a request to ``/foo/15/`` would cause the ``foo_detail``
- template to be rendered with a context variable ``{{ params.id }}`` that is
- set to ``15``.
+If the given URL is ``None``, Django will return an ``HttpResponseGone`` (410).
-``redirect_to``
- Issue a redirect to a given URL.
+**Required arguments:**
- 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 urlpattern::
+ * ``url``: The URL to redirect to, as a string. Or ``None`` to raise a 410
+ (Gone) HTTP error.
- urlpatterns = patterns('django.views.generic.simple',
- ('^foo/(?p<id>\d+)/$', 'redirect_to', {'url' : '/bar/%(id)s/'}),
- )
+**Example:**
- If the given URL is ``None``, an ``HttpResponseGone`` (410) will be issued.
+This example redirects from ``/foo/<id>/`` to ``/bar/<id>/``::
-Using date-based generic views
-==============================
+ 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``)
-feature six functions for dealing with date-based data. Besides ``app_label``
-and ``module_name``, all date-based generic views require that the
-``date_field`` argument be passed to them. This is the name of the field that
-stores the date the objects should key off of.
+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.
+
+**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.
-Additionally, all date-based generic views have the following 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).
- ======================= ==================================================
- Argument Description
- ======================= ==================================================
- ``template_name`` Overrides the default template name used for the
- view.
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
- ``extra_lookup_kwargs`` A dictionary of extra lookup parameters (see
- the `database API docs`_).
+ * ``extra_context``: A dictionary of values to add to the template context.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
- ``extra_context`` A dictionary of extra data to put into the
- template's context.
+ * ``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``.
- ``processors`` A tuple of processors to apply to the
- ``DjangoContext`` of this view's template. See the
- `DjangoContext docs`_
- ======================= ==================================================
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
-.. _database API docs: http://www.djangoproject.com/documentation/db_api/
-.. _DjangoContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
+**Template name:**
-The date-based generic functions are:
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_archive.html`` by default, where:
-``archive_index``
- A top-level index page showing the "latest" objects.
+ * ``<model_name>`` is your model's name in all lowercase. For a model
+ ``StaffMember``, that'd be ``staffmember``.
- Takes the following optional arguments:
+ * ``<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``.
- ======================= =================================================
- Argument Description
- ======================= =================================================
- ``num_latest`` The number of items to display on the page.
- Defaults to 15.
+**Template context:**
- ``allow_empty`` If ``False`` and there are no objects to display,
- the view will raise a 404 instead of displaying
- an empty index page. ``False`` is default.
- ======================= =================================================
+In addition to ``extra_context``, the template's context will be:
- Uses the template ``app_label/module_name_archive`` by default.
+ * ``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``.
- Has the following template context:
+.. _RequestContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
- ``date_list``
- List of years with objects
- ``latest``
- Latest objects by date
+``django.views.generic.date_based.archive_year``
+------------------------------------------------
-``archive_year``
- Yearly archive. Requires that the ``year`` argument be present in the URL
- pattern.
+**Description:**
- **New in Django development version:** Takes an optional ``allow_empty``
- parameter, as ``archive_index``.
+A yearly archive page showing all available months in a given year. Objects
+with a date in the *future* are not displayed.
- Uses the template ``app_label/module_name_archive_year`` by default.
+**Required arguments:**
- Has the following template context:
+ * ``year``: The four-digit year for which the archive serves.
- ``date_list``
- List of months in the given year with objects
- ``year``
- The given year (an integer)
+ * ``queryset``: A ``QuerySet`` of objects for which the archive serves.
-``archive_month``
- Monthly archive. Requires that ``year`` and ``month`` arguments be given.
- You can pass the additional option ``month_format`` if you'd like to change
- the way months are specified in the URL.
+ * ``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.
- ``month_format`` is a format string in the same 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"``.
+**Optional arguments:**
- **New in Django development version:** Takes an optional ``allow_empty``
- parameter, as ``archive_index``.
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
- **New in Django development version:** Takes an optional
- ``template_object_name`` parameter, which designates the name of the
- template variable to use. Default is ``'object'``.
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
- Uses the template ``app_label/module_name_archive_month`` by default.
+ * ``extra_context``: A dictionary of values to add to the template context.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
- Has the following template context:
+ * ``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``.
- ``month``
- The given month (a datetime.date object)
- ``next_month``
- **New in Django development version.** The first day of the next
- month, or None if the next month is in the future (a datetime.date
- object)
- ``previous_month``
- **New in Django development version.** The first day of the
- previous month (a datetime.date object)
- ``object_list``
- List of objects published in the given month.
- In the Django development version, you can change this variable
- name from ``object_list`` by using the ``template_object_name``
- parameter. (See above.) For example, if ``template_object_name`` is
- ``foo``, the variable will be ``foo_list``.
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
-``archive_day``
- Daily archive. Requires that ``year``, ``month``, and ``day`` arguments be
- given.
+**Template name:**
- As in ``archive_month``, you can pass an optional ``month_format``. You can
- also pass ``day_format``, which defaults to ``"%d"`` (day of the month as a
- decimal number, 01-31).
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_archive_year.html`` by default.
- **New in Django development version:** Takes an optional
- ``template_object_name`` parameter, which designates the name of the
- template variable to use. Default is ``'object'``.
+**Template context:**
- Uses the template ``app_label/module_name_archive_day`` by default.
+In addition to ``extra_context``, the template's context will be:
- Has the following template context:
+ * ``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``
- List of objects published on the given day.
- In the Django development version, you can change this variable
- name from ``object_list`` by using the ``template_object_name``
- parameter. (See above.) For example, if ``template_object_name`` is
- ``foo``, the variable will be ``foo_list``.
- ``day``
- The given day (a datetime.datetime object)
- ``previous_day``
- The previous day (a datetime.datetime object)
- ``next_day``
- The next day (a datetime.datetime object), or None if the given
- day is today
+``django.views.generic.date_based.archive_month``
+-------------------------------------------------
-``archive_today``
- List of objects for today. Exactly the same as ``archive_day``, except
- the year/month/day arguments are not given, and today's date is used
- instead.
+**Description:**
-``object_detail``
- Individual object page. Requires ``year``/``month``/``day`` arguments like
- ``archive_day``. This function can be used with two types of URLs: either
- ``/year/month/day/slug/`` or ``/year/month/day/object_id/``.
+A monthly archive page showing all objects in a given month. Objects with a
+date in the *future* are not displayed.
- If you're using the slug-style URLs, you'll need to have a ``slug`` item in
- your URLconf, and you'll need to pass a ``slug_field`` key in your info
- dictionary to indicate the name of the slug field.
+**Required arguments:**
- If you're using the object_id-style URLs, you'll just need to give the URL
- pattern an ``object_id`` field.
+ * ``year``: The four-digit year for which the archive serves (a string).
- You can also pass the ``template_name_field`` argument to indicate that the
- the object stores the name of its template in a field on the object itself.
+ * ``month``: The month for which the archive serves, formatted according to
+ the ``month_format`` argument.
- As in ``archive_day``, ``object_detail`` takes optional ``month_format``
- and ``day_format`` parameters.
+ * ``queryset``: A ``QuerySet`` of objects for which the archive serves.
- **New in Django development version:** Takes an optional
- ``template_object_name`` parameter, which designates the name of the
- template variable to use. Default is ``'object'``.
+ * ``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.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
+
+ * ``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.
+
+**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
-Using list/detail generic views
-===============================
+``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.
+
+**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.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
+
+ * ``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.
+
+**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.
+
+**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.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
+
+ * ``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.
+
+**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.
+
+**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.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
+
+ * ``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>_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.
-All these views take the same four optional arguments as the date-based ones
--- and, clearly, they don't accept the ``date_field`` argument.
+``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 a ``page`` query
+ string (GET) parameter containing a zero-indexed page number.
+
+ * ``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.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
+
+ * ``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.
+
+**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.
+
+ * ``pages``: The total number of pages, as an integer.
+
+ * ``hits``: The total number of objects across *all* pages, not just this
+ page.
+
+``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:**
-Individual views are:
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
-``object_list``
- List of objects.
+ * ``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'``.
- Takes the following optional arguments:
+ It's a bit of a brain-bender, but it's useful in some cases.
- ======================== =================================================
- Argument Description
- ======================== =================================================
- ``paginate_by`` If set to an integer, the view will paginate
- objects with ``paginate_by`` objects per page.
- The view will expect a ``page`` GET param with
- the (zero-indexed) page number.
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
- ``allow_empty`` If ``False`` and there are no objects to display,
- the view will raise a 404 instead of displaying
- an empty index page. ``False`` is default.
+ * ``extra_context``: A dictionary of values to add to the template context.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
- ``template_object_name`` **New in Django development version.** Designates
- the name of the object template variable. Default
- is ``'object'``.
- ======================== =================================================
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
- Uses the template ``app_label/module_name_list`` by default.
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``.
- Has the following template context:
+**Template name:**
- ``object_list``
- List of objects. In the Django development version, you can change
- this variable name from ``object_list`` by using the
- ``template_object_name`` parameter. (See above.) For example, if
- ``template_object_name`` is ``foo``, the variable will be
- ``foo_list``.
- ``is_paginated``
- Are the results paginated? Either True or False
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_detail.html`` by default.
- If the results are paginated, the context will have some extra variables:
+**Template context:**
- ``results_per_page``
- Number of objects per page
- ``has_next``
- Is there a next page?
- ``has_previous``
- Is there a previous page?
- ``page``
- The current page number
- ``next``
- The next page number
- ``previous``
- The previous page
- ``pages``
- Number of pages total
- ``hits``
- Total number of objects
+In addition to ``extra_context``, the template's context will be:
-``object_detail``
- Object detail page. This works like and takes the same arguments as
- the date-based ``object_detail`` above, except this one, obviously,
- does not take the year/month/day arguments.
+ * ``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``.
-Using create/update/delete generic views
-========================================
+Create/update/delete generic views
+==================================
The ``django.views.generic.create_update`` module contains a set of functions
-for creating, editing and deleting objects. These views take the same global
-arguments as the above sets of generic views. They also have a
-``login_required`` argument which, if ``True``, requires the user to be logged
-in to have access to the page. (``login_required`` defaults to ``False``.)
+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.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
+
+ * ``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.forms.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: http://www.djangoproject.com/documentation/authentication/
+.. _manipulator and formfield documentation: http://www.djangoproject.com/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.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
+
+ * ``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.forms.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:**
-The create/update/delete views are:
+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.
-``create_object``
- Create a new object. Has an extra optional argument, ``post_save_redirect``,
- which is a URL to which the view will redirect after saving the object.
- It defaults to ``object.get_absolute_url()``.
+**Required arguments:**
- ``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/"``.
+ * ``model``: The Django model class of the object that the form will
+ create.
- Uses the template ``app_label/module_name_form`` by default. This is the
- same template as the ``update_object`` view below. Your template can tell
- the difference by the presence or absence of ``{{ object }}`` in the
- context.
+ * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
- Has the following template context:
+ If you provide ``object_id``, it should be the value of the primary-key
+ field for the object being displayed on this page.
- form
- The form wrapper for the object
+ 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.
- .. admonition:: Note
+ * ``post_delete_redirect``: A URL to which the view will redirect after
+ deleting the object.
- See the `manipulator and formfield documentation`_ for more information
- about using form wrappers in templates.
+**Optional arguments:**
-.. _`manipulator and formfield documentation`: http://www.djangoproject.com/documentation/forms/
+ * ``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``.
-``update_object``
- Edit an existing object. Has the same extra slug/ID parameters as
- ``list_detail.object_detail`` does (see above), and the same
- ``post_save_redirect`` as ``create_object`` does.
+ 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/``.
- **New in Django development version:** Takes an optional
- ``template_object_name`` parameter, which designates the name of the
- template variable to use. Default is ``'object'``.
+ * ``template_name``: The full name of a template to use in rendering the
+ page. This lets you override the default template name (see below).
- Uses the template ``app_label/module_name_form`` by default.
+ * ``template_loader``: The template loader to use when loading the
+ template. By default, it's ``django.template.loader``.
- Has the following template context:
+ * ``extra_context``: A dictionary of values to add to the template context.
+ If a value in the dictionary is callable, the generic view will call it
+ just before rendering the template. By default, this is an empty
+ dictionary.
- form
- The form wrapper for the object
- object
- The original object being edited.
- In the Django development version, you can change this variable
- name from ``object`` by using the ``template_object_name``
- parameter. (See above.) For example, if ``template_object_name`` is
- ``foo``, the variable will be ``foo`` instead of ``object``.
+ * ``context_processors``: A list of template-context processors to apply to
+ the view's template. See the `RequestContext docs`_.
-``delete_object``
- Delete an existing object. The given object will only actually be deleted
- if the request method is POST. If this view is fetched with GET, it will
- display a confirmation page that should contain a form that POSTs to the
- same URL.
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``.
- You must provide the ``post_delete_redirect`` argument to this function, so
- that the view knows where to go after the object is deleted.
+**Template name:**
- If fetched with GET, it uses the template
- ``app_label/module_name_confirm_delete`` by default. It uses no template
- if POSTed -- it simply deletes the object and redirects.
+If ``template_name`` isn't specified, this view will use the template
+``<app_label>/<model_name>_confirm_delete.html`` by default.
- **New in Django development version:** Takes an optional
- ``template_object_name`` parameter, which designates the name of the
- template variable to use. Default is ``'object'``.
+**Template context:**
- Has the following template context:
+In addition to ``extra_context``, the template's context will be:
- object
- The object about to be deleted
- In the Django development version, you can change this variable
- name from ``object`` by using the ``template_object_name``
- parameter. (See above.) For example, if ``template_object_name`` is
- ``foo``, the variable will be ``foo`` instead of ``object``.
+ * ``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/docs/i18n.txt b/docs/i18n.txt
index 4282b0aeea..9199a74295 100644
--- a/docs/i18n.txt
+++ b/docs/i18n.txt
@@ -150,14 +150,14 @@ If you don't like the verbose name ``gettext_lazy``, you can just alias it as
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``
+explicit ``verbose_name`` and ``verbose_name_plural`` options in the ``Meta``
class, though::
from django.utils.translation import gettext_lazy as _
class MyThing(meta.Model):
name = meta.CharField(_('name'), help_text=_('This is the help text'))
- class META:
+ class Meta:
verbose_name = _('my thing')
verbose_name_plural = _('mythings')
@@ -224,14 +224,14 @@ To pluralize, specify both the singular and plural forms with the
Internally, all block and inline translations use the appropriate
``gettext`` / ``ngettext`` call.
-Each ``DjangoContext`` has access to two translation-specific variables:
+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.)
-If you don't use the ``DjangoContext`` extension, you can get those values with
+If you don't use the ``RequestContext`` extension, you can get those values with
two tags::
{% get_current_language as LANGUAGE_CODE %}
@@ -404,7 +404,7 @@ should follow these guidelines:
For example, your ``MIDDLEWARE_CLASSES`` might look like this::
MIDDLEWARE_CLASSES = (
- 'django.middleware.sessions.SessionMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
)
diff --git a/docs/install.txt b/docs/install.txt
index 54b792464f..51746e001d 100644
--- a/docs/install.txt
+++ b/docs/install.txt
@@ -77,9 +77,9 @@ It's easy either way.
Installing the official version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-1. Download Django-0.91.tar.gz from our `download page`_.
-2. ``tar xzvf Django-0.91.tar.gz``
-3. ``cd Django-0.91``
+1. Download Django-0.92.tar.gz from our `download page`_.
+2. ``tar xzvf Django-0.92.tar.gz``
+3. ``cd Django-0.92``
4. ``sudo python setup.py install``
Note that the last command will automatically download and install setuptools_
diff --git a/docs/legacy_databases.txt b/docs/legacy_databases.txt
index f1b8f85970..66cb1a2ef4 100644
--- a/docs/legacy_databases.txt
+++ b/docs/legacy_databases.txt
@@ -18,6 +18,7 @@ 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`_
@@ -26,6 +27,7 @@ what the name of the database is. Do that by editing these settings in your
* `DATABASE_PORT`_
.. _settings file: http://www.djangoproject.com/documentation/settings/
+.. _DATABASE_NAME: http://www.djangoproject.com/documentation/settings/#database-name
.. _DATABASE_ENGINE: http://www.djangoproject.com/documentation/settings/#database-engine
.. _DATABASE_USER: http://www.djangoproject.com/documentation/settings/#database-user
.. _DATABASE_PASSWORD: http://www.djangoproject.com/documentation/settings/#database-password
@@ -39,57 +41,29 @@ 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 [databasename] --settings=path.to.settings
-
-...where "[databasename]" is the name of your database.
+ django-admin.py inspectdb --settings=path.to.settings
Save this as a file by using standard Unix output redirection::
- django-admin.py inspectdb [databasename] --settings=path.to.settings > appname.py
+ 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 the model, put the module in the ``models`` directory of
-your app, and add it to your ``INSTALLED_APPS`` setting.
+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: http://www.djangoproject.com/documentation/django_admin/
Install the core Django tables
==============================
-Next, run the ``django-admin.py init`` command to install Django's core tables
-in your database::
+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
-This won't work if your database already contains tables that have any of the
-following names:
-
- * ``sites``
- * ``packages``
- * ``content_types``
- * ``core_sessions``
- * ``auth_permissions``
- * ``auth_groups``
- * ``auth_users``
- * ``auth_messages``
- * ``auth_groups_permissions``
- * ``auth_users_groups``
- * ``auth_users_user_permissions``
-
-If that's the case, try renaming one of your tables to resolve naming
-conflicts. Currently, there's no way of customizing the names of Django's
-database tables without editing Django's source code itself.
-
-Install metadata about your app
-===============================
-
-Django has a couple of database tables that contain metadata about your apps.
-You'll need to execute the SQL output by this command::
-
- django-admin.py sqlinitialdata [appname] --settings=path.to.settings
-
See whether it worked
=====================
diff --git a/docs/middleware.txt b/docs/middleware.txt
index b55d8a1696..1fec98a39f 100644
--- a/docs/middleware.txt
+++ b/docs/middleware.txt
@@ -24,6 +24,8 @@ name. For example, here's the default ``MIDDLEWARE_CLASSES`` created by
MIDDLEWARE_CLASSES = (
"django.middleware.common.CommonMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.middleware.doc.XViewMiddleware",
)
@@ -95,13 +97,37 @@ Handles conditional GET operations. If the response has a ``ETag`` or
Also removes the content from any response to a HEAD request and sets the
``Date`` and ``Content-Length`` response-headers.
-django.middleware.sessions.SessionMiddleware
---------------------------------------------
+django.contrib.sessions.middleware.SessionMiddleware
+----------------------------------------------------
Enables session support. See the `session documentation`_.
.. _`session documentation`: http://www.djangoproject.com/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: http://www.djangoproject.com/documentation/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`: http://www.djangoproject.com/documentation/transaction/
+
Writing your own middleware
===========================
@@ -176,8 +202,8 @@ Guidelines
to it.
* Feel free to look at Django's available middleware for examples. The
- default Django middleware classes are in ``django/middleware/`` in the
- Django distribution.
+ 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
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 5d61d5ba7e..44253f301b 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -8,465 +8,617 @@ model maps to a single database table.
The basics:
- * Each model is a Python class that subclasses ``django.core.meta.Model``.
+ * 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``.
+ * 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`_.
-.. _`official repository of model examples`: http://www.djangoproject.com/documentation/models/
+.. _Database API reference: http://www.djangoproject.com/documentation/db_api/
+.. _official repository of model examples: http://www.djangoproject.com/documentation/models/
-Field objects
+Quick example
=============
-The most important part of a model is the list of database fields it defines.
-Fields are defined by class attributes. Each class attribute in a model, aside
-from the optional inner ``class META``, should be an instance of a
-``meta.Field`` subclass.
+This example model defines a ``Person``, which has a ``first_name`` and
+``last_name``::
-In this example, there are two fields, ``first_name`` and ``last_name`` ::
+ from django.db import models
- class Person(meta.Model):
- first_name = meta.CharField(maxlength=30)
- last_name = meta.CharField(maxlength=30)
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
-Django will use ``first_name`` and ``last_name`` as the database column names.
+``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.
-Each field type, except for ``ForeignKey``, ``ManyToManyField`` and
-``OneToOneField``, takes an optional first positional argument -- a
-human-readable name. If the human-readable name isn't given, Django will
-automatically create the human-readable name by using the machine-readable
-name, converting underscores to spaces.
+The above ``Person`` model would create an SQL table like this::
-In this example, the human-readable name is ``"Person's first name"``::
+ CREATE TABLE myapp_person (
+ "id" serial NOT NULL PRIMARY KEY,
+ "first_name" varchar(30) NOT NULL,
+ "last_name" varchar(30) NOT NULL
+ );
- first_name = meta.CharField("Person's first name", maxlength=30)
+Three technical notes:
-In this example, the human-readable name is ``"first name"``::
+ * 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`_.
- first_name = meta.CharField(maxlength=30)
+.. _settings file: http://www.djangoproject.com/documentation/settings/
-``ForeignKey``, ``ManyToManyField`` and ``OneToOneField`` require the first
-argument to be a model class, so use the ``verbose_name`` keyword argument to
-specify the human-readable name::
+Fields
+======
- poll = meta.ForeignKey(Poll, verbose_name="the related poll")
- sites = meta.ManyToManyField(Site, verbose_name="list of sites")
- place = meta.OneToOneField(Place, verbose_name="related place")
+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.
-Convention is not to capitalize the first letter of the ``verbose_name``.
-Django will automatically capitalize the first letter where it needs to.
+Example::
-General field options
----------------------
+ class Musician(models.Model):
+ first_name = models.CharField(maxlength=50)
+ last_name = models.CharField(maxlength=50)
+ instrument = models.CharField(maxlength=100)
-The following arguments are available to all field types. All are optional.
+ class Album(models.Model):
+ artist = models.ForeignKey(Musician)
+ name = models.CharField(maxlength=100)
+ release_date = models.DateField()
+ num_stars = models.IntegerField()
-``null``
- If ``True``, Django will store empty values as ``NULL`` in the database.
- Default is ``False``.
+Field name restrictions
+-----------------------
- 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.
+Django places only two restrictions on model field names:
- 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``.
+ 1. A field name cannot be a Python reserved word, because that would result
+ in a Python syntax error. For example::
-``blank``
- If ``True``, the field is allowed to be blank.
+ class Example(models.Model):
+ pass = models.IntegerField() # 'pass' is a reserved word!
- 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.
+ 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::
-``choices``
- A list of 2-tuples to use as choices for this field.
+ class Example(models.Model):
+ foo__bar = models.IntegerField() 'foo__bar' has two underscores!
- 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.
+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.
- A choices list looks like this::
+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.
- YEAR_IN_SCHOOL_CHOICES = (
- ('FR', 'Freshman'),
- ('SO', 'Sophomore'),
- ('JR', 'Junior'),
- ('SR', 'Senior'),
- ('GR', 'Graduate'),
- )
+Field types
+-----------
- The first element in each tuple is the actual value to be stored. The
- second element is the human-readable name for the option.
+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:
- Define the choices list **outside** of your model class, not inside it.
- For example, this is not valid::
+ * 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.
- class Foo(meta.Model):
- GENDER_CHOICES = (
- ('M', 'Male'),
- ('F', 'Female'),
- )
- gender = meta.CharField(maxlength=1, choices=GENDER_CHOICES)
+Here are all available field types:
- But this is valid::
+``AutoField``
+~~~~~~~~~~~~~
- GENDER_CHOICES = (
- ('M', 'Male'),
- ('F', 'Female'),
- )
- class Foo(meta.Model):
- gender = meta.CharField(maxlength=1, choices=GENDER_CHOICES)
+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`.
-``core``
- For objects that are edited inline to a related object.
+``BooleanField``
+~~~~~~~~~~~~~~~~
- In the Django admin, if all "core" fields in an inline-edited object are
- cleared, the object will be deleted.
+A true/false field.
- It is an error to have an inline-editable relation without at least one
- ``core=True`` field.
+The admin represents this as a checkbox.
-``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.
+``CharField``
+~~~~~~~~~~~~~
- 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.
+A string field, for small- to large-sized strings.
-``db_index``
- If ``True``, ``django-admin.py sqlindexes`` will output a ``CREATE INDEX``
- statement for this field.
+For large amounts of text, use ``TextField``.
-``default``
- The default value for the field.
+The admin represents this as an ``<input type="text">`` (a single-line input).
-``editable``
- If ``False``, the field will not be editable in the admin. Default is ``True``.
+``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.
-``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.
+``CommaSeparatedIntegerField``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-``primary_key``
- If ``True``, this field is the primary key for the model.
+A field of integers separated by commas. As in ``CharField``, the ``maxlength``
+argument is required.
- If you don't specify ``primary_key=True`` for any fields in your model,
- Django will automatically add this field::
+``DateField``
+~~~~~~~~~~~~~
- id = meta.AutoField('ID', primary_key=True)
+A date field. Has a few extra optional arguments:
- 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.
+ ====================== ===================================================
+ Argument Description
+ ====================== ===================================================
+ ``auto_now`` Automatically set the field to now every time the
+ object is saved. Useful for "last-modified"
+ timestamps.
- ``primary_key=True`` implies ``blank=False``, ``null=False`` and
- ``unique=True``. Only one primary key is allowed on an object.
+ ``auto_now_add`` Automatically set the field to now when the object
+ is first created. Useful for creation of
+ timestamps.
+ ====================== ===================================================
-``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.
+The admin represents this as an ``<input type="text">`` with a JavaScript
+calendar and a shortcut for "Today."
- Don't use this for a field unless it's a ``ForeignKey`` or has ``choices``
- set.
+``DateTimeField``
+~~~~~~~~~~~~~~~~~
-``unique``
- If ``True``, this field must be unique throughout the table.
+A date and time field. Takes the same extra options as ``DateField``.
- This is enforced at the database level and at the Django admin-form level.
+The admin represents this as two ``<input type="text">`` fields, with
+JavaScript shortcuts.
-``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.
+``EmailField``
+~~~~~~~~~~~~~~
- 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``.
+A ``CharField`` that checks that the value is a valid e-mail address.
+This doesn't accept ``maxlength``.
- This is enforced at the Django admin-form level but not at the database level.
+``FileField``
+~~~~~~~~~~~~~
-``unique_for_month``
- Like ``unique_for_date``, but requires the field to be unique with respect
- to the month.
+A file-upload field.
-``unique_for_year``
- Like ``unique_for_date`` and ``unique_for_month``.
+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).
-``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`_.)
+The admin represents this as an ``<input type="file">`` (a file-upload widget).
- Django comes with quite a few validators. They're in ``django.core.validators``.
+Using a ``FileField`` or an ``ImageField`` (see below) in a model takes a few
+steps:
-.. _validator docs: http://www.djangoproject.com/documentation/forms/#validators
+ 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.
-Field types
------------
+ 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.
-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:
+ 3. 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 }}``.
- * The database column type (e.g. ``INTEGER``, ``VARCHAR``).
- * The widget to use in Django's admin (e.g. ``<input type="text">``, ``<select>``).
- * The minimal validation requirements, used in Django's admin and in manipulators.
+.. _`strftime formatting`: http://docs.python.org/lib/module-time.html#l2h-1941
-Here are all available field types:
+``FilePathField``
+~~~~~~~~~~~~~~~~~
-``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
- ``primary_key`` in ``General field options`` above.)
+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:
-``BooleanField``
- A true/false field.
+ ====================== ===================================================
+ Argument Description
+ ====================== ===================================================
+ ``path`` Required. The absolute filesystem path to a
+ directory from which this ``FilePathField`` should
+ get its choices. Example: ``"/home/images"``.
- The admin represents this as a checkbox.
+ ``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``.
-``CharField``
- A string field, for small- to large-sized strings.
+ ``recursive`` Optional. Either ``True`` or ``False``. Default is
+ ``False``. Specifies whether all subdirectories of
+ ``path`` should be included.
+ ====================== ===================================================
- For large amounts of text, use ``TextField``.
+Of course, these arguments can be used together.
- The admin represents this as an ``<input type="text">`` (a single-line input).
+The one potential gotcha is that ``match`` applies to the base filename,
+not the full path. So, this example::
- ``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.
+ FilePathField(path="/home/images", match="foo.*", recursive=True)
-``CommaSeparatedIntegerField``
- A field of integers separated by commas. As in ``CharField``, the
- ``maxlength`` argument is required.
+...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``).
-``DateField``
- A date field. Has a few extra optional arguments:
+``FloatField``
+~~~~~~~~~~~~~~
- ====================== ===================================================
- Argument Description
- ====================== ===================================================
- ``auto_now`` Automatically set the field to now every time the
- object is saved. Useful for "last-modified"
- timestamps.
+A floating-point number. Has two **required** arguments:
- ``auto_now_add`` Automatically set the field to now when the object
- is first created. Useful for creation of
- timestamps.
- ====================== ===================================================
+ ====================== ===================================================
+ Argument Description
+ ====================== ===================================================
+ ``max_digits`` The maximum number of digits allowed in the number.
- The admin represents this as an ``<input type="text">`` with a JavaScript
- calendar and a shortcut for "Today."
+ ``decimal_places`` The number of decimal places to store with the
+ number.
+ ====================== ===================================================
-``DateTimeField``
- A date and time field. Takes the same extra options as ``DateField``.
+For example, to store numbers up to 999 with a resolution of 2 decimal places,
+you'd use::
- The admin represents this as two ``<input type="text">`` fields, with
- JavaScript shortcuts.
+ models.FloatField(..., max_digits=5, decimal_places=2)
-``EmailField``
- A ``CharField`` that checks that the value is a valid e-mail address.
- This doesn't accept ``maxlength``.
+And to store numbers up to approximately one billion with a resolution of 10
+decimal places::
-``FileField``
- A file-upload field.
+ models.FloatField(..., max_digits=19, decimal_places=10)
- 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="text">`` (a single-line input).
- The admin represents this as an ``<input type="file">`` (a file-upload widget).
+``ImageField``
+~~~~~~~~~~~~~~
- Using a ``FileField`` or an ``ImageField`` (see below) in a model takes a few
- steps:
+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.
- 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.
+Requires the `Python Imaging Library`_.
- 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.
+.. _Python Imaging Library: http://www.pythonware.com/products/pil/
- 3. 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 }}``.
+``IntegerField``
+~~~~~~~~~~~~~~~~
- .. _`strftime formatting`: http://docs.python.org/lib/module-time.html#l2h-1941
+An integer.
-``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:
+The admin represents this as an ``<input type="text">`` (a single-line input).
- ====================== ===================================================
- Argument Description
- ====================== ===================================================
- ``path`` Required. The absolute filesystem path to a
- directory from which this ``FilePathField`` should
- get its choices. Example: ``"/home/images"``.
+``IPAddressField``
+~~~~~~~~~~~~~~~~~~
- ``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``.
+An IP address, in string format (i.e. "24.124.1.30").
- ``recursive`` Optional. Either ``True`` or ``False``. Default is
- ``False``. Specifies whether all subdirectories of
- ``path`` should be included.
- ====================== ===================================================
+The admin represents this as an ``<input type="text">`` (a single-line input).
- Of course, these arguments can be used together.
+``NullBooleanField``
+~~~~~~~~~~~~~~~~~~~~
- The one potential gotcha is that ``match`` applies to the base filename,
- not the full path. So, this example::
+Like a ``BooleanField``, but allows ``NULL`` as one of the options. Use this
+instead of a ``BooleanField`` with ``null=True``.
- FilePathField(path="/home/images", match="foo.*", recursive=True)
+The admin represents this as a ``<select>`` box with "Unknown", "Yes" and "No" choices.
- ...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``).
+``PhoneNumberField``
+~~~~~~~~~~~~~~~~~~~~
-``FloatField``
- A floating-point number. Has two **required** arguments:
+A ``CharField`` that checks that the value is a valid U.S.A.-style phone
+number (in the format ``XXX-XXX-XXXX``).
- ====================== ===================================================
- Argument Description
- ====================== ===================================================
- ``max_digits`` The maximum number of digits allowed in the number.
+``PositiveIntegerField``
+~~~~~~~~~~~~~~~~~~~~~~~~
- ``decimal_places`` The number of decimal places to store with the
- number.
- ====================== ===================================================
+Like an ``IntegerField``, but must be positive.
- For example, to store numbers up to 999 with a resolution of 2 decimal places,
- you'd use::
+``PositiveSmallIntegerField``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- meta.FloatField(..., max_digits=5, decimal_places=2)
+Like a ``PositiveIntegerField``, but only allows values under a certain
+(database-dependent) point.
- And to store numbers up to approximately one billion with a resolution of 10
- decimal places::
+``SlugField``
+~~~~~~~~~~~~~
- meta.FloatField(..., max_digits=19, decimal_places=10)
+"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.
- The admin represents this as an ``<input type="text">`` (a single-line input).
+In the Django development version, you can specify ``maxlength``. If
+``maxlength`` is not specified, Django will use a default length of 50. In
+previous Django versions, there's no way to override the length of 50.
-``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.
+Implies ``db_index=True``.
- Requires the `Python Imaging Library`_.
+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::
- .. _Python Imaging Library: http://www.pythonware.com/products/pil/
+ models.SlugField(prepopulate_from=("pre_name", "name"))
-``IntegerField``
- An integer.
+``prepopulate_from`` doesn't accept DateTimeFields.
- The admin represents this as an ``<input type="text">`` (a single-line input).
+The admin represents ``SlugField`` as an ``<input type="text">`` (a
+single-line input).
-``IPAddressField``
- An IP address, in string format (i.e. "24.124.1.30").
+``SmallIntegerField``
+~~~~~~~~~~~~~~~~~~~~~
- The admin represents this as an ``<input type="text">`` (a single-line input).
+Like an ``IntegerField``, but only allows values under a certain
+(database-dependent) point.
-``NullBooleanField``
- Like a ``BooleanField``, but allows ``NULL`` as one of the options. Use this
- instead of a ``BooleanField`` with ``null=True``.
+``TextField``
+~~~~~~~~~~~~~
- The admin represents this as a ``<select>`` box with "Unknown", "Yes" and "No" choices.
+A large text field.
-``PhoneNumberField``
- A ``CharField`` that checks that the value is a valid U.S.A.-style phone
- number (in the format ``XXX-XXX-XXXX``).
+The admin represents this as a ``<textarea>`` (a multi-line input).
-``PositiveIntegerField``
- Like an ``IntegerField``, but must be positive.
+``TimeField``
+~~~~~~~~~~~~~
-``PositiveSmallIntegerField``
- Like a ``PositiveIntegerField``, but only allows values under a certain
- (database-dependent) point.
+A time. Accepts the same auto-population options as ``DateField`` and
+``DateTimeField``.
-``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.
+The admin represents this as an ``<input type="text">`` with some
+JavaScript shortcuts.
- In the Django development version, you can specify ``maxlength``. If
- ``maxlength`` is not specified, Django will use a default length of 50. In
- previous Django versions, there's no way to override the length of 50.
+``URLField``
+~~~~~~~~~~~~
- Implies ``db_index=True``.
+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).
- 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::
+The admin represents this as an ``<input type="text">`` (a single-line input).
- meta.SlugField(prepopulate_from=("pre_name", "name"))
+``USStateField``
+~~~~~~~~~~~~~~~~
- ``prepopulate_from`` doesn't accept DateTimeFields.
+A two-letter U.S. state abbreviation.
- The admin represents ``SlugField`` as an ``<input type="text">`` (a
- single-line input).
+The admin represents this as an ``<input type="text">`` (a single-line input).
-``SmallIntegerField``
- Like an ``IntegerField``, but only allows values under a certain
- (database-dependent) point.
+``XMLField``
+~~~~~~~~~~~~
-``TextField``
- A large text field.
+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.
- The admin represents this as a ``<textarea>`` (a multi-line input).
+.. _RelaxNG: http://www.relaxng.org/
-``TimeField``
- A time. Accepts the same auto-population options as ``DateField`` and
- ``DateTimeField``.
+Field options
+-------------
- The admin represents this as an ``<input type="text">`` with some
- JavaScript shortcuts.
+The following arguments are available to all field types. All are optional.
-``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).
+``null``
+~~~~~~~~
- The admin represents this as an ``<input type="text">`` (a single-line input).
+If ``True``, Django will store empty values as ``NULL`` in the database.
+Default is ``False``.
-``USStateField``
- A two-letter U.S. state abbreviation.
+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.
- The admin represents this as an ``<input type="text">`` (a single-line input).
+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``.
-``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.
+``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``
+~~~~~~~~~~~
+
+A list 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.
- .. _RelaxNG: http://www.relaxng.org/
+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)
+
+``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.
+
+``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. 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 most common types of database
+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
@@ -479,23 +631,34 @@ any other ``Field`` type: by including it as a class attribute of your model.
related.
For example, if a ``Place`` model is in a ``City`` -- that is, a ``City``
-contains multiple places but each ``Place`` is only in one ``City`` -- here's
-how you'd represent that::
+contains multiple places but each ``Place`` is only in one ``City`` -- use the
+following definitions::
- class City(meta.Model):
+ class City(models.Model):
# ...
- class Place(meta.Model):
+ class Place(models.Model):
# ...
- city = meta.ForeignKey(City)
+ city = models.ForeignKey(City)
To create a recursive relationship -- an object that has a many-to-one
-relationship with itself -- use ``meta.ForeignKey("self")``.
+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 Place(models.Model):
+ # ...
+ city = models.ForeignKey("City")
+
+ class City(models.Model):
+ # ...
The name of a ``ForeignKey`` (``city`` in the example above) generally should
-be the name of the model, singular. Behind the scenes, Django appends "_id" to
-the field name to create its database column name. But your code should never
-have to deal with the database column name, unless you write custom SQL.
+be the name of the model, in singular form. Behind the scenes, Django appends
+"_id" to the field name to create its database column name. However, your code
+should never have to deal with the database column name, unless you write
+custom SQL.
See the `Many-to-one relationship model example`_ for a full example.
@@ -510,7 +673,7 @@ relationship should work. All are optional:
``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 ``meta.TABULAR`` or ``meta.STACKED``,
+ 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.
@@ -518,10 +681,10 @@ relationship should work. All are optional:
``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 ``meta.LazyDate`` to limit choices of objects
+ with ``models.LazyDate`` to limit choices of objects
by date. For example::
- limit_choices_to = {'pub_date__lte' : meta.LazyDate()}
+ 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
@@ -565,20 +728,18 @@ relationship should work. All are optional:
object back to this one. For example, when if
``Topping`` has this field::
- meta.ForeignKey(Pizza)
+ models.ForeignKey(Pizza)
- the ``related_name`` will be "topping" (taken from
+ the ``related_name`` will be "topping_set" (taken from
the class name), which will in turn give ``Pizza``
- the methods ``get_topping_list()`` and
- ``get_topping_count()``.
+ a ``topping_set`` Object Set Descriptor.
If you instead were to use::
- meta.ForeignKey(Pizza, related_name="munchie")
+ models.ForeignKey(Pizza, related_name="munchies")
- then the methods would be called
- ``get_munchie_list()``, ``get_munchie_count()``,
- etc.
+ then the Object Set Descriptor on ``Topping`` would
+ be called ``munchies``.
This is only really useful when you have a single
object that relates to the same object more than
@@ -587,12 +748,12 @@ relationship should work. All are optional:
fields, to make sure that the ``Category`` objects
have the correct methods, you'd use fields like::
- meta.ForeignKey(Category, related_name="primary_story")
- meta.ForeignKey(Category, related_name="secondary_story")
+ models.ForeignKey(Category, related_name="primary_stories")
+ models.ForeignKey(Category, related_name="secondary_stories")
- ...which would give the ``Category`` objects
- methods named ``get_primary_story_list()`` and
- ``get_secondary_story_list()``.
+ ...which would give ``Category`` objects two Object Set
+ descriptors - one called ``primary_stories`` and one
+ called ``secondary_stories``.
``to_field`` The field on the related object that the relation
is to. By default, Django uses the primary key of
@@ -615,15 +776,19 @@ 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(meta.Model):
+ class Topping(models.Model):
# ...
- class Pizza(meta.Model):
+ class Pizza(models.Model):
# ...
- toppings = meta.ManyToManyField(Topping)
+ toppings = models.ManyToManyField(Topping)
-The name of a ``ManyToManyField`` (``toppings`` in the example above) generally
-should be the name of the model, plural.
+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.
+
+The name of a ``ManyToManyField`` (``toppings`` in the example above) should
+generally 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.
@@ -654,7 +819,7 @@ the relationship should work. All are optional:
``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
- ``meta.HORIZONTAL`` or ``meta.VERTICAL`` (i.e.
+ ``models.HORIZONTAL`` or ``models.VERTICAL`` (i.e.
should the interface be stacked horizontally or
vertically).
@@ -667,6 +832,24 @@ the relationship should work. All are optional:
version of the class being linked to. Use the singular
parameter to change this, which is if you want one model to
have multiple ``ManyToMany`` relationships to another model.
+
+ ``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, allow
+ ManyToMany relationships to be non-symmetrical.
+
======================= ============================================================
One-to-one relationships
@@ -689,171 +872,178 @@ 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).
-This ``OneToOneField`` will actually replace the primary key ``id`` field
-(since one-to-one relations share the same primary key), and has a few
-differences in the admin interface:
-
- * No ``Place`` selection interface is displayed on ``Restaurant`` pages.
- There will be one (and only one) ``Restaurant`` for each ``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.
- * On the ``Restaurant`` change list, every ``Place`` -- whether it has an
- associated ``Restaurant`` or not -- will be displayed. Adding a
- ``Restaurant`` to a ``Place`` just means filling out the required
- ``Restaurant`` fields.
+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
+Meta options
============
-Give your model metadata by using an inner ``"class META"``, like so::
+Give your model metadata by using an inner ``class Meta``, like so::
- class Foo(meta.Model):
- bar = meta.CharField(maxlength=30)
- # ...
- class META:
- admin = meta.Admin()
- # ...
+ class Foo(models.Model):
+ bar = models.CharField(maxlength=30)
-Model metadata is "anything that's not a field" -- ordering options, admin
-options, etc.
+ class Meta:
+ # ...
-Here's a list of all possible ``META`` options. No options are required. Adding
-``class META`` to a model is completely optional.
+Model metadata is "anything that's not a field", such as ordering options, etc.
-``admin``
- A ``meta.Admin`` object; see `Admin options`_. If this field is given, the
- object will have an admin interface. If it isn't given, the object won't
- have one.
+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 module::
+------------
- db_table = "pizza_orders"
+The name of the database table to use for the module::
- If this isn't given, Django will use ``app_label + '_' + module_name``.
+ db_table = "pizza_orders"
- 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.
+If this isn't given, Django will use ``app_label + '_' + model_class_name``.
-``exceptions``
- Names of extra exception subclasses to include in the generated module.
- These exceptions are available from instance methods and from module-level
- methods::
-
- exceptions = ("DisgustingToppingsException", "BurntCrust")
+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``. If given, the module will
- have a ``get_latest()`` function that fetches the "latest" object according
- to that field::
-
- get_latest_by = "order_date"
-
- See `Getting the "latest" object`_ for a full example.
-
- .. _Getting the "latest" object: http://www.djangoproject.com/documentation/models/get_latest/
+-----------------
-``module_constants``
- A dictionary of names/values to use as extra module-level constants::
+The name of a ``DateField`` or ``DateTimeField``. If given, the module will
+have a ``get_latest()`` function that fetches the "latest" object according
+to that field::
- module_constants = {
- 'MEAT_TYPE_PEPPERONI' : 1,
- 'MEAT_TYPE_SAUSAGE' : 2,
- }
+ get_latest_by = "order_date"
-``module_name``
- The name of the module::
+See `Getting the "latest" object`_ for a full example.
- module_name = "pizza_orders"
-
- If this isn't given, Django will use a lowercased version of the class
- name, plus ``"s"``. This "poor man's pluralization" is intentional: Any
- other level of magic pluralization would get confusing.
+.. _Getting the "latest" object: http://www.djangoproject.com/documentation/models/get_latest/
``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 a ``PizzaToppping`` relates to
- a ``Pizza`` object, you might use::
+-------------------------
+
+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 a ``PizzaToppping`` relates to
+a ``Pizza`` object, you might use::
- order_with_respect_to = 'pizza'
+ order_with_respect_to = 'pizza'
- to allow the toppings to be ordered with respect to the associated pizza.
+...to allow the toppings to be ordered with respect to the associated pizza.
``ordering``
- The default ordering for the object, for use by ``get_list`` and the admin::
+------------
+
+The default ordering for the object, for use when obtaining lists of objects::
- ordering = ['-order_date']
+ 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.
+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::
+For example, to order by a ``pub_date`` field ascending, use this::
- ordering = ['pub_date']
+ ordering = ['pub_date']
- To order by ``pub_date`` descending, use this::
+To order by ``pub_date`` descending, use this::
- ordering = ['-pub_date']
+ ordering = ['-pub_date']
- To order by ``pub_date`` descending, then by ``author`` ascending, use this::
+To order by ``pub_date`` descending, then by ``author`` ascending, use this::
- ordering = ['-pub_date', 'author']
+ ordering = ['-pub_date', 'author']
- See `Specifying ordering`_ for more examples.
+See `Specifying ordering`_ for more examples.
- Note that, regardless of how many fields are in ``ordering``, the admin
- site uses only the first field.
+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/
+.. _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"),)
+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``::
- This is a list or tuple of 2-tuples in the format
- ``(permission_code, human_readable_permission_name)``.
+ 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"),)
+Sets of field names that, taken together, must be unique::
- 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).
+ 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::
+----------------
+
+A human-readable name for the object, singular::
- verbose_name = "pizza"
+ verbose_name = "pizza"
- If this isn't given, Django will use a munged version of the class name:
- ``CamelCase`` becomes ``camel case``.
+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::
+-----------------------
+
+The plural name for the object::
+
+ verbose_name_plural = "stories"
- verbose_name_plural = "stories"
+If this isn't given, Django will use ``verbose_name + "s"``.
+
+
+
+========================================
+THE REST OF THIS HAS NOT YET BEEN EDITED
+========================================
+
+
+
+Table names
+===========
+
+Automatic primary key fields
+============================
- If this isn't given, Django will use ``verbose_name + "s"``.
Admin options
=============
-The ``admin`` field in the model tells Django how to construct the admin
-interface for the object. The field is an instance of the ``meta.Admin``
-object, which takes the following parameters. All are optional.
+If you want your model to be visible to the automatic Administration
+system, your model must have an inner ``"class Admin"``, like so::
+
+ class Foo(models.Model):
+ bar = models.CharField(maxlength=30)
+ # ...
+ class Admin:
+ # ...
+
+The Admin class gives instructions to Django on how to display the Model
+to the Administration system.
+
+Here's a list of all possible ``Admin`` options. No options are required. Adding
+``class Admin`` to a model is completely optional.
``date_hierarchy``
To allow filtering of objects in the admin by date, set ``date_hierarchy``
@@ -885,24 +1075,31 @@ object, which takes the following parameters. All are optional.
"click to expand" link. Fieldsets with the ``wide`` style will be
given extra horizontal space.
+ ``description``
+ Optional extra text to be displayed at the top of each fieldset,
+ underneath the heading of the fieldset. It is used verbatim,
+ so you can use any HTML and you must escape any special HTML
+ characters (such as ampersand) yourself.
+
For example (taken from the ``django.contrib.flatpages`` model)::
- fields = (
- (None, {
- 'fields': ('url', 'title', 'content', 'sites')
- }),
- ('Advanced options', {
- 'classes': 'collapse',
- 'fields' : ('enable_comments', 'registration_required', 'template_name')
- }),
- ),
+ class Admin:
+ ...
+ fields = (
+ (None, {
+ 'fields': ('url', 'title', 'content', 'sites')
+ }),
+ ('Advanced options', {
+ 'classes': 'collapse',
+ 'fields' : ('enable_comments', 'registration_required', 'template_name')
+ }),
+ )
results in an admin that looks like:
.. image:: http://media.djangoproject.com/img/doc/flatfiles_admin.png
- If ``fields`` isn't given but a model does define ``admin`` as a
- ``meta.Admin`` object, Django will default to displaying each field that
+ 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.
@@ -931,9 +1128,9 @@ object, which takes the following parameters. All are optional.
``short_description`` function attribute, for use as the header for
the field.
- * Use the string ``"__repr__"`` to output the representation of the
- object, according to your model's ``__repr__()`` function. If you
- don't define ``list_display``, Django will use the ``__repr__`` by
+ * Use the string ``"__str__"`` to output the representation of the
+ object, according to your model's ``__str__()`` function. If you
+ don't define ``list_display``, Django will use the ``__str__`` by
default.
See the example below.
@@ -945,8 +1142,10 @@ object, which takes the following parameters. All are optional.
Here's an example of how ``list_display`` and ``list_filter`` work (taken
from the ``auth.user`` model)::
- list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff'),
- list_filter = ('is_staff', 'is_superuser'),
+ 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 that looks like this:
@@ -963,7 +1162,7 @@ object, which takes the following parameters. All are optional.
if one of the ``list_display`` fields is a ``ForeignKey``.
``ordering``
- A list or tuple (see the `META options`_, above) that gives a
+ A list or tuple (see the `Meta options`_, above) that gives a
different ordering for the admin change list. If this isn't given, the
model's default ordering will be used.
@@ -985,6 +1184,84 @@ object, which takes the following parameters. All are optional.
obviously, be some kind of text field, such as ``CharField`` or
``TextField``.
+Managers
+========
+
+The Manager is the interface through which database query operations
+are provided to Django applications. At least one Manager exists for
+every model in a Django application.
+
+By default, Django will add a Manager with the name of ``objects`` to
+every Django model. However, if you wish to use ``objects`` as a field
+name, or if you wish to use a name other than ``objects`` for the Manager,
+you can rename the Manager 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()
+
+In this example, ``Person.objects.all()`` will generate an error, but
+``Person.people.all()`` will provide a list of all ``Person`` objects.
+
+Managers can also be customized. This is achieved by extending the
+base Manager class, and instantiating the new Manager on your model.
+There are two reasons that you may want to customize a Manager: firstly,
+to add utility methods to the Manager, and secondly, to modify the
+initial Query Set provided by the Manager.
+
+To modify the initial Query Set provided by a Manager, override the
+``get_query_set()`` method to return a Query Set with the properties
+you require. For example::
+
+ class PersonManager(models.Manager):
+ # Add some custom behavior to the Manager
+ def move_house(self):
+ # Some logic to help a person move house
+
+ # Modify the initial Query Set provided by the manager
+ def get_query_set(self):
+ return super(Manager, self).get_query_set().filter(name__startswith="Fred")
+
+ class Person(models.Model):
+ #...
+ objects = PersonManager()
+
+In this example, ``Person.objects.all()`` will only return people whose name starts
+with "Fred"; ``Person.objects.move_house()`` will also be available.
+
+If required, you can add multiple Managers to a model. Every Manager attribute
+added to a model can be accessed and used as a manager. This is an easy way
+to define common filters types for your models. For example, the model::
+
+ class MaleManager(models.Manager):
+ def get_query_set(self):
+ return super(Manager, self).get_query_set().filter(sex='M')
+
+ class FemaleManager(models.Manager):
+ def get_query_set(self):
+ return super(Manager, self).get_query_set().filter(sex='F')
+
+ class Person(models.Model):
+ #...
+ people = models.Manager()
+ men = MaleManager()
+ women = FemaleManager()
+
+... will allow end users to request ``Person.men.all()``, ``Person.women.all()``,
+and ``Person.people.all()``, yielding predictable results.
+
+If you are going to install a customized Manager, be warned that the first
+Manager that Django encounters in a model definition has special status.
+Django interprets the first Manager defined in a class as the default Manager.
+Certain operations use the default Manager to obtain lists of objects, so it
+is generally a good idea for the first Manager to be relatively unfiltered.
+In the last example, ``people`` is defined first - so the default Manager
+will include everyone.
+
Model methods
=============
@@ -992,11 +1269,11 @@ There are a number of methods you can define on model objects to control the
object's behavior. First, any methods you define will be available as methods
of object instances. For example::
- class Pizza(meta.Model):
+ class Pizza(models.Model):
# ...
def is_disgusting(self):
- return "anchovies" in [topping.name for topping in self.get_topping_list()]
+ return "anchovies" in [topping.name for topping in self.toppings.all()]
Now, every ``Pizza`` object will have a ``is_disgusting()`` method.
@@ -1016,16 +1293,16 @@ See `Giving models custom methods`_ for a full example.
A few object methods have special meaning:
-``__repr__``
- Django uses ``repr(obj)`` in a number of places, most notably as the value
+``__str__``
+ Django uses ``str(obj)`` in a number of places, most notably 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 ``__repr__``.
+ return a nice, human-readable string for the object's ``__str__``.
- Although defining ``__repr__()`` isn't required, it's strongly encouraged.
+ Although defining ``__str__()`` isn't required, it's strongly encouraged.
- See `Adding repr`_ for a full example.
+ See `Adding str`_ for a full example.
- .. _Adding repr: http://www.djangoproject.com/documentation/models/repr/
+ .. _Adding str: http://www.djangoproject.com/documentation/models/repr/
``get_absolute_url``
Define a ``get_absolute_url`` method to tell Django how to calculate the
@@ -1041,49 +1318,34 @@ A few object methods have special meaning:
It's good practice to use ``get_absolute_url()`` in templates, instead of
hard-coding your objects' URLs.
-``_pre_save``
- This method is called just before an object is saved to the database. For
- example, you can use it to calculate aggregate values from other fields
- before the object is saved.
-
- See `Adding hooks before/after saving and deleting`_ for a full example.
-
- .. _Adding hooks before/after saving and deleting: http://www.djangoproject.com/documentation/models/save_delete_hooks/
-
-``_post_save``
- This method is called just after the object is saved to the database. This
- could be used to update other tables, update cached information, etc.
-
-``_pre_delete``
- Like ``_pre_save``, but for deletion.
-
-``_post_delete``
- Like ``_post_save``, but for deletion.
-
Module-level methods
--------------------
-Since each data class effectively turns into a "magic" Python module under
-``django.models``, there are times you'll want to write methods that live in
-that module. Any model method that begins with "_module_" is turned into a
-module-level function::
+If you want to add a method to the Model, rather than instances of the model,
+you can use the Python ``staticmethod`` and ``classmethod`` operators. For
+example::
- class Pizza(meta.Model):
+ class Pizza(models.Model):
# ...
- def _module_get_pizzas_to_deliver():
+ def get_pizzas_to_deliver():
return get_list(delivered__exact=False)
+ get_pizzas_to_deliver = staticmethod(get_pizzas_to_deliver)
+
+Or, using Python 2.4 decorators::
+
+ # ...
+ @staticmethod
+ def get_pizzas_to_deliver():
+ # ...
This will make the top-level ``pizzas`` module have a ``get_pizzas_to_deliver()``
method::
- >>> from django.models.pizza_hut import pizzas
- >>> pizzas.get_pizzas_to_deliver()
+ >>> from pizza_hut.models import Pizza
+ >>> Pizza.get_pizzas_to_deliver()
[ ... ]
-Note that the scope of these methods is modified to be the same as the module
-scope. These methods do NOT have access to globals within your model's module.
-
Manipulator methods
-------------------
@@ -1092,7 +1354,7 @@ that being with "_manipulator_". This is most useful for providing custom
validators for certain fields, because manipulators automatically call any
method that begins with "validate"::
- class Pizza(meta.Model):
+ class Pizza(models.Model):
# ...
def _manipulator_validate_customer_id(self, field_data, all_data):
@@ -1106,14 +1368,15 @@ Executing custom SQL
--------------------
Feel free to write custom SQL statements in custom model methods and
-module-level methods. Each custom method automatically has access to the
-variable ``db``, which is the current database connection. To use it, call
-``db.cursor()`` to get a cursor object. Then, call ``cursor.execute(sql, [params])``
+module-level methods. The object ``django.db.connection`` object 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):
- cursor = db.cursor()
+ from django.db import connection
+ cursor = connection.cursor()
cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
row = cursor.fetchone()
return row
@@ -1122,11 +1385,12 @@ If your custom SQL statement alters the data in your database -- for example,
via a ``DELETE`` or ``UPDATE`` -- you'll need to call ``db.commit()``. Example::
def my_custom_sql2(self):
- cursor = db.cursor()
+ from django.db import connection
+ cursor = connection.cursor()
cursor.execute("DELETE FROM bar WHERE baz = %s", [self.baz])
- db.commit()
+ connection.commit()
-``db`` and ``cursor`` simply use the standard `Python DB-API`_. If you're not
+``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
@@ -1138,83 +1402,34 @@ 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/#other-lookup-options
+.. _Other lookup options: http://www.djangoproject.com/documentation/db_api/#extra-params-select-where-tables
Using models
============
-Once you've defined a model, you'll need to "enable" it in Django. This section
-explains how Django searches for available models.
-
-Save your models in a normal Python module. Put this module within a package
-called "models", which should itself be a subpackage of some other package on
-your Python path. The ``__init__.py`` in your ``models`` package should contain
-an ``__all__`` variable that is set to a list of all model module names within
-the ``models`` directory.
-
-If this sounds confusing, just use ``django-admin.py startapp`` -- it'll create
-the proper directory structure and ``__init__.py`` files. (See the
-`django-admin.py documentation`_ .)
-
-For example, if you save your models in a module called ``mymodels.py``, here's
-a directory layout you might use::
-
- myapp/
- __init__.py # Empty file
- models/
- __init__.py # Contains "__all__ = ['mymodels']"
- mymodels.py # Contains your models
-
-Then, you'll have to tell Django that the ``myapp`` application is installed.
-Do this by editing your settings file and adding ``"myapp"`` to the
-``INSTALLED_APPS`` tuple.
-
-Again, if this sounds confusing, use ``django-admin.py startapp`` to take care
-of package creation for you. This documentation exists only to explain how
-Django works.
+Once you have created your model, you have to tell Django about your new application.
+This is done by editing your settings file and adding the name of the module that
+contains your models module to the ``INSTALLED_APPS`` tuple.
-Once you've added your app to ``INSTALLED_APPS``, you can open a Python
-interactive interpreter and play with your model::
+For example, if the models for your application are contained in the module
+``project.myapp.models`` (the package structure that is created for an application
+by the ``django-admin.py startapp`` script), ``INSTALLED_APPS`` should read, in part::
- >>> from django.models.mymodels import pizzas
- >>> pizzas.get_list()
-
-Note that the import is from ``django.models``, not ``myapp.models``. Django
-creates a "magic" module within ``django.models`` for every installed
-application. Each of those magic modules has a dynamic API. See the
-`database API reference`_ for full information on how to use this API.
-
-.. admonition:: Why is the INSTALLED_APPS setting necessary?
-
- Model relationships work both ways, and the dynamically-generated Django API
- creates API lookups in both directions. Thus, for Django to figure out all
- the other models related to a particular model, it has to know the complete
- spectrum of installed apps.
-
-.. _`django-admin.py documentation`: http://www.djangoproject.com/documentation/django_admin/
-.. _`database API reference`: http://www.djangoproject.com/documentation/db_api/
+ INSTALLED_APPS = (
+ #...
+ project.myapp,
+ #...
+ )
Models across files
===================
It's perfectly OK to relate a model to one from another module. To do this,
-just import the model module at the top of your model module, like so::
+just import the model module at the top of your model module. Then, just
+refer to the other model class wherever needed. For example::
- from django.models import core
+ from myproject.otherapp import Site
-Make sure you're importing from ``django.models``, not directly from your model
-module.
-
-Then, just refer to the other model class wherever needed. For example::
-
- class MyModel(meta.Model):
+ class MyModel(models.Model):
# ...
- sites = meta.ManyToManyField(core.Site)
-
-Models in multiple files
-========================
-
-If you want to have multiple model modules in a ``"models"`` directory, make
-sure you edit ``"models/__init__.py"`` and add the name of your model module
-to the ``__all__`` variable. If your ``models`` package doesn't have your model
-module in ``__all__``, Django won't see any of the models in that module.
+ sites = models.ManyToManyField(Site)
diff --git a/docs/modpython.txt b/docs/modpython.txt
index 447bd70573..2b57d0514c 100644
--- a/docs/modpython.txt
+++ b/docs/modpython.txt
@@ -31,11 +31,11 @@ Then edit your ``httpd.conf`` file and add the following::
<Location "/mysite/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
- SetEnv DJANGO_SETTINGS_MODULE myproject.settings
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
PythonDebug On
</Location>
-...and replace ``myproject.settings`` with the Python path to your settings file.
+...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``
@@ -71,13 +71,13 @@ instance. Just use ``VirtualHost`` for that, like so::
<VirtualHost *>
ServerName www.example.com
# ...
- SetEnv DJANGO_SETTINGS_MODULE myproject.settings
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
</VirtualHost>
<VirtualHost *>
ServerName www2.example.com
# ...
- SetEnv DJANGO_SETTINGS_MODULE myproject.other_settings
+ SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
</VirtualHost>
If you need to put two Django installations within the same ``VirtualHost``,
@@ -89,13 +89,13 @@ mess things up. Use the ``PythonInterpreter`` directive to give different
ServerName www.example.com
# ...
<Location "/something">
- SetEnv DJANGO_SETTINGS_MODULE myproject.settings
- PythonInterpreter myproject
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ PythonInterpreter mysite
</Location>
<Location "/otherthing">
- SetEnv DJANGO_SETTINGS_MODULE myproject.other_settings
- PythonInterpreter myproject_other
+ SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
+ PythonInterpreter mysite_other
</Location>
</VirtualHost>
@@ -153,7 +153,7 @@ the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
- SetEnv DJANGO_SETTINGS_MODULE myproject.settings
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
</Location>
<Location "media">
diff --git a/docs/outputting_csv.txt b/docs/outputting_csv.txt
index 3f95b3600d..1970261891 100644
--- a/docs/outputting_csv.txt
+++ b/docs/outputting_csv.txt
@@ -30,7 +30,7 @@ and Django's ``HttpResponse`` objects are file-like objects.
Here's an example::
import csv
- from django.utils.httpwrappers import HttpResponse
+ from django.http import HttpResponse
def some_view(request):
# Create the HttpResponse object with the appropriate CSV header.
@@ -49,10 +49,10 @@ 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 would result in ugly, scary gobbledygook in the browser window.
+ 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
+ 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.
@@ -79,8 +79,8 @@ template output the commas in a ``{% for %}`` loop.
Here's an example, which generates the same CSV file as above::
- from django.utils.httpwrappers import HttpResponse
- from django.core.template import loader, Context
+ from django.http import HttpResponse
+ from django.template import loader, Context
def some_view(request):
# Create the HttpResponse object with the appropriate CSV header.
@@ -94,7 +94,7 @@ Here's an example, which generates the same CSV file as above::
('Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"),
)
- t = loader.get_template('my_template_name')
+ t = loader.get_template('my_template_name.txt')
c = Context({
'data': csv_data,
})
@@ -105,7 +105,7 @@ 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``, with this template code::
+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 %}
diff --git a/docs/outputting_pdf.txt b/docs/outputting_pdf.txt
index 9f3d6b26eb..a58cf2c217 100644
--- a/docs/outputting_pdf.txt
+++ b/docs/outputting_pdf.txt
@@ -48,7 +48,7 @@ objects.
Here's a "Hello World" example::
from reportlab.pdfgen import canvas
- from django.utils.httpwrappers import HttpResponse
+ from django.http import HttpResponse
def some_view(request):
# Create the HttpResponse object with the appropriate PDF headers.
diff --git a/docs/overview.txt b/docs/overview.txt
index 11fa7d1fa9..544a897ac6 100644
--- a/docs/overview.txt
+++ b/docs/overview.txt
@@ -16,34 +16,39 @@ to start a project.
Design your model
=================
-Start by describing your database layout in Python code. Django's data-model API
-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::
+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.
- class Reporter(meta.Model):
- full_name = meta.CharField(maxlength=70)
+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::
- def __repr__(self):
+ class Reporter(models.Model):
+ full_name = models.CharField(maxlength=70)
+
+ def __str__(self):
return self.full_name
- class Article(meta.Model):
- pub_date = meta.DateTimeField()
- headline = meta.CharField(maxlength=200)
- article = meta.TextField()
- reporter = meta.ForeignKey(Reporter)
+ class Article(models.Model):
+ pub_date = models.DateTimeField()
+ headline = models.CharField(maxlength=200)
+ article = models.TextField()
+ reporter = models.ForeignKey(Reporter)
- def __repr__(self):
+ def __str__(self):
return self.headline
Install it
==========
-Next, run the Django command-line utility. It'll create the database tables for
-you automatically, in the database specified in your Django settings. Django
-works best with PostgreSQL, although we've recently added beta MySQL
-support and other database adapters are on the way::
+Next, run the Django command-line utility to create the database tables
+automatically::
+
+ manage.py syncdb
- django-admin.py install news
+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
==================
@@ -51,16 +56,14 @@ 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::
- # Modules are dynamically created within django.models.
- # Their names are plural versions of the model class names.
- >>> from django.models.news import reporters, articles
+ >>> from mysite.models import Reporter, Article
# No reporters are in the system yet.
- >>> reporters.get_list()
+ >>> Reporter.objects.all()
[]
# Create a new Reporter.
- >>> r = reporters.Reporter(full_name='John Smith')
+ >>> r = Reporter(full_name='John Smith')
# Save the object into the database. You have to call save() explicitly.
>>> r.save()
@@ -70,52 +73,48 @@ is created on the fly: No code generation necessary::
1
# Now the new reporter is in the database.
- >>> reporters.get_list()
+ >>> 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 that's entirely driven by keyword arguments.
- >>> reporters.get_object(id__exact=1)
+ # Django provides a rich database lookup API.
+ >>> Reporter.objects.get(id=1)
John Smith
- >>> reporters.get_object(full_name__startswith='John')
+ >>> Reporter.objects.get(full_name__startswith='John')
John Smith
- >>> reporters.get_object(full_name__contains='mith')
+ >>> Reporter.objects.get(full_name__contains='mith')
John Smith
- >>> reporters.get_object(id__exact=2)
+ >>> Reporter.objects.get(id=2)
Traceback (most recent call last):
...
- django.models.news.ReporterDoesNotExist: Reporter does not exist for {'id__exact': 2}
-
- # 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 reporters.get_object(id__exact=1).
- >>> reporters.get_object(pk=1)
- John Smith
+ DoesNotExist: Reporter does not exist for {'id__exact': 2}
# Create an article.
>>> from datetime import datetime
- >>> a = articles.Article(pub_date=datetime.now(), headline='Django is cool', article='Yeah.', reporter_id=1)
+ >>> a = Article(pub_date=datetime.now(), headline='Django is cool',
+ ... article='Yeah.', reporter=r)
>>> a.save()
# Now the article is in the database.
- >>> articles.get_list()
+ >>> Article.objects.all()
[Django is cool]
# Article objects get API access to related Reporter objects.
- >>> r = a.get_reporter()
+ >>> r = a.reporter
>>> r.full_name
'John Smith'
# And vice versa: Reporter objects get API access to Article objects.
- >>> r.get_article_list()
+ >>> r.article_set.all()
[Django is cool]
- # The API follows relationships as far as you need.
- # Find all articles by a reporter whose name starts with "John".
- >>> articles.get_list(reporter__full_name__startswith="John")
+ # 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().
@@ -128,61 +127,64 @@ is created on the fly: No code generation necessary::
A dynamic admin interface: It's not just scaffolding -- it's the whole house
============================================================================
-Once your models are defined, Django can automatically create an administrative
-interface -- a Web site that lets authenticated users add, change and
-delete objects. It's as easy as adding an extra ``admin`` attribute to your
-model classes::
+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(meta.Model):
- pub_date = meta.DateTimeField()
- headline = meta.CharField(maxlength=200)
- article = meta.TextField()
- reporter = meta.ForeignKey(Reporter)
- class META:
- admin = meta.Admin()
+ 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.
-Our typical workflow at World Online is to create models and get the admin sites
-up and running as fast as possible, so our staff journalists can start
-populating data. Then we develop the way data is presented to the public.
+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 lets you design URLs however you want, with no framework
-limitations.
+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. For the above
-Reporter/Article example, here's what that might look like::
+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 above ``Reporter``/``Article``
+example above::
from django.conf.urls.defaults import *
urlpatterns = patterns('',
- (r'^/articles/(?P<year>\d{4})/$', 'myproject.news.views.year_archive'),
- (r'^/articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'myproject.news.views.month_archive'),
- (r'^/articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<article_id>\d+)/$', 'myproject.news.views.article_detail'),
+ (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 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
-regular expression, in order, and stops at the first one that matches the
-requested URL. (If none of them matches, Django calls a special 404 view.) This
-is blazingly fast, because the regular expressions are compiled at load time.
+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 lets you access GET and POST data as simple
-dictionaries -- and the values captured in the regex, via keyword
-arguments.
+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 ``myproject.news.views.article_detail(request,
-year='2005', month='05', article_id='39323')``.
+'2005', '05', '39323')``.
Write your views
================
@@ -193,29 +195,29 @@ 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
-article_detail from above::
+``year_archive`` from above::
- def article_detail(request, year, month, article_id):
- # Use the Django API to find an object matching the URL criteria.
- a = get_object_or_404(articles, pub_date__year=year, pub_date__month=month, pk=article_id)
- return render_to_response('news/article_detail', {'article': a})
+ def year_archive(request, year):
+ a_list = Article.objects.filter(pub_date__year=year)
+ return render_to_response('news/year_archive.html', {'article_list': a_list})
-This example uses Django's template system, which has several key features.
+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/article_detail`` template.
+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`` template was found. Here's what that might
-look like::
+Let's say the ``news/article_detail.html`` template was found. Here's what that
+might look like::
- {% extends "base" %}
+ {% extends "base.html" %}
{% block title %}{{ article.headline }}{% endblock %}
@@ -226,12 +228,10 @@ look like::
{{ article.article }}
{% endblock %}
-
-It should look straightforward. 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 (as is the case with
-``article.get_reporter``).
+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 (as is the case with ``article.get_reporter``).
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
@@ -243,13 +243,13 @@ 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" %}`` 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.
+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" template might look like::
+Here's what the "base.html" template might look like::
<html>
<head>
@@ -265,13 +265,18 @@ 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 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.
+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
========================
diff --git a/docs/redirects.txt b/docs/redirects.txt
index 08fae8a8dc..e0bcb2f1fa 100644
--- a/docs/redirects.txt
+++ b/docs/redirects.txt
@@ -8,12 +8,12 @@ redirects in a database and handles the redirecting for you.
Installation
============
-To install the redirects app, follow these two steps:
+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"``
+ 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 ``django-admin.py install redirects``.
+ 3. Run the command ``manage.py syncdb``.
.. _INSTALLED_APPS: http://www.djangoproject.com/documentation/settings/#installed-apps
.. _MIDDLEWARE_CLASSES: http://www.djangoproject.com/documentation/settings/#middleware-classes
@@ -21,9 +21,8 @@ To install the redirects app, follow these two steps:
How it works
============
-``django-admin.py install redirects`` creates a ``django_redirects`` table in
-your database. This is a simple lookup table with ``site_id``, ``old_path`` and
-``new_path`` fields.
+``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
diff --git a/docs/request_response.txt b/docs/request_response.txt
index fc78a3459b..6fa4311f61 100644
--- a/docs/request_response.txt
+++ b/docs/request_response.txt
@@ -37,6 +37,8 @@ All attributes except ``session`` should be considered read-only.
A dictionary-like object containing all given HTTP POST parameters. See the
``QueryDict`` documentation below.
+ 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``.
@@ -86,9 +88,9 @@ All attributes except ``session`` should be considered read-only.
* ``SERVER_PORT`` -- The port of the server.
``user``
- A ``django.models.auth.users.User`` object representing the currently
+ 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.parts.auth.anonymoususers.AnonymousUser``. You
+ to an instance of ``django.contrib.auth.models.AnonymousUser``. You
can tell them apart with ``is_anonymous()``, like so::
if request.user.is_anonymous():
@@ -96,6 +98,12 @@ All attributes except ``session`` should be considered read-only.
else:
# Do something for logged-in users.
+ ``user`` is only available if your Django installation has the
+ ``AuthenticationMiddleware`` activated. For more, see
+ `Authentication in Web requests`_.
+
+ .. Authentication in Web requests: http://www.djangoproject.com/documentation/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
@@ -131,10 +139,10 @@ QueryDict objects
-----------------
In an ``HttpRequest`` object, the ``GET`` and ``POST`` attributes are instances
-of ``django.utils.httpwrappers.QueryDict``. ``QueryDict`` is a dictionary-like
+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>``, pass
-multiple values for the same key.
+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``
@@ -269,11 +277,14 @@ 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.utils.httpwrappers.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::
@@ -297,16 +308,27 @@ You can add and delete headers using dictionary syntax::
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_MIME_TYPE)``
Instantiates an ``HttpResponse`` object with the given page content (a
- string) and MIME type. The ``DEFAULT_MIME_TYPE`` is ``"text/html"``.
+ string) and MIME type. The ``DEFAULT_MIME_TYPE`` is ``'text/html'``.
- **New in Django development version:** ``content`` can be an iterator
- instead of a string. This iterator should return strings, and those strings
- will be joined together to form the content of the response.
+ ``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
@@ -343,14 +365,10 @@ Methods
Deletes the cookie with the given key. Fails silently if the key doesn't
exist.
-``get_content_as_string(encoding)``
- Returns the content as a Python string, encoding it from a Unicode object
- if necessary. **Removed in Django development version.**
-
``content``
- **New in Django development version.** 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()``.
+ 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.
@@ -360,12 +378,12 @@ HttpResponse subclasses
Django includes a number of ``HttpResponse`` subclasses that handle different
types of HTTP responses. Like ``HttpResponse``, these subclasses live in
-``django.utils.httpwrappers``.
+``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
+ 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``
diff --git a/docs/sessions.txt b/docs/sessions.txt
index 6a208a1461..10d94cd4fd 100644
--- a/docs/sessions.txt
+++ b/docs/sessions.txt
@@ -10,33 +10,30 @@ Cookies contain a session ID -- not the data itself.
Enabling sessions
=================
-Session functionality is enabled by default.
+Sessions are implemented via middleware_.
-You can turn session functionality on and off by editing the
-``MIDDLEWARE_CLASSES`` setting. To activate sessions, make sure
-``MIDDLEWARE_CLASSES`` contains ``"django.middleware.sessions.SessionMiddleware"``.
+Turn session functionality on and off by editing the ``MIDDLEWARE_CLASSES``
+setting. To activate sessions, make sure ``MIDDLEWARE_CLASSES`` contains
+``"django.contrib.sessions.middleware.SessionMiddleware"``.
-If you're dealing with an admin site, make sure the ``SessionMiddleware`` line
-appears before the ``AdminUserRequired`` line. (The middleware classes are
-applied in order, and the admin middleware requires that the session middleware
-come first.)
+The default ``settings.py`` created by ``django-admin.py startproject`` has
+``SessionMiddleware`` activated.
If you don't want to use sessions, you might as well remove the
``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES``. It'll save you a small
bit of overhead.
+.. _middleware: http://www.djangoproject.com/documentation/middleware/
+
Using sessions in views
=======================
-Each ``HttpRequest`` object -- the first argument to any Django view function --
-has a ``session`` attribute, which is a dictionary-like object. You can read
-it and write to it.
+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:
- * ``__contains__(key)``
- Example: ``'fav_color' in request.session``
-
* ``__getitem__(key)``
Example: ``fav_color = request.session['fav_color']``
@@ -47,14 +44,15 @@ It implements the following standard dictionary methods:
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()``
- **New in Django development version.**
* ``items()``
- **New in Django development version.**
It also has these three methods:
@@ -146,17 +144,17 @@ Here's a typical usage example::
else:
return HttpResponse("Please enable cookies and try again.")
request.session.set_test_cookie()
- return render_to_response('foo/login_form')
+ 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/models/core.py``. Because it's a normal model, you can
-access sessions using the normal Django database API::
+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.models.core import sessions
- >>> s = sessions.get_object(pk='2b1189a188b44ad18c35e113ac6ceead')
+ >>> 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)
@@ -244,7 +242,7 @@ 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 ``core_sessions`` .
+ * 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.
diff --git a/docs/settings.txt b/docs/settings.txt
index 25c07785fd..d4666468fc 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -19,7 +19,7 @@ Here are a couple of example settings::
Because a settings file is a Python module, the following apply:
- * It shouldn't have Python syntax errors.
+ * It doesn't allow for Python syntax errors.
* It can assign settings dynamically using normal Python syntax.
For example::
@@ -34,7 +34,7 @@ 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.
-``"myproject.settings"``. Note that the settings module should be on the
+``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
@@ -47,17 +47,17 @@ once, or explicitly pass in the settings module each time you run the utility.
Example (Unix Bash shell)::
- export DJANGO_SETTINGS_MODULE=myproject.settings
+ export DJANGO_SETTINGS_MODULE=mysite.settings
django-admin.py runserver
Example (Windows shell)::
- set DJANGO_SETTINGS_MODULE=myproject.settings
+ 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=myproject.settings
+ django-admin.py runserver --settings=mysite.settings
.. _django-admin.py: http://www.djangoproject.com/documentation/django_admin/
@@ -70,7 +70,7 @@ settings file to use. Do that with ``SetEnv``::
<Location "/mysite/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
- SetEnv DJANGO_SETTINGS_MODULE myproject.settings
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
</Location>
Read the `Django mod_python documentation`_ for more information.
@@ -93,6 +93,17 @@ Here's the algorithm Django uses in compiling settings:
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: http://www.djangoproject.com/documentation/django_admin/#diffsettings
+
Using settings in Python code
=============================
@@ -107,6 +118,8 @@ In your Django apps, use settings by importing them from
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
============================
@@ -156,6 +169,9 @@ 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
------------------
@@ -228,6 +244,14 @@ 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
-------------
@@ -341,8 +365,6 @@ EMAIL_HOST_PASSWORD
Default: ``''`` (Empty string)
-**New in Django development version.**
-
Username to use for the SMTP server defined in ``EMAIL_HOST``. If empty,
Django won't attempt authentication.
@@ -353,8 +375,6 @@ EMAIL_HOST_USER
Default: ``''`` (Empty string)
-**New in Django development version.**
-
Username to use for the SMTP server defined in ``EMAIL_HOST``. If empty,
Django won't attempt authentication.
@@ -365,8 +385,6 @@ EMAIL_PORT
Default: ``25``
-**New in Django development version.**
-
Port to use for the SMTP server defined in ``EMAIL_HOST``.
EMAIL_SUBJECT_PREFIX
@@ -383,8 +401,6 @@ ENABLE_PSYCO
Default: ``False``
-**New in Django development version.**
-
Whether to enable Psyco, which optimizes Python code. Requires Psyco_.
.. _Psyco: http://psyco.sourceforge.net/
@@ -509,7 +525,8 @@ MIDDLEWARE_CLASSES
Default::
- ("django.middleware.sessions.SessionMiddleware",
+ ("django.contrib.sessions.middleware.SessionMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.doc.XViewMiddleware")
@@ -598,9 +615,9 @@ SITE_ID
Default: Not defined
-The ID, as an integer, of the current site in the ``sites`` database. This is
-used so that application data can hook into specific site(s) and a single
-database can manage content for multiple sites.
+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.
TEMPLATE_CONTEXT_PROCESSORS
---------------------------
@@ -611,7 +628,7 @@ Default::
"django.core.context_processors.debug",
"django.core.context_processors.i18n")
-A tuple of callables that are used to populate the context in ``DjangoContext``.
+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.
@@ -625,8 +642,8 @@ 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.
+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.
@@ -640,18 +657,10 @@ these paths should use Unix-style forward slashes, even on Windows.
See the `template documentation`_.
-TEMPLATE_FILE_EXTENSION
------------------------
-
-Default: ``'.html'``
-
-The file extension to append to all template names when searching for
-templates. See the `template documentation`_.
-
TEMPLATE_LOADERS
----------------
-Default: ``('django.core.template.loaders.filesystem.load_template_source',)``
+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`_.
@@ -661,8 +670,6 @@ TEMPLATE_STRING_IF_INVALID
Default: ``''`` (Empty string)
-**New in Django development version.**
-
Output, as a string, that the template system should use for invalid (e.g.
misspelled) variables. See `How invalid variables are handled`_.
diff --git a/docs/static_files.txt b/docs/static_files.txt
index 333bb12e22..d8d90e52d5 100644
--- a/docs/static_files.txt
+++ b/docs/static_files.txt
@@ -31,7 +31,7 @@ How to do it
Just put this in your URLconf_::
- (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media'}),
+ (r'^site_media/(.*)$', '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.
@@ -60,7 +60,7 @@ listings for directories.
Example::
- (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media', 'show_indexes': True}),
+ (r'^site_media/(.*)$', '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:
@@ -100,7 +100,7 @@ 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.settings import DEBUG
+ from django.conf import settings
urlpatterns = patterns('',
(r'^/articles/2003/$', 'news.views.special_case_2003'),
@@ -109,15 +109,15 @@ Do this by wrapping an ``if DEBUG`` statement around the
(r'^/articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'news.views.article_detail'),
)
- if DEBUG:
+ 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 `DEBUG setting`_ and checks its
-value. 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.
+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.
diff --git a/docs/syndication_feeds.txt b/docs/syndication_feeds.txt
index d234c2b7ec..b7b0a9047b 100644
--- a/docs/syndication_feeds.txt
+++ b/docs/syndication_feeds.txt
@@ -93,7 +93,7 @@ This simple example, taken from `chicagocrime.org`_, describes a feed of the
latest five news items::
from django.contrib.syndication.feeds import Feed
- from django.models.chicagocrime import newsitems
+ from chicagocrime.models import NewsItem
class SiteNewsFeed(Feed):
title = "Chicagocrime.org site news"
@@ -101,7 +101,7 @@ latest five news items::
description = "Updates on changes and additions to chicagocrime.org."
def items(self):
- return newsitems.get_list(order_by=('-pub_date',), limit=5)
+ return NewsItem.objects.order_by('-pub_date')[:5]
Note:
@@ -120,10 +120,11 @@ One thing's left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
put into those elements.
* To specify the contents of ``<title>`` and ``<description>``, create
- `Django templates`_ called ``feeds/sitenews_title`` and
- ``feeds/sitenews_description``, where ``sitenews`` is the ``slug``
- specified in the URLconf for the given feed. The RSS system renders that
- template for each item, passing it two template context variables:
+ `Django templates`_ called ``feeds/sitenews_title.html`` and
+ ``feeds/sitenews_description.html``, where ``sitenews`` 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()``).
@@ -175,7 +176,7 @@ An example makes this clear. Here's the code for these beat-specific feeds::
# check that bits has only one member.
if len(bits) != 1:
raise ObjectDoesNotExist
- return beats.get_object(beat__exact=bits[0])
+ return Beat.objects.get(beat__exact=bits[0])
def title(self, obj):
return "Chicagocrime.org: Crimes for beat %s" % obj.beat
@@ -187,7 +188,7 @@ An example makes this clear. Here's the code for these beat-specific feeds::
return "Crimes recently reported in police beat %s" % obj.beat
def items(self, obj):
- return crimes.get_list(beat__id__exact=obj.id, order_by=(('-crime_date'),), limit=30)
+ 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/``:
@@ -203,8 +204,8 @@ request to the URL ``/rss/beats/0613/``:
the beat. Note that ``get_object()`` should raise
``django.core.exceptions.ObjectDoesNotExist`` if given invalid
parameters. There's no ``try``/``except`` around the
- ``beats.get_object()`` call, because it's not necessary; that function
- raises ``BeatDoesNotExist`` on failure, and ``BeatDoesNotExist`` is a
+ ``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>``,
@@ -292,7 +293,7 @@ URLconf to add the extra versions.
Here's a full example::
from django.contrib.syndication.feeds import Feed
- from django.models.chicagocrime import newsitems
+ from chicagocrime.models import NewsItem
from django.utils.feedgenerator import Atom1Feed
class RssSiteNewsFeed(Feed):
@@ -301,7 +302,7 @@ Here's a full example::
description = "Updates on changes and additions to chicagocrime.org."
def items(self):
- return newsitems.get_list(order_by=('-pub_date',), limit=5)
+ return NewsItem.objects.order_by('-pub_date')[:5]
class AtomSiteNewsFeed(RssSiteNewsFeed):
feed_type = Atom1Feed
@@ -328,7 +329,6 @@ 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
diff --git a/docs/templates.txt b/docs/templates.txt
index 22c10caf15..6cc99e93e3 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -13,9 +13,8 @@ or CheetahTemplate_, you should feel right at home with Django's templates.
Templates
=========
-A template is simply a text file. All Django templates, by convention, have
-".html" extensions, but they can generate any text-based format (HTML, XML,
-CSV, etc.).
+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.
@@ -23,7 +22,7 @@ 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" %}
+ {% extends "base_generic.html" %}
{% block title %}{{ section.title }}{% endblock %}
@@ -48,6 +47,8 @@ explained later in this document.::
JavaScript and CSV. You can use the template language for any text-based
format.
+ Oh, and one more thing: Making humans edit XML is masochistic!
+
Variables
=========
@@ -70,32 +71,27 @@ Use a dot (``.``) to access attributes of a variable.
In the above example, ``{{ section.title }}`` will be replaced with the
``title`` attribute of the ``section`` object.
-In Django 0.91, if you use a variable that doesn't exist, it will be silently
-ignored; the variable will be replaced by nothingness. In the Django
-development version, if a variable doesn't exist, the template system inserts
+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.
-If you use a variable that doesn't exist, it will be silently ignored. The
-variable will be replaced by nothingness.
-
See `Using the built-in reference`_, below, for help on finding what variables
are available in a given template.
-You can modify variables for display by using **filters**.
-
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 applied to the next:
-``{{ text|escape|linebreaks }}`` is a common idiom for escaping text contents
-and then converting line breaks to ``<p>`` tags.
+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.
-Certain filters take arguments. A filter argument looks like this:
+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.
@@ -156,7 +152,7 @@ engine that a child template may override those portions of the template.
A child template might look like this::
- {% extends "base" %}
+ {% extends "base.html" %}
{% block title %}My amazing blog{% endblock %}
@@ -169,12 +165,12 @@ A child template might look like this::
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" (note the
-lack of an ".html" extension in the ``{% extends %}`` tag).
+this template, first it locates the parent -- in this case, "base.html".
-At that point, the template engine will notice the three blocks 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::
+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">
@@ -205,29 +201,36 @@ 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.
-Template inheritance isn't limited to a single level. Multi-level inheritance
-is possible and, indeed, quite useful.
+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.
+ 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.
+ 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.
- * The recommended template layout is to use three levels: a single base
- template for the entire site, a set of mid-level templates for each
- section of the site, and then the individual templates for each view.
- This maximizes code reuse and makes it easier to add items to shared
- content areas (such as section-wide navigation).
-
* 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
@@ -243,17 +246,11 @@ wouldn't know which one of the blocks' content to use.
Using the built-in reference
============================
-Because Django can be used to develop any sort of site, the tags, filters and
-variables available are different depending on the application. To make it
-easy to figure out what's available in a given site, the admin interface has a
-complete reference of all the template goodies available to that site. To get
-that reference, go to your Django admin interface and append ``'doc'`` onto the
-admin URL. Example: ``http://127.0.0.1/admin/doc/``. In the Django development
-version, you'll see a "Documentation" link in the upper right of every
-admin-site page.
+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 integrated into the administration interface for your site(s)
-and is divided into 4 sections: tags, filters, models, and views.
+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
@@ -264,13 +261,13 @@ 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.
+ * 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 generally revolves around database objects, the **models**
+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.
@@ -304,9 +301,9 @@ 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 %}`` will *not* have access to
-the comments template tags and filters. The child template is responsible for
-its own ``{% load comments %}``.
+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.
@@ -366,10 +363,10 @@ extends
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 the name of the parent
-template to extend.
+This tag may 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, or ``{% extends variable %}`` uses the value of ``variable`` as the
+name of the parent template to extend.
See `Template inheritance`_ for more information.
@@ -489,7 +486,7 @@ ifchanged
Check if a value has changed from the last iteration of a loop.
-The 'ifchanged' block tag is used within a loop. It checks its own rendered
+The ``ifchanged`` block tag is used within a loop. It checks its own rendered
contents against its previous state and only displays its content if the value
has changed::
@@ -533,9 +530,9 @@ Loads a template and renders it with the current context. This is a way of
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"``::
+This example includes the contents of the template ``"foo/bar.html"``::
- {% include "foo/bar" %}
+ {% include "foo/bar.html" %}
This example includes the contents of the template whose name is contained in
the variable ``template_name``::
@@ -548,9 +545,9 @@ including it. This example produces the output ``"Hello, John"``:
* Context: variable ``person`` is set to ``"john"``.
* Template::
- {% include "name_snippet" %}
+ {% include "name_snippet.html" %}
- * The ``name_snippet`` template::
+ * The ``name_snippet.html`` template::
Hello, {{ person }}
@@ -693,8 +690,6 @@ i.e.::
spaceless
~~~~~~~~~
-**New in Django development version.**
-
Normalizes whitespace between HTML tags to a single space. This includes tab
characters and newlines.
@@ -769,7 +764,7 @@ 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 %}' />
+ <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
@@ -844,13 +839,14 @@ Escapes a string's HTML. Specifically, it makes these replacements:
* ``"&"`` to ``"&amp;"``
* ``<`` to ``"&lt;"``
* ``>`` to ``"&gt;"``
- * ``'"'`` (double quote) to ``"&quot;"``
+ * ``'"'`` (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).
+Format the value like a 'human-readable' file size (i.e. ``'13 KB'``,
+``'4.1 MB'``, ``'102 bytes'``, etc).
first
~~~~~
@@ -898,12 +894,12 @@ Returns a boolean of whether the value's length is the argument.
linebreaks
~~~~~~~~~~
-Converts newlines into <p> and <br />s.
+Converts newlines into ``<p>`` and ``<br />``s.
linebreaksbr
~~~~~~~~~~~~
-Converts newlines into <br />s.
+Converts newlines into ``<br />``s.
linenumbers
~~~~~~~~~~~
@@ -941,7 +937,11 @@ any string.
pluralize
~~~~~~~~~
-Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'.
+Returns ``'s'`` if the value is not 1.
+
+Example::
+
+ You have {{ num_messages }} message{{ num_messages|pluralize }}.
pprint
~~~~~~
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index b1d968cd47..21ae595624 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -55,13 +55,13 @@ Compiling a string
------------------
The easiest way to create a ``Template`` object is by instantiating it
-directly. The class lives at ``django.core.template.Template``. The constructor
+directly. The class lives at ``django.template.Template``. The constructor
takes one argument -- the raw template code::
- >>> from django.core.template import Template
+ >>> from django.template import Template
>>> t = Template("My name is {{ my_name }}.")
>>> print t
- <django.core.template.Template instance>
+ <django.template.Template instance>
.. admonition:: Behind the scenes
@@ -77,12 +77,12 @@ 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.core.template.Context``, and the constructor takes one (optional)
+``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.core.template import Context, Template
+ >>> from django.template import Context, Template
>>> t = Template("My name is {{ my_name }}.")
>>> c = Context({"my_name": "Adrian"})
@@ -110,7 +110,7 @@ logic.
Here are a few examples::
- >>> from django.core.template import Context, Template
+ >>> 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))
@@ -139,10 +139,10 @@ 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 subclasses
- ``django.core.template.SilentVariableFailure``. If the exception
- subclasses ``SilentVariableFailure``, the variable will render as an
- empty string. Example::
+ 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:
@@ -154,15 +154,21 @@ some things to keep in mind:
...
AssertionError: foo
- >>> from django.core.template import SilentVariableFailure
- >>> class SilentAssertionError(SilentVariableFailure): pass
+ >>> class SilentAssertionError(Exception):
+ ... silent_variable_failure = True
>>> class PersonClass4:
... def first_name(self):
- ... raise SilentAssertionError, "foo"
+ ... 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).
@@ -203,9 +209,9 @@ This applies to any level of lookup::
>>> t.render(c)
"My name is Stan ."
-In the Django development version, 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.
+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.
Playing with Context objects
----------------------------
@@ -227,7 +233,7 @@ dictionary syntax::
A ``Context`` object is a stack. That is, you can ``push()`` and ``pop()`` it.
If you ``pop()`` too much, it'll raise
-``django.core.template.ContextPopException``::
+``django.template.ContextPopException``::
>>> c = Context()
>>> c['foo'] = 'first level'
@@ -244,20 +250,20 @@ If you ``pop()`` too much, it'll raise
>>> c.pop()
Traceback (most recent call last):
...
- django.core.template.ContextPopException
+ django.template.ContextPopException
Using a ``Context`` as a stack comes in handy in some custom template tags, as
you'll see below.
-Subclassing Context: DjangoContext
-----------------------------------
+Subclassing Context: RequestContext
+-----------------------------------
Django comes with a special ``Context`` class,
-``django.core.extensions.DjangoContext``, that acts slightly differently than
-the normal ``django.core.template.Context``. The first difference is that takes
+``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 = DjangoContext(request, {
+ c = RequestContext(request, {
'foo': 'bar',
}
@@ -277,16 +283,16 @@ 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 ``DjangoContext`` a list of additional processors, using the
+Also, you can give ``RequestContext`` a list of additional processors, using the
optional, third positional argument, ``processors``. In this example, the
-``DjangoContext`` instance gets a ``ip_address`` variable::
+``RequestContext`` instance gets a ``ip_address`` variable::
def ip_address_processor(request):
return {'ip_address': request.META['REMOTE_ADDR']}
def some_view(request):
# ...
- return DjangoContext(request, {
+ return RequestContext(request, {
'foo': 'bar',
}, [ip_address_processor])
@@ -299,7 +305,7 @@ django.core.context_processors.auth
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
-``DjangoContext`` will contain these three variables:
+``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
@@ -317,7 +323,7 @@ django.core.context_processors.debug
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
-``DjangoContext`` will contain these two variables -- but only if your
+``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:
@@ -331,7 +337,7 @@ django.core.context_processors.i18n
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
-``DjangoContext`` will contain these two variables:
+``RequestContext`` will contain these two variables:
* ``LANGUAGES`` -- The value of the `LANGUAGES setting`_.
* ``LANGUAGE_CODE`` -- ``request.LANGUAGE_CODE``, if it exists. Otherwise,
@@ -346,8 +352,6 @@ See the `internationalization docs`_ for more.
django.core.context_processors.request
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-**New in Django development version**
-
If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
``DjangoContext`` will contain a variable ``request``, which is the current
`HttpRequest object`_. Note that this processor is not enabled by default;
@@ -357,15 +361,8 @@ 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 file with an
-".html" extension in a directory specified as a **template directory**.
-
-If you don't like the requirement that templates have an ".html" extension,
-change your ``TEMPLATE_FILE_EXTENSION`` setting. It's set to ``".html"`` by
-default.
-
-Also, the .html extension doesn't mean templates can contain only HTML. They
-can contain whatever textual content you want.
+the low-level ``Template`` API yourself. Save templates in a directory
+specified as a **template directory**.
The TEMPLATE_DIRS setting
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -380,7 +377,8 @@ that contain full paths to your template directory(ies). Example::
)
Your templates can go anywhere you want, as long as the directories and
-templates are readable by the Web server.
+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.
@@ -389,23 +387,24 @@ The Python API
Django has two ways to load templates from files:
-``django.core.template.loader.get_template(template_name)``
+``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.core.template.TemplateDoesNotExist``.
+ ``django.template.TemplateDoesNotExist``.
-``django.core.template.loader.select_template(template_name_list)``
+``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")`` and have the above
-``TEMPLATE_DIRS`` setting, here are the files Django will look for, in order:
+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", "story_detail"])``, here's
-what Django will look for:
+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``
@@ -416,10 +415,10 @@ When Django finds a template that exists, it stops looking.
.. admonition:: Tip
- You can use ``select_template`` for super-flexible "templatability." For
+ 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" % story.id, "story_detail"])``.
+ ``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.
@@ -435,21 +434,30 @@ single directory gets messy.
To load a template that's within a subdirectory, just use a slash, like so::
- get_template("news/story_detail")
+ 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. They're 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 built-in template loaders:
+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.core.template.loaders.filesystem.load_template_source``
+``django.template.loaders.filesystem.load_template_source``
Loads templates from the filesystem, according to ``TEMPLATE_DIRS``.
-``django.core.template.loaders.app_directories.load_template_source``
+``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.
@@ -461,7 +469,7 @@ a template loader. Here are the built-in template loaders:
INSTALLED_APPS = ('myproject.polls', 'myproject.music')
- ...then ``get_template("foo")`` will look for templates in these
+ ...then ``get_template('foo.html')`` will look for templates in these
directories, in this order:
* ``/path/to/myproject/polls/templates/foo.html``
@@ -471,7 +479,7 @@ a template loader. Here are the built-in template loaders:
It caches a list of which ``INSTALLED_APPS`` packages have a ``templates``
subdirectory.
-``django.core.template.loaders.eggs.load_template_source``
+``django.template.loaders.eggs.load_template_source``
Just like ``app_directories`` above, but it loads templates from Python
eggs rather than from the filesystem.
@@ -521,15 +529,15 @@ 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.core import template
+ 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/core/template/defaultfilters.py`` and
- ``django/core/template/defaulttags.py``, respectively.
+ and tags. They're in ``django/template/defaultfilters.py`` and
+ ``django/template/defaulttags.py``, respectively.
Writing custom template filters
-------------------------------
@@ -603,7 +611,7 @@ 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.core.template.Node`` and has
+''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.
@@ -632,7 +640,7 @@ else. In our case, let's say the tag should be used like this::
The parser for this function should grab the parameter and create a ``Node``
object::
- from django.core import template
+ from django import template
def do_current_time(parser, token):
try:
# Splitting by None == splitting by spaces.
@@ -652,7 +660,7 @@ Notes:
example, it's ``'current_time "%Y-%M-%d %I:%M %p"'``.
* This function is responsible for raising
- ``django.core.template.TemplateSyntaxError``, with helpful messages, for
+ ``django.template.TemplateSyntaxError``, with helpful messages, for
any syntax error.
* The ``TemplateSyntaxError`` exceptions use the ``tag_name`` variable.
@@ -679,7 +687,7 @@ has a ``render()`` method.
Continuing the above example, we need to define ``CurrentTimeNode``::
- from django.core import template
+ from django import template
import datetime
class CurrentTimeNode(template.Node):
def __init__(self, format_string):
@@ -817,7 +825,7 @@ Here's how the standard ``{% comment %}`` tag is implemented::
return ''
``parser.parse()`` takes a tuple of names of block tags ''to parse until''. It
-returns an instance of ``django.core.template.NodeList``, which is a list of
+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.
@@ -867,4 +875,4 @@ The only new concept here is the ``self.nodelist.render(context)`` in
For more examples of complex rendering, see the source code for ``{% if %}``,
``{% for %}``, ``{% ifequal %}`` and ``{% ifchanged %}``. They live in
-``django/core/template/defaulttags.py``.
+``django/template/defaulttags.py``.
diff --git a/docs/transactions.txt b/docs/transactions.txt
new file mode 100644
index 0000000000..6b2e6fda8f
--- /dev/null
+++ b/docs/transactions.txt
@@ -0,0 +1,146 @@
+==============================
+Managing database transactions
+==============================
+
+Django gives you a few ways to control how database transactions are managed.
+
+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.
diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt
index 67b4053ef5..02d2795261 100644
--- a/docs/tutorial01.txt
+++ b/docs/tutorial01.txt
@@ -2,35 +2,41 @@
Writing your first Django app, part 1
=====================================
-By Adrian Holovaty <holovaty@gmail.com>
-
Let's learn by example.
-Throughout this tutorial, we'll walk you through the creation of a simple Web
-poll application.
+Throughout this tutorial, we'll walk you through the creation of a basic
+blogging application.
It'll consist of two parts:
-* A public site that lets people vote in polls and view poll results.
-* An admin site that lets you add, change and delete polls behind the scenes.
+ * A public site that lets people read your blog entries and submit
+ comments.
+ * An admin site that lets you add, change and delete entries and comments.
-We'll assume you have `Django installed`_ already.
+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`: http://www.djangoproject.com/documentation/install/
-Initial setup
-=============
+Creating a project
+==================
If this is your first time using Django, you'll have to take care of some
-initial setup.
+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.
-Run the command ``django-admin.py startproject myproject``. That'll create a
-``myproject`` directory in your current directory.
+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.
(``django-admin.py`` 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
+its ``setup.py`` utility. If it's not on your path, you can find it in
``site-packages/django/bin``; consider symlinking to it from some place
-on your path, such as /usr/local/bin.)
+on your path, such as ``/usr/local/bin``.)
.. admonition:: Where should this code live?
@@ -44,11 +50,9 @@ on your path, such as /usr/local/bin.)
Put your code in some directory **outside** of the document root, such as
``/home/mycode``.
-A project is a collection of settings for an instance of Django -- including
-database configuration, Django-specific options and application-specific
-settings. Let's look at what ``startproject`` created::
+Let's look at what ``startproject`` created::
- myproject/
+ mysite/
__init__.py
manage.py
settings.py
@@ -56,50 +60,62 @@ settings. Let's look at what ``startproject`` created::
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 on packages: http://docs.python.org/tut/node8.html#packages
+
The development server
----------------------
-Change into the ``myproject`` directory, if you haven't already, and run the
-command ``python manage.py runserver``. You'll see the following output on the
-command line::
+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.
- Starting server on port 8000 with settings module 'myproject.settings'.
- Go to http://127.0.0.1:8000/ for Django.
+ Django version 0.92, 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).
-(If you get an error about ``DATABASE_ENGINE``, edit your ``settings.py`` file
-to change the ``DATABASE_ENGINE`` setting to point to the correct database, and
-make sure you have the right database libraries installed -- such as PostgreSQL's
-psycopg or MySQL's MySQLdb.)
-
You've started the Django development server, a lightweight, pure-Python Web
-server that builds on the BaseHTTPServer included in Python's standard library.
-We've included this with Django so you can develop things rapidly, without
-having to deal with configuring Apache until you're ready for production.
+server. 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.
-DON'T use this server in anything resembling a production environment. It's
-intended only for use while developing.
+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::
+ argument. For instance, this command starts the server on port 8080::
python manage.py runserver 8080
-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!
+ Full docs for the development server are at `django-admin documentation`_.
+
+.. _django-admin documentation: http://www.djangoproject.com/documentation/django_admin/
+
+Your first page
+---------------
+
+Let's create our first Django-powered Web page, a classic "hello world" example.
+
+
+
Database setup
--------------
@@ -124,30 +140,52 @@ database's connection parameters:
point. Do that with "``CREATE DATABASE database_name;``" within your
database's interactive prompt.
-Run the following command to initialize your database with Django's core
-database tables::
+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.
- python manage.py init
+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::
-If you don't see any errors, it worked.
+ 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:: About those database tables
+.. admonition:: For the minimalists
- The tables created by ``manage.py init`` are for sessions, authentication
- and other features Django provides. The next release of Django will have
- a "lite" version of the ``init`` command that won't install any database
- tables if you don't want them.
+ 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. (You won't have to take care of that boring administrative stuff
-again.)
+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
@@ -162,12 +200,12 @@ so you can focus on writing code rather than creating directories.
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 ``myproject`` directory,
+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 ``myproject.polls``.
+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 ``myproject`` directory and type
+To create your app, make sure you're in the ``mysite`` directory and type
this command::
python manage.py startapp polls
@@ -176,9 +214,7 @@ That'll create a directory ``polls``, which is laid out like this::
polls/
__init__.py
- models/
- __init__.py
- polls.py
+ models.py
views.py
This directory structure will house the poll application.
@@ -198,28 +234,28 @@ 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/polls.py`` file so it looks like this::
+``polls/models.py`` file so it looks like this::
- from django.core import meta
+ from django.db import models
- class Poll(meta.Model):
- question = meta.CharField(maxlength=200)
- pub_date = meta.DateTimeField('date published')
+ class Poll(models.Model):
+ question = models.CharField(maxlength=200)
+ pub_date = models.DateTimeField('date published')
- class Choice(meta.Model):
- poll = meta.ForeignKey(Poll)
- choice = meta.CharField(maxlength=200)
- votes = meta.IntegerField()
+ 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.core.meta.Model``. Each model has a number of class
+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 ``meta.*Field`` class -- e.g.,
-``meta.CharField`` for character fields and ``meta.DateTimeField`` for
+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 ``meta.*Field`` instance (e.g. ``question`` or ``pub_date`` )
+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.
@@ -230,11 +266,11 @@ 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 ``meta.*Field`` classes have required elements. ``meta.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.
+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 ``meta.ForeignKey``. That tells
+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.
@@ -259,28 +295,29 @@ But first we need to tell our project that the ``polls`` app is installed.
Django installation.
Edit the ``settings.py`` file again, and change the ``INSTALLED_APPS`` setting
-to include the string ``'myproject.polls'``. So it'll look like this::
+to include the string ``'mysite.polls'``. So it'll look like this::
INSTALLED_APPS = (
- 'myproject.polls',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'mysite.polls'
)
-(Don't forget the trailing comma, because of Python's rule about single-value
-tuples: Without a trailing comma, Python wouldn't know this was a tuple.)
-
-Now Django knows ``myproject`` includes the ``polls`` app. Let's run another command::
+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_polls" (
+ 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_choices" (
+ CREATE TABLE "polls_choice" (
"id" serial NOT NULL PRIMARY KEY,
"poll_id" integer NOT NULL REFERENCES "polls_polls" ("id"),
"choice" varchar(200) NOT NULL,
@@ -291,12 +328,12 @@ You should see the following (the CREATE TABLE SQL statements for the polls app)
Note the following:
* Table names are automatically generated by combining the name of the app
- (``polls``) with a plural version of the object name (polls and choices).
- (You can override this behavior.)
+ (``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.)
- * Django appends ``"_id"`` to the foreign key field name, by convention.
+ * 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.
@@ -306,12 +343,18 @@ Note the following:
``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 inPostgreSQL syntax.
+ 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 sqlinitialdata polls`` -- Outputs the initial-data
- inserts required for Django's admin framework.
+ * ``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
@@ -320,20 +363,21 @@ If you're interested, also run the following commands:
* ``python manage.py sqlindexes polls`` -- Outputs the ``CREATE INDEX``
statements for this app.
- * ``python manage.py sqlall polls`` -- A combination of 'sql' and
- 'sqlinitialdata'.
+ * ``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 this command to create the database tables for the polls app
-automatically::
+Now, run ``syncdb`` again to create those model tables in your database::
- python manage.py install polls
+ python manage.py syncdb
-Behind the scenes, all that command does is take the output of
-``python manage.py sqlall polls`` and execute it in the database pointed-to by
-your Django settings file.
+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.
@@ -352,10 +396,10 @@ 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 ``myproject`` on ``sys.path``. For flexibility, several pieces of
+ * Putting ``mysite`` on ``sys.path``. For flexibility, several pieces of
Django refer to projects in Python dotted-path notation (e.g.
- ``'myproject.polls.models'``). In order for this to work, the
- ``myproject`` package has to be on ``sys.path``.
+ ``'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.
@@ -366,25 +410,24 @@ things:
.. admonition:: Bypassing manage.py
If you'd rather not use ``manage.py``, no problem. Just make sure
- ``myproject`` is at the root level on the Python path (i.e.,
- ``import myproject`` works) and set the ``DJANGO_SETTINGS_MODULE``
- environment variable to ``myproject.settings``.
+ ``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::
- # Modules are dynamically created within django.models.
- # Their names are plural versions of the model class names.
- >>> from django.models.polls import polls, choices
+ # Import the model classes we just wrote.
+ >>> from mysite.polls.models import Poll, Choice
# No polls are in the system yet.
- >>> polls.get_list()
+ >>> Poll.objects.all()
[]
# Create a new Poll.
>>> from datetime import datetime
- >>> p = polls.Poll(question="What's up?", pub_date=datetime.now())
+ >>> p = Poll(question="What's up?", pub_date=datetime.now())
# Save the object into the database. You have to call save() explicitly.
>>> p.save()
@@ -406,107 +449,109 @@ Once you're in the shell, explore the database API::
>>> p.pub_date = datetime(2005, 4, 1, 0, 0)
>>> p.save()
- # get_list() displays all the polls in the database.
- >>> polls.get_list()
+ # objects.all() displays all the polls in the database.
+ >>> Poll.objects.all()
[<Poll object>]
+
Wait a minute. ``<Poll object>`` is, utterly, an unhelpful representation of
this object. Let's fix that by editing the polls model
-(in the ``polls/models/polls.py`` file) and adding a ``__repr__()`` method to
+(in the ``polls/models.py`` file) and adding a ``__str__()`` method to
both ``Poll`` and ``Choice``::
- class Poll(meta.Model):
+ class Poll(models.Model):
# ...
- def __repr__(self):
+ def __str__(self):
return self.question
- class Choice(meta.Model):
+ class Choice(models.Model):
# ...
- def __repr__(self):
+ def __str__(self):
return self.choice
-It's important to add ``__repr__()`` methods to your models, not only for your
+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::
- class Poll(meta.Model):
+ import datetime
+ # ...
+ class Poll(models.Model):
# ...
def was_published_today(self):
return self.pub_date.date() == datetime.date.today()
-Note ``import datetime`` wasn't necessary. Each model method has access to
-a handful of commonly-used variables for convenience, including the
-``datetime`` module from the Python standard library.
+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 django.models.polls import polls, choices
- # Make sure our __repr__() addition worked.
- >>> polls.get_list()
+ >>> from mysite.polls.models import Poll, Choice
+
+ # Make sure our __str__() addition worked.
+ >>> Poll.objects.all()
[What's up?]
# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
- >>> polls.get_object(id__exact=1)
- What's up?
- >>> polls.get_object(question__startswith='What')
- What's up?
+ >>> Poll.objects.filter(id=1)
+ [What's up?]
+ >>> Poll.objects.filter(question__startswith='What')
+ [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.
- >>> polls.get_object(pub_date__year=2005)
+ >>> Poll.objects.get(pub_date__year=2005)
What's up?
- >>> polls.get_object(id__exact=2)
+ >>> Poll.objects.get(id=2)
Traceback (most recent call last):
...
- PollDoesNotExist: Poll does not exist for {'id__exact': 2}
- >>> polls.get_list(question__startswith='What')
- [What's up?]
+ DoesNotExist: Poll does not exist for {'id': 2}
# 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 polls.get_object(id__exact=1).
- >>> polls.get_object(pk=1)
+ # The following is identical to Poll.objects.get(id=1).
+ >>> Poll.objects.get(pk=1)
What's up?
# Make sure our custom method worked.
- >>> p = polls.get_object(pk=1)
+ >>> p = Poll.objects.get(pk=1)
>>> p.was_published_today()
False
- # Give the Poll a couple of Choices. Each one of these method calls does an
- # INSERT statement behind the scenes and returns the new Choice object.
- >>> p = polls.get_object(pk=1)
- >>> p.add_choice(choice='Not much', votes=0)
+ # 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)
Not much
- >>> p.add_choice(choice='The sky', votes=0)
+ >>> p.choice_set.create(choice='The sky', votes=0)
The sky
- >>> c = p.add_choice(choice='Just hacking again', votes=0)
+ >>> c = p.choice_set.create(choice='Just hacking again', votes=0)
# Choice objects have API access to their related Poll objects.
- >>> c.get_poll()
+ >>> c.poll
What's up?
# And vice versa: Poll objects get access to Choice objects.
- >>> p.get_choice_list()
+ >>> p.choice_set.all()
[Not much, The sky, Just hacking again]
- >>> p.get_choice_count()
+ >>> 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.
- >>> choices.get_list(poll__pub_date__year=2005)
+ >>> Choice.objects.filter(poll__pub_date__year=2005)
[Not much, The sky, Just hacking again]
# Let's delete one of the choices. Use delete() for that.
- >>> c = p.get_choice(choice__startswith='Just hacking')
+ >>> c = p.choice_set.filter(choice__startswith='Just hacking')
>>> c.delete()
For full details on the database API, see our `Database API reference`_.
diff --git a/docs/tutorial02.txt b/docs/tutorial02.txt
index e96be12020..6cdda32fbb 100644
--- a/docs/tutorial02.txt
+++ b/docs/tutorial02.txt
@@ -2,8 +2,6 @@
Writing your first Django app, part 2
=====================================
-By Adrian Holovaty <holovaty@gmail.com>
-
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.
@@ -31,22 +29,13 @@ 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 the command ``python manage.py install admin``. This will create an
- extra database table that the admin needs.
- * Edit your ``myproject/urls.py`` file and uncomment the line below
+ * 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.
-Create a user account
-=====================
-
-Run the following command to create a superuser account for your admin site::
-
- python manage.py createsuperuser
-
-The script will prompt you for a username, e-mail address and password (twice).
-
Start the development server
============================
@@ -82,26 +71,27 @@ 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 ``myproject/polls/models/polls.py``
-file and make the following change to add an inner ``META`` class with an
-``admin`` attribute::
+objects have an admin interface. Edit the ``mysite/polls/models/polls.py``
+file and make the following change to add an inner ``Admin`` class::
- class Poll(meta.Model):
+ class Poll(models.Model):
# ...
- class META:
- admin = meta.Admin()
+ class Admin:
+ pass
-The ``class META`` contains all `non-field metadata`_ about this model.
+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 -- it auto-reloads code.
-
-.. _non-field metadata: http://www.djangoproject.com/documentation/model_api/#meta-options
+to restart the development server -- the server will auto-reloads your project,
+so any modifications code will be seen immediately in your browser.
Explore the free admin functionality
====================================
-Now that ``Poll`` has the ``admin`` attribute, Django knows that it should be
+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
@@ -125,7 +115,7 @@ Click the "What's up?" poll to edit it:
Things to note here:
* The form is automatically generated from the Poll model.
-* The different model field types (``meta.DateTimeField``, ``meta.CharField``)
+* 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"
@@ -157,13 +147,12 @@ 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 ``meta.Admin``::
+``fields`` parameter to ``Admin``::
- admin = meta.Admin(
+ class Admin:
fields = (
(None, {'fields': ('pub_date', 'question')}),
- ),
- )
+ )
That made the "Publication date" show up first instead of second:
@@ -176,12 +165,11 @@ 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::
- admin = meta.Admin(
+ 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:
@@ -195,12 +183,11 @@ You can assign arbitrary HTML classes to each fieldset. Django provides a
This is useful when you have a long form that contains a number of fields that
aren't commonly used::
- admin = meta.Admin(
+ 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
@@ -214,13 +201,13 @@ 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 ``admin`` attribute, just as we did with ``Poll``. Here's what
+model its own inner ``Admin`` class, just as we did with ``Poll``. Here's what
that would look like::
- class Choice(meta.Model):
+ class Choice(models.Model):
# ...
- class META:
- admin = meta.Admin()
+ class Admin:
+ pass
Now "Choices" is an available option in the Django admin. The "Add choice" form
looks like this:
@@ -242,18 +229,18 @@ 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)``
+Remove the ``Admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)``
field like so::
- poll = meta.ForeignKey(Poll, edit_inline=meta.STACKED, num_in_admin=3)
+ 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 = meta.CharField(maxlength=200, core=True)
- votes = meta.IntegerField(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
@@ -277,9 +264,9 @@ 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 = meta.ForeignKey(Poll, edit_inline=meta.TABULAR, num_in_admin=3)
+ poll = models.ForeignKey(Poll, edit_inline=models.TABULAR, num_in_admin=3)
-With that ``edit_inline=meta.TABULAR`` (instead of ``meta.STACKED``), the
+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
@@ -302,18 +289,16 @@ 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(meta.Model):
+ class Poll(models.Model):
# ...
- class META:
- admin = meta.Admin(
- # ...
- list_display = ('question', 'pub_date'),
- )
+ 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'),
+ list_display = ('question', 'pub_date', 'was_published_today')
Now the poll change list page looks like this:
@@ -336,7 +321,7 @@ method a ``short_description`` attribute::
Let's add another improvement to the Poll change list page: Filters. Add the
following line to ``Poll.admin``::
- list_filter = ['pub_date'],
+ list_filter = ['pub_date']
That adds a "Filter" sidebar that lets people filter the change list by the
``pub_date`` field:
@@ -352,7 +337,7 @@ filter options for DateTimeFields: "Any date," "Today," "Past 7 days,"
This is shaping up well. Let's add some search capability::
- search_fields = ['question'],
+ 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
@@ -362,7 +347,7 @@ 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',
+ 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
@@ -383,7 +368,7 @@ 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 (``myproject/settings.py``, remember) and look at the
+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.
diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt
index c5367270ab..6433831a73 100644
--- a/docs/tutorial03.txt
+++ b/docs/tutorial03.txt
@@ -2,8 +2,6 @@
Writing your first Django app, part 3
=====================================
-By Adrian Holovaty <holovaty@gmail.com>
-
This tutorial begins where `Tutorial 2`_ left off. We're continuing the Web-poll
application and will focus on creating the public interface -- "views."
@@ -62,44 +60,46 @@ 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 myproject`` at the beginning of
-Tutorial 1, it created a default URLconf in ``myproject/urls.py``. It also
+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 = 'myproject.urls'
+ ROOT_URLCONF = 'mysite.urls'
-Time for an example. Edit ``myproject/urls.py`` so it looks like this::
+Time for an example. Edit ``mysite/urls.py`` so it looks like this::
from django.conf.urls.defaults import *
urlpatterns = patterns('',
- (r'^polls/$', 'myproject.polls.views.index'),
- (r'^polls/(\d+)/$', 'myproject.polls.views.detail'),
- (r'^polls/(\d+)/results/$', 'myproject.polls.views.results'),
- (r'^polls/(\d+)/vote/$', 'myproject.polls.views.vote'),
+ (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/(\d+)/$'`` -- it loads the
-associated Python package/module: ``myproject.polls.views.detail``. That
-corresponds to the function ``detail()`` in ``myproject/polls/views.py``.
+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 ``(\d+)``. Using parenthesis around a
+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.
+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 experession 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$', 'myproject.polls.views.index'),
+ (r'^polls/latest\.php$', 'mysite.polls.views.index'),
But, don't do that. It's silly.
@@ -135,20 +135,20 @@ You should get a pleasantly-colored error page with the following message::
ViewDoesNotExist at /polls/
- Tried index in module myproject.polls.views. Error was: 'module'
+ 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 ``myproject/polls/views.py``.
+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 ``myproject/polls/views.py``
+Time to write the first view. Open the file ``mysite/polls/views.py``
and put the following Python code in it::
- from django.utils.httpwrappers import HttpResponse
+ from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the poll index.")
@@ -185,11 +185,11 @@ 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 django.models.polls import polls
- from django.utils.httpwrappers import HttpResponse
+ from mysite.polls.models import Poll
+ from django.http import HttpResponse
def index(request):
- latest_poll_list = polls.get_list(order_by=['-pub_date'], limit=5)
+ latest_poll_list = Poll.objects.all().order_by('-pub_date')
output = ', '.join([p.question for p in latest_poll_list])
return HttpResponse(output)
@@ -197,13 +197,13 @@ 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.core.template import Context, loader
- from django.models.polls import polls
- from django.utils.httpwrappers import HttpResponse
+ from django.template import Context, loader
+ from mysite.polls.models import Poll
+ from django.http import HttpResponse
def index(request):
- latest_poll_list = polls.get_list(order_by=['-pub_date'], limit=5)
- t = loader.get_template('polls/index')
+ latest_poll_list = Poll.objects.all().order_by('-pub_date')
+ t = loader.get_template('polls/index.html')
c = Context({
'latest_poll_list': latest_poll_list,
})
@@ -227,9 +227,8 @@ 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``. Django requires that
-templates have ".html" extension. Note that our
-``loader.get_template('polls/index')`` code from above maps to
+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::
@@ -254,12 +253,12 @@ 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.core.extensions import render_to_response
- from django.models.polls import polls
+ from django.shortcuts import render_to_response
+ from mysite.polls.models import Poll
def index(request):
- latest_poll_list = polls.get_list(order_by=['-pub_date'], limit=5)
- return render_to_response('polls/index', {'latest_poll_list': latest_poll_list})
+ latest_poll_list = Poll.objects.all().order_by('-pub_date')
+ return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
Note that we no longer need to import ``loader``, ``Context`` or
``HttpResponse``.
@@ -274,15 +273,16 @@ 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.core.exceptions import Http404
+ from django.http import Http404
+ # ...
def detail(request, poll_id):
try:
- p = polls.get_object(pk=poll_id)
- except polls.PollDoesNotExist:
+ p = Poll.objects.get(pk=poll_id)
+ except Poll.DoesNotExist:
raise Http404
- return render_to_response('polls/detail', {'poll': p})
+ return render_to_response('polls/detail.html', {'poll': p})
-The new concept here: The view raises the ``django.core.exceptions.Http404``
+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()
@@ -292,10 +292,11 @@ It's a very common idiom to use ``get_object()`` and raise ``Http404`` if the
object doesn't exist. Django provides a shortcut. Here's the ``detail()`` view,
rewritten::
- from django.core.extensions import get_object_or_404
+ from django.shortcuts import render_to_response, get_object_or_404
+ # ...
def detail(request, poll_id):
- p = get_object_or_404(polls, pk=poll_id)
- return render_to_response('polls/detail', {'poll': p})
+ 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
@@ -305,8 +306,8 @@ 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``?
+ 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.
@@ -359,7 +360,7 @@ what the template might look like::
<h1>{{ poll.question }}</h1>
<ul>
- {% for choice in poll.get_choice_list %}
+ {% for choice in poll.choice_set.all %}
<li>{{ choice.choice }}</li>
{% endfor %}
</ul>
@@ -370,9 +371,9 @@ 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.get_choice_list`` is
-interpreted as the Python code ``poll.get_choice_list()``, which returns a list
-of Choice objects and is suitable for iteration via the ``{% for %}`` tag.
+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.
@@ -385,19 +386,19 @@ 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/$', 'myproject.polls.views.index'),
- (r'^polls/(?P<poll_id>\d+)/$', 'myproject.polls.views.detail'),
- (r'^polls/(?P<poll_id>\d+)/results/$', 'myproject.polls.views.results'),
- (r'^polls/(?P<poll_id>\d+)/vote/$', 'myproject.polls.views.vote'),
+ (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, ``myproject.polls.views`` is in every callback.
+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('myproject.polls.views',
+ urlpatterns = patterns('mysite.polls.views',
(r'^polls/$', 'index'),
(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
@@ -419,15 +420,15 @@ 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 ``myproject/urls.py``, but the URL design of an
+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 ``myproject/urls.py`` to ``myproject/polls/urls.py``. Then,
-change ``myproject/urls.py`` to remove the poll-specific URLs and insert an
+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('myproject.polls.urls')),
+ (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
@@ -439,14 +440,14 @@ 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 'myproject.polls.urls' urlconf for
+ text -- ``"34/"`` -- to the 'mysite.polls.urls' urlconf for
further processing.
Now that we've decoupled that, we need to decouple the
-'myproject.polls.urls' urlconf by removing the leading "polls/" from each
+'mysite.polls.urls' urlconf by removing the leading "polls/" from each
line::
- urlpatterns = patterns('myproject.polls.views',
+ urlpatterns = patterns('mysite.polls.views',
(r'^$', 'index'),
(r'^(?P<poll_id>\d+)/$', 'detail'),
(r'^(?P<poll_id>\d+)/results/$', 'results'),
diff --git a/docs/tutorial04.txt b/docs/tutorial04.txt
index f6a0abb6d1..67974327a3 100644
--- a/docs/tutorial04.txt
+++ b/docs/tutorial04.txt
@@ -2,8 +2,6 @@
Writing your first Django app, part 4
=====================================
-By Adrian Holovaty <holovaty@gmail.com>
-
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.
@@ -18,7 +16,7 @@ template contains an HTML ``<form>`` element::
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="/polls/{{ poll.id }}/vote/" method="post">
- {% for choice in poll.get_choice_list %}
+ {% 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 %}
@@ -41,24 +39,24 @@ A quick rundown:
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 create a URLconf that
-included this line::
-
- (r'^polls/(?P<poll_id>\d+)/vote/$', 'myproject.polls.views.vote'),
+something with it. Remember, in `Tutorial 3`_, we created a URLconf for the
+polls application that includes this line::
-So let's create a ``vote()`` function in ``myproject/polls/views.py``::
+ (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
- from django.core.extensions import get_object_or_404, render_to_response
- from django.models.polls import choices, polls
- from django.utils.httpwrappers import HttpResponseRedirect
+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(polls, pk=poll_id)
+ p = get_object_or_404(Poll, pk=poll_id)
try:
- selected_choice = p.get_choice(pk=request.POST['choice'])
- except (KeyError, choices.ChoiceDoesNotExist):
+ 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', {
+ return render_to_response('polls/detail.html', {
'poll': p,
'error_message': "You didn't select a choice.",
})
@@ -102,8 +100,8 @@ 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(polls, pk=poll_id)
- return render_to_response('polls/results', {'poll': p})
+ 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.
@@ -113,7 +111,7 @@ Now, create a ``results.html`` template::
<h1>{{ poll.question }}</h1>
<ul>
- {% for choice in poll.get_choice_list %}
+ {% for choice in poll.choice_set.all %}
<li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
@@ -153,12 +151,12 @@ conversion.
You should know basic math before you start using a calculator.
-First, open the polls.py URLconf. It looks like this, according to the tutorial
-so far::
+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('myproject.polls.views',
+ urlpatterns = patterns('mysite.polls.views',
(r'^$', 'index'),
(r'^(?P<poll_id>\d+)/$', 'detail'),
(r'^(?P<poll_id>\d+)/results/$', 'results'),
@@ -168,56 +166,62 @@ so far::
Change it like so::
from django.conf.urls.defaults import *
+ from mysite.polls.models import Poll
info_dict = {
- 'app_label': 'polls',
- 'module_name': 'polls',
+ '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')),
- (r'^(?P<poll_id>\d+)/vote/$', 'myproject.polls.views.vote'),
+ (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 which ``app_label`` and ``module_name``
- it's acting on. Thus, we've defined ``info_dict``, a dictionary that's
- passed to each of the generic views via the third parameter to the URL
- tuples.
+ * 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 that the ID value captured
- from the URL is called ``"object_id"``, so we've changed ``poll_id`` to
+ * 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_label>/<module_name>_detail``. In our case, it'll use the template
-``"polls/polls_detail"``. Thus, rename your ``polls/detail.html`` template to
-``polls/polls_detail.html``, and change the ``render_to_response()`` line in
+``<app name>/<module 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_label>/<module_name>_list``. Thus, rename ``polls/index.html`` to
-``polls/polls_list.html``.
+``<app name>/<module name>_list.html``. Thus, rename ``poll/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'``. Otherwise, both views would use the same
+``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.
-The generic views pass ``object`` and ``object_list`` to their templates, so
-change your templates so that ``latest_poll_list`` becomes ``object_list`` and
-``poll`` becomes ``object``.
+In previous versions 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.
-In the ``vote()`` view, change the template call from ``polls/detail`` to
-``polls/polls_detail``, and pass ``object`` in the context instead of ``poll``.
+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``
+to ``polls/polls_detail``, and pass ``object`` in the context instead of ``poll``.
-Finally, you can delete the ``index()``, ``detail()`` and ``results()`` views
-from ``polls/views.py``. We don't need them anymore.
+Run the server, and use your new polling app based on generic views.
For full details on generic views, see the `generic views documentation`_.
diff --git a/docs/url_dispatch.txt b/docs/url_dispatch.txt
index 66bab747de..3b83a98804 100644
--- a/docs/url_dispatch.txt
+++ b/docs/url_dispatch.txt
@@ -32,18 +32,18 @@ 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. The system 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. The system 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. The system runs through each URL pattern, in order, and stops at the
- first one that matches the requested URL.
+ 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`_ and any values captured in the regex as function
- arguments.
+ `request object`_ as its first argument and any values captured in the
+ regex as remaining arguments.
.. _settings file: http://www.djangoproject.com/documentation/settings/
.. _request object: http://www.djangoproject.com/documentation/request_response/#httprequest-objects
@@ -64,7 +64,7 @@ Here's a sample URLconf::
Notes:
- * ``from django.conf.urls.defaults import *`` makes the ``patterns``
+ * ``from django.conf.urls.defaults import *`` makes the ``patterns()``
function available.
* To capture a value from the URL, just put parenthesis around it.
@@ -72,11 +72,11 @@ Notes:
* 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
+ * 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`_.
-Examples:
+Example requests:
* A request to ``/articles/2005/03/`` would match the third entry in the
list. Django would call the function
@@ -121,8 +121,8 @@ Here's the above example URLconf, rewritten to use named groups::
)
This accomplishes exactly the same thing as the previous example, with one
-subtle difference: The captured values are passed as keyword arguments rather
-than positional arguments. For example:
+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
@@ -134,7 +134,7 @@ than positional arguments. For example:
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 folks find the named-group syntax ugly and too verbose.
+some developers find the named-group syntax ugly and too verbose.
The matching/grouping algorithm
-------------------------------
@@ -160,6 +160,10 @@ 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
==================================
@@ -183,8 +187,8 @@ The remaining arguments should be tuples in this format::
(regular expression, Python callback function [, optional dictionary])
-...where ``dictionary_of_extra_arguments`` is optional. (See
-"Passing extra options to view functions" below.)
+...where ``optional dictionary`` is optional. (See
+_`Passing extra options to view functions` below.)
handler404
----------
@@ -209,7 +213,7 @@ 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.
+be "included" in this place. See _`Including other URLconfs` below.
Notes on capturing text in URLs
===============================
@@ -259,12 +263,12 @@ Here's the example URLconf from the `Django overview`_::
from django.conf.urls.defaults import *
urlpatterns = patterns('',
- (r'^articles/(?P<year>\d{4})/$', 'myproject.news.views.year_archive'),
- (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'myproject.news.views.month_archive'),
- (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'myproject.news.views.article_detail'),
+ (r'^articles/(\d{4})/$', 'myproject.news.views.year_archive'),
+ (r'^articles/(\d{4})/(\d{2})/$', 'myproject.news.views.month_archive'),
+ (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'myproject.news.views.article_detail'),
)
-In this example, each view has a common prefix -- ``"myproject.news.views"``.
+In this example, each view has a common prefix -- ``'myproject.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.
@@ -274,9 +278,9 @@ With this in mind, the above example can be written more concisely as::
from django.conf.urls.defaults import *
urlpatterns = patterns('myproject.news.views',
- (r'^articles/(?P<year>\d{4})/$', 'year_archive'),
- (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'month_archive'),
- (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'article_detail'),
+ (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
@@ -299,7 +303,6 @@ number of other URLconfs::
(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')),
- (r'^rss/', include('django.conf.urls.rss')),
)
Note that the regular expressions in this example don't have a ``$``
diff --git a/examples/__init__.py b/examples/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/examples/__init__.py
diff --git a/examples/hello/__init__.py b/examples/hello/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/examples/hello/__init__.py
diff --git a/examples/hello/urls.py b/examples/hello/urls.py
new file mode 100644
index 0000000000..e57c2b8e8c
--- /dev/null
+++ b/examples/hello/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('examples.hello.views',
+ (r'^html/$', 'hello_html'),
+ (r'^text/$', 'hello_text'),
+ (r'^write/$', 'hello_write'),
+ (r'^metadata/$', 'metadata'),
+ (r'^getdata/$', 'get_data'),
+ (r'^postdata/$', 'post_data'),
+)
diff --git a/examples/hello/views.py b/examples/hello/views.py
new file mode 100644
index 0000000000..07f955a38d
--- /dev/null
+++ b/examples/hello/views.py
@@ -0,0 +1,55 @@
+from django.http import HttpResponse
+from django.utils.html import escape
+
+def hello_html(request):
+ "This view is a basic 'hello world' example in HTML."
+ return HttpResponse('<h1>Hello, world.</h1>')
+
+def hello_text(request):
+ "This view is a basic 'hello world' example in plain text."
+ return HttpResponse('Hello, world.', mimetype='text/plain')
+
+def hello_write(request):
+ "This view demonstrates how an HttpResponse object has a write() method."
+ r = HttpResponse()
+ r.write("<p>Here's a paragraph.</p>")
+ r.write("<p>Here's another paragraph.</p>")
+ return r
+
+def metadata(request):
+ "This view demonstrates how to retrieve request metadata, such as HTTP headers."
+ r = HttpResponse('<h1>All about you</h1>')
+ r.write("<p>Here's all known metadata about your request, according to <code>request.META</code>:</p>")
+ r.write('<table>')
+ meta_items = request.META.items()
+ meta_items.sort()
+ for k, v in meta_items:
+ r.write('<tr><th>%s</th><td>%r</td></tr>' % (k, v))
+ r.write('</table>')
+ return r
+
+def get_data(request):
+ "This view demonstrates how to retrieve GET data."
+ r = HttpResponse()
+ if request.GET:
+ r.write('<p>GET data found! Here it is:</p>')
+ r.write('<ul>%s</ul>' % ''.join(['<li><strong>%s:</strong> %r</li>' % (escape(k), escape(v)) for k, v in request.GET.items()]))
+ r.write('<form action="" method="get">')
+ r.write('<p>First name: <input type="text" name="first_name"></p>')
+ r.write('<p>Last name: <input type="text" name="last_name"></p>')
+ r.write('<p><input type="submit" value="Submit"></p>')
+ r.write('</form>')
+ return r
+
+def post_data(request):
+ "This view demonstrates how to retrieve POST data."
+ r = HttpResponse()
+ if request.POST:
+ r.write('<p>POST data found! Here it is:</p>')
+ r.write('<ul>%s</ul>' % ''.join(['<li><strong>%s:</strong> %r</li>' % (escape(k), escape(v)) for k, v in request.POST.items()]))
+ r.write('<form action="" method="post">')
+ r.write('<p>First name: <input type="text" name="first_name"></p>')
+ r.write('<p>Last name: <input type="text" name="last_name"></p>')
+ r.write('<p><input type="submit" value="Submit"></p>')
+ r.write('</form>')
+ return r
diff --git a/examples/manage.py b/examples/manage.py
new file mode 100644
index 0000000000..008aeeb72b
--- /dev/null
+++ b/examples/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" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/examples/settings.py b/examples/settings.py
new file mode 100644
index 0000000000..aa54a34386
--- /dev/null
+++ b/examples/settings.py
@@ -0,0 +1,5 @@
+# Django settings for the example project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+ROOT_URLCONF = 'examples.urls'
diff --git a/examples/urls.py b/examples/urls.py
new file mode 100644
index 0000000000..69b465f65d
--- /dev/null
+++ b/examples/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ (r'^$', 'examples.views.index'),
+ (r'^hello/', include('examples.hello.urls')),
+)
diff --git a/examples/views.py b/examples/views.py
new file mode 100644
index 0000000000..902bfda0f8
--- /dev/null
+++ b/examples/views.py
@@ -0,0 +1,12 @@
+from django import http
+
+def index(request):
+ r = http.HttpResponse('<h1>Django examples</h1><ul>')
+ r.write('<li><a href="hello/html/">Hello world (HTML)</a></li>')
+ r.write('<li><a href="hello/text/">Hello world (text)</a></li>')
+ r.write('<li><a href="hello/write/">HttpResponse objects are file-like objects</a></li>')
+ r.write('<li><a href="hello/metadata/">Displaying request metadata</a></li>')
+ r.write('<li><a href="hello/getdata/">Displaying GET data</a></li>')
+ r.write('<li><a href="hello/postdata/">Displaying POST data</a></li>')
+ r.write('</ul>')
+ return r
diff --git a/extras/README.TXT b/extras/README.TXT
new file mode 100644
index 0000000000..3fb2a846a5
--- /dev/null
+++ b/extras/README.TXT
@@ -0,0 +1 @@
+This directory contains extra stuff that can improve your Django experience.
diff --git a/extras/django_bash_completion b/extras/django_bash_completion
new file mode 100644
index 0000000000..71ef495010
--- /dev/null
+++ b/extras/django_bash_completion
@@ -0,0 +1,105 @@
+# #########################################################################
+# This bash script adds tab-completion feature to django-admin.py and
+# manage.py.
+#
+# Testing it out without installing
+# =================================
+#
+# To test out the completion without "installing" this, just run this file
+# directly, like so:
+#
+# . ~/path/to/django_bash_completion
+#
+# Note: There's a dot ('.') at the beginning of that command.
+#
+# After you do that, tab completion will immediately be made available in your
+# current Bash shell. But it won't be available next time you log in.
+#
+# Installing
+# ==========
+#
+# To install this, point to this file from your .bash_profile, like so:
+#
+# . ~/path/to/django_bash_completion
+#
+# Do the same in your .bashrc if .bashrc doesn't invoke .bash_profile.
+#
+# Settings will take effect the next time you log in.
+#
+# Uninstalling
+# ============
+#
+# To uninstall, just remove the line from your .bash_profile and .bashrc.
+
+_django_completion()
+{
+ local cur prev opts actions action_shell_opts
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+ # Standalone options
+ opts="--help --settings --pythonpath --version"
+ # Actions
+ actions="adminindex createcachetable dbshell diffsettings \
+ inspectdb install reset runserver \
+ shell sql sqlall sqlclear sqlindexes sqlinitialdata \
+ sqlreset sqlsequencereset startapp startproject \
+ syncdb validate"
+ # Action's options
+ action_shell_opts="--plain"
+
+ if [[ # django-admin.py, ./manage, manage.py
+ ( ${COMP_CWORD} -eq 1 &&
+ ( ${COMP_WORDS[0]} == django-admin.py ||
+ ${COMP_WORDS[0]} == ./manage.py ||
+ ${COMP_WORDS[0]} == manage.py ) )
+ ||
+ # python manage.py, /some/path/python manage.py (if manage.py exists)
+ ( ${COMP_CWORD} -eq 2 &&
+ ( $( basename ${COMP_WORDS[0]} ) == python ) &&
+ ( $( basename ${COMP_WORDS[1]} ) == manage.py) &&
+ ( -r ${COMP_WORDS[1]} ) ) ]] ; then
+
+ case ${cur} in
+ -*)
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ *)
+ COMPREPLY=( $(compgen -W "${actions}" -- ${cur}) )
+ return 0
+ ;;
+ esac
+ else
+ case ${prev} in
+ adminindex|install|reset| \
+ sql|sqlall|sqlclear|sqlindexes| \
+ sqlinitialdata|sqlreset|sqlsequencereset)
+ # App completion isn't yet implemented, but here's where that
+ # would go.
+ # COMPREPLY=( $(compgen -W "auth core" -- ${cur}) )
+ COMPREPLY=()
+ return 0
+ ;;
+
+ createcachetable|dbshell|diffsettings| \
+ inspectdb|runserver|startapp|startproject|syncdb| \
+ validate)
+ COMPREPLY=()
+ return 0
+ ;;
+ shell)
+ COMPREPLY=( $(compgen -W "$action_shell_opts" -- ${cur}) )
+ return 0
+ ;;
+ *)
+ #COMPREPLY=( $(compgen -W "auth core" -- ${cur}) )
+ COMPREPLY=()
+ return 0
+ ;;
+ esac
+ fi
+}
+
+complete -F _django_completion django-admin.py manage.py
diff --git a/setup.py b/setup.py
index 2ff9b700fd..e26fcb1ba8 100644
--- a/setup.py
+++ b/setup.py
@@ -19,21 +19,26 @@ setup(
'locale/cy/LC_MESSAGES/*',
'locale/da/LC_MESSAGES/*',
'locale/de/LC_MESSAGES/*',
+ 'locale/el/LC_MESSAGES/*',
'locale/en/LC_MESSAGES/*',
'locale/es/LC_MESSAGES/*',
'locale/fr/LC_MESSAGES/*',
'locale/gl/LC_MESSAGES/*',
+ 'locale/he/LC_MESSAGES/*',
'locale/is/LC_MESSAGES/*',
'locale/it/LC_MESSAGES/*',
'locale/ja/LC_MESSAGES/*',
'locale/nl/LC_MESSAGES/*',
'locale/no/LC_MESSAGES/*',
+ 'locale/pl/LC_MESSAGES/*',
'locale/pt_BR/LC_MESSAGES/*',
'locale/ro/LC_MESSAGES/*',
'locale/ru/LC_MESSAGES/*',
'locale/sk/LC_MESSAGES/*',
+ 'locale/sl/LC_MESSAGES/*',
'locale/sr/LC_MESSAGES/*',
'locale/sv/LC_MESSAGES/*',
+ 'locale/uk/LC_MESSAGES/*',
'locale/zh_CN/LC_MESSAGES/*',
'locale/zh_TW/LC_MESSAGES/*'],
'django.contrib.admin': ['templates/admin/*.html',
diff --git a/tests/modeltests/__init__.py b/tests/modeltests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/__init__.py
diff --git a/tests/modeltests/basic/__init__.py b/tests/modeltests/basic/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/basic/__init__.py
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
new file mode 100644
index 0000000000..c96cd993ad
--- /dev/null
+++ b/tests/modeltests/basic/models.py
@@ -0,0 +1,339 @@
+"""
+1. Bare-bones model
+
+This is a basic model with only two non-primary-key fields.
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100, default='Default headline')
+ pub_date = models.DateTimeField()
+
+ def __repr__(self):
+ return self.headline
+API_TESTS = """
+
+# No articles are in the system yet.
+>>> Article.objects.all()
+[]
+
+# Create an Article.
+>>> from datetime import datetime
+>>> a = Article(id=None, headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
+
+# Save it into the database. You have to call save() explicitly.
+>>> a.save()
+
+# Now it has an ID. Note it's a long integer, as designated by the trailing "L".
+>>> a.id
+1L
+
+# Access database columns via Python attributes.
+>>> a.headline
+'Area man programs in Python'
+>>> a.pub_date
+datetime.datetime(2005, 7, 28, 0, 0)
+
+# Change values by changing the attributes, then calling save().
+>>> a.headline = 'Area woman programs in Python'
+>>> a.save()
+
+# Article.objects.all() returns all the articles in the database.
+>>> Article.objects.all()
+[Area woman programs in Python]
+
+# Django provides a rich database lookup API.
+>>> Article.objects.get(id__exact=1)
+Area woman programs in Python
+>>> Article.objects.get(headline__startswith='Area woman')
+Area woman programs in Python
+>>> Article.objects.get(pub_date__year=2005)
+Area woman programs in Python
+>>> Article.objects.get(pub_date__year=2005, pub_date__month=7)
+Area woman programs in Python
+>>> Article.objects.get(pub_date__year=2005, pub_date__month=7, pub_date__day=28)
+Area woman programs in Python
+
+# The "__exact" lookup type can be omitted, as a shortcut.
+>>> Article.objects.get(id=1)
+Area woman programs in Python
+>>> Article.objects.get(headline='Area woman programs in Python')
+Area woman programs in Python
+
+>>> Article.objects.filter(pub_date__year=2005)
+[Area woman programs in Python]
+>>> Article.objects.filter(pub_date__year=2004)
+[]
+>>> Article.objects.filter(pub_date__year=2005, pub_date__month=7)
+[Area woman programs in Python]
+
+# Django raises an Article.DoesNotExist exception for get() if the parameters
+# don't match any object.
+>>> Article.objects.get(id__exact=2)
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for {'id__exact': 2}
+
+>>> Article.objects.get(pub_date__year=2005, pub_date__month=8)
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for ...
+
+# 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 articles.get(id=1).
+>>> Article.objects.get(pk=1)
+Area woman programs in Python
+
+# Model instances of the same type and same ID are considered equal.
+>>> a = Article.objects.get(pk=1)
+>>> b = Article.objects.get(pk=1)
+>>> a == b
+True
+
+# You can initialize a model instance using positional arguments, which should
+# match the field order as defined in the model.
+>>> a2 = Article(None, 'Second article', datetime(2005, 7, 29))
+>>> a2.save()
+>>> a2.id
+2L
+>>> a2.headline
+'Second article'
+>>> a2.pub_date
+datetime.datetime(2005, 7, 29, 0, 0)
+
+# ...or, you can use keyword arguments.
+>>> a3 = Article(id=None, headline='Third article', pub_date=datetime(2005, 7, 30))
+>>> a3.save()
+>>> a3.id
+3L
+>>> a3.headline
+'Third article'
+>>> a3.pub_date
+datetime.datetime(2005, 7, 30, 0, 0)
+
+# You can also mix and match position and keyword arguments, but be sure not to
+# duplicate field information.
+>>> a4 = Article(None, 'Fourth article', pub_date=datetime(2005, 7, 31))
+>>> a4.save()
+>>> a4.headline
+'Fourth article'
+
+# Don't use invalid keyword arguments.
+>>> a5 = Article(id=None, headline='Invalid', pub_date=datetime(2005, 7, 31), foo='bar')
+Traceback (most recent call last):
+ ...
+TypeError: 'foo' is an invalid keyword argument for this function
+
+# You can leave off the value for an AutoField when creating an object, because
+# it'll get filled in automatically when you save().
+>>> a5 = Article(headline='Article 6', pub_date=datetime(2005, 7, 31))
+>>> a5.save()
+>>> a5.id
+5L
+>>> a5.headline
+'Article 6'
+
+# If you leave off a field with "default" set, Django will use the default.
+>>> a6 = Article(pub_date=datetime(2005, 7, 31))
+>>> a6.save()
+>>> a6.headline
+'Default headline'
+
+# For DateTimeFields, Django saves as much precision (in seconds) as you
+# give it.
+>>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 31, 12, 30))
+>>> a7.save()
+>>> Article.objects.get(id__exact=7).pub_date
+datetime.datetime(2005, 7, 31, 12, 30)
+
+>>> a8 = Article(headline='Article 8', pub_date=datetime(2005, 7, 31, 12, 30, 45))
+>>> a8.save()
+>>> Article.objects.get(id__exact=8).pub_date
+datetime.datetime(2005, 7, 31, 12, 30, 45)
+>>> a8.id
+8L
+
+# Saving an object again doesn't create a new object -- it just saves the old one.
+>>> a8.save()
+>>> a8.id
+8L
+>>> a8.headline = 'Updated article 8'
+>>> a8.save()
+>>> a8.id
+8L
+
+>>> a7 == a8
+False
+>>> a8 == Article.objects.get(id__exact=8)
+True
+>>> a7 != a8
+True
+>>> Article.objects.get(id__exact=8) != Article.objects.get(id__exact=7)
+True
+>>> Article.objects.get(id__exact=8) == Article.objects.get(id__exact=7)
+False
+
+# dates() returns a list of available dates of the given scope for the given field.
+>>> Article.objects.dates('pub_date', 'year')
+[datetime.datetime(2005, 1, 1, 0, 0)]
+>>> Article.objects.dates('pub_date', 'month')
+[datetime.datetime(2005, 7, 1, 0, 0)]
+>>> Article.objects.dates('pub_date', 'day')
+[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)]
+>>> Article.objects.dates('pub_date', 'day', order='ASC')
+[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)]
+>>> Article.objects.dates('pub_date', 'day', order='DESC')
+[datetime.datetime(2005, 7, 31, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 28, 0, 0)]
+
+# dates() requires valid arguments.
+
+>>> Article.objects.dates()
+Traceback (most recent call last):
+ ...
+TypeError: dates() takes at least 3 arguments (1 given)
+
+>>> Article.objects.dates('invalid_field', 'year')
+Traceback (most recent call last):
+ ...
+FieldDoesNotExist: name=invalid_field
+
+>>> Article.objects.dates('pub_date', 'bad_kind')
+Traceback (most recent call last):
+ ...
+AssertionError: 'kind' must be one of 'year', 'month' or 'day'.
+
+>>> Article.objects.dates('pub_date', 'year', order='bad order')
+Traceback (most recent call last):
+ ...
+AssertionError: 'order' must be either 'ASC' or 'DESC'.
+
+# Use iterator() with dates() to return a generator that lazily requests each
+# result one at a time, to save memory.
+>>> for a in Article.objects.dates('pub_date', 'day', order='DESC').iterator():
+... print repr(a)
+datetime.datetime(2005, 7, 31, 0, 0)
+datetime.datetime(2005, 7, 30, 0, 0)
+datetime.datetime(2005, 7, 29, 0, 0)
+datetime.datetime(2005, 7, 28, 0, 0)
+
+# You can combine queries with & and |.
+>>> s1 = Article.objects.filter(id__exact=1)
+>>> s2 = Article.objects.filter(id__exact=2)
+>>> s1 | s2
+[Area woman programs in Python, Second article]
+>>> s1 & s2
+[]
+
+# You can get the number of objects like this:
+>>> len(Article.objects.filter(id__exact=1))
+1
+
+# You can get items using index and slice notation.
+>>> Article.objects.all()[0]
+Area woman programs in Python
+>>> Article.objects.all()[1:3]
+[Second article, Third article]
+>>> s3 = Article.objects.filter(id__exact=3)
+>>> (s1 | s2 | s3)[::2]
+[Area woman programs in Python, Third article]
+
+# Slices (without step) are lazy:
+>>> Article.objects.all()[0:5].filter()
+[Area woman programs in Python, Second article, Third article, Fourth article, Article 6]
+
+# Slicing again works:
+>>> Article.objects.all()[0:5][0:2]
+[Area woman programs in Python, Second article]
+>>> Article.objects.all()[0:5][:2]
+[Area woman programs in Python, Second article]
+>>> Article.objects.all()[0:5][4:]
+[Article 6]
+>>> Article.objects.all()[0:5][5:]
+[]
+
+# Some more tests!
+>>> Article.objects.all()[2:][0:2]
+[Third article, Fourth article]
+>>> Article.objects.all()[2:][:2]
+[Third article, Fourth article]
+>>> Article.objects.all()[2:][2:3]
+[Article 6]
+
+# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work:
+>>> Article.objects.all()[2:]
+Traceback (most recent call last):
+ ...
+AssertionError: 'offset' is not allowed without 'limit'
+
+# Also, once you have sliced you can't filter, re-order or combine
+>>> Article.objects.all()[0:5].filter(id=1)
+Traceback (most recent call last):
+ ...
+AssertionError: Cannot filter a query once a slice has been taken.
+
+>>> Article.objects.all()[0:5].order_by('id')
+Traceback (most recent call last):
+ ...
+AssertionError: Cannot reorder a query once a slice has been taken.
+
+>>> Article.objects.all()[0:1] & Article.objects.all()[4:5]
+Traceback (most recent call last):
+ ...
+AssertionError: Cannot combine queries once a slice has been taken.
+
+
+# An Article instance doesn't have access to the "objects" attribute.
+# That's only available on the class.
+>>> a7.objects.all()
+Traceback (most recent call last):
+ ...
+AttributeError: Manager isn't accessible via Article instances
+
+>>> a7.objects
+Traceback (most recent call last):
+ ...
+AttributeError: Manager isn't accessible via Article instances
+
+# Bulk delete test: How many objects before and after the delete?
+>>> Article.objects.all()
+[Area woman programs in Python, Second article, Third article, Fourth article, Article 6, Default headline, Article 7, Updated article 8]
+>>> Article.objects.filter(id__lte=4).delete()
+>>> Article.objects.all()
+[Article 6, Default headline, Article 7, Updated article 8]
+
+"""
+
+from django.conf import settings
+
+building_docs = getattr(settings, 'BUILDING_DOCS', False)
+
+if building_docs or settings.DATABASE_ENGINE == 'postgresql':
+ API_TESTS += """
+# In PostgreSQL, microsecond-level precision is available.
+>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
+>>> a9.save()
+>>> Article.objects.get(id__exact=9).pub_date
+datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
+"""
+
+if building_docs or settings.DATABASE_ENGINE == 'mysql':
+ API_TESTS += """
+# In MySQL, microsecond-level precision isn't available. You'll lose
+# microsecond-level precision once the data is saved.
+>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
+>>> a9.save()
+>>> Article.objects.get(id__exact=9).pub_date
+datetime.datetime(2005, 7, 31, 12, 30, 45)
+"""
+
+API_TESTS += """
+
+# You can manually specify the primary key when creating a new object.
+>>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
+>>> a101.save()
+>>> a101 = Article.objects.get(pk=101)
+>>> a101.headline
+'Article 101'
+"""
diff --git a/tests/modeltests/choices/__init__.py b/tests/modeltests/choices/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/choices/__init__.py
diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py
new file mode 100644
index 0000000000..38dcb934c5
--- /dev/null
+++ b/tests/modeltests/choices/models.py
@@ -0,0 +1,39 @@
+"""
+21. Specifying 'choices' for a field
+
+Most fields take a ``choices`` parameter, which should be a tuple of tuples
+specifying which are the valid values for that field.
+
+For each field that has ``choices``, a model instance gets a
+``get_fieldname_display()`` method, where ``fieldname`` is the name of the
+field. This method returns the "human-readable" value of the field.
+"""
+
+from django.db import models
+
+GENDER_CHOICES = (
+ ('M', 'Male'),
+ ('F', 'Female'),
+)
+
+class Person(models.Model):
+ name = models.CharField(maxlength=20)
+ gender = models.CharField(maxlength=1, choices=GENDER_CHOICES)
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> a = Person(name='Adrian', gender='M')
+>>> a.save()
+>>> s = Person(name='Sara', gender='F')
+>>> s.save()
+>>> a.gender
+'M'
+>>> s.gender
+'F'
+>>> a.get_gender_display()
+'Male'
+>>> s.get_gender_display()
+'Female'
+"""
diff --git a/tests/modeltests/custom_columns/__init__.py b/tests/modeltests/custom_columns/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_columns/__init__.py
diff --git a/tests/testapp/models/custom_columns.py b/tests/modeltests/custom_columns/models.py
index c900091b64..4958517e69 100644
--- a/tests/testapp/models/custom_columns.py
+++ b/tests/modeltests/custom_columns/models.py
@@ -6,38 +6,38 @@ If your database column name is different than your model attribute, use the
name, in API usage.
"""
-from django.core import meta
+from django.db import models
-class Person(meta.Model):
- first_name = meta.CharField(maxlength=30, db_column='firstname')
- last_name = meta.CharField(maxlength=30, db_column='last')
+class Person(models.Model):
+ first_name = models.CharField(maxlength=30, db_column='firstname')
+ last_name = models.CharField(maxlength=30, db_column='last')
def __repr__(self):
return '%s %s' % (self.first_name, self.last_name)
API_TESTS = """
# Create a Person.
->>> p = persons.Person(first_name='John', last_name='Smith')
+>>> p = Person(first_name='John', last_name='Smith')
>>> p.save()
>>> p.id
1
->>> persons.get_list()
+>>> Person.objects.all()
[John Smith]
->>> persons.get_list(first_name__exact='John')
+>>> Person.objects.filter(first_name__exact='John')
[John Smith]
->>> persons.get_object(first_name__exact='John')
+>>> Person.objects.get(first_name__exact='John')
John Smith
->>> persons.get_list(firstname__exact='John')
+>>> Person.objects.filter(firstname__exact='John')
Traceback (most recent call last):
...
-TypeError: got unexpected keyword argument 'firstname__exact'
+TypeError: Cannot resolve keyword 'firstname' into field
->>> p = persons.get_object(last_name__exact='Smith')
+>>> p = Person.objects.get(last_name__exact='Smith')
>>> p.first_name
'John'
>>> p.last_name
diff --git a/tests/modeltests/custom_managers/__init__.py b/tests/modeltests/custom_managers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_managers/__init__.py
diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py
new file mode 100644
index 0000000000..a6ae80029a
--- /dev/null
+++ b/tests/modeltests/custom_managers/models.py
@@ -0,0 +1,100 @@
+"""
+23. Giving models a custom manager
+"""
+
+from django.db import models
+
+# An example of a custom manager called "objects".
+
+class PersonManager(models.Manager):
+ def get_fun_people(self):
+ return self.filter(fun=True)
+
+class Person(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+ fun = models.BooleanField()
+ objects = PersonManager()
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+# An example of a custom manager that sets a core_filter on its lookups.
+
+class PublishedBookManager(models.Manager):
+ def get_query_set(self):
+ return super(PublishedBookManager, self).get_query_set().filter(is_published=True)
+
+class Book(models.Model):
+ title = models.CharField(maxlength=50)
+ author = models.CharField(maxlength=30)
+ is_published = models.BooleanField()
+ published_objects = PublishedBookManager()
+ authors = models.ManyToManyField(Person, related_name='books')
+
+ def __repr__(self):
+ return self.title
+
+# An example of providing multiple custom managers.
+
+class FastCarManager(models.Manager):
+ def get_query_set(self):
+ return super(FastCarManager, self).get_query_set().filter(top_speed__gt=150)
+
+class Car(models.Model):
+ name = models.CharField(maxlength=10)
+ mileage = models.IntegerField()
+ top_speed = models.IntegerField(help_text="In miles per hour.")
+ cars = models.Manager()
+ fast_cars = FastCarManager()
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> p1 = Person(first_name='Bugs', last_name='Bunny', fun=True)
+>>> p1.save()
+>>> p2 = Person(first_name='Droopy', last_name='Dog', fun=False)
+>>> p2.save()
+>>> Person.objects.get_fun_people()
+[Bugs Bunny]
+
+# The RelatedManager used on the 'books' descriptor extends the default manager
+>>> from modeltests.custom_managers.models import PublishedBookManager
+>>> isinstance(p2.books, PublishedBookManager)
+True
+
+>>> b1 = Book(title='How to program', author='Rodney Dangerfield', is_published=True)
+>>> b1.save()
+>>> b2 = Book(title='How to be smart', author='Albert Einstein', is_published=False)
+>>> b2.save()
+
+# The default manager, "objects", doesn't exist,
+# because a custom one was provided.
+>>> Book.objects
+Traceback (most recent call last):
+ ...
+AttributeError: type object 'Book' has no attribute 'objects'
+
+# The RelatedManager used on the 'authors' descriptor extends the default manager
+>>> from modeltests.custom_managers.models import PersonManager
+>>> isinstance(b2.authors, PersonManager)
+True
+
+>>> Book.published_objects.all()
+[How to program]
+
+>>> c1 = Car(name='Corvette', mileage=21, top_speed=180)
+>>> c1.save()
+>>> c2 = Car(name='Neon', mileage=31, top_speed=100)
+>>> c2.save()
+>>> Car.cars.order_by('name')
+[Corvette, Neon]
+>>> Car.fast_cars.all()
+[Corvette]
+
+# Each model class gets a "_default_manager" attribute, which is a reference
+# to the first manager defined in the class. In this case, it's "cars".
+>>> Car._default_manager.order_by('name')
+[Corvette, Neon]
+"""
diff --git a/tests/modeltests/custom_methods/__init__.py b/tests/modeltests/custom_methods/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_methods/__init__.py
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
new file mode 100644
index 0000000000..3fdefca6bf
--- /dev/null
+++ b/tests/modeltests/custom_methods/models.py
@@ -0,0 +1,58 @@
+"""
+3. Giving models custom methods and custom managers
+
+Any method you add to a model will be available to instances.
+"""
+
+from django.db import models
+import datetime
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+
+ def __repr__(self):
+ return self.headline
+
+ def was_published_today(self):
+ return self.pub_date == datetime.date.today()
+
+ def get_articles_from_same_day_1(self):
+ return Article.objects.filter(pub_date=self.pub_date).exclude(id=self.id)
+
+ def get_articles_from_same_day_2(self):
+ """
+ Verbose version of get_articles_from_same_day_1, which does a custom
+ database query for the sake of demonstration.
+ """
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT id, headline, pub_date
+ FROM custom_methods_article
+ WHERE pub_date = %s
+ AND id != %s""", [str(self.pub_date), self.id])
+ # The asterisk in "(*row)" tells Python to expand the list into
+ # positional arguments to Article().
+ return [self.__class__(*row) for row in cursor.fetchall()]
+
+API_TESTS = """
+# Create a couple of Articles.
+>>> from datetime import date
+>>> a = Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27))
+>>> a.save()
+>>> b = Article(id=None, headline='Beatles reunite', pub_date=date(2005, 7, 27))
+>>> b.save()
+
+# Test the custom methods.
+>>> a.was_published_today()
+False
+>>> a.get_articles_from_same_day_1()
+[Beatles reunite]
+>>> a.get_articles_from_same_day_2()
+[Beatles reunite]
+>>> b.get_articles_from_same_day_1()
+[Area man programs in Python]
+>>> b.get_articles_from_same_day_2()
+[Area man programs in Python]
+"""
diff --git a/tests/modeltests/custom_pk/__init__.py b/tests/modeltests/custom_pk/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_pk/__init__.py
diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
new file mode 100644
index 0000000000..df127ba3a8
--- /dev/null
+++ b/tests/modeltests/custom_pk/models.py
@@ -0,0 +1,90 @@
+"""
+14. Using a custom primary key
+
+By default, Django adds an ``"id"`` field to each model. But you can override
+this behavior by explicitly adding ``primary_key=True`` to a field.
+"""
+
+from django.db import models
+
+class Employee(models.Model):
+ employee_code = models.CharField(maxlength=10, primary_key=True)
+ first_name = models.CharField(maxlength=20)
+ last_name = models.CharField(maxlength=20)
+ class Meta:
+ ordering = ('last_name', 'first_name')
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Business(models.Model):
+ name = models.CharField(maxlength=20, primary_key=True)
+ employees = models.ManyToManyField(Employee)
+ class Meta:
+ verbose_name_plural = 'businesses'
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> dan = Employee(employee_code='ABC123', first_name='Dan', last_name='Jones')
+>>> dan.save()
+>>> Employee.objects.all()
+[Dan Jones]
+
+>>> fran = Employee(employee_code='XYZ456', first_name='Fran', last_name='Bones')
+>>> fran.save()
+>>> Employee.objects.all()
+[Fran Bones, Dan Jones]
+
+>>> Employee.objects.get(pk='ABC123')
+Dan Jones
+>>> Employee.objects.get(pk='XYZ456')
+Fran Bones
+>>> Employee.objects.get(pk='foo')
+Traceback (most recent call last):
+ ...
+DoesNotExist: Employee does not exist for {'pk': 'foo'}
+
+# Use the name of the primary key, rather than pk.
+>>> Employee.objects.get(employee_code__exact='ABC123')
+Dan Jones
+
+# Fran got married and changed her last name.
+>>> fran = Employee.objects.get(pk='XYZ456')
+>>> fran.last_name = 'Jones'
+>>> fran.save()
+>>> Employee.objects.filter(last_name__exact='Jones')
+[Dan Jones, Fran Jones]
+>>> Employee.objects.in_bulk(['ABC123', 'XYZ456'])
+{'XYZ456': Fran Jones, 'ABC123': Dan Jones}
+
+>>> b = Business(name='Sears')
+>>> b.save()
+>>> b.employees.add(dan, fran)
+>>> b.employees.all()
+[Dan Jones, Fran Jones]
+>>> fran.business_set.all()
+[Sears]
+>>> Business.objects.in_bulk(['Sears'])
+{'Sears': Sears}
+
+>>> Business.objects.filter(name__exact='Sears')
+[Sears]
+>>> Business.objects.filter(pk='Sears')
+[Sears]
+
+# Queries across tables, involving primary key
+>>> Employee.objects.filter(business__name__exact='Sears')
+[Dan Jones, Fran Jones]
+>>> Employee.objects.filter(business__pk='Sears')
+[Dan Jones, Fran Jones]
+
+>>> Business.objects.filter(employees__employee_code__exact='ABC123')
+[Sears]
+>>> Business.objects.filter(employees__pk='ABC123')
+[Sears]
+>>> Business.objects.filter(employees__first_name__startswith='Fran')
+[Sears]
+
+"""
diff --git a/tests/modeltests/field_defaults/__init__.py b/tests/modeltests/field_defaults/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/field_defaults/__init__.py
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
new file mode 100644
index 0000000000..e5b7fd8e6d
--- /dev/null
+++ b/tests/modeltests/field_defaults/models.py
@@ -0,0 +1,48 @@
+"""
+XXX. Callable defaults
+
+???
+"""
+
+from django.db import models
+from datetime import datetime
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100, default='Default headline')
+ pub_date = models.DateTimeField(default = datetime.now)
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+>>> from datetime import datetime
+
+# No articles are in the system yet.
+>>> Article.objects.all()
+[]
+
+# Create an Article.
+>>> a = Article(id=None)
+
+# Grab the current datetime it should be very close to the default that just
+# got saved as a.pub_date
+>>> now = datetime.now()
+
+# Save it into the database. You have to call save() explicitly.
+>>> a.save()
+
+# Now it has an ID. Note it's a long integer, as designated by the trailing "L".
+>>> a.id
+1L
+
+# Access database columns via Python attributes.
+>>> a.headline
+'Default headline'
+
+# make sure the two dates are sufficiently close
+>>> d = now - a.pub_date
+>>> d.seconds < 5
+True
+
+
+"""
diff --git a/tests/modeltests/get_latest/__init__.py b/tests/modeltests/get_latest/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/get_latest/__init__.py
diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
new file mode 100644
index 0000000000..4b81dc837c
--- /dev/null
+++ b/tests/modeltests/get_latest/models.py
@@ -0,0 +1,79 @@
+"""
+8. get_latest_by
+
+Models can have a ``get_latest_by`` attribute, which should be set to the name
+of a DateField or DateTimeField. If ``get_latest_by`` exists, the model's
+module will get a ``get_latest()`` function, which will return the latest
+object in the database according to that field. "Latest" means "having the
+date farthest into the future."
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+ expire_date = models.DateField()
+ class Meta:
+ get_latest_by = 'pub_date'
+
+ def __repr__(self):
+ return self.headline
+
+class Person(models.Model):
+ name = models.CharField(maxlength=30)
+ birthday = models.DateField()
+
+ # Note that this model doesn't have "get_latest_by" set.
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+# Because no Articles exist yet, get_latest() raises ArticleDoesNotExist.
+>>> Article.objects.latest()
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for ...
+
+# Create a couple of Articles.
+>>> from datetime import datetime
+>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26), expire_date=datetime(2005, 9, 1))
+>>> a1.save()
+>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 7, 28))
+>>> a2.save()
+>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 8, 27))
+>>> a3.save()
+>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28), expire_date=datetime(2005, 7, 30))
+>>> a4.save()
+
+# Get the latest Article.
+>>> Article.objects.latest()
+Article 4
+
+# Get the latest Article that matches certain filters.
+>>> Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest()
+Article 1
+
+# Pass a custom field name to latest() to change the field that's used to
+# determine the latest object.
+>>> Article.objects.latest('expire_date')
+Article 1
+
+>>> Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date')
+Article 3
+
+# You can still use latest() with a model that doesn't have "get_latest_by"
+# set -- just pass in the field name manually.
+>>> p1 = Person(name='Ralph', birthday=datetime(1950, 1, 1))
+>>> p1.save()
+>>> p2 = Person(name='Stephanie', birthday=datetime(1960, 2, 3))
+>>> p2.save()
+>>> Person.objects.latest()
+Traceback (most recent call last):
+ ...
+AssertionError: latest() requires either a field_name parameter or 'get_latest_by' in the model
+
+>>> Person.objects.latest('birthday')
+Stephanie
+"""
diff --git a/tests/modeltests/invalid_models/__init__.py b/tests/modeltests/invalid_models/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/tests/modeltests/invalid_models/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
new file mode 100644
index 0000000000..0b3ffcf073
--- /dev/null
+++ b/tests/modeltests/invalid_models/models.py
@@ -0,0 +1,117 @@
+"""
+26. A test to check that the model validator works can correctly identify errors in a model.
+"""
+
+from django.db import models
+
+class FieldErrors(models.Model):
+ charfield = models.CharField()
+ floatfield = models.FloatField()
+ filefield = models.FileField()
+ prepopulate = models.CharField(maxlength=10, prepopulate_from='bad')
+ choices = models.CharField(maxlength=10, choices='bad')
+ choices2 = models.CharField(maxlength=10, choices=[(1,2,3),(1,2,3)])
+ index = models.CharField(maxlength=10, db_index='bad')
+
+class Target(models.Model):
+ tgt_safe = models.CharField(maxlength=10)
+
+ clash1_set = models.CharField(maxlength=10)
+
+class Clash1(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ foreign = models.ForeignKey(Target)
+ m2m = models.ManyToManyField(Target)
+
+class Clash2(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ foreign_1 = models.ForeignKey(Target, related_name='id')
+ foreign_2 = models.ForeignKey(Target, related_name='src_safe')
+
+ m2m_1 = models.ManyToManyField(Target, related_name='id')
+ m2m_2 = models.ManyToManyField(Target, related_name='src_safe')
+
+class Target2(models.Model):
+ foreign_tgt = models.ForeignKey(Target)
+ clashforeign_set = models.ForeignKey(Target)
+
+ m2m_tgt = models.ManyToManyField(Target)
+ clashm2m_set = models.ManyToManyField(Target)
+
+class Clash3(models.Model):
+ foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt')
+ foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt')
+
+ m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt')
+ m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt')
+
+class ClashForeign(models.Model):
+ foreign = models.ForeignKey(Target2)
+
+class ClashM2M(models.Model):
+ m2m = models.ManyToManyField(Target2)
+
+class SelfClashForeign(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ selfclashforeign_set = models.ForeignKey("SelfClashForeign")
+ foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
+ foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
+
+class SelfClashM2M(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ selfclashm2m_set = models.ManyToManyField("SelfClashM2M")
+ m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id')
+ m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe')
+
+error_log = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
+invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute.
+invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute.
+invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
+invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple.
+invalid_models.fielderrors: "choices": "choices" should be either a tuple or list.
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
+invalid_models.fielderrors: "index": "db_index" should be either None, True or False.
+invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'foreign'.
+invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign'.
+invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'm2m'.
+invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another related field. Add a related_name argument to the definition for 'm2m'.
+invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash2: 'foreign_2' accessor name 'Target.src_safe' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash2: 'm2m_2' m2m accessor name 'Target.src_safe' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'.
+invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'm2m_2'.
+invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'.
+invalid_models.clashforeign: 'foreign' accessor name 'Target2.clashforeign_set' clashes with another field. Add a related_name argument to the definition for 'foreign'.
+invalid_models.clashm2m: 'm2m' m2m accessor name 'Target2.clashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'm2m'.
+invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'.
+invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'.
+invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'foreign_tgt'.
+invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'.
+invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'.
+invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashforeign_set'.
+invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'm2m_tgt'.
+invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'.
+invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'.
+invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashm2m_set'.
+invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'.
+invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'.
+invalid_models.selfclashforeign: 'selfclashforeign_set' accessor name 'SelfClashForeign.selfclashforeign_set' clashes with another field. Add a related_name argument to the definition for 'selfclashforeign_set'.
+invalid_models.selfclashforeign: 'foreign_1' accessor name 'SelfClashForeign.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.selfclashforeign: 'foreign_2' accessor name 'SelfClashForeign.src_safe' clashes with another field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.selfclashm2m: 'selfclashm2m_set' m2m accessor name 'SelfClashM2M.selfclashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'selfclashm2m_set'.
+invalid_models.selfclashm2m: 'm2m_1' m2m accessor name 'SelfClashM2M.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.selfclashm2m: 'm2m_2' m2m accessor name 'SelfClashM2M.src_safe' clashes with another field. Add a related_name argument to the definition for 'm2m_2'.
+"""
diff --git a/tests/modeltests/lookup/__init__.py b/tests/modeltests/lookup/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/lookup/__init__.py
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
new file mode 100644
index 0000000000..605c05adbe
--- /dev/null
+++ b/tests/modeltests/lookup/models.py
@@ -0,0 +1,179 @@
+"""
+7. The lookup API
+
+This demonstrates features of the database API.
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
+ class Meta:
+ ordering = ('-pub_date', 'headline')
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+# Create a couple of Articles.
+>>> from datetime import datetime
+>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
+>>> a1.save()
+>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
+>>> a2.save()
+>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
+>>> a3.save()
+>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
+>>> a4.save()
+>>> a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0))
+>>> a5.save()
+>>> a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0))
+>>> a6.save()
+>>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
+>>> a7.save()
+
+# Each QuerySet gets iterator(), which is a generator that "lazily" returns
+# results using database-level iteration.
+>>> for a in Article.objects.iterator():
+... print a.headline
+Article 5
+Article 6
+Article 4
+Article 2
+Article 3
+Article 7
+Article 1
+
+# iterator() can be used on any QuerySet.
+>>> for a in Article.objects.filter(headline__endswith='4').iterator():
+... print a.headline
+Article 4
+
+# count() returns the number of objects matching search criteria.
+>>> Article.objects.count()
+7L
+>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).count()
+3L
+>>> Article.objects.filter(headline__startswith='Blah blah').count()
+0L
+
+# in_bulk() takes a list of IDs and returns a dictionary mapping IDs
+# to objects.
+>>> Article.objects.in_bulk([1, 2])
+{1: Article 1, 2: Article 2}
+>>> Article.objects.in_bulk([3])
+{3: Article 3}
+>>> Article.objects.in_bulk([1000])
+{}
+>>> Article.objects.in_bulk([])
+{}
+>>> Article.objects.in_bulk('foo')
+Traceback (most recent call last):
+ ...
+AssertionError: in_bulk() must be provided with a list of IDs.
+>>> Article.objects.in_bulk()
+Traceback (most recent call last):
+ ...
+TypeError: in_bulk() takes exactly 2 arguments (1 given)
+>>> Article.objects.in_bulk(headline__startswith='Blah')
+Traceback (most recent call last):
+ ...
+TypeError: in_bulk() got an unexpected keyword argument 'headline__startswith'
+
+# values() returns a list of dictionaries instead of object instances -- and
+# you can specify which fields you want to retrieve.
+>>> Article.objects.values('headline')
+[{'headline': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 7'}, {'headline': 'Article 1'}]
+>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id')
+[{'id': 2}, {'id': 3}, {'id': 7}]
+>>> list(Article.objects.values('id', 'headline')) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}]
+True
+
+>>> for d in Article.objects.values('id', 'headline'):
+... i = d.items()
+... i.sort()
+... i
+[('headline', 'Article 5'), ('id', 5)]
+[('headline', 'Article 6'), ('id', 6)]
+[('headline', 'Article 4'), ('id', 4)]
+[('headline', 'Article 2'), ('id', 2)]
+[('headline', 'Article 3'), ('id', 3)]
+[('headline', 'Article 7'), ('id', 7)]
+[('headline', 'Article 1'), ('id', 1)]
+
+# You can use values() with iterator() for memory savings, because iterator()
+# uses database-level iteration.
+>>> for d in Article.objects.values('id', 'headline').iterator():
+... i = d.items()
+... i.sort()
+... i
+[('headline', 'Article 5'), ('id', 5)]
+[('headline', 'Article 6'), ('id', 6)]
+[('headline', 'Article 4'), ('id', 4)]
+[('headline', 'Article 2'), ('id', 2)]
+[('headline', 'Article 3'), ('id', 3)]
+[('headline', 'Article 7'), ('id', 7)]
+[('headline', 'Article 1'), ('id', 1)]
+
+# if you don't specify which fields, all are returned
+>>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}]
+True
+
+# Every DateField and DateTimeField creates get_next_by_FOO() and
+# get_previous_by_FOO() methods.
+# 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.
+>>> a1.get_next_by_pub_date()
+Article 2
+>>> a2.get_next_by_pub_date()
+Article 3
+>>> a3.get_next_by_pub_date()
+Article 7
+>>> a4.get_next_by_pub_date()
+Article 6
+>>> a5.get_next_by_pub_date()
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for ...
+>>> a6.get_next_by_pub_date()
+Article 5
+>>> a7.get_next_by_pub_date()
+Article 4
+
+>>> a7.get_previous_by_pub_date()
+Article 3
+>>> a6.get_previous_by_pub_date()
+Article 4
+>>> a5.get_previous_by_pub_date()
+Article 6
+>>> a4.get_previous_by_pub_date()
+Article 7
+>>> a3.get_previous_by_pub_date()
+Article 2
+>>> a2.get_previous_by_pub_date()
+Article 1
+
+# Underscores and percent signs have special meaning in the underlying
+# database library, but Django handles the quoting of them automatically.
+>>> a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20))
+>>> a8.save()
+>>> Article.objects.filter(headline__startswith='Article')
+[Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.filter(headline__startswith='Article_')
+[Article_ with underscore]
+>>> a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21))
+>>> a9.save()
+>>> Article.objects.filter(headline__startswith='Article')
+[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.filter(headline__startswith='Article%')
+[Article% with percent sign]
+
+# exclude() is the opposite of filter() when doing lookups:
+>>> Article.objects.filter(headline__contains='Article').exclude(headline__contains='with')
+[Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.exclude(headline__startswith="Article_")
+[Article% with percent sign, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.exclude(headline="Article 7")
+[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 1]
+"""
diff --git a/tests/modeltests/m2m_and_m2o/__init__.py b/tests/modeltests/m2m_and_m2o/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_and_m2o/__init__.py
diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py
new file mode 100644
index 0000000000..29631e5779
--- /dev/null
+++ b/tests/modeltests/m2m_and_m2o/models.py
@@ -0,0 +1,66 @@
+"""
+27. Many-to-many and many-to-one relationships to the same table.
+
+This is a response to bug #1535
+
+"""
+
+from django.db import models
+
+class User(models.Model):
+ username = models.CharField(maxlength=20)
+
+class Issue(models.Model):
+ num = models.IntegerField()
+ cc = models.ManyToManyField(User, blank=True, related_name='test_issue_cc')
+ client = models.ForeignKey(User, related_name='test_issue_client')
+ def __repr__(self):
+ return "<Issue %d>" % (self.num,)
+
+ class Meta:
+ ordering = ('num',)
+
+
+API_TESTS = """
+>>> Issue.objects.all()
+[]
+>>> r = User(username='russell')
+>>> r.save()
+>>> g = User(username='gustav')
+>>> g.save()
+>>> i = Issue(num=1)
+>>> i.client = r
+>>> i.validate()
+{}
+>>> i.save()
+>>> i2 = Issue(num=2)
+>>> i2.client = r
+>>> i2.validate()
+{}
+>>> i2.save()
+>>> i2.cc.add(r)
+>>> i3 = Issue(num=3)
+>>> i3.client = g
+>>> i3.validate()
+{}
+>>> i3.save()
+>>> i3.cc.add(r)
+>>> from django.db.models.query import Q
+>>> Issue.objects.filter(client=r.id)
+[<Issue 1>, <Issue 2>]
+>>> Issue.objects.filter(client=g.id)
+[<Issue 3>]
+>>> Issue.objects.filter(cc__id__exact=g.id)
+[]
+>>> Issue.objects.filter(cc__id__exact=r.id)
+[<Issue 2>, <Issue 3>]
+
+# Queries that combine results from the m2m and the m2o relationship.
+# 3 ways of saying the same thing:
+>>> Issue.objects.filter(Q(cc__id__exact=r.id) | Q(client=r.id))
+[<Issue 1>, <Issue 2>, <Issue 3>]
+>>> Issue.objects.filter(cc__id__exact=r.id) | Issue.objects.filter(client=r.id)
+[<Issue 1>, <Issue 2>, <Issue 3>]
+>>> Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id))
+[<Issue 1>, <Issue 2>, <Issue 3>]
+"""
diff --git a/tests/modeltests/m2m_intermediary/__init__.py b/tests/modeltests/m2m_intermediary/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_intermediary/__init__.py
diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py
new file mode 100644
index 0000000000..869b188521
--- /dev/null
+++ b/tests/modeltests/m2m_intermediary/models.py
@@ -0,0 +1,68 @@
+"""
+9. Many-to-many relationships via an intermediary table
+
+For many-to-many relationships that need extra fields on the intermediary
+table, use an intermediary model.
+
+In this example, an ``Article`` can have multiple ``Reporter``s, and each
+``Article``-``Reporter`` combination (a ``Writer``) has a ``position`` field,
+which specifies the ``Reporter``'s position for the given article (e.g. "Staff
+writer").
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+
+ def __repr__(self):
+ return self.headline
+
+class Writer(models.Model):
+ reporter = models.ForeignKey(Reporter)
+ article = models.ForeignKey(Article)
+ position = models.CharField(maxlength=100)
+
+ def __repr__(self):
+ return '%r (%s)' % (self.reporter, self.position)
+
+API_TESTS = """
+# Create a few Reporters.
+>>> r1 = Reporter(first_name='John', last_name='Smith')
+>>> r1.save()
+>>> r2 = Reporter(first_name='Jane', last_name='Doe')
+>>> r2.save()
+
+# Create an Article.
+>>> from datetime import datetime
+>>> a = Article(headline='This is a test', pub_date=datetime(2005, 7, 27))
+>>> a.save()
+
+# Create a few Writers.
+>>> w1 = Writer(reporter=r1, article=a, position='Main writer')
+>>> w1.save()
+>>> w2 = Writer(reporter=r2, article=a, position='Contributor')
+>>> w2.save()
+
+# Play around with the API.
+>>> a.writer_set.select_related().order_by('-position')
+[John Smith (Main writer), Jane Doe (Contributor)]
+>>> w1.reporter
+John Smith
+>>> w2.reporter
+Jane Doe
+>>> w1.article
+This is a test
+>>> w2.article
+This is a test
+>>> r1.writer_set.all()
+[John Smith (Main writer)]
+"""
diff --git a/tests/modeltests/m2m_multiple/__init__.py b/tests/modeltests/m2m_multiple/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_multiple/__init__.py
diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py
new file mode 100644
index 0000000000..3fec427c1d
--- /dev/null
+++ b/tests/modeltests/m2m_multiple/models.py
@@ -0,0 +1,79 @@
+"""
+20. Multiple many-to-many relationships between the same two tables
+
+In this example, an Article can have many Categories (as "primary") and many
+Categories (as "secondary").
+
+Set ``related_name`` to designate what the reverse relationship is called.
+"""
+
+from django.db import models
+
+class Category(models.Model):
+ name = models.CharField(maxlength=20)
+ class Meta:
+ ordering = ('name',)
+
+ def __repr__(self):
+ return self.name
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=50)
+ pub_date = models.DateTimeField()
+ primary_categories = models.ManyToManyField(Category, related_name='primary_article_set')
+ secondary_categories = models.ManyToManyField(Category, related_name='secondary_article_set')
+ class Meta:
+ ordering = ('pub_date',)
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+>>> from datetime import datetime
+
+>>> c1 = Category(name='Sports')
+>>> c1.save()
+>>> c2 = Category(name='News')
+>>> c2.save()
+>>> c3 = Category(name='Crime')
+>>> c3.save()
+>>> c4 = Category(name='Life')
+>>> c4.save()
+
+>>> a1 = Article(headline='Area man steals', pub_date=datetime(2005, 11, 27))
+>>> a1.save()
+>>> a1.primary_categories.add(c2, c3)
+>>> a1.secondary_categories.add(c4)
+
+>>> a2 = Article(headline='Area man runs', pub_date=datetime(2005, 11, 28))
+>>> a2.save()
+>>> a2.primary_categories.add(c1, c2)
+>>> a2.secondary_categories.add(c4)
+
+>>> a1.primary_categories.all()
+[Crime, News]
+
+>>> a2.primary_categories.all()
+[News, Sports]
+
+>>> a1.secondary_categories.all()
+[Life]
+
+
+>>> c1.primary_article_set.all()
+[Area man runs]
+>>> c1.secondary_article_set.all()
+[]
+>>> c2.primary_article_set.all()
+[Area man steals, Area man runs]
+>>> c2.secondary_article_set.all()
+[]
+>>> c3.primary_article_set.all()
+[Area man steals]
+>>> c3.secondary_article_set.all()
+[]
+>>> c4.primary_article_set.all()
+[]
+>>> c4.secondary_article_set.all()
+[Area man steals, Area man runs]
+"""
diff --git a/tests/modeltests/m2m_recursive/__init__.py b/tests/modeltests/m2m_recursive/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_recursive/__init__.py
diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py
new file mode 100644
index 0000000000..ff8a5a8f47
--- /dev/null
+++ b/tests/modeltests/m2m_recursive/models.py
@@ -0,0 +1,192 @@
+"""
+26. Many-to-many relationships between the same two tables
+
+In this example, A Person can have many friends, who are also people. Friendship is a
+symmetrical relationshiup - if I am your friend, you are my friend.
+
+A person can also have many idols - but while I may idolize you, you may not think
+the same of me. 'Idols' is an example of a non-symmetrical m2m field. Only recursive
+m2m fields may be non-symmetrical, and they are symmetrical by default.
+
+This test validates that the m2m table will create a mangled name for the m2m table if
+there will be a clash, and tests that symmetry is preserved where appropriate.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ name = models.CharField(maxlength=20)
+ friends = models.ManyToManyField('self')
+ idols = models.ManyToManyField('self', symmetrical=False, related_name='stalkers')
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> a = Person(name='Anne')
+>>> a.save()
+>>> b = Person(name='Bill')
+>>> b.save()
+>>> c = Person(name='Chuck')
+>>> c.save()
+>>> d = Person(name='David')
+>>> d.save()
+
+# Add some friends in the direction of field definition
+# Anne is friends with Bill and Chuck
+>>> a.friends.add(b,c)
+
+# David is friends with Anne and Chuck - add in reverse direction
+>>> d.friends.add(a,c)
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[Bill, Chuck, David]
+
+# Who is friends with Bill?
+>>> b.friends.all()
+[Anne]
+
+# Who is friends with Chuck?
+>>> c.friends.all()
+[Anne, David]
+
+# Who is friends with David?
+>>> d.friends.all()
+[Anne, Chuck]
+
+# Bill is already friends with Anne - add Anne again, but in the reverse direction
+>>> b.friends.add(a)
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[Bill, Chuck, David]
+
+# Who is friends with Bill?
+>>> b.friends.all()
+[Anne]
+
+# Remove Anne from Bill's friends
+>>> b.friends.remove(a)
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[Chuck, David]
+
+# Who is friends with Bill?
+>>> b.friends.all()
+[]
+
+# Clear Anne's group of friends
+>>> a.friends.clear()
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[]
+
+# Reverse relationships should also be gone
+# Who is friends with Chuck?
+>>> c.friends.all()
+[David]
+
+# Who is friends with David?
+>>> d.friends.all()
+[Chuck]
+
+
+# Add some idols in the direction of field definition
+# Anne idolizes Bill and Chuck
+>>> a.idols.add(b,c)
+
+# Bill idolizes Anne right back
+>>> b.idols.add(a)
+
+# David is idolized by Anne and Chuck - add in reverse direction
+>>> d.stalkers.add(a,c)
+
+# Who are Anne's idols?
+>>> a.idols.all()
+[Bill, Chuck, David]
+
+# Who is stalking Anne?
+>>> a.stalkers.all()
+[Bill]
+
+# Who are Bill's idols?
+>>> b.idols.all()
+[Anne]
+
+# Who is stalking Bill?
+>>> b.stalkers.all()
+[Anne]
+
+# Who are Chuck's idols?
+>>> c.idols.all()
+[David]
+
+# Who is stalking Chuck?
+>>> c.stalkers.all()
+[Anne]
+
+# Who are David's idols?
+>>> d.idols.all()
+[]
+
+# Who is stalking David
+>>> d.stalkers.all()
+[Anne, Chuck]
+
+# Bill is already being stalked by Anne - add Anne again, but in the reverse direction
+>>> b.stalkers.add(a)
+
+# Who are Anne's idols?
+>>> a.idols.all()
+[Bill, Chuck, David]
+
+# Who is stalking Anne?
+[Bill]
+
+# Who are Bill's idols
+>>> b.idols.all()
+[Anne]
+
+# Who is stalking Bill?
+>>> b.stalkers.all()
+[Anne]
+
+# Remove Anne from Bill's list of stalkers
+>>> b.stalkers.remove(a)
+
+# Who are Anne's idols?
+>>> a.idols.all()
+[Chuck, David]
+
+# Who is stalking Anne?
+>>> a.stalkers.all()
+[Bill]
+
+# Who are Bill's idols?
+>>> b.idols.all()
+[Anne]
+
+# Who is stalking Bill?
+>>> b.stalkers.all()
+[]
+
+# Clear Anne's group of idols
+>>> a.idols.clear()
+
+# Who are Anne's idols
+>>> a.idols.all()
+[]
+
+# Reverse relationships should also be gone
+# Who is stalking Chuck?
+>>> c.stalkers.all()
+[]
+
+# Who is friends with David?
+>>> d.stalkers.all()
+[Chuck]
+
+"""
diff --git a/tests/modeltests/m2o_recursive/__init__.py b/tests/modeltests/m2o_recursive/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2o_recursive/__init__.py
diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py
new file mode 100644
index 0000000000..e7996bc15f
--- /dev/null
+++ b/tests/modeltests/m2o_recursive/models.py
@@ -0,0 +1,40 @@
+"""
+11. Relating an object to itself, many-to-one
+
+To define a many-to-one relationship between a model and itself, use
+``ForeignKey('self')``.
+
+In this example, a ``Category`` is related to itself. That is, each
+``Category`` has a parent ``Category``.
+
+Set ``related_name`` to designate what the reverse relationship is called.
+"""
+
+from django.db import models
+
+class Category(models.Model):
+ name = models.CharField(maxlength=20)
+ parent = models.ForeignKey('self', null=True, related_name='child_set')
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+# Create a few Category objects.
+>>> r = Category(id=None, name='Root category', parent=None)
+>>> r.save()
+>>> c = Category(id=None, name='Child category', parent=r)
+>>> c.save()
+
+>>> r.child_set.all()
+[Child category]
+>>> r.child_set.get(name__startswith='Child')
+Child category
+>>> print r.parent
+None
+
+>>> c.child_set.all()
+[]
+>>> c.parent
+Root category
+"""
diff --git a/tests/modeltests/m2o_recursive2/__init__.py b/tests/modeltests/m2o_recursive2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2o_recursive2/__init__.py
diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py
new file mode 100644
index 0000000000..40b842dc92
--- /dev/null
+++ b/tests/modeltests/m2o_recursive2/models.py
@@ -0,0 +1,43 @@
+"""
+12. Relating a model to another model more than once
+
+In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of
+which are other ``Person`` objects.
+
+Set ``related_name`` to designate what the reverse relationship is called.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ full_name = models.CharField(maxlength=20)
+ mother = models.ForeignKey('self', null=True, related_name='mothers_child_set')
+ father = models.ForeignKey('self', null=True, related_name='fathers_child_set')
+
+ def __repr__(self):
+ return self.full_name
+
+API_TESTS = """
+# Create two Person objects -- the mom and dad in our family.
+>>> dad = Person(full_name='John Smith Senior', mother=None, father=None)
+>>> dad.save()
+>>> mom = Person(full_name='Jane Smith', mother=None, father=None)
+>>> mom.save()
+
+# Give mom and dad a kid.
+>>> kid = Person(full_name='John Smith Junior', mother=mom, father=dad)
+>>> kid.save()
+
+>>> kid.mother
+Jane Smith
+>>> kid.father
+John Smith Senior
+>>> dad.fathers_child_set.all()
+[John Smith Junior]
+>>> mom.mothers_child_set.all()
+[John Smith Junior]
+>>> kid.mothers_child_set.all()
+[]
+>>> kid.fathers_child_set.all()
+[]
+"""
diff --git a/tests/modeltests/manipulators/__init__.py b/tests/modeltests/manipulators/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/manipulators/__init__.py
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
new file mode 100644
index 0000000000..da47c0afd5
--- /dev/null
+++ b/tests/modeltests/manipulators/models.py
@@ -0,0 +1,89 @@
+"""
+25. Default manipulators
+"""
+
+from django.db import models
+
+class Musician(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Album(models.Model):
+ name = models.CharField(maxlength=100)
+ musician = models.ForeignKey(Musician)
+ release_date = models.DateField(blank=True, null=True)
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> from django.utils.datastructures import MultiValueDict
+
+# Create a Musician object via the default AddManipulator.
+>>> man = Musician.AddManipulator()
+>>> data = MultiValueDict({'first_name': ['Ella'], 'last_name': ['Fitzgerald']})
+
+>>> man.get_validation_errors(data)
+{}
+>>> man.do_html2python(data)
+>>> m1 = man.save(data)
+
+# Verify it worked.
+>>> Musician.objects.all()
+[Ella Fitzgerald]
+>>> [m1] == list(Musician.objects.all())
+True
+
+# Attempt to add a Musician without a first_name.
+>>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']}))
+{'first_name': ['This field is required.']}
+
+# Attempt to add a Musician without a first_name and last_name.
+>>> man.get_validation_errors(MultiValueDict({}))
+{'first_name': ['This field is required.'], 'last_name': ['This field is required.']}
+
+# Attempt to create an Album without a name or musician.
+>>> man = Album.AddManipulator()
+>>> man.get_validation_errors(MultiValueDict({}))
+{'musician': ['This field is required.'], 'name': ['This field is required.']}
+
+# Attempt to create an Album with an invalid musician.
+>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']}))
+{'musician': ["Select a valid choice; 'foo' is not in ['', '1']."]}
+
+# Attempt to create an Album with an invalid release_date.
+>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'}))
+{'release_date': ['Enter a valid date in YYYY-MM-DD format.']}
+
+# Create an Album without a release_date (because it's optional).
+>>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']})
+>>> man.get_validation_errors(data)
+{}
+>>> man.do_html2python(data)
+>>> a1 = man.save(data)
+
+# Verify it worked.
+>>> Album.objects.all()
+[Ella and Basie]
+>>> Album.objects.get().musician
+Ella Fitzgerald
+
+# Create an Album with a release_date.
+>>> data = MultiValueDict({'name': ['Ultimate Ella'], 'musician': ['1'], 'release_date': ['2005-02-13']})
+>>> man.get_validation_errors(data)
+{}
+>>> man.do_html2python(data)
+>>> a2 = man.save(data)
+
+# Verify it worked.
+>>> Album.objects.order_by('name')
+[Ella and Basie, Ultimate Ella]
+>>> a2 = Album.objects.get(pk=2)
+>>> a2
+Ultimate Ella
+>>> a2.release_date
+datetime.date(2005, 2, 13)
+"""
diff --git a/tests/modeltests/many_to_many/__init__.py b/tests/modeltests/many_to_many/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/many_to_many/__init__.py
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
new file mode 100644
index 0000000000..dc69f9a49f
--- /dev/null
+++ b/tests/modeltests/many_to_many/models.py
@@ -0,0 +1,206 @@
+"""
+5. Many-to-many relationships
+
+To define a many-to-many relationship, use ManyToManyField().
+
+In this example, an article can be published in multiple publications,
+and a publication has multiple articles.
+"""
+
+from django.db import models
+
+class Publication(models.Model):
+ title = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return self.title
+
+ class Meta:
+ ordering = ('title',)
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ publications = models.ManyToManyField(Publication)
+
+ def __repr__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
+API_TESTS = """
+# Create a couple of Publications.
+>>> p1 = Publication(id=None, title='The Python Journal')
+>>> p1.save()
+>>> p2 = Publication(id=None, title='Science News')
+>>> p2.save()
+>>> p3 = Publication(id=None, title='Science Weekly')
+>>> p3.save()
+
+# Create an Article.
+>>> a1 = Article(id=None, headline='Django lets you build Web apps easily')
+>>> a1.save()
+
+# Associate the Article with a Publication.
+>>> a1.publications.add(p1)
+
+# Create another Article, and set it to appear in both Publications.
+>>> a2 = Article(id=None, headline='NASA uses Python')
+>>> a2.save()
+>>> a2.publications.add(p1, p2)
+>>> a2.publications.add(p3)
+
+# Adding a second time is OK
+>>> a2.publications.add(p3)
+
+# Add a Publication directly via publications.add by using keyword arguments.
+>>> new_publication = a2.publications.create(title='Highlights for Children')
+
+# Article objects have access to their related Publication objects.
+>>> a1.publications.all()
+[The Python Journal]
+>>> a2.publications.all()
+[Highlights for Children, Science News, Science Weekly, The Python Journal]
+
+# Publication objects have access to their related Article objects.
+>>> p2.article_set.all()
+[NASA uses Python]
+>>> p1.article_set.all()
+[Django lets you build Web apps easily, NASA uses Python]
+>>> Publication.objects.get(id=4).article_set.all()
+[NASA uses Python]
+
+# We can perform kwarg queries across m2m relationships
+>>> Article.objects.filter(publications__id__exact=1)
+[Django lets you build Web apps easily, NASA uses Python]
+>>> Article.objects.filter(publications__pk=1)
+[Django lets you build Web apps easily, NASA uses Python]
+
+>>> Article.objects.filter(publications__title__startswith="Science")
+[NASA uses Python, NASA uses Python]
+
+>>> Article.objects.filter(publications__title__startswith="Science").distinct()
+[NASA uses Python]
+
+# Reverse m2m queries are supported (i.e., starting at the table that doesn't
+# have a ManyToManyField).
+>>> Publication.objects.filter(id__exact=1)
+[The Python Journal]
+>>> Publication.objects.filter(pk=1)
+[The Python Journal]
+
+>>> Publication.objects.filter(article__headline__startswith="NASA")
+[Highlights for Children, Science News, Science Weekly, The Python Journal]
+
+>>> Publication.objects.filter(article__id__exact=1)
+[The Python Journal]
+
+>>> Publication.objects.filter(article__pk=1)
+[The Python Journal]
+
+# If we delete a Publication, its Articles won't be able to access it.
+>>> p1.delete()
+>>> Publication.objects.all()
+[Highlights for Children, Science News, Science Weekly]
+>>> a1 = Article.objects.get(pk=1)
+>>> a1.publications.all()
+[]
+
+# If we delete an Article, its Publications won't be able to access it.
+>>> a2.delete()
+>>> Article.objects.all()
+[Django lets you build Web apps easily]
+>>> p1.article_set.all()
+[Django lets you build Web apps easily]
+
+# Adding via the 'other' end of an m2m
+>>> a4 = Article(headline='NASA finds intelligent life on Earth')
+>>> a4.save()
+>>> p2.article_set.add(a4)
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth]
+>>> a4.publications.all()
+[Science News]
+
+# Adding via the other end using keywords
+>>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders')
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth, Oxygen-free diet works wonders]
+>>> a5 = p2.article_set.all()[1]
+>>> a5.publications.all()
+[Science News]
+
+# Removing publication from an article:
+>>> a4.publications.remove(p2)
+>>> p2.article_set.all()
+[Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[]
+
+# And from the other end
+>>> p2.article_set.remove(a5)
+>>> p2.article_set.all()
+[]
+>>> a5.publications.all()
+[]
+
+# Relation sets can be assigned. Assignment clears any existing set members
+>>> p2.article_set = [a4, a5]
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth, Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[Science News]
+>>> a4.publications = [p3]
+>>> p2.article_set.all()
+[Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[Science Weekly]
+
+# Relation sets can be cleared:
+>>> p2.article_set.clear()
+>>> p2.article_set.all()
+[]
+>>> a4.publications.all()
+[Science Weekly]
+
+# And you can clear from the other end
+>>> p2.article_set.add(a4, a5)
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth, Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[Science News, Science Weekly]
+>>> a4.publications.clear()
+>>> a4.publications.all()
+[]
+>>> p2.article_set.all()
+[Oxygen-free diet works wonders]
+
+# Recreate the article and Publication we just deleted.
+>>> p1 = Publication(id=None, title='The Python Journal')
+>>> p1.save()
+>>> a2 = Article(id=None, headline='NASA uses Python')
+>>> a2.save()
+>>> a2.publications.add(p1, p2, p3)
+
+# Bulk delete some Publications - references to deleted publications should go
+>>> Publication.objects.filter(title__startswith='Science').delete()
+>>> Publication.objects.all()
+[Highlights for Children, The Python Journal]
+>>> Article.objects.all()
+[Django lets you build Web apps easily, NASA finds intelligent life on Earth, NASA uses Python, Oxygen-free diet works wonders]
+>>> a2.publications.all()
+[The Python Journal]
+
+# Bulk delete some articles - references to deleted objects should go
+>>> q = Article.objects.filter(headline__startswith='Django')
+>>> print q
+[Django lets you build Web apps easily]
+>>> q.delete()
+
+# After the delete, the QuerySet cache needs to be cleared, and the referenced objects should be gone
+>>> print q
+[]
+>>> p1.article_set.all()
+[NASA uses Python]
+
+"""
diff --git a/tests/modeltests/many_to_one/__init__.py b/tests/modeltests/many_to_one/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/many_to_one/__init__.py
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
new file mode 100644
index 0000000000..165a36c91c
--- /dev/null
+++ b/tests/modeltests/many_to_one/models.py
@@ -0,0 +1,232 @@
+"""
+4. Many-to-one relationships
+
+To define a many-to-one relationship, use ``ForeignKey()`` .
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+ email = models.EmailField()
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+ reporter = models.ForeignKey(Reporter)
+
+ def __repr__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
+API_TESTS = """
+# Create a few Reporters.
+>>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
+>>> r.save()
+
+>>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com')
+>>> r2.save()
+
+# Create an Article.
+>>> from datetime import datetime
+>>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
+>>> a.save()
+
+>>> a.reporter.id
+1
+
+>>> a.reporter
+John Smith
+
+# Article objects have access to their related Reporter objects.
+>>> r = a.reporter
+>>> r.first_name, r.last_name
+('John', 'Smith')
+
+# Create an Article via the Reporter object.
+>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))
+>>> new_article
+John's second story
+>>> new_article.reporter.id
+1
+
+# Create a new article, and add it to the article set.
+>>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17))
+>>> r.article_set.add(new_article2)
+>>> new_article2.reporter.id
+1
+>>> r.article_set.all()
+[John's second story, Paul's story, This is a test]
+
+# Add the same article to a different article set - check that it moves.
+>>> r2.article_set.add(new_article2)
+>>> new_article2.reporter.id
+2
+>>> r.article_set.all()
+[John's second story, This is a test]
+>>> r2.article_set.all()
+[Paul's story]
+
+# Assign the article to the reporter directly using the descriptor
+>>> new_article2.reporter = r
+>>> new_article2.save()
+>>> new_article2.reporter
+John Smith
+>>> new_article2.reporter.id
+1
+>>> r.article_set.all()
+[John's second story, Paul's story, This is a test]
+>>> r2.article_set.all()
+[]
+
+# Set the article back again using set descriptor.
+>>> r2.article_set = [new_article, new_article2]
+>>> r.article_set.all()
+[This is a test]
+>>> r2.article_set.all()
+[John's second story, Paul's story]
+
+# Funny case - assignment notation can only go so far; because the
+# ForeignKey cannot be null, existing members of the set must remain
+>>> r.article_set = [new_article]
+>>> r.article_set.all()
+[John's second story, This is a test]
+>>> r2.article_set.all()
+[Paul's story]
+
+# Reporter cannot be null - there should not be a clear or remove method
+>>> hasattr(r2.article_set, 'remove')
+False
+>>> hasattr(r2.article_set, 'clear')
+False
+
+# Reporter objects have access to their related Article objects.
+>>> r.article_set.all()
+[John's second story, This is a test]
+
+>>> r.article_set.filter(headline__startswith='This')
+[This is a test]
+
+>>> r.article_set.count()
+2
+
+>>> r2.article_set.count()
+1
+
+# Get articles by id
+>>> Article.objects.filter(id__exact=1)
+[This is a test]
+>>> Article.objects.filter(pk=1)
+[This is a test]
+
+# Query on an article property
+>>> Article.objects.filter(headline__startswith='This')
+[This is a test]
+
+# 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 Articles for any Reporter whose first name is "John".
+>>> Article.objects.filter(reporter__first_name__exact='John')
+[John's second story, This is a test]
+
+# Query twice over the related field.
+>>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith')
+[John's second story, This is a test]
+
+# The underlying query only makes one join when a related table is referenced twice.
+>>> query = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith')
+>>> null, sql, null = query._get_sql_clause()
+>>> sql.count('INNER JOIN')
+1
+
+# The automatically joined table has a predictable name.
+>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='Smith'"])
+[John's second story, This is a test]
+
+# Find all Articles for the Reporter whose ID is 1.
+>>> Article.objects.filter(reporter__id__exact=1)
+[John's second story, This is a test]
+>>> Article.objects.filter(reporter__pk=1)
+[John's second story, This is a test]
+
+# You need two underscores between "reporter" and "id" -- not one.
+>>> Article.objects.filter(reporter_id__exact=1)
+Traceback (most recent call last):
+ ...
+TypeError: Cannot resolve keyword 'reporter_id' into field
+
+# You need to specify a comparison clause
+>>> Article.objects.filter(reporter_id=1)
+Traceback (most recent call last):
+ ...
+TypeError: Cannot resolve keyword 'reporter_id' into field
+
+# "pk" shortcut syntax works in a related context, too.
+>>> Article.objects.filter(reporter__pk=1)
+[John's second story, This is a test]
+
+# You can also instantiate an Article by passing
+# the Reporter's ID instead of a Reporter object.
+>>> a3 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id)
+>>> a3.save()
+>>> a3.reporter.id
+1
+>>> a3.reporter
+John Smith
+
+# Similarly, the reporter ID can be a string.
+>>> a4 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1")
+>>> a4.save()
+>>> a4.reporter
+John Smith
+
+# Reporters can be queried
+>>> Reporter.objects.filter(id__exact=1)
+[John Smith]
+>>> Reporter.objects.filter(pk=1)
+[John Smith]
+>>> Reporter.objects.filter(first_name__startswith='John')
+[John Smith]
+
+# Reporters can query in opposite direction of ForeignKey definition
+>>> Reporter.objects.filter(article__id__exact=1)
+[John Smith]
+>>> Reporter.objects.filter(article__pk=1)
+[John Smith]
+>>> Reporter.objects.filter(article__headline__startswith='This')
+[John Smith, John Smith, John Smith]
+>>> Reporter.objects.filter(article__headline__startswith='This').distinct()
+[John Smith]
+
+# Queries can go round in circles.
+>>> Reporter.objects.filter(article__reporter__first_name__startswith='John')
+[John Smith, John Smith, John Smith, John Smith]
+>>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct()
+[John Smith]
+
+# If you delete a reporter, his articles will be deleted.
+>>> Article.objects.all()
+[John's second story, Paul's story, This is a test, This is a test, This is a test]
+>>> Reporter.objects.order_by('first_name')
+[John Smith, Paul Jones]
+>>> r2.delete()
+>>> Article.objects.all()
+[John's second story, This is a test, This is a test, This is a test]
+>>> Reporter.objects.order_by('first_name')
+[John Smith]
+
+# Deletes using a join in the query
+>>> Reporter.objects.filter(article__headline__startswith='This').delete()
+>>> Reporter.objects.all()
+[]
+>>> Article.objects.all()
+[]
+
+"""
diff --git a/tests/modeltests/many_to_one_null/__init__.py b/tests/modeltests/many_to_one_null/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/many_to_one_null/__init__.py
diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py
new file mode 100644
index 0000000000..6818493ee3
--- /dev/null
+++ b/tests/modeltests/many_to_one_null/models.py
@@ -0,0 +1,124 @@
+"""
+16. Many-to-one relationships that can be null
+
+To define a many-to-one relationship that can have a null foreign key, use
+``ForeignKey()`` with ``null=True`` .
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ name = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return self.name
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ reporter = models.ForeignKey(Reporter, null=True)
+
+ def __repr__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
+API_TESTS = """
+# Create a Reporter.
+>>> r = Reporter(name='John Smith')
+>>> r.save()
+
+# Create an Article.
+>>> a = Article(headline="First", reporter=r)
+>>> a.save()
+
+>>> a.reporter.id
+1
+
+>>> a.reporter
+John Smith
+
+# Article objects have access to their related Reporter objects.
+>>> r = a.reporter
+
+# Create an Article via the Reporter object.
+>>> a2 = r.article_set.create(headline="Second")
+>>> a2
+Second
+>>> a2.reporter.id
+1
+
+# Reporter objects have access to their related Article objects.
+>>> r.article_set.all()
+[First, Second]
+>>> r.article_set.filter(headline__startswith='Fir')
+[First]
+>>> r.article_set.count()
+2
+
+# Create an Article with no Reporter by passing "reporter=None".
+>>> a3 = Article(headline="Third", reporter=None)
+>>> a3.save()
+>>> a3.id
+3
+>>> print a3.reporter
+None
+
+# Need to reget a3 to refresh the cache
+>>> a3 = Article.objects.get(pk=3)
+>>> print a3.reporter.id
+Traceback (most recent call last):
+ ...
+AttributeError: 'NoneType' object has no attribute 'id'
+
+# Accessing an article's 'reporter' attribute returns None
+# if the reporter is set to None.
+>>> print a3.reporter
+None
+
+# To retrieve the articles with no reporters set, use "reporter__isnull=True".
+>>> Article.objects.filter(reporter__isnull=True)
+[Third]
+
+# Set the reporter for the Third article
+>>> r.article_set.add(a3)
+>>> r.article_set.all()
+[First, Second, Third]
+
+# Remove an article from the set, and check that it was removed.
+>>> r.article_set.remove(a3)
+>>> r.article_set.all()
+[First, Second]
+>>> Article.objects.filter(reporter__isnull=True)
+[Third]
+
+# Create another article and reporter
+>>> r2 = Reporter(name='Paul Jones')
+>>> r2.save()
+>>> a4 = r2.article_set.create(headline='Fourth')
+>>> r2.article_set.all()
+[Fourth]
+
+# Try to remove a4 from a set it does not belong to
+>>> r.article_set.remove(a4)
+Traceback (most recent call last):
+...
+DoesNotExist: 'Fourth' is not related to 'John Smith'.
+
+>>> r2.article_set.all()
+[Fourth]
+
+# Use descriptor assignment to allocate ForeignKey. Null is legal, so
+# existing members of set that are not in the assignment set are set null
+>>> r2.article_set = [a2, a3]
+>>> r2.article_set.all()
+[Second, Third]
+
+# Clear the rest of the set
+>>> r.article_set.clear()
+>>> r.article_set.all()
+[]
+>>> Article.objects.filter(reporter__isnull=True)
+[First, Fourth]
+
+"""
diff --git a/tests/modeltests/model_inheritance/__init__.py b/tests/modeltests/model_inheritance/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/model_inheritance/__init__.py
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
new file mode 100644
index 0000000000..cdc4b4e2ac
--- /dev/null
+++ b/tests/modeltests/model_inheritance/models.py
@@ -0,0 +1,52 @@
+"""
+XX. Model inheritance
+
+"""
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(maxlength=50)
+ address = models.CharField(maxlength=80)
+
+ def __repr__(self):
+ return "%s the place" % self.name
+
+class Restaurant(Place):
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __repr__(self):
+ return "%s the restaurant" % self.name
+
+class ItalianRestaurant(Restaurant):
+ serves_gnocchi = models.BooleanField()
+
+ def __repr__(self):
+ return "%s the italian restaurant" % self.name
+
+API_TESTS = """
+# Make sure Restaurant has the right fields in the right order.
+>>> [f.name for f in Restaurant._meta.fields]
+['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza']
+
+# Make sure ItalianRestaurant has the right fields in the right order.
+>>> [f.name for f in ItalianRestaurant._meta.fields]
+['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza', 'serves_gnocchi']
+
+# Create a couple of Places.
+>>> p1 = Place(name='Master Shakes', address='666 W. Jersey')
+>>> p1.save()
+>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
+>>> p2.save()
+
+# Test constructor for Restaurant.
+>>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton', serves_hot_dogs=True, serves_pizza=False)
+>>> r.save()
+
+# Test the constructor for ItalianRestaurant.
+>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Elm', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True)
+>>> ir.save()
+
+
+"""
diff --git a/tests/modeltests/mutually_referential/__init__.py b/tests/modeltests/mutually_referential/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/mutually_referential/__init__.py
diff --git a/tests/modeltests/mutually_referential/models.py b/tests/modeltests/mutually_referential/models.py
new file mode 100644
index 0000000000..07b52effbc
--- /dev/null
+++ b/tests/modeltests/mutually_referential/models.py
@@ -0,0 +1,32 @@
+"""
+24. Mutually referential many-to-one relationships
+
+To define a many-to-one relationship, use ``ForeignKey()`` .
+"""
+
+from django.db.models import *
+
+class Parent(Model):
+ name = CharField(maxlength=100, core=True)
+ bestchild = ForeignKey("Child", null=True, related_name="favoured_by")
+
+class Child(Model):
+ name = CharField(maxlength=100)
+ parent = ForeignKey(Parent)
+
+API_TESTS = """
+# Create a Parent
+>>> q = Parent(name='Elizabeth')
+>>> q.save()
+
+# Create some children
+>>> c = q.child_set.create(name='Charles')
+>>> e = q.child_set.create(name='Edward')
+
+# Set the best child
+>>> q.bestchild = c
+>>> q.save()
+
+>>> q.delete()
+
+""" \ No newline at end of file
diff --git a/tests/modeltests/one_to_one/__init__.py b/tests/modeltests/one_to_one/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/one_to_one/__init__.py
diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py
new file mode 100644
index 0000000000..ef8a166afb
--- /dev/null
+++ b/tests/modeltests/one_to_one/models.py
@@ -0,0 +1,130 @@
+"""
+10. One-to-one relationships
+
+To define a one-to-one relationship, use ``OneToOneField()``.
+
+In this example, a ``Place`` optionally can be a ``Restaurant``.
+"""
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(maxlength=50)
+ address = models.CharField(maxlength=80)
+
+ def __repr__(self):
+ return "%s the place" % self.name
+
+class Restaurant(models.Model):
+ place = models.OneToOneField(Place)
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __repr__(self):
+ return "%s the restaurant" % self.place.name
+
+class Waiter(models.Model):
+ restaurant = models.ForeignKey(Restaurant)
+ name = models.CharField(maxlength=50)
+
+ def __repr__(self):
+ return "%s the waiter at %r" % (self.name, self.restaurant)
+
+API_TESTS = """
+# Create a couple of Places.
+>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
+>>> p1.save()
+>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
+>>> p2.save()
+
+# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
+>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
+>>> r.save()
+
+# A Restaurant can access its place.
+>>> r.place
+Demon Dogs the place
+
+# A Place can access its restaurant, if available.
+>>> p1.restaurant
+Demon Dogs the restaurant
+
+# p2 doesn't have an associated restaurant.
+>>> p2.restaurant
+Traceback (most recent call last):
+ ...
+DoesNotExist: Restaurant does not exist for {'place__pk': ...}
+
+# Set the place using assignment notation. Because place is the primary key on Restaurant,
+# the save will create a new restaurant
+>>> r.place = p2
+>>> r.save()
+>>> p2.restaurant
+Ace Hardware the restaurant
+>>> r.place
+Ace Hardware the place
+
+# Set the place back again, using assignment in the reverse direction
+# Need to reget restaurant object first, because the reverse set
+# can't update the existing restaurant instance
+>>> p1.restaurant = r
+>>> r.save()
+>>> p1.restaurant
+Demon Dogs the restaurant
+
+>>> r = Restaurant.objects.get(pk=1)
+>>> r.place
+Demon Dogs the place
+
+# Restaurant.objects.all() just returns the Restaurants, not the Places.
+# Note that there are two restaurants - Ace Hardware the Restaurant was created
+# in the call to r.place = p2. This means there are multiple restaurants referencing
+# a single place...
+>>> Restaurant.objects.all()
+[Demon Dogs the restaurant, Ace Hardware the restaurant]
+
+# Place.objects.all() returns all Places, regardless of whether they have
+# Restaurants.
+>>> Place.objects.order_by('name')
+[Ace Hardware the place, Demon Dogs the place]
+
+>>> Restaurant.objects.get(place__id__exact=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(pk=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(place__exact=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(place__pk=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(place__name__startswith="Demon")
+Demon Dogs the restaurant
+
+>>> Place.objects.get(id__exact=1)
+Demon Dogs the place
+>>> Place.objects.get(pk=1)
+Demon Dogs the place
+>>> Place.objects.get(restaurant__place__exact=1)
+Demon Dogs the place
+>>> Place.objects.get(restaurant__pk=1)
+Demon Dogs the place
+
+# Add a Waiter to the Restaurant.
+>>> w = r.waiter_set.create(name='Joe')
+>>> w.save()
+>>> w
+Joe the waiter at Demon Dogs the restaurant
+
+# Query the waiters
+>>> Waiter.objects.filter(restaurant__place__exact=1)
+[Joe the waiter at Demon Dogs the restaurant]
+>>> Waiter.objects.filter(restaurant__pk=1)
+[Joe the waiter at Demon Dogs the restaurant]
+>>> Waiter.objects.filter(id__exact=1)
+[Joe the waiter at Demon Dogs the restaurant]
+>>> Waiter.objects.filter(pk=1)
+[Joe the waiter at Demon Dogs the restaurant]
+
+# Delete the restaurant; the waiter should also be removed
+>>> r = Restaurant.objects.get(pk=1)
+>>> r.delete()
+"""
diff --git a/tests/modeltests/or_lookups/__init__.py b/tests/modeltests/or_lookups/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/or_lookups/__init__.py
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
new file mode 100644
index 0000000000..9d62a1266c
--- /dev/null
+++ b/tests/modeltests/or_lookups/models.py
@@ -0,0 +1,89 @@
+"""
+19. OR lookups
+
+To perform an OR lookup, or a lookup that combines ANDs and ORs,
+combine QuerySet objects using & and | operators.
+
+Alternatively, use positional arguments, and pass one or more expressions
+of clauses using the variable ``django.db.models.Q`` (or any object with
+a get_sql method).
+
+
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=50)
+ pub_date = models.DateTimeField()
+ class Meta:
+ ordering = ('pub_date',)
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+>>> from datetime import datetime
+>>> from django.db.models import Q
+
+>>> a1 = Article(headline='Hello', pub_date=datetime(2005, 11, 27))
+>>> a1.save()
+
+>>> a2 = Article(headline='Goodbye', pub_date=datetime(2005, 11, 28))
+>>> a2.save()
+
+>>> a3 = Article(headline='Hello and goodbye', pub_date=datetime(2005, 11, 29))
+>>> a3.save()
+
+>>> Article.objects.filter(headline__startswith='Hello') | Article.objects.filter(headline__startswith='Goodbye')
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye'))
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye'))
+[]
+
+>>> Article.objects.filter(headline__startswith='Hello') & Article.objects.filter(headline__startswith='Goodbye')
+[]
+
+>>> Article.objects.filter(headline__startswith='Hello') & Article.objects.filter(headline__contains='bye')
+[Hello and goodbye]
+
+>>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello')
+[Hello and goodbye]
+
+>>> Article.objects.filter(headline__contains='Hello') | Article.objects.filter(headline__contains='bye')
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(headline__iexact='Hello') | Article.objects.filter(headline__contains='ood')
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(Q(pk=1) | Q(pk=2))
+[Hello, Goodbye]
+
+>>> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))
+[Hello, Goodbye, Hello and goodbye]
+
+# Q arg objects are ANDed
+>>> Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye'))
+[Hello and goodbye]
+
+# Q arg AND order is irrelevant
+>>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello')
+[Hello and goodbye]
+
+# Try some arg queries with operations other than get_list
+>>> Article.objects.get(Q(headline__startswith='Hello'), Q(headline__contains='bye'))
+Hello and goodbye
+
+>>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count()
+3
+
+>>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values())
+[{'headline': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}]
+
+>>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2])
+{1: Hello}
+
+"""
diff --git a/tests/modeltests/ordering/__init__.py b/tests/modeltests/ordering/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/ordering/__init__.py
diff --git a/tests/testapp/models/ordering.py b/tests/modeltests/ordering/models.py
index 6256e4daf7..de08a75755 100644
--- a/tests/testapp/models/ordering.py
+++ b/tests/modeltests/ordering/models.py
@@ -13,12 +13,12 @@ The ordering attribute is not required. If you leave it off, ordering will be
undefined -- not random, just undefined.
"""
-from django.core import meta
+from django.db import models
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
- class META:
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
+ class Meta:
ordering = ('-pub_date', 'headline')
def __repr__(self):
@@ -27,37 +27,37 @@ class Article(meta.Model):
API_TESTS = """
# Create a couple of Articles.
>>> from datetime import datetime
->>> a1 = articles.Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
+>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
>>> a1.save()
->>> a2 = articles.Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
+>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
>>> a2.save()
->>> a3 = articles.Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
+>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
>>> a3.save()
->>> a4 = articles.Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
+>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
>>> a4.save()
-# By default, articles.get_list() orders by pub_date descending, then
+# By default, Article.objects.all() orders by pub_date descending, then
# headline ascending.
->>> articles.get_list()
+>>> Article.objects.all()
[Article 4, Article 2, Article 3, Article 1]
# Override ordering with order_by, which is in the same format as the ordering
# attribute in models.
->>> articles.get_list(order_by=['headline'])
+>>> Article.objects.order_by('headline')
[Article 1, Article 2, Article 3, Article 4]
->>> articles.get_list(order_by=['pub_date', '-headline'])
+>>> Article.objects.order_by('pub_date', '-headline')
[Article 1, Article 3, Article 2, Article 4]
-# Use the "limit" parameter to limit the results.
->>> articles.get_list(order_by=['headline'], limit=2)
+# Use the 'stop' part of slicing notation to limit the results.
+>>> Article.objects.order_by('headline')[:2]
[Article 1, Article 2]
-# Use the "offset" parameter with "limit" to offset the result list.
->>> articles.get_list(order_by=['headline'], offset=1, limit=2)
+# Use the 'stop' and 'start' parts of slicing notation to offset the result list.
+>>> Article.objects.order_by('headline')[1:3]
[Article 2, Article 3]
# Use '?' to order randomly. (We're using [...] in the output to indicate we
# don't know what order the output will be in.
->>> articles.get_list(order_by=['?'])
+>>> Article.objects.order_by('?')
[...]
"""
diff --git a/tests/modeltests/pagination/__init__.py b/tests/modeltests/pagination/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/pagination/__init__.py
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
new file mode 100644
index 0000000000..6525168b97
--- /dev/null
+++ b/tests/modeltests/pagination/models.py
@@ -0,0 +1,59 @@
+"""
+20. Object Pagination
+
+Django provides a framework for paginating a list of objects in a few.
+This is often useful for dividing search results or long lists of objects
+in to easily readable pages.
+
+
+"""
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100, default='Default headline')
+ pub_date = models.DateTimeField()
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+# prepare a list of objects for pagination
+>>> from datetime import datetime
+>>> for x in range(1, 10):
+... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
+... a.save()
+
+# create a basic paginator, 5 articles per page
+>>> from django.core.paginator import ObjectPaginator, InvalidPage
+>>> paginator = ObjectPaginator(Article.objects.all(), 5)
+
+# the paginator knows how many hits and pages it contains
+>>> paginator.hits
+9
+
+>>> paginator.pages
+2
+
+# get the first page (zero-based)
+>>> paginator.get_page(0)
+[Article 1, Article 2, Article 3, Article 4, Article 5]
+
+# get the second page
+>>> paginator.get_page(1)
+[Article 6, Article 7, Article 8, Article 9]
+
+# does the first page have a next or previous page?
+>>> paginator.has_next_page(0)
+True
+
+>>> paginator.has_previous_page(0)
+False
+
+# check the second page
+>>> paginator.has_next_page(1)
+False
+
+>>> paginator.has_previous_page(1)
+True
+
+"""
diff --git a/tests/modeltests/properties/__init__.py b/tests/modeltests/properties/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/properties/__init__.py
diff --git a/tests/modeltests/properties/models.py b/tests/modeltests/properties/models.py
new file mode 100644
index 0000000000..2c2190e989
--- /dev/null
+++ b/tests/modeltests/properties/models.py
@@ -0,0 +1,26 @@
+"""
+22. Using properties on models
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ def _get_full_name(self):
+ return "%s %s" % (self.first_name, self.last_name)
+ full_name = property(_get_full_name)
+
+API_TESTS = """
+>>> a = Person(first_name='John', last_name='Lennon')
+>>> a.save()
+>>> a.full_name
+'John Lennon'
+
+# The "full_name" property hasn't provided a "set" method.
+>>> a.full_name = 'Paul McCartney'
+Traceback (most recent call last):
+ ...
+AttributeError: can't set attribute
+"""
diff --git a/tests/modeltests/repr/__init__.py b/tests/modeltests/repr/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/repr/__init__.py
diff --git a/tests/testapp/models/repr.py b/tests/modeltests/repr/models.py
index 3d4daf22c9..7e5b98c4a5 100644
--- a/tests/testapp/models/repr.py
+++ b/tests/modeltests/repr/models.py
@@ -8,11 +8,11 @@ because objects' representations are used throughout Django's
automatically-generated admin.
"""
-from django.core import meta
+from django.db import models
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
def __repr__(self):
return self.headline
@@ -20,7 +20,7 @@ class Article(meta.Model):
API_TESTS = """
# Create an Article.
>>> from datetime import datetime
->>> a = articles.Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
+>>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
>>> a.save()
>>> repr(a)
diff --git a/tests/modeltests/reserved_names/__init__.py b/tests/modeltests/reserved_names/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/reserved_names/__init__.py
diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py
new file mode 100644
index 0000000000..51d32e22c5
--- /dev/null
+++ b/tests/modeltests/reserved_names/models.py
@@ -0,0 +1,56 @@
+"""
+18. Using SQL reserved names
+
+Need to use a reserved SQL name as a column name or table name? Need to include
+a hyphen in a column or table name? No problem. Django quotes names
+appropriately behind the scenes, so your database won't complain about
+reserved-name usage.
+"""
+
+from django.db import models
+
+class Thing(models.Model):
+ when = models.CharField(maxlength=1, primary_key=True)
+ join = models.CharField(maxlength=1)
+ like = models.CharField(maxlength=1)
+ drop = models.CharField(maxlength=1)
+ alter = models.CharField(maxlength=1)
+ having = models.CharField(maxlength=1)
+ where = models.DateField(maxlength=1)
+ has_hyphen = models.CharField(maxlength=1, db_column='has-hyphen')
+ class Meta:
+ db_table = 'select'
+
+ def __repr__(self):
+ return self.when
+
+API_TESTS = """
+>>> import datetime
+>>> day1 = datetime.date(2005, 1, 1)
+>>> day2 = datetime.date(2006, 2, 2)
+>>> t = Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where=day1, has_hyphen='h')
+>>> t.save()
+>>> print t.when
+a
+
+>>> u = Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where=day2)
+>>> u.save()
+>>> print u.when
+h
+
+>>> Thing.objects.order_by('when')
+[a, h]
+>>> v = Thing.objects.get(pk='a')
+>>> print v.join
+b
+>>> print v.where
+2005-01-01
+>>> Thing.objects.order_by('select.when')
+[a, h]
+
+>>> Thing.objects.dates('where', 'year')
+[datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)]
+
+>>> Thing.objects.filter(where__month=1)
+[a]
+"""
diff --git a/tests/modeltests/reverse_lookup/__init__.py b/tests/modeltests/reverse_lookup/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/reverse_lookup/__init__.py
diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py
new file mode 100644
index 0000000000..2fe77bdbeb
--- /dev/null
+++ b/tests/modeltests/reverse_lookup/models.py
@@ -0,0 +1,56 @@
+"""
+25. Reverse lookups
+
+This demonstrates the reverse lookup features of the database API.
+"""
+
+from django.db import models
+
+class User(models.Model):
+ name = models.CharField(maxlength=200)
+ def __repr__(self):
+ return self.name
+
+class Poll(models.Model):
+ question = models.CharField(maxlength=200)
+ creator = models.ForeignKey(User)
+ def __repr__(self):
+ return self.question
+
+class Choice(models.Model):
+ name = models.CharField(maxlength=100)
+ poll = models.ForeignKey(Poll, related_name="poll_choice")
+ related_poll = models.ForeignKey(Poll, related_name="related_choice")
+ def __repr(self):
+ return self.name
+
+API_TESTS = """
+>>> john = User(name="John Doe")
+>>> john.save()
+>>> jim = User(name="Jim Bo")
+>>> jim.save()
+>>> first_poll = Poll(question="What's the first question?", creator=john)
+>>> first_poll.save()
+>>> second_poll = Poll(question="What's the second question?", creator=jim)
+>>> second_poll.save()
+>>> new_choice = Choice(poll=first_poll, related_poll=second_poll, name="This is the answer.")
+>>> new_choice.save()
+
+>>> # Reverse lookups by field name:
+>>> User.objects.get(poll__question__exact="What's the first question?")
+John Doe
+>>> User.objects.get(poll__question__exact="What's the second question?")
+Jim Bo
+
+>>> # Reverse lookups by related_name:
+>>> Poll.objects.get(poll_choice__name__exact="This is the answer.")
+What's the first question?
+>>> Poll.objects.get(related_choice__name__exact="This is the answer.")
+What's the second question?
+
+>>> # If a related_name is given you can't use the field name instead:
+>>> Poll.objects.get(choice__name__exact="This is the answer")
+Traceback (most recent call last):
+ ...
+TypeError: Cannot resolve keyword 'choice' into field
+"""
diff --git a/tests/modeltests/save_delete_hooks/__init__.py b/tests/modeltests/save_delete_hooks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/save_delete_hooks/__init__.py
diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py
new file mode 100644
index 0000000000..47748082da
--- /dev/null
+++ b/tests/modeltests/save_delete_hooks/models.py
@@ -0,0 +1,42 @@
+"""
+13. Adding hooks before/after saving and deleting
+
+To execute arbitrary code around ``save()`` and ``delete()``, just subclass
+the methods.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ first_name = models.CharField(maxlength=20)
+ last_name = models.CharField(maxlength=20)
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+ def save(self):
+ print "Before save"
+ super(Person, self).save() # Call the "real" save() method
+ print "After save"
+
+ def delete(self):
+ print "Before deletion"
+ super(Person, self).delete() # Call the "real" delete() method
+ print "After deletion"
+
+API_TESTS = """
+>>> p1 = Person(first_name='John', last_name='Smith')
+>>> p1.save()
+Before save
+After save
+
+>>> Person.objects.all()
+[John Smith]
+
+>>> p1.delete()
+Before deletion
+After deletion
+
+>>> Person.objects.all()
+[]
+"""
diff --git a/tests/modeltests/transactions/__init__.py b/tests/modeltests/transactions/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/transactions/__init__.py
diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
new file mode 100644
index 0000000000..22f38f7a0c
--- /dev/null
+++ b/tests/modeltests/transactions/models.py
@@ -0,0 +1,92 @@
+"""
+XXX. Transactions
+
+Django handles transactions in three different ways. The default is to commit
+each transaction upon a write, but you can decorate a function to get
+commit-on-success behavior. Alternatively, you can manage the transaction
+manually.
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+ email = models.EmailField()
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+API_TESTS = """
+>>> from django.db import connection, transaction
+
+# the default behavior is to autocommit after each save() action
+>>> def create_a_reporter_then_fail(first, last):
+... a = Reporter(first_name=first, last_name=last)
+... a.save()
+... raise Exception("I meant to do that")
+...
+>>> create_a_reporter_then_fail("Alice", "Smith")
+Traceback (most recent call last):
+ ...
+Exception: I meant to do that
+
+# The object created before the exception still exists
+>>> Reporter.objects.all()
+[Alice Smith]
+
+# the autocommit decorator works exactly the same as the default behavior
+>>> autocomitted_create_then_fail = transaction.autocommit(create_a_reporter_then_fail)
+>>> autocomitted_create_then_fail("Ben", "Jones")
+Traceback (most recent call last):
+ ...
+Exception: I meant to do that
+
+# Same behavior as before
+>>> Reporter.objects.all()
+[Alice Smith, Ben Jones]
+
+# With the commit_on_success decorator, the transaction is only comitted if the
+# function doesn't throw an exception
+>>> committed_on_success = transaction.commit_on_success(create_a_reporter_then_fail)
+>>> committed_on_success("Carol", "Doe")
+Traceback (most recent call last):
+ ...
+Exception: I meant to do that
+
+# This time the object never got saved
+>>> Reporter.objects.all()
+[Alice Smith, Ben Jones]
+
+# If there aren't any exceptions, the data will get saved
+>>> def remove_a_reporter():
+... r = Reporter.objects.get(first_name="Alice")
+... r.delete()
+...
+>>> remove_comitted_on_success = transaction.commit_on_success(remove_a_reporter)
+>>> remove_comitted_on_success()
+>>> Reporter.objects.all()
+[Ben Jones]
+
+# You can manually manage transactions if you really want to, but you
+# have to remember to commit/rollback
+>>> def manually_managed():
+... r = Reporter(first_name="Carol", last_name="Doe")
+... r.save()
+... transaction.commit()
+>>> manually_managed = transaction.commit_manually(manually_managed)
+>>> manually_managed()
+>>> Reporter.objects.all()
+[Ben Jones, Carol Doe]
+
+# If you forget, you'll get bad errors
+>>> def manually_managed_mistake():
+... r = Reporter(first_name="David", last_name="Davidson")
+... r.save()
+... # oops, I forgot to commit/rollback!
+>>> manually_managed_mistake = transaction.commit_manually(manually_managed_mistake)
+>>> manually_managed_mistake()
+Traceback (most recent call last):
+ ...
+TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK
+""" \ No newline at end of file
diff --git a/tests/modeltests/validation/__init__.py b/tests/modeltests/validation/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/validation/__init__.py
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
new file mode 100644
index 0000000000..d03fffea25
--- /dev/null
+++ b/tests/modeltests/validation/models.py
@@ -0,0 +1,147 @@
+"""
+XX. Validation
+
+Each model instance has a validate() method that returns a dictionary of
+validation errors in the instance's fields. This method has a side effect
+of converting each field to its appropriate Python data type.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ is_child = models.BooleanField()
+ name = models.CharField(maxlength=20)
+ birthdate = models.DateField()
+ favorite_moment = models.DateTimeField()
+ email = models.EmailField()
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+
+>>> import datetime
+>>> valid_params = {
+... 'is_child': True,
+... 'name': 'John',
+... 'birthdate': datetime.date(2000, 5, 3),
+... 'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
+... 'email': 'john@example.com'
+... }
+>>> p = Person(**valid_params)
+>>> p.validate()
+{}
+
+>>> p = Person(**dict(valid_params, id='23'))
+>>> p.validate()
+{}
+>>> p.id
+23
+
+>>> p = Person(**dict(valid_params, id='foo'))
+>>> p.validate()
+{'id': ['This value must be an integer.']}
+
+>>> p = Person(**dict(valid_params, id=None))
+>>> p.validate()
+{}
+>>> repr(p.id)
+'None'
+
+>>> p = Person(**dict(valid_params, is_child='t'))
+>>> p.validate()
+{}
+>>> p.is_child
+True
+
+>>> p = Person(**dict(valid_params, is_child='f'))
+>>> p.validate()
+{}
+>>> p.is_child
+False
+
+>>> p = Person(**dict(valid_params, is_child=True))
+>>> p.validate()
+{}
+>>> p.is_child
+True
+
+>>> p = Person(**dict(valid_params, is_child=False))
+>>> p.validate()
+{}
+>>> p.is_child
+False
+
+>>> p = Person(**dict(valid_params, is_child='foo'))
+>>> p.validate()
+{'is_child': ['This value must be either True or False.']}
+
+>>> p = Person(**dict(valid_params, name=u'Jose'))
+>>> p.validate()
+{}
+>>> p.name
+u'Jose'
+
+>>> p = Person(**dict(valid_params, name=227))
+>>> p.validate()
+{}
+>>> p.name
+'227'
+
+>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate='foo'))
+>>> p.validate()
+{'birthdate': ['Enter a valid date in YYYY-MM-DD format.']}
+
+>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
+>>> p.validate()
+{}
+>>> p.favorite_moment
+datetime.datetime(2002, 4, 3, 13, 23)
+
+>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
+>>> p.validate()
+{}
+>>> p.favorite_moment
+datetime.datetime(2002, 4, 3, 0, 0)
+
+>>> p = Person(**dict(valid_params, email='john@example.com'))
+>>> p.validate()
+{}
+>>> p.email
+'john@example.com'
+
+>>> p = Person(**dict(valid_params, email=u'john@example.com'))
+>>> p.validate()
+{}
+>>> p.email
+u'john@example.com'
+
+>>> p = Person(**dict(valid_params, email=22))
+>>> p.validate()
+{'email': ['Enter a valid e-mail address.']}
+
+"""
diff --git a/tests/othertests/dateformat.py b/tests/othertests/dateformat.py
index 3350a1f8ab..0287587b4a 100644
--- a/tests/othertests/dateformat.py
+++ b/tests/othertests/dateformat.py
@@ -1,14 +1,16 @@
-"""
+r"""
>>> format(my_birthday, '')
''
>>> format(my_birthday, 'a')
'p.m.'
>>> format(my_birthday, 'A')
'PM'
+>>> format(my_birthday, 'd')
+'08'
>>> format(my_birthday, 'j')
-'7'
+'8'
>>> format(my_birthday, 'l')
-'Saturday'
+'Sunday'
>>> format(my_birthday, 'L')
'False'
>>> format(my_birthday, 'm')
@@ -24,7 +26,7 @@
>>> format(my_birthday, 'P')
'10 p.m.'
>>> format(my_birthday, 'r')
-'Sat, 7 Jul 1979 22:00:00 +0100'
+'Sun, 8 Jul 1979 22:00:00 +0100'
>>> format(my_birthday, 's')
'00'
>>> format(my_birthday, 'S')
@@ -34,9 +36,9 @@
>>> format(my_birthday, 'T')
'CET'
>>> format(my_birthday, 'U')
-'300445200'
+'300531600'
>>> format(my_birthday, 'w')
-'6'
+'0'
>>> format(my_birthday, 'W')
'27'
>>> format(my_birthday, 'y')
@@ -44,7 +46,7 @@
>>> format(my_birthday, 'Y')
'1979'
>>> format(my_birthday, 'z')
-'188'
+'189'
>>> format(my_birthday, 'Z')
'3600'
@@ -57,8 +59,11 @@
>>> format(wintertime, 'O')
'+0100'
->>> format(my_birthday, 'Y z \\C\\E\\T')
-'1979 188 CET'
+>>> format(my_birthday, r'Y z \C\E\T')
+'1979 189 CET'
+
+>>> format(my_birthday, r'jS o\f F')
+'8th of July'
"""
from django.utils import dateformat, translation
@@ -70,6 +75,6 @@ translation.activate('en-us')
time.tzset()
-my_birthday = datetime.datetime(1979, 7, 7, 22, 00)
+my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
summertime = datetime.datetime(2005, 10, 30, 1, 00)
wintertime = datetime.datetime(2005, 10, 30, 4, 00)
diff --git a/tests/othertests/db_typecasts.py b/tests/othertests/db_typecasts.py
index 52cab666de..ffc9b34aec 100644
--- a/tests/othertests/db_typecasts.py
+++ b/tests/othertests/db_typecasts.py
@@ -1,6 +1,6 @@
-# Unit tests for django.core.db.typecasts
+# Unit tests for typecast functions in django.db.backends.util
-from django.core.db import typecasts
+from django.db.backends import util as typecasts
import datetime
TEST_CASES = {
diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py
index d0d5d21e58..46f2519285 100644
--- a/tests/othertests/defaultfilters.py
+++ b/tests/othertests/defaultfilters.py
@@ -1,4 +1,4 @@
-"""
+r"""
>>> floatformat(7.7)
'7.7'
>>> floatformat(7.0)
@@ -12,8 +12,8 @@
>>> floatformat(0.0)
'0'
->>> addslashes('"double quotes" and \\'single quotes\\'')
-'\\\\"double quotes\\\\" and \\\\\\'single quotes\\\\\\''
+>>> addslashes('"double quotes" and \'single quotes\'')
+'\\"double quotes\\" and \\\'single quotes\\\''
>>> capfirst('hello world')
'Hello world'
@@ -21,17 +21,17 @@
>>> fix_ampersands('Jack & Jill & Jeroboam')
'Jack &amp; Jill &amp; Jeroboam'
->>> linenumbers('line 1\\nline 2')
-'1. line 1\\n2. line 2'
+>>> linenumbers('line 1\nline 2')
+'1. line 1\n2. line 2'
->>> linenumbers('\\n'.join(['x'] * 10))
-'01. x\\n02. x\\n03. x\\n04. x\\n05. x\\n06. x\\n07. x\\n08. x\\n09. x\\n10. x'
+>>> linenumbers('\n'.join(['x'] * 10))
+'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. x\n08. x\n09. x\n10. x'
>>> lower('TEST')
'test'
->>> lower(u'\\xcb') # uppercase E umlaut
-u'\\xeb'
+>>> lower(u'\xcb') # uppercase E umlaut
+u'\xeb'
>>> make_list('abc')
['a', 'b', 'c']
@@ -48,7 +48,7 @@ u'\\xeb'
>>> stringformat(1, 'z')
''
->>> title('a nice title, isn\\'t it?')
+>>> title('a nice title, isn\'t it?')
"A Nice Title, Isn't It?"
@@ -68,8 +68,8 @@ u'\\xeb'
>>> upper('Mixed case input')
'MIXED CASE INPUT'
->>> upper(u'\\xeb') # lowercase e umlaut
-u'\\xcb'
+>>> upper(u'\xeb') # lowercase e umlaut
+u'\xcb'
>>> urlencode('jack & jill')
@@ -91,8 +91,8 @@ u'\\xcb'
>>> wordcount('lots of words')
3
->>> wordwrap('this is a long paragraph of text that really needs to be wrapped I\\'m afraid', 14)
-"this is a long\\nparagraph of\\ntext that\\nreally needs\\nto be wrapped\\nI'm afraid"
+>>> wordwrap('this is a long paragraph of text that really needs to be wrapped I\'m afraid', 14)
+"this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\nI'm afraid"
>>> ljust('test', 10)
'test '
@@ -124,7 +124,7 @@ u'\\xcb'
>>> linebreaks('line 1')
'<p>line 1</p>'
->>> linebreaks('line 1\\nline 2')
+>>> linebreaks('line 1\nline 2')
'<p>line 1<br />line 2</p>'
>>> removetags('some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags', 'script img')
@@ -133,19 +133,15 @@ u'\\xcb'
>>> striptags('some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags')
'some html with alert("You smell") disallowed tags'
->>> dictsort([{'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 63, 'name': 'Ra Ra Rasputin'},\
- {'name': 'Jonny B Goode', 'age': 18}], 'age')
-[{'age': 18, 'name': 'Jonny B Goode'},\
- {'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 63, 'name': 'Ra Ra Rasputin'}]
+>>> dictsort([{'age': 23, 'name': 'Barbara-Ann'},
+... {'age': 63, 'name': 'Ra Ra Rasputin'},
+... {'name': 'Jonny B Goode', 'age': 18}], 'age')
+[{'age': 18, 'name': 'Jonny B Goode'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 63, 'name': 'Ra Ra Rasputin'}]
->>> dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 63, 'name': 'Ra Ra Rasputin'},\
- {'name': 'Jonny B Goode', 'age': 18}], 'age')
-[{'age': 63, 'name': 'Ra Ra Rasputin'},\
- {'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 18, 'name': 'Jonny B Goode'}]
+>>> dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},
+... {'age': 63, 'name': 'Ra Ra Rasputin'},
+... {'name': 'Jonny B Goode', 'age': 18}], 'age')
+[{'age': 63, 'name': 'Ra Ra Rasputin'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 18, 'name': 'Jonny B Goode'}]
>>> first([0,1,2])
0
@@ -196,13 +192,13 @@ False
'aceg'
>>> unordered_list(['item 1', []])
-'\\t<li>item 1</li>'
+'\t<li>item 1</li>'
>>> unordered_list(['item 1', [['item 1.1', []]]])
-'\\t<li>item 1\\n\\t<ul>\\n\\t\\t<li>item 1.1</li>\\n\\t</ul>\\n\\t</li>'
+'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>'
>>> unordered_list(['item 1', [['item 1.1', []], ['item 1.2', []]]])
-'\\t<li>item 1\\n\\t<ul>\\n\\t\\t<li>item 1.1</li>\\n\\t\\t<li>item 1.2</li>\\n\\t</ul>\\n\\t</li>'
+'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t\t<li>item 1.2</li>\n\t</ul>\n\t</li>'
>>> add('1', '2')
3
@@ -228,6 +224,8 @@ False
# real testing of date() is in dateformat.py
>>> date(datetime.datetime(2005, 12, 29), "d F Y")
'29 December 2005'
+>>> date(datetime.datetime(2005, 12, 29), r'jS o\f F')
+'29th of December'
# real testing of time() is done in dateformat.py
>>> time(datetime.time(13), "h")
@@ -322,7 +320,7 @@ False
"""
-from django.core.template.defaultfilters import *
+from django.template.defaultfilters import *
import datetime
if __name__ == '__main__':
diff --git a/tests/othertests/httpwrappers.py b/tests/othertests/httpwrappers.py
index e3aa7c4e42..385c3048d9 100644
--- a/tests/othertests/httpwrappers.py
+++ b/tests/othertests/httpwrappers.py
@@ -351,7 +351,7 @@ AttributeError: This QueryDict instance is immutable
"""
-from django.utils.httpwrappers import QueryDict
+from django.http import QueryDict
if __name__ == "__main__":
import doctest
diff --git a/tests/othertests/markup.py b/tests/othertests/markup.py
index 5a8f9e1cdc..3fa5a3a883 100644
--- a/tests/othertests/markup.py
+++ b/tests/othertests/markup.py
@@ -1,6 +1,6 @@
# Quick tests for the markup templatetags (django.contrib.markup)
-from django.core.template import Template, Context, add_to_builtins
+from django.template import Template, Context, add_to_builtins
add_to_builtins('django.contrib.markup.templatetags.markup')
diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py
index bfb62c7bd1..6d8487c67a 100644
--- a/tests/othertests/templates.py
+++ b/tests/othertests/templates.py
@@ -1,11 +1,10 @@
from django.conf import settings
-# Turn TEMPLATE_DEBUG off, because tests assume that.
-settings.TEMPLATE_DEBUG = False
-from django.core import template
-from django.core.template import loader
+from django import template
+from django.template import loader
from django.utils.translation import activate, deactivate, install
+from datetime import datetime
import traceback
#################################
@@ -32,6 +31,12 @@ template.libraries['django.templatetags.testtags'] = register
# Helper objects for template tests #
#####################################
+class SomeException(Exception):
+ silent_variable_failure = True
+
+class SomeOtherException(Exception):
+ pass
+
class SomeClass:
def __init__(self):
self.otherclass = OtherClass()
@@ -42,6 +47,12 @@ class SomeClass:
def method2(self, o):
return o
+ def method3(self):
+ raise SomeException
+
+ def method4(self):
+ raise SomeOtherException
+
class OtherClass:
def method(self):
return "OtherClass.method"
@@ -133,12 +144,20 @@ TEMPLATE_TESTS = {
'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
# Default argument testing
- 'basic-syntax32' : (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
+ 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
- ### IF TAG ################################################################
- 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
- 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
- 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+ # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
+ 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "12"),
+
+ # In methods that raise an exception without a "silent_variable_attribute" set to True,
+ # the exception propogates
+ 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
+
+ # Escaped backslash in argument
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
+
+ # Escaped backslash using known escape char
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
### COMMENT TAG ###########################################################
'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
@@ -149,6 +168,45 @@ TEMPLATE_TESTS = {
'comment-tag04': ("foo{% comment %} {% endblock %} {% endcomment %}", {}, "foo"),
'comment-tag05': ("foo{% comment %} {% somerandomtag %} {% endcomment %}", {}, "foo"),
+ ### CYCLE TAG #############################################################
+ #'cycleXX': ('', {}, ''),
+ 'cycle01': ('{% cycle a, %}', {}, 'a'),
+ 'cycle02': ('{% cycle a,b,c as abc %}{% cycle abc %}', {}, 'ab'),
+ 'cycle03': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}', {}, 'abc'),
+ 'cycle04': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}', {}, 'abca'),
+ 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
+ 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
+
+ ### EXCEPTIONS ############################################################
+
+ # Raise exception for invalid template name
+ 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for invalid template name (in variable)
+ 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for extra {% extends %} tags
+ 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
+ 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
+
+ ### FILTER TAG ############################################################
+ #'filterXX': ('', {}, ''),
+ 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
+ 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
+ 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
+
+ ### FIRSTOF TAG ###########################################################
+ #'firstofXX': ('', {}, ''),
+ 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
+ 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
+ 'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
+ 'firstof04': ('{% firstof a b c %}', {'a':0,'b':0,'c':3}, '3'),
+ 'firstof05': ('{% firstof a b c %}', {'a':1,'b':2,'c':3}, '1'),
+ 'firstof06': ('{% firstof %}', {}, template.TemplateSyntaxError),
+
### FOR TAG ###############################################################
'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"),
'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"),
@@ -157,6 +215,17 @@ TEMPLATE_TESTS = {
'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
+ ### IF TAG ################################################################
+ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
+ 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
+ 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+
+ ### IFCHANGED TAG #########################################################
+ #'ifchangedXX': ('', {}, ''),
+ 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
+ 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
+ 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
+
### IFEQUAL TAG ###########################################################
'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
@@ -169,20 +238,6 @@ TEMPLATE_TESTS = {
'ifequal09': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {}, "no"),
'ifequal10': ('{% ifequal a b %}yes{% else %}no{% endifequal %}', {}, "yes"),
- # Integers
- 'ifequal11': ('{% ifequal a 1 %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal12': ('{% ifequal a 1 %}yes{% else %}no{% endifequal %}', {"a": 1}, "yes"),
- 'ifequal13': ('{% ifequal a 1 %}yes{% else %}no{% endifequal %}', {"a": "1"}, "no"),
- 'ifequal14': ('{% ifequal a "1" %}yes{% else %}no{% endifequal %}', {"a": 1}, "no"),
- 'ifequal15': ('{% ifequal a "1" %}yes{% else %}no{% endifequal %}', {"a": "1"}, "yes"),
-
- # Floats
- 'ifequal16': ('{% ifequal a 1.2 %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal17': ('{% ifequal a 1.2 %}yes{% else %}no{% endifequal %}', {"a": 1.2}, "yes"),
- 'ifequal18': ('{% ifequal a 1.2 %}yes{% else %}no{% endifequal %}', {"a": "1.2"}, "no"),
- 'ifequal19': ('{% ifequal a "1.2" %}yes{% else %}no{% endifequal %}', {"a": 1.2}, "no"),
- 'ifequal20': ('{% ifequal a "1.2" %}yes{% else %}no{% endifequal %}', {"a": "1.2"}, "yes"),
-
### IFNOTEQUAL TAG ########################################################
'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
@@ -266,37 +321,7 @@ TEMPLATE_TESTS = {
# Three-level inheritance with {{ block.super }} from parent and grandparent
'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'),
- ### EXCEPTIONS ############################################################
-
- # Raise exception for invalid template name
- 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for invalid template name (in variable)
- 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for extra {% extends %} tags
- 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
- 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
-
- 'multiline01': ("""
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """,
- {},
- """
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """),
+ ### I18N ##################################################################
# {% spaceless %} tag
'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
@@ -341,6 +366,89 @@ TEMPLATE_TESTS = {
# translation of a constant string
'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
+
+ ### MULTILINE #############################################################
+
+ 'multiline01': ("""
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """,
+ {},
+ """
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """),
+
+ ### REGROUP TAG ###########################################################
+ #'regroupXX': ('', {}, ''),
+ 'regroup01': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {'data': [ {'foo':'c', 'bar':1},
+ {'foo':'d', 'bar':1},
+ {'foo':'a', 'bar':2},
+ {'foo':'b', 'bar':2},
+ {'foo':'x', 'bar':3} ]},
+ '1:cd,2:ab,3:x,'),
+
+ # Test for silent failure when target variable isn't found
+ 'regroup02': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {}, ''),
+
+ ### TEMPLATETAG TAG #######################################################
+ #'templatetagXX': ('', {}, ''),
+ 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
+ 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
+ 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
+ 'templatetag04': ('{% templatetag closevariable %}', {}, '}}'),
+ 'templatetag05': ('{% templatetag %}', {}, template.TemplateSyntaxError),
+ 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
+
+ ### WIDTHRATIO TAG ########################################################
+ #'widthratioXX': ('', {}, ''),
+ 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
+ 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
+ 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
+ 'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
+ 'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
+
+ # 62.5 should round to 63
+ 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
+
+ # 71.4 should round to 71
+ 'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
+
+ # Raise exception if we don't have 3 args, last one an integer
+ 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
+ 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+ 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+
+ ### NOW TAG ########################################################
+ # Simple case
+ 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
+
+ # Check parsing of escaped and special characters
+ 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
+# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
+# 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
}
def test_template_loader(template_name, template_dirs=None):
@@ -358,6 +466,9 @@ def run_tests(verbosity=0, standalone=False):
failed_tests = []
tests = TEMPLATE_TESTS.items()
tests.sort()
+
+ # Turn TEMPLATE_DEBUG off, because tests assume that.
+ old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
for name, vals in tests:
install()
if 'LANGUAGE_CODE' in vals[1]:
@@ -387,6 +498,8 @@ def run_tests(verbosity=0, standalone=False):
failed_tests.append(name)
loader.template_source_loaders = old_template_loaders
deactivate()
+ settings.TEMPLATE_DEBUG = old_td
+
if failed_tests and not standalone:
msg = "Template tests %s failed." % failed_tests
if not verbosity:
diff --git a/tests/runtests.py b/tests/runtests.py
index 9226361090..3f8eda6ab4 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -7,7 +7,7 @@ import os, re, sys, time, traceback
# and Django aims to work with Python 2.3+.
import doctest
-APP_NAME = 'testapp'
+MODEL_TESTS_DIR_NAME = 'modeltests'
OTHER_TESTS_DIR = "othertests"
TEST_DATABASE_NAME = 'django_test_db'
@@ -18,10 +18,10 @@ def log_error(model_name, title, description):
'description': description,
})
-MODEL_DIR = os.path.join(os.path.dirname(__file__), APP_NAME, 'models')
+MODEL_TEST_DIR = os.path.join(os.path.dirname(__file__), MODEL_TESTS_DIR_NAME)
def get_test_models():
- return [f[:-3] for f in os.listdir(MODEL_DIR) if f.endswith('.py') and not f.startswith('__init__')]
+ return [f for f in os.listdir(MODEL_TEST_DIR) if not f.startswith('__init__') and not f.startswith('.')]
class DjangoDoctestRunner(doctest.DocTestRunner):
def __init__(self, verbosity_level, *args, **kwargs):
@@ -39,9 +39,13 @@ class DjangoDoctestRunner(doctest.DocTestRunner):
"Code: %r\nLine: %s\nExpected: %r\nGot: %r" % (example.source.strip(), example.lineno, example.want, got))
def report_unexpected_exception(self, out, test, example, exc_info):
+ from django.db import transaction
tb = ''.join(traceback.format_exception(*exc_info)[1:])
log_error(test.name, "API test raised an exception",
"Code: %r\nLine: %s\nException: %s" % (example.source.strip(), example.lineno, tb))
+ # Rollback, in case of database errors. Otherwise they'd have
+ # side effects on other tests.
+ transaction.rollback_unless_managed()
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
@@ -68,14 +72,27 @@ class TestRunner:
def run_tests(self):
from django.conf import settings
- from django.core.db import db
- from django.core import management, meta
- # Manually set INSTALLED_APPS to point to the test app.
- settings.INSTALLED_APPS = (APP_NAME,)
+ # Manually set INSTALLED_APPS to point to the test models.
+ settings.INSTALLED_APPS = [MODEL_TESTS_DIR_NAME + '.' + a for a in get_test_models()]
+
+ # Manually set DEBUG = False.
+ settings.DEBUG = False
+
+ from django.db import connection
+ from django.core import management
+ import django.db.models
# Determine which models we're going to test.
test_models = get_test_models()
+ if 'othertests' in self.which_tests:
+ self.which_tests.remove('othertests')
+ run_othertests = True
+ if not self.which_tests:
+ test_models = []
+ else:
+ run_othertests = not self.which_tests
+
if self.which_tests:
# Only run the specified tests.
bad_models = [m for m in self.which_tests if m not in test_models]
@@ -96,9 +113,9 @@ class TestRunner:
# Create the test database and connect to it. We need autocommit()
# because PostgreSQL doesn't allow CREATE DATABASE statements
# within transactions.
- cursor = db.cursor()
+ cursor = connection.cursor()
try:
- db.connection.autocommit(1)
+ connection.connection.autocommit(1)
except AttributeError:
pass
self.output(1, "Creating test database")
@@ -113,43 +130,62 @@ class TestRunner:
else:
print "Tests cancelled."
return
- db.close()
+ connection.close()
old_database_name = settings.DATABASE_NAME
settings.DATABASE_NAME = TEST_DATABASE_NAME
# Initialize the test database.
- cursor = db.cursor()
- self.output(1, "Initializing test database")
- management.init()
+ cursor = connection.cursor()
# Run the tests for each test model.
self.output(1, "Running app tests")
for model_name in test_models:
self.output(1, "%s model: Importing" % model_name)
try:
- mod = meta.get_app(model_name)
+ # TODO: Abstract this into a meta.get_app() replacement?
+ mod = __import__(MODEL_TESTS_DIR_NAME + '.' + model_name + '.models', '', '', [''])
except Exception, e:
log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
continue
- self.output(1, "%s model: Installing" % model_name)
- management.install(mod)
- # Run the API tests.
- p = doctest.DocTestParser()
- test_namespace = dict([(m._meta.module_name, getattr(mod, m._meta.module_name)) for m in mod._MODELS])
- dtest = p.get_doctest(mod.API_TESTS, test_namespace, model_name, None, None)
- # Manually set verbose=False, because "-v" command-line parameter
- # has side effects on doctest TestRunner class.
- runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
- self.output(1, "%s model: Running tests" % model_name)
- try:
+ if not getattr(mod, 'error_log', None):
+ # Model is not marked as an invalid model
+ self.output(1, "%s model: Installing" % model_name)
+ management.install(mod)
+
+ # Run the API tests.
+ p = doctest.DocTestParser()
+ test_namespace = dict([(m._meta.object_name, m) \
+ for m in django.db.models.get_models(mod)])
+ dtest = p.get_doctest(mod.API_TESTS, test_namespace, model_name, None, None)
+ # Manually set verbose=False, because "-v" command-line parameter
+ # has side effects on doctest TestRunner class.
+ runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
+ self.output(1, "%s model: Running tests" % model_name)
runner.run(dtest, clear_globs=True, out=sys.stdout.write)
- finally:
- # Rollback, in case of database errors. Otherwise they'd have
- # side effects on other tests.
- db.rollback()
+ else:
+ # Check that model known to be invalid is invalid for the right reasons.
+ self.output(1, "%s model: Validating" % model_name)
+
+ from cStringIO import StringIO
+ s = StringIO()
+ count = management.get_validation_errors(s, mod)
+ s.seek(0)
+ error_log = s.read()
+ actual = error_log.split('\n')
+ expected = mod.error_log.split('\n')
+
+ unexpected = [err for err in actual if err not in expected]
+ missing = [err for err in expected if err not in actual]
+
+ if unexpected or missing:
+ unexpected_log = '\n'.join(unexpected)
+ missing_log = '\n'.join(missing)
+ log_error(model_name,
+ "Validator found %d validation errors, %d expected" % (count, len(expected) - 1),
+ "Missing errors:\n%s\n\nUnexpected errors:\n%s" % (missing_log, unexpected_log))
- if not self.which_tests:
+ if run_othertests:
# Run the non-model tests in the other tests dir
self.output(1, "Running other tests")
other_tests_dir = os.path.join(os.path.dirname(__file__), OTHER_TESTS_DIR)
@@ -180,12 +216,12 @@ class TestRunner:
# to do so, because it's not allowed to delete a database while being
# connected to it.
if settings.DATABASE_ENGINE != "sqlite3":
- db.close()
+ connection.close()
settings.DATABASE_NAME = old_database_name
- cursor = db.cursor()
+ cursor = connection.cursor()
self.output(1, "Deleting test database")
try:
- db.connection.autocommit(1)
+ connection.connection.autocommit(1)
except AttributeError:
pass
else:
diff --git a/tests/testapp/models/__init__.py b/tests/testapp/models/__init__.py
deleted file mode 100644
index a5a41035d6..0000000000
--- a/tests/testapp/models/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-__all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many',
- 'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one',
- 'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk',
- 'subclassing', 'many_to_one_null', 'custom_columns', 'reserved_names',
- 'or_lookups', 'm2m_multiple']
diff --git a/tests/testapp/models/basic.py b/tests/testapp/models/basic.py
deleted file mode 100644
index 7261b8783f..0000000000
--- a/tests/testapp/models/basic.py
+++ /dev/null
@@ -1,204 +0,0 @@
-"""
-1. Bare-bones model
-
-This is a basic model with only two non-primary-key fields.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100, default='Default headline')
- pub_date = meta.DateTimeField()
-
-API_TESTS = """
-# No articles are in the system yet.
->>> articles.get_list()
-[]
-
-# Create an Article.
->>> from datetime import datetime
->>> a = articles.Article(id=None, headline='Area man programs in Python',
-... pub_date=datetime(2005, 7, 28))
-
-# Save it into the database. You have to call save() explicitly.
->>> a.save()
-
-# Now it has an ID. Note it's a long integer, as designated by the trailing "L".
->>> a.id
-1L
-
-# Access database columns via Python attributes.
->>> a.headline
-'Area man programs in Python'
->>> a.pub_date
-datetime.datetime(2005, 7, 28, 0, 0)
-
-# Change values by changing the attributes, then calling save().
->>> a.headline = 'Area woman programs in Python'
->>> a.save()
-
-# get_list() displays all the articles in the database. Note that the article
-# is represented by "<Article object>", because we haven't given the Article
-# model a __repr__() method.
->>> articles.get_list()
-[<Article object>]
-
-# Django provides a rich database lookup API that's entirely driven by
-# keyword arguments.
->>> articles.get_object(id__exact=1)
-<Article object>
->>> articles.get_object(headline__startswith='Area woman')
-<Article object>
->>> articles.get_object(pub_date__year=2005)
-<Article object>
->>> articles.get_object(pub_date__year=2005, pub_date__month=7)
-<Article object>
->>> articles.get_object(pub_date__year=2005, pub_date__month=7, pub_date__day=28)
-<Article object>
-
->>> articles.get_list(pub_date__year=2005)
-[<Article object>]
->>> articles.get_list(pub_date__year=2004)
-[]
->>> articles.get_list(pub_date__year=2005, pub_date__month=7)
-[<Article object>]
-
-# Django raises an ArticleDoesNotExist exception for get_object()
->>> articles.get_object(id__exact=2)
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for {'order_by': (), 'id__exact': 2}
-
->>> articles.get_object(pub_date__year=2005, pub_date__month=8)
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for ...
-
-# 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 articles.get_object(id__exact=1).
->>> articles.get_object(pk=1)
-<Article object>
-
-# Model instances of the same type and same ID are considered equal.
->>> a = articles.get_object(pk=1)
->>> b = articles.get_object(pk=1)
->>> a == b
-True
-
-# You can initialize a model instance using positional arguments, which should
-# match the field order as defined in the model...
->>> a2 = articles.Article(None, 'Second article', datetime(2005, 7, 29))
->>> a2.save()
->>> a2.id
-2L
->>> a2.headline
-'Second article'
->>> a2.pub_date
-datetime.datetime(2005, 7, 29, 0, 0)
-
-# ...or, you can use keyword arguments.
->>> a3 = articles.Article(id=None, headline='Third article',
-... pub_date=datetime(2005, 7, 30))
->>> a3.save()
->>> a3.id
-3L
->>> a3.headline
-'Third article'
->>> a3.pub_date
-datetime.datetime(2005, 7, 30, 0, 0)
-
-# You can also mix and match position and keyword arguments, but be sure not to
-# duplicate field information.
->>> a4 = articles.Article(None, 'Fourth article', pub_date=datetime(2005, 7, 31))
->>> a4.save()
->>> a4.headline
-'Fourth article'
-
-# Don't use invalid keyword arguments.
->>> a5 = articles.Article(id=None, headline='Invalid', pub_date=datetime(2005, 7, 31), foo='bar')
-Traceback (most recent call last):
- ...
-TypeError: 'foo' is an invalid keyword argument for this function
-
-# You can leave off the ID.
->>> a5 = articles.Article(headline='Article 6', pub_date=datetime(2005, 7, 31))
->>> a5.save()
->>> a5.id
-5L
->>> a5.headline
-'Article 6'
-
-# If you leave off a field with "default" set, Django will use the default.
->>> a6 = articles.Article(pub_date=datetime(2005, 7, 31))
->>> a6.save()
->>> a6.headline
-'Default headline'
-
-# For DateTimeFields, Django saves as much precision (in seconds) as you
-# give it.
->>> a7 = articles.Article(headline='Article 7', pub_date=datetime(2005, 7, 31, 12, 30))
->>> a7.save()
->>> articles.get_object(id__exact=7).pub_date
-datetime.datetime(2005, 7, 31, 12, 30)
-
->>> a8 = articles.Article(headline='Article 8', pub_date=datetime(2005, 7, 31, 12, 30, 45))
->>> a8.save()
->>> articles.get_object(id__exact=8).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45)
->>> a8.id
-8L
-
-# Saving an object again shouldn't create a new object -- it just saves the old one.
->>> a8.save()
->>> a8.id
-8L
->>> a8.headline = 'Updated article 8'
->>> a8.save()
->>> a8.id
-8L
-
->>> a7 == a8
-False
->>> a8 == articles.get_object(id__exact=8)
-True
->>> a7 != a8
-True
->>> articles.get_object(id__exact=8) != articles.get_object(id__exact=7)
-True
->>> articles.get_object(id__exact=8) == articles.get_object(id__exact=7)
-False
-"""
-
-from django.conf import settings
-
-building_docs = getattr(settings, 'BUILDING_DOCS', False)
-
-if building_docs or settings.DATABASE_ENGINE == 'postgresql':
- API_TESTS += """
-# In PostgreSQL, microsecond-level precision is available.
->>> a9 = articles.Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
->>> a9.save()
->>> articles.get_object(id__exact=9).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
-"""
-
-if building_docs or settings.DATABASE_ENGINE == 'mysql':
- API_TESTS += """
-# In MySQL, microsecond-level precision isn't available. You'll lose
-# microsecond-level precision once the data is saved.
->>> a9 = articles.Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
->>> a9.save()
->>> articles.get_object(id__exact=9).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45)
-"""
-
-API_TESTS += """
-
-# You can manually specify the primary key when creating a new object
->>> a101 = articles.Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
->>> a101.save()
->>> a101 = articles.get_object(pk=101)
->>> a101.headline
-'Article 101'
-"""
diff --git a/tests/testapp/models/custom_methods.py b/tests/testapp/models/custom_methods.py
deleted file mode 100644
index 4f175752b4..0000000000
--- a/tests/testapp/models/custom_methods.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-3. Giving models custom methods and custom module-level functions
-
-Any method you add to a model will be available to instances.
-
-Custom methods have the same namespace as if the model class were defined
-in the dynamically-generated module. That is, methods can access
-``get_list()``, ``get_object()``, ``AddManipulator``, and all other
-module-level objects.
-
-Also, custom methods have access to a few commonly-used objects for
-convenience:
-
- * The ``datetime`` module from Python's standard library.
- * The ``db`` object from ``django.core.db``. This represents the database
- connection, so you can do custom queries via a cursor object.
-
-If your model method starts with "_module_", it'll be a module-level function
-instead of a method. Otherwise, custom module-level functions have the same
-namespace as custom methods.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateField()
-
- def __repr__(self):
- return self.headline
-
- def was_published_today(self):
- return self.pub_date == datetime.date.today()
-
- def get_articles_from_same_day_1(self):
- return get_list(id__ne=self.id, pub_date__exact=self.pub_date)
-
- def get_articles_from_same_day_2(self):
- """
- Verbose version of get_articles_from_same_day_1, which does a custom
- database query for the sake of demonstration.
- """
- cursor = db.cursor()
- cursor.execute("""
- SELECT id, headline, pub_date
- FROM custom_methods_articles
- WHERE pub_date = %s
- AND id != %s""", [str(self.pub_date), self.id])
- # The asterisk in "Article(*row)" tells Python to expand the list into
- # positional arguments to Article().
- return [Article(*row) for row in cursor.fetchall()]
-
-API_TESTS = """
-# Create a couple of Articles.
->>> from datetime import date
->>> a = articles.Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27))
->>> a.save()
->>> b = articles.Article(id=None, headline='Beatles reunite', pub_date=date(2005, 7, 27))
->>> b.save()
-
-# Test the custom methods.
->>> a.was_published_today()
-False
->>> a.get_articles_from_same_day_1()
-[Beatles reunite]
->>> a.get_articles_from_same_day_2()
-[Beatles reunite]
->>> b.get_articles_from_same_day_1()
-[Area man programs in Python]
->>> b.get_articles_from_same_day_2()
-[Area man programs in Python]
-"""
diff --git a/tests/testapp/models/custom_pk.py b/tests/testapp/models/custom_pk.py
deleted file mode 100644
index 24041d64cd..0000000000
--- a/tests/testapp/models/custom_pk.py
+++ /dev/null
@@ -1,69 +0,0 @@
-"""
-14. Using a custom primary key
-
-By default, Django adds an ``"id"`` field to each model. But you can override
-this behavior by explicitly adding ``primary_key=True`` to a field.
-"""
-
-from django.core import meta
-
-class Employee(meta.Model):
- employee_code = meta.CharField(maxlength=10, primary_key=True)
- first_name = meta.CharField(maxlength=20)
- last_name = meta.CharField(maxlength=20)
- class META:
- ordering = ('last_name', 'first_name')
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
-class Business(meta.Model):
- name = meta.CharField(maxlength=20, primary_key=True)
- employees = meta.ManyToManyField(Employee)
- class META:
- verbose_name_plural = 'businesses'
- module_name = 'businesses'
-
- def __repr__(self):
- return self.name
-
-API_TESTS = """
->>> dan = employees.Employee(employee_code='ABC123', first_name='Dan', last_name='Jones')
->>> dan.save()
->>> employees.get_list()
-[Dan Jones]
-
->>> fran = employees.Employee(employee_code='XYZ456', first_name='Fran', last_name='Bones')
->>> fran.save()
->>> employees.get_list()
-[Fran Bones, Dan Jones]
-
->>> employees.get_object(pk='ABC123')
-Dan Jones
->>> employees.get_object(pk='XYZ456')
-Fran Bones
->>> employees.get_object(pk='foo')
-Traceback (most recent call last):
- ...
-EmployeeDoesNotExist: Employee does not exist for {'pk': 'foo', 'order_by': ()}
-
-# Fran got married and changed her last name.
->>> fran = employees.get_object(pk='XYZ456')
->>> fran.last_name = 'Jones'
->>> fran.save()
->>> employees.get_list(last_name__exact='Jones')
-[Dan Jones, Fran Jones]
->>> employees.get_in_bulk(['ABC123', 'XYZ456'])
-{'XYZ456': Fran Jones, 'ABC123': Dan Jones}
-
->>> b = businesses.Business(name='Sears')
->>> b.save()
->>> b.set_employees([dan.employee_code, fran.employee_code])
-True
->>> b.get_employee_list()
-[Dan Jones, Fran Jones]
->>> fran.get_business_list()
-[Sears]
->>> businesses.get_in_bulk(['Sears'])
-{'Sears': Sears}
-"""
diff --git a/tests/testapp/models/get_latest.py b/tests/testapp/models/get_latest.py
deleted file mode 100644
index 86697e85a7..0000000000
--- a/tests/testapp/models/get_latest.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-8. get_latest_by
-
-Models can have a ``get_latest_by`` attribute, which should be set to the name
-of a DateField or DateTimeField. If ``get_latest_by`` exists, the model's
-module will get a ``get_latest()`` function, which will return the latest
-object in the database according to that field. "Latest" means "having the
-date farthest into the future."
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
- class META:
- get_latest_by = 'pub_date'
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Because no Articles exist yet, get_latest() raises ArticleDoesNotExist.
->>> articles.get_latest()
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for {'order_by': ('-pub_date',), 'limit': 1}
-
-# Create a couple of Articles.
->>> from datetime import datetime
->>> a1 = articles.Article(id=None, headline='Article 1', pub_date=datetime(2005, 7, 26))
->>> a1.save()
->>> a2 = articles.Article(id=None, headline='Article 2', pub_date=datetime(2005, 7, 27))
->>> a2.save()
->>> a3 = articles.Article(id=None, headline='Article 3', pub_date=datetime(2005, 7, 27))
->>> a3.save()
->>> a4 = articles.Article(id=None, headline='Article 4', pub_date=datetime(2005, 7, 28))
->>> a4.save()
-
-# Get the latest Article.
->>> articles.get_latest()
-Article 4
-"""
diff --git a/tests/testapp/models/lookup.py b/tests/testapp/models/lookup.py
deleted file mode 100644
index 03f5c7ff71..0000000000
--- a/tests/testapp/models/lookup.py
+++ /dev/null
@@ -1,153 +0,0 @@
-"""
-7. The lookup API
-
-This demonstrates features of the database API.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
- class META:
- ordering = ('-pub_date', 'headline')
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a couple of Articles.
->>> from datetime import datetime
->>> a1 = articles.Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
->>> a1.save()
->>> a2 = articles.Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
->>> a2.save()
->>> a3 = articles.Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
->>> a3.save()
->>> a4 = articles.Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
->>> a4.save()
->>> a5 = articles.Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0))
->>> a5.save()
->>> a6 = articles.Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0))
->>> a6.save()
->>> a7 = articles.Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
->>> a7.save()
-
-# get_iterator() is just like get_list(), but it's a generator.
->>> for a in articles.get_iterator():
-... print a.headline
-Article 5
-Article 6
-Article 4
-Article 2
-Article 3
-Article 7
-Article 1
-
-# get_iterator() takes the same lookup arguments as get_list().
->>> for a in articles.get_iterator(headline__endswith='4'):
-... print a.headline
-Article 4
-
-# get_count() returns the number of objects matching search criteria.
->>> articles.get_count()
-7L
->>> articles.get_count(pub_date__exact=datetime(2005, 7, 27))
-3L
->>> articles.get_count(headline__startswith='Blah blah')
-0L
-
-# get_in_bulk() takes a list of IDs and returns a dictionary mapping IDs
-# to objects.
->>> articles.get_in_bulk([1, 2])
-{1: Article 1, 2: Article 2}
->>> articles.get_in_bulk([3])
-{3: Article 3}
->>> articles.get_in_bulk([1000])
-{}
->>> articles.get_in_bulk([])
-Traceback (most recent call last):
- ...
-AssertionError: get_in_bulk() cannot be passed an empty list.
-
-# get_values() is just like get_list(), except it returns a list of
-# dictionaries instead of object instances -- and you can specify which fields
-# you want to retrieve.
->>> articles.get_values(fields=['headline'])
-[{'headline': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 7'}, {'headline': 'Article 1'}]
->>> articles.get_values(pub_date__exact=datetime(2005, 7, 27), fields=['id'])
-[{'id': 2}, {'id': 3}, {'id': 7}]
->>> articles.get_values(fields=['id', 'headline']) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}]
-True
-
-# get_values_iterator() is just like get_values(), but it's a generator.
->>> for d in articles.get_values_iterator(fields=['id', 'headline']):
-... i = d.items()
-... i.sort()
-... i
-[('headline', 'Article 5'), ('id', 5)]
-[('headline', 'Article 6'), ('id', 6)]
-[('headline', 'Article 4'), ('id', 4)]
-[('headline', 'Article 2'), ('id', 2)]
-[('headline', 'Article 3'), ('id', 3)]
-[('headline', 'Article 7'), ('id', 7)]
-[('headline', 'Article 1'), ('id', 1)]
-
-# Every DateField and DateTimeField creates get_next_by_FOO() and
-# get_previous_by_FOO() methods.
-# 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.
->>> a1.get_next_by_pub_date()
-Article 2
->>> a2.get_next_by_pub_date()
-Article 3
->>> a3.get_next_by_pub_date()
-Article 7
->>> a4.get_next_by_pub_date()
-Article 6
->>> a5.get_next_by_pub_date()
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for ...
->>> a6.get_next_by_pub_date()
-Article 5
->>> a7.get_next_by_pub_date()
-Article 4
-
->>> a7.get_previous_by_pub_date()
-Article 3
->>> a6.get_previous_by_pub_date()
-Article 4
->>> a5.get_previous_by_pub_date()
-Article 6
->>> a4.get_previous_by_pub_date()
-Article 7
->>> a3.get_previous_by_pub_date()
-Article 2
->>> a2.get_previous_by_pub_date()
-Article 1
-
-# Every DateField and DateTimeField give their model module a get_FOO_list
-# function.
->>> articles.get_pub_date_list('year')
-[datetime.datetime(2005, 1, 1, 0, 0)]
->>> articles.get_pub_date_list('month')
-[datetime.datetime(2005, 7, 1, 0, 0), datetime.datetime(2005, 8, 1, 0, 0)]
->>> articles.get_pub_date_list('day')
-[datetime.datetime(2005, 7, 26, 0, 0), datetime.datetime(2005, 7, 27, 0, 0), datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 8, 1, 0, 0)]
-
-# Underscores and percent signs have special meaning in the underlying
-# database library, but Django handles the quoting of them automatically.
->>> a8 = articles.Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20))
->>> a8.save()
->>> articles.get_list(headline__startswith='Article')
-[Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
->>> articles.get_list(headline__startswith='Article_')
-[Article_ with underscore]
->>> a9 = articles.Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21))
->>> a9.save()
->>> articles.get_list(headline__startswith='Article')
-[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
->>> articles.get_list(headline__startswith='Article%')
-[Article% with percent sign]
-"""
diff --git a/tests/testapp/models/m2m_intermediary.py b/tests/testapp/models/m2m_intermediary.py
deleted file mode 100644
index 2a20072e03..0000000000
--- a/tests/testapp/models/m2m_intermediary.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""
-9. Many-to-many relationships via an intermediary table
-
-For many-to-many relationships that need extra fields on the intermediary
-table, use an intermediary model.
-
-In this example, an ``Article`` can have multiple ``Reporter``s, and each
-``Article``-``Reporter`` combination (a ``Writer``) has a ``position`` field,
-which specifies the ``Reporter``'s position for the given article (e.g. "Staff
-writer").
-"""
-
-from django.core import meta
-
-class Reporter(meta.Model):
- first_name = meta.CharField(maxlength=30)
- last_name = meta.CharField(maxlength=30)
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateField()
-
- def __repr__(self):
- return self.headline
-
-class Writer(meta.Model):
- reporter = meta.ForeignKey(Reporter)
- article = meta.ForeignKey(Article)
- position = meta.CharField(maxlength=100)
-
- def __repr__(self):
- return '%r (%s)' % (self.get_reporter(), self.position)
-
-API_TESTS = """
-# Create a few Reporters.
->>> r1 = reporters.Reporter(first_name='John', last_name='Smith')
->>> r1.save()
->>> r2 = reporters.Reporter(first_name='Jane', last_name='Doe')
->>> r2.save()
-
-# Create an Article.
->>> from datetime import datetime
->>> a = articles.Article(headline='This is a test', pub_date=datetime(2005, 7, 27))
->>> a.save()
-
-# Create a few Writers.
->>> w1 = writers.Writer(reporter=r1, article=a, position='Main writer')
->>> w1.save()
->>> w2 = writers.Writer(reporter=r2, article=a, position='Contributor')
->>> w2.save()
-
-# Play around with the API.
->>> a.get_writer_list(order_by=['-position'], select_related=True)
-[John Smith (Main writer), Jane Doe (Contributor)]
->>> w1.get_reporter()
-John Smith
->>> w2.get_reporter()
-Jane Doe
->>> w1.get_article()
-This is a test
->>> w2.get_article()
-This is a test
->>> r1.get_writer_list()
-[John Smith (Main writer)]
-"""
diff --git a/tests/testapp/models/m2m_multiple.py b/tests/testapp/models/m2m_multiple.py
deleted file mode 100644
index d8793acb73..0000000000
--- a/tests/testapp/models/m2m_multiple.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-20. Multiple many-to-many relationships between the same two tables
-
-In this example, an Article can have many Categories (as "primary") and many
-Categories (as "secondary").
-
-Set ``related_name`` to designate what the reverse relationship is called.
-
-Set ``singular`` to designate what the category object is called. This is
-required if a model has multiple ``ManyToManyFields`` to the same object.
-"""
-
-from django.core import meta
-
-class Category(meta.Model):
- name = meta.CharField(maxlength=20)
- class META:
- module_name = 'categories'
- ordering = ('name',)
-
- def __repr__(self):
- return self.name
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=50)
- pub_date = meta.DateTimeField()
- primary_categories = meta.ManyToManyField(Category,
- singular='primary_category', related_name='primary_article')
- secondary_categories = meta.ManyToManyField(Category,
- singular='secondary_category', related_name='secondary_article')
- class META:
- ordering = ('pub_date',)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
->>> from datetime import datetime
-
->>> c1 = categories.Category(name='Sports')
->>> c1.save()
->>> c2 = categories.Category(name='News')
->>> c2.save()
->>> c3 = categories.Category(name='Crime')
->>> c3.save()
->>> c4 = categories.Category(name='Life')
->>> c4.save()
-
->>> a1 = articles.Article(headline='Area man steals', pub_date=datetime(2005, 11, 27))
->>> a1.save()
->>> a1.set_primary_categories([c2.id, c3.id])
-True
->>> a1.set_secondary_categories([c4.id])
-True
-
->>> a2 = articles.Article(headline='Area man runs', pub_date=datetime(2005, 11, 28))
->>> a2.save()
->>> a2.set_primary_categories([c1.id, c2.id])
-True
->>> a2.set_secondary_categories([c4.id])
-True
-
-# The "primary_category" here comes from the "singular" parameter. If we hadn't
-# specified the "singular" parameter, Django would just use "category", which
-# would cause a conflict because the "primary_categories" and
-# "secondary_categories" fields both relate to Category.
->>> a1.get_primary_category_list()
-[Crime, News]
-
-# Ditto for the "primary_category" here.
->>> a2.get_primary_category_list()
-[News, Sports]
-
-# Ditto for the "secondary_category" here.
->>> a1.get_secondary_category_list()
-[Life]
-
-# Ditto for the "secondary_category" here.
->>> a2.get_secondary_category_list()
-[Life]
-
-
->>> c1.get_primary_article_list()
-[Area man runs]
->>> c1.get_secondary_article_list()
-[]
->>> c2.get_primary_article_list()
-[Area man steals, Area man runs]
->>> c2.get_secondary_article_list()
-[]
->>> c3.get_primary_article_list()
-[Area man steals]
->>> c3.get_secondary_article_list()
-[]
->>> c4.get_primary_article_list()
-[]
->>> c4.get_secondary_article_list()
-[Area man steals, Area man runs]
-"""
diff --git a/tests/testapp/models/m2o_recursive.py b/tests/testapp/models/m2o_recursive.py
deleted file mode 100644
index 27d13b4e7e..0000000000
--- a/tests/testapp/models/m2o_recursive.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-11. Relating an object to itself, many-to-one
-
-To define a many-to-one relationship between a model and itself, use
-``ForeignKey('self')``.
-
-In this example, a ``Category`` is related to itself. That is, each
-``Category`` has a parent ``Category``.
-
-Set ``related_name`` to designate what the reverse relationship is called.
-"""
-
-from django.core import meta
-
-class Category(meta.Model):
- name = meta.CharField(maxlength=20)
- parent = meta.ForeignKey('self', null=True, related_name='child')
- class META:
- module_name = 'categories'
-
- def __repr__(self):
- return self.name
-
-API_TESTS = """
-# Create a few Category objects.
->>> r = categories.Category(id=None, name='Root category', parent=None)
->>> r.save()
->>> c = categories.Category(id=None, name='Child category', parent=r)
->>> c.save()
-
->>> r.get_child_list()
-[Child category]
->>> r.get_child(name__startswith='Child')
-Child category
->>> r.get_parent()
-Traceback (most recent call last):
- ...
-CategoryDoesNotExist
-
->>> c.get_child_list()
-[]
->>> c.get_parent()
-Root category
-"""
diff --git a/tests/testapp/models/m2o_recursive2.py b/tests/testapp/models/m2o_recursive2.py
deleted file mode 100644
index 52aa0f8b69..0000000000
--- a/tests/testapp/models/m2o_recursive2.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-12. Relating a model to another model more than once
-
-In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of
-which are other ``Person`` objects.
-
-Set ``related_name`` to designate what the reverse relationship is called.
-"""
-
-from django.core import meta
-
-class Person(meta.Model):
- full_name = meta.CharField(maxlength=20)
- mother = meta.ForeignKey('self', null=True, related_name='mothers_child')
- father = meta.ForeignKey('self', null=True, related_name='fathers_child')
-
- def __repr__(self):
- return self.full_name
-
-API_TESTS = """
-# Create two Person objects -- the mom and dad in our family.
->>> dad = persons.Person(full_name='John Smith Senior', mother=None, father=None)
->>> dad.save()
->>> mom = persons.Person(full_name='Jane Smith', mother=None, father=None)
->>> mom.save()
-
-# Give mom and dad a kid.
->>> kid = persons.Person(full_name='John Smith Junior', mother=mom, father=dad)
->>> kid.save()
-
->>> kid.get_mother()
-Jane Smith
->>> kid.get_father()
-John Smith Senior
->>> dad.get_fathers_child_list()
-[John Smith Junior]
->>> mom.get_mothers_child_list()
-[John Smith Junior]
->>> kid.get_mothers_child_list()
-[]
->>> kid.get_fathers_child_list()
-[]
-"""
diff --git a/tests/testapp/models/many_to_many.py b/tests/testapp/models/many_to_many.py
deleted file mode 100644
index 91addafe9b..0000000000
--- a/tests/testapp/models/many_to_many.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-5. Many-to-many relationships
-
-To define a many-to-many relationship, use ManyToManyField().
-
-In this example, an article can be published in multiple publications,
-and a publication has multiple articles.
-"""
-
-from django.core import meta
-
-class Publication(meta.Model):
- title = meta.CharField(maxlength=30)
-
- def __repr__(self):
- return self.title
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- publications = meta.ManyToManyField(Publication)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a couple of Publications.
->>> p1 = publications.Publication(id=None, title='The Python Journal')
->>> p1.save()
->>> p2 = publications.Publication(id=None, title='Science News')
->>> p2.save()
-
-# Create an Article.
->>> a1 = articles.Article(id=None, headline='Django lets you build Web apps easily')
->>> a1.save()
-
-# Associate the Article with one Publication. set_publications() returns a
-# boolean, representing whether any records were added or deleted.
->>> a1.set_publications([p1.id])
-True
-
-# If we set it again, it'll return False, because the list of Publications
-# hasn't changed.
->>> a1.set_publications([p1.id])
-False
-
-# Create another Article, and set it to appear in both Publications.
->>> a2 = articles.Article(id=None, headline='NASA uses Python')
->>> a2.save()
->>> a2.set_publications([p1.id, p2.id])
-True
->>> a2.set_publications([p1.id])
-True
->>> a2.set_publications([p1.id, p2.id])
-True
-
-# Article objects have access to their related Publication objects.
->>> a1.get_publication_list()
-[The Python Journal]
->>> a2.get_publication_list()
-[The Python Journal, Science News]
-
-# Publication objects have access to their related Article objects.
->>> p2.get_article_list()
-[NASA uses Python]
->>> p1.get_article_list(order_by=['headline'])
-[Django lets you build Web apps easily, NASA uses Python]
-
-# If we delete a Publication, its Articles won't be able to access it.
->>> p1.delete()
->>> publications.get_list()
-[Science News]
->>> a1 = articles.get_object(pk=1)
->>> a1.get_publication_list()
-[]
-
-# If we delete an Article, its Publications won't be able to access it.
->>> a2.delete()
->>> articles.get_list()
-[Django lets you build Web apps easily]
->>> p1.get_article_list(order_by=['headline'])
-[Django lets you build Web apps easily]
-"""
diff --git a/tests/testapp/models/many_to_one.py b/tests/testapp/models/many_to_one.py
deleted file mode 100644
index 37828b6d82..0000000000
--- a/tests/testapp/models/many_to_one.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-4. Many-to-one relationships
-
-To define a many-to-one relationship, use ``ForeignKey()`` .
-"""
-
-from django.core import meta
-
-class Reporter(meta.Model):
- first_name = meta.CharField(maxlength=30)
- last_name = meta.CharField(maxlength=30)
- email = meta.EmailField()
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateField()
- reporter = meta.ForeignKey(Reporter)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a Reporter.
->>> r = reporters.Reporter(first_name='John', last_name='Smith', email='john@example.com')
->>> r.save()
-
-# Create an Article.
->>> from datetime import datetime
->>> a = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
->>> a.save()
-
->>> a.reporter_id
-1
-
->>> a.get_reporter()
-John Smith
-
-# Article objects have access to their related Reporter objects.
->>> r = a.get_reporter()
->>> r.first_name, r.last_name
-('John', 'Smith')
-
-# Create an Article via the Reporter object.
->>> new_article = r.add_article(headline="John's second story", pub_date=datetime(2005, 7, 29))
->>> new_article
-John's second story
->>> new_article.reporter_id
-1
-
-# Reporter objects have access to their related Article objects.
->>> r.get_article_list(order_by=['pub_date'])
-[This is a test, John's second story]
-
->>> r.get_article(headline__startswith='This')
-This is a test
-
->>> r.get_article_count()
-2
-
-# 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 Articles for any Reporter whose first name is "John".
->>> articles.get_list(reporter__first_name__exact='John', order_by=['pub_date'])
-[This is a test, John's second story]
-
-# Find all Articles for the Reporter whose ID is 1.
->>> articles.get_list(reporter__id__exact=1, order_by=['pub_date'])
-[This is a test, John's second story]
-
-# Note you need two underscores between "reporter" and "id" -- not one.
->>> articles.get_list(reporter_id__exact=1)
-Traceback (most recent call last):
- ...
-TypeError: got unexpected keyword argument 'reporter_id__exact'
-
-# "pk" shortcut syntax works in a related context, too.
->>> articles.get_list(reporter__pk=1, order_by=['pub_date'])
-[This is a test, John's second story]
-
-# You can also instantiate an Article by passing
-# the Reporter's ID instead of a Reporter object.
->>> a3 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id)
->>> a3.save()
->>> a3.reporter_id
-1
->>> a3.get_reporter()
-John Smith
-
-# Similarly, the reporter ID can be a string.
->>> a4 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1")
->>> a4.save()
->>> a4.get_reporter()
-John Smith
-"""
diff --git a/tests/testapp/models/many_to_one_null.py b/tests/testapp/models/many_to_one_null.py
deleted file mode 100644
index c3c92601f7..0000000000
--- a/tests/testapp/models/many_to_one_null.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-16. Many-to-one relationships that can be null
-
-To define a many-to-one relationship that can have a null foreign key, use
-``ForeignKey()`` with ``null=True`` .
-"""
-
-from django.core import meta
-
-class Reporter(meta.Model):
- name = meta.CharField(maxlength=30)
-
- def __repr__(self):
- return self.name
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- reporter = meta.ForeignKey(Reporter, null=True)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a Reporter.
->>> r = reporters.Reporter(name='John Smith')
->>> r.save()
-
-# Create an Article.
->>> a = articles.Article(headline="First", reporter=r)
->>> a.save()
-
->>> a.reporter_id
-1
-
->>> a.get_reporter()
-John Smith
-
-# Article objects have access to their related Reporter objects.
->>> r = a.get_reporter()
-
-# Create an Article via the Reporter object.
->>> a2 = r.add_article(headline="Second")
->>> a2
-Second
->>> a2.reporter_id
-1
-
-# Reporter objects have access to their related Article objects.
->>> r.get_article_list(order_by=['headline'])
-[First, Second]
->>> r.get_article(headline__startswith='Fir')
-First
->>> r.get_article_count()
-2
-
-# Create an Article with no Reporter by passing "reporter=None".
->>> a3 = articles.Article(headline="Third", reporter=None)
->>> a3.save()
->>> a3.id
-3
->>> a3.reporter_id
->>> print a3.reporter_id
-None
->>> a3 = articles.get_object(pk=3)
->>> print a3.reporter_id
-None
-
-# An article's get_reporter() method throws ReporterDoesNotExist
-# if the reporter is set to None.
->>> a3.get_reporter()
-Traceback (most recent call last):
- ...
-ReporterDoesNotExist
-
-# To retrieve the articles with no reporters set, use "reporter__isnull=True".
->>> articles.get_list(reporter__isnull=True)
-[Third]
-"""
diff --git a/tests/testapp/models/one_to_one.py b/tests/testapp/models/one_to_one.py
deleted file mode 100644
index b5d749c25d..0000000000
--- a/tests/testapp/models/one_to_one.py
+++ /dev/null
@@ -1,80 +0,0 @@
-"""
-10. One-to-one relationships
-
-To define a one-to-one relationship, use ``OneToOneField()``.
-
-In this example, a ``Place`` optionally can be a ``Restaurant``.
-"""
-
-from django.core import meta
-
-class Place(meta.Model):
- name = meta.CharField(maxlength=50)
- address = meta.CharField(maxlength=80)
-
- def __repr__(self):
- return "%s the place" % self.name
-
-class Restaurant(meta.Model):
- place = meta.OneToOneField(Place)
- serves_hot_dogs = meta.BooleanField()
- serves_pizza = meta.BooleanField()
-
- def __repr__(self):
- return "%s the restaurant" % self.get_place().name
-
-class Waiter(meta.Model):
- restaurant = meta.ForeignKey(Restaurant)
- name = meta.CharField(maxlength=50)
-
- def __repr__(self):
- return "%s the waiter at %r" % (self.name, self.get_restaurant())
-
-API_TESTS = """
-# Create a couple of Places.
->>> p1 = places.Place(name='Demon Dogs', address='944 W. Fullerton')
->>> p1.save()
->>> p2 = places.Place(name='Ace Hardware', address='1013 N. Ashland')
->>> p2.save()
-
-# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
->>> r = restaurants.Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
->>> r.save()
-
-# A Restaurant can access its place.
->>> r.get_place()
-Demon Dogs the place
-
-# A Place can access its restaurant, if available.
->>> p1.get_restaurant()
-Demon Dogs the restaurant
-
-# p2 doesn't have an associated restaurant.
->>> p2.get_restaurant()
-Traceback (most recent call last):
- ...
-RestaurantDoesNotExist: Restaurant does not exist for {'order_by': (), 'place__id__exact': ...}
-
-# restaurants.get_list() just returns the Restaurants, not the Places.
->>> restaurants.get_list()
-[Demon Dogs the restaurant]
-
-# places.get_list() returns all Places, regardless of whether they have
-# Restaurants.
->>> places.get_list(order_by=['name'])
-[Ace Hardware the place, Demon Dogs the place]
-
->>> restaurants.get_object(place__id__exact=1)
-Demon Dogs the restaurant
->>> restaurants.get_object(pk=1)
-Demon Dogs the restaurant
-
-# Add a Waiter to the Restaurant.
->>> w = r.add_waiter(name='Joe')
->>> w.save()
->>> w
-Joe the waiter at Demon Dogs the restaurant
-
->>> r = restaurants.get_object(pk=1)
->>> r.delete()
-"""
diff --git a/tests/testapp/models/or_lookups.py b/tests/testapp/models/or_lookups.py
deleted file mode 100644
index 0bf554e408..0000000000
--- a/tests/testapp/models/or_lookups.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
-19. OR lookups
-
-To perform an OR lookup, or a lookup that combines ANDs and ORs, use the
-``complex`` keyword argument, and pass it an expression of clauses using the
-variable ``django.core.meta.Q``.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=50)
- pub_date = meta.DateTimeField()
- class META:
- ordering = ('pub_date',)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
->>> from datetime import datetime
->>> from django.core.meta import Q
-
->>> a1 = articles.Article(headline='Hello', pub_date=datetime(2005, 11, 27))
->>> a1.save()
-
->>> a2 = articles.Article(headline='Goodbye', pub_date=datetime(2005, 11, 28))
->>> a2.save()
-
->>> a3 = articles.Article(headline='Hello and goodbye', pub_date=datetime(2005, 11, 29))
->>> a3.save()
-
->>> articles.get_list(complex=(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye')))
-[Hello, Goodbye, Hello and goodbye]
-
->>> articles.get_list(complex=(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye')))
-[]
-
->>> articles.get_list(complex=(Q(headline__startswith='Hello') & Q(headline__contains='bye')))
-[Hello and goodbye]
-
->>> articles.get_list(headline__startswith='Hello', complex=Q(headline__contains='bye'))
-[Hello and goodbye]
-
->>> articles.get_list(complex=(Q(headline__contains='Hello') | Q(headline__contains='bye')))
-[Hello, Goodbye, Hello and goodbye]
-
->>> articles.get_list(complex=(Q(headline__iexact='Hello') | Q(headline__contains='ood')))
-[Hello, Goodbye, Hello and goodbye]
-
->>> articles.get_list(complex=(Q(pk=1) | Q(pk=2)))
-[Hello, Goodbye]
-
->>> articles.get_list(complex=(Q(pk=1) | Q(pk=2) | Q(pk=3)))
-[Hello, Goodbye, Hello and goodbye]
-
-"""
diff --git a/tests/testapp/models/reserved_names.py b/tests/testapp/models/reserved_names.py
deleted file mode 100644
index eabe41e5bd..0000000000
--- a/tests/testapp/models/reserved_names.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
-18. Using SQL reserved names
-
-Need to use a reserved SQL name as a column name or table name? Need to include
-a hyphen in a column or table name? No problem. Django quotes names
-appropriately behind the scenes, so your database won't complain about
-reserved-name usage.
-"""
-
-from django.core import meta
-
-class Thing(meta.Model):
- when = meta.CharField(maxlength=1, primary_key=True)
- join = meta.CharField(maxlength=1)
- like = meta.CharField(maxlength=1)
- drop = meta.CharField(maxlength=1)
- alter = meta.CharField(maxlength=1)
- having = meta.CharField(maxlength=1)
- where = meta.CharField(maxlength=1)
- has_hyphen = meta.CharField(maxlength=1, db_column='has-hyphen')
- class META:
- db_table = 'select'
-
- def __repr__(self):
- return self.when
-
-API_TESTS = """
->>> t = things.Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where='g', has_hyphen='h')
->>> t.save()
->>> print t.when
-a
-
->>> u = things.Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where='n')
->>> u.save()
->>> print u.when
-h
-
->>> things.get_list(order_by=['when'])
-[a, h]
->>> v = things.get_object(pk='a')
->>> print v.join
-b
->>> print v.where
-g
->>> things.get_list(order_by=['select.when'])
-[a, h]
-"""
diff --git a/tests/testapp/models/save_delete_hooks.py b/tests/testapp/models/save_delete_hooks.py
deleted file mode 100644
index f0fa836f71..0000000000
--- a/tests/testapp/models/save_delete_hooks.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""
-13. Adding hooks before/after saving and deleting
-
-Django provides hooks for executing arbitrary code around ``save()`` and
-``delete()``. Just add any of the following methods to your model:
-
- * ``_pre_save()`` is called before an object is saved.
- * ``_post_save()`` is called after an object is saved.
- * ``_pre_delete()`` is called before an object is deleted.
- * ``_post_delete()`` is called after an object is deleted.
-"""
-
-from django.core import meta
-
-class Person(meta.Model):
- first_name = meta.CharField(maxlength=20)
- last_name = meta.CharField(maxlength=20)
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
- def _pre_save(self):
- print "Before save"
-
- def _post_save(self):
- print "After save"
-
- def _pre_delete(self):
- print "Before deletion"
-
- def _post_delete(self):
- print "After deletion"
-
-API_TESTS = """
->>> p1 = persons.Person(first_name='John', last_name='Smith')
->>> p1.save()
-Before save
-After save
-
->>> persons.get_list()
-[John Smith]
-
->>> p1.delete()
-Before deletion
-After deletion
-
->>> persons.get_list()
-[]
-"""
diff --git a/tests/testapp/models/subclassing.py b/tests/testapp/models/subclassing.py
deleted file mode 100644
index e0dad26acb..0000000000
--- a/tests/testapp/models/subclassing.py
+++ /dev/null
@@ -1,180 +0,0 @@
-"""
-15. Subclassing models
-
-You can subclass another model to create a copy of it that behaves slightly
-differently.
-"""
-
-from django.core import meta
-
-# From the "Bare-bones model" example
-from django.models.basic import Article
-
-# From the "Adding __repr__()" example
-from django.models.repr import Article as ArticleWithRepr
-
-# From the "Specifying ordering" example
-from django.models.ordering import Article as ArticleWithOrdering
-
-# This uses all fields and metadata from Article and
-# adds a "section" field.
-class ArticleWithSection(Article):
- section = meta.CharField(maxlength=30)
- class META:
- module_name = 'subarticles1'
-
-# This uses all fields and metadata from Article but
-# removes the "pub_date" field.
-class ArticleWithoutPubDate(Article):
- class META:
- module_name = 'subarticles2'
- remove_fields = ('pub_date',)
-
-# This uses all fields and metadata from Article but
-# overrides the "pub_date" field.
-class ArticleWithFieldOverride(Article):
- pub_date = meta.DateField() # overrides the old field, a DateTimeField
- class META:
- module_name = 'subarticles3'
- # No need to add remove_fields = ('pub_date',)
-
-# This uses all fields and metadata from ArticleWithRepr and
-# makes a few additions/changes.
-class ArticleWithManyChanges(ArticleWithRepr):
- section = meta.CharField(maxlength=30)
- is_popular = meta.BooleanField()
- pub_date = meta.DateField() # overrides the old field, a DateTimeField
- class META:
- module_name = 'subarticles4'
-
-# This uses all fields from ArticleWithOrdering but
-# changes the ordering parameter.
-class ArticleWithChangedMeta(ArticleWithOrdering):
- class META:
- module_name = 'subarticles5'
- ordering = ('headline', 'pub_date')
-
-# These two models don't define a module_name.
-class NoModuleNameFirst(Article):
- section = meta.CharField(maxlength=30)
-
-class NoModuleNameSecond(Article):
- section = meta.CharField(maxlength=30)
-
-API_TESTS = """
-# No data is in the system yet.
->>> subarticles1.get_list()
-[]
->>> subarticles2.get_list()
-[]
->>> subarticles3.get_list()
-[]
-
-# Create an ArticleWithSection.
->>> from datetime import date, datetime
->>> a1 = subarticles1.ArticleWithSection(headline='First', pub_date=datetime(2005, 8, 22), section='News')
->>> a1.save()
->>> a1
-<ArticleWithSection object>
->>> a1.id
-1
->>> a1.headline
-'First'
->>> a1.pub_date
-datetime.datetime(2005, 8, 22, 0, 0)
-
-# Retrieve it again, to prove the fields have been saved.
->>> a1 = subarticles1.get_object(pk=1)
->>> a1.headline
-'First'
->>> a1.pub_date
-datetime.datetime(2005, 8, 22, 0, 0)
->>> a1.section
-'News'
-
-# Create an ArticleWithoutPubDate.
->>> a2 = subarticles2.ArticleWithoutPubDate(headline='Second')
->>> a2.save()
->>> a2
-<ArticleWithoutPubDate object>
->>> a2.id
-1
->>> a2.pub_date
-Traceback (most recent call last):
- ...
-AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
-
-# Retrieve it again, to prove the fields have been saved.
->>> a2 = subarticles2.get_object(pk=1)
->>> a2.headline
-'Second'
->>> a2.pub_date
-Traceback (most recent call last):
- ...
-AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
-
-# Create an ArticleWithFieldOverride.
->>> a3 = subarticles3.ArticleWithFieldOverride(headline='Third', pub_date=date(2005, 8, 22))
->>> a3.save()
->>> a3
-<ArticleWithFieldOverride object>
->>> a3.id
-1
->>> a3.pub_date
-datetime.date(2005, 8, 22)
-
-# Retrieve it again, to prove the fields have been saved.
->>> a3 = subarticles3.get_object(pk=1)
->>> a3.headline
-'Third'
->>> a3.pub_date
-datetime.date(2005, 8, 22)
-
-# Create an ArticleWithManyChanges.
->>> a4 = subarticles4.ArticleWithManyChanges(headline='Fourth', section='Arts',
-... is_popular=True, pub_date=date(2005, 8, 22))
->>> a4.save()
-
-# a4 inherits __repr__() from its parent model (ArticleWithRepr).
->>> a4
-Fourth
-
-# Retrieve it again, to prove the fields have been saved.
->>> a4 = subarticles4.get_object(pk=1)
->>> a4.headline
-'Fourth'
->>> a4.section
-'Arts'
->>> a4.is_popular == True
-True
->>> a4.pub_date
-datetime.date(2005, 8, 22)
-
-# Test get_list().
->>> subarticles1.get_list()
-[<ArticleWithSection object>]
->>> subarticles2.get_list()
-[<ArticleWithoutPubDate object>]
->>> subarticles3.get_list()
-[<ArticleWithFieldOverride object>]
->>> subarticles4.get_list()
-[Fourth]
-
-# Create a couple of ArticleWithChangedMeta objects.
->>> a5 = subarticles5.ArticleWithChangedMeta(headline='A', pub_date=datetime(2005, 3, 1))
->>> a5.save()
->>> a6 = subarticles5.ArticleWithChangedMeta(headline='B', pub_date=datetime(2005, 4, 1))
->>> a6.save()
->>> a7 = subarticles5.ArticleWithChangedMeta(headline='C', pub_date=datetime(2005, 5, 1))
->>> a7.save()
-
-# Ordering has been overridden, so objects are ordered
-# by headline ASC instead of pub_date DESC.
->>> subarticles5.get_list()
-[A, B, C]
-
->>> nomodulenamefirsts.get_list()
-[]
->>> nomodulenameseconds.get_list()
-[]
-"""