summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
Diffstat (limited to 'django')
-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__.py0
-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
227 files changed, 9581 insertions, 6974 deletions
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/django/db/backends/sqlite3/__init__.py b/django/db/backends/sqlite3/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ 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 = []