{% 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 %}
@@ -13,8 +21,10 @@
{% blocktrans %}Are you sure you want to delete the {{ object_name }} "{{ object }}"? All of the following related items will be deleted:{% endblocktrans %}
{{ deleted_objects|unordered_list }}
{% 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 %}
-
\ No newline at end of file
+
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 %} {{ bound_field.existing_display|truncatewords:"14" }}{% endif %}
- {% endif %}
- {% endif %}
{% if bound_field.field.help_text %}
-
' % (ADMIN_MEDIA_PREFIX, script_path)
+ return '' % (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 '\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 field_widget(parser, token):
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
+ return FieldWidgetNode(bits[1])
+field_widget = register.tag(field_widget)
-def 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)
-
-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[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
+ ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
+ ('^doc/models/(?P[^\.]+)\.(?P[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
+# ('^doc/templates/$', 'django.views.admin.doc.template_index'),
+ ('^doc/templates/(?P.*)/$', '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[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
- ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
- ('^doc/models/(?P[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
-# ('^doc/templates/$', 'django.views.admin.doc.template_index'),
- ('^doc/templates/(?P.*)/$', 'django.contrib.admin.views.doc.template_detail'),
-)
-
-if 'ellington.events' in INSTALLED_APPS:
- urlpatterns += (
- ("^events/usersubmittedevents/(?P\d+)/$", 'ellington.events.views.admin.user_submitted_event_change_stage'),
- ("^events/usersubmittedevents/(?P\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
{%% 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 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
-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
+ disable_termcolors()
- # 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)
+ # First, try validating the models.
+ _check_for_validation_errors()
- # 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)
-
- # 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
-def get_admin_index(mod):
- "Returns admin-index template snippet (in list form) for the given module."
+ 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(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('
%s
' % 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('
')
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 install(mod):
+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)
+
+ 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)
-
- # 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))
-
- # 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.')
- 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)
-
- # 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 core=True, if needed.
- for related in opts.get_followed_related_objects():
+ # 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:
- 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
+ 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))
+
+ 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, models.AdminOptions):
+ e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.')
+ else:
+ # list_display
+ if not isinstance(opts.admin.list_display, (list, tuple)):
+ e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.')
+ else:
+ for fn in opts.admin.list_display:
+ try:
+ f = opts.get_field(fn)
+ except models.FieldDoesNotExist:
+ if not hasattr(cls, fn):
+ e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn)
+ else:
+ if isinstance(f, models.ManyToManyField):
+ e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn)
+ # list_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 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 unique_together.
+ for ut in opts.unique_together:
+ for field_name in ut:
+ try:
+ f = opts.get_field(field_name, many_to_many=True)
+ except models.FieldDoesNotExist:
+ e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name)
+ else:
+ if isinstance(f.rel, models.ManyToManyRel):
+ e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name)
- # 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 "" % 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 "" % ( 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 '' % 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:
- # [, ]
- # 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('%s' % 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
similarity index 100%
rename from django/parts/__init__.py
rename to django/db/backends/__init__.py
diff --git a/django/parts/auth/__init__.py b/django/db/backends/ado_mssql/__init__.py
similarity index 100%
rename from django/parts/auth/__init__.py
rename to 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
similarity index 64%
rename from django/core/db/backends/ado_mssql.py
rename to 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
similarity index 100%
rename from django/parts/media/__init__.py
rename to 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
similarity index 100%
rename from django/views/auth/__init__.py
rename to 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
similarity index 100%
rename from django/views/registration/__init__.py
rename to django/db/backends/postgresql/__init__.py
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
new file mode 100644
index 0000000000..3c807f2120
--- /dev/null
+++ b/django/db/backends/postgresql/base.py
@@ -0,0 +1,128 @@
+"""
+PostgreSQL database backend for Django.
+
+Requires psycopg 1: http://initd.org/projects/psycopg1
+"""
+
+from django.db.backends import util
+import psycopg as Database
+
+DatabaseError = Database.DatabaseError
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+class DatabaseWrapper(local):
+ def __init__(self):
+ self.connection = None
+ self.queries = []
+
+ def cursor(self):
+ from django.conf import settings
+ if self.connection is None:
+ if settings.DATABASE_NAME == '':
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
+ conn_string = "dbname=%s" % settings.DATABASE_NAME
+ if settings.DATABASE_USER:
+ conn_string = "user=%s %s" % (settings.DATABASE_USER, conn_string)
+ if settings.DATABASE_PASSWORD:
+ conn_string += " password='%s'" % settings.DATABASE_PASSWORD
+ if settings.DATABASE_HOST:
+ conn_string += " host=%s" % settings.DATABASE_HOST
+ if settings.DATABASE_PORT:
+ conn_string += " port=%s" % settings.DATABASE_PORT
+ self.connection = Database.connect(conn_string)
+ self.connection.set_isolation_level(1) # make transactions transparent to all cursors
+ cursor = self.connection.cursor()
+ cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(cursor, self)
+ return cursor
+
+ def _commit(self):
+ return self.connection.commit()
+
+ def _rollback(self):
+ if self.connection:
+ return self.connection.rollback()
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+supports_constraints = True
+
+def quote_name(name):
+ if name.startswith('"') and name.endswith('"'):
+ return name # Quoting once is enough.
+ return '"%s"' % name
+
+def dictfetchone(cursor):
+ "Returns a row from the cursor as a dict"
+ return cursor.dictfetchone()
+
+def dictfetchmany(cursor, number):
+ "Returns a certain number of rows from a cursor as a dict"
+ return cursor.dictfetchmany(number)
+
+def dictfetchall(cursor):
+ "Returns all rows from a cursor as a dict"
+ return cursor.dictfetchall()
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
+ return cursor.fetchone()[0]
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
+ return "EXTRACT('%s' FROM %s)" % (lookup_type, table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
+ return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
+
+def get_limit_offset_sql(limit, offset=None):
+ sql = "LIMIT %s" % limit
+ if offset and offset != 0:
+ sql += " OFFSET %s" % offset
+ return sql
+
+def get_random_function_sql():
+ return "RANDOM()"
+
+def get_drop_foreignkey_sql():
+ return "DROP CONSTRAINT"
+
+# Register these custom typecasts, because Django expects dates/times to be
+# in Python's native (standard-library) datetime/time format, whereas psycopg
+# use mx.DateTime by default.
+try:
+ Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date))
+except AttributeError:
+ raise Exception, "You appear to be using psycopg version 2, which isn't supported yet, because it's still in beta. Use psycopg version 1 instead: http://initd.org/projects/psycopg1"
+Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time))
+Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
+Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'ILIKE %s',
+ 'contains': 'LIKE %s',
+ 'icontains': 'ILIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE %s',
+ 'endswith': 'LIKE %s',
+ 'istartswith': 'ILIKE %s',
+ 'iendswith': 'ILIKE %s',
+}
diff --git a/django/db/backends/postgresql/client.py b/django/db/backends/postgresql/client.py
new file mode 100644
index 0000000000..3d0d7a0d2a
--- /dev/null
+++ b/django/db/backends/postgresql/client.py
@@ -0,0 +1,14 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ['']
+ args += ["-U%s" % settings.DATABASE_USER]
+ if settings.DATABASE_PASSWORD:
+ args += ["-W"]
+ if settings.DATABASE_HOST:
+ args += ["-h %s" % settings.DATABASE_HOST]
+ if settings.DATABASE_PORT:
+ args += ["-p %s" % settings.DATABASE_PORT]
+ args += [settings.DATABASE_NAME]
+ os.execvp('psql', args)
diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
new file mode 100644
index 0000000000..65a804ec40
--- /dev/null
+++ b/django/db/backends/postgresql/creation.py
@@ -0,0 +1,30 @@
+# This dictionary maps Field objects to their associated PostgreSQL column
+# types, as strings. Column-type strings can contain format strings; they'll
+# be interpolated against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPES = {
+ 'AutoField': 'serial',
+ 'BooleanField': 'boolean',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'timestamp with time zone',
+ 'FileField': 'varchar(100)',
+ 'FilePathField': 'varchar(100)',
+ 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'inet',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'boolean',
+ 'OneToOneField': 'integer',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
+ 'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
+ 'SlugField': 'varchar(%(maxlength)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'text',
+ 'TimeField': 'time',
+ 'URLField': 'varchar(200)',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
new file mode 100644
index 0000000000..c4f759da10
--- /dev/null
+++ b/django/db/backends/postgresql/introspection.py
@@ -0,0 +1,85 @@
+from django.db import transaction
+from django.db.backends.postgresql.base import quote_name
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ cursor.execute("""
+ SELECT c.relname
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('r', 'v', '')
+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
+ AND pg_catalog.pg_table_is_visible(c.oid)""")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
+ return cursor.description
+
+def get_relations(cursor, table_name):
+ """
+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+ representing all relationships to the given table. Indexes are 0-based.
+ """
+ cursor.execute("""
+ SELECT con.conkey, con.confkey, c2.relname
+ FROM pg_constraint con, pg_class c1, pg_class c2
+ WHERE c1.oid = con.conrelid
+ AND c2.oid = con.confrelid
+ AND c1.relname = %s
+ AND con.contype = 'f'""", [table_name])
+ relations = {}
+ for row in cursor.fetchall():
+ try:
+ # row[0] and row[1] are like "{2}", so strip the curly braces.
+ relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
+ except ValueError:
+ continue
+ return relations
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ # Get the table description because we only have the column indexes, and we
+ # need the column names.
+ desc = get_table_description(cursor, table_name)
+ # This query retrieves each index on the given table.
+ cursor.execute("""
+ SELECT idx.indkey, idx.indisunique, idx.indisprimary
+ FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
+ pg_catalog.pg_index idx
+ WHERE c.oid = idx.indrelid
+ AND idx.indexrelid = c2.oid
+ AND c.relname = %s""", [table_name])
+ indexes = {}
+ for row in cursor.fetchall():
+ # row[0] (idx.indkey) is stored in the DB as an array. It comes out as
+ # a string of space-separated integers. This designates the field
+ # indexes (1-based) of the fields that have indexes on the table.
+ # Here, we skip any indexes across multiple fields.
+ if ' ' in row[0]:
+ continue
+ col_name = desc[int(row[0])-1][0]
+ indexes[col_name] = {'primary_key': row[2], 'unique': row[1]}
+ return indexes
+
+# Maps type codes to Django Field types.
+DATA_TYPES_REVERSE = {
+ 16: 'BooleanField',
+ 21: 'SmallIntegerField',
+ 23: 'IntegerField',
+ 25: 'TextField',
+ 869: 'IPAddressField',
+ 1043: 'CharField',
+ 1082: 'DateField',
+ 1083: 'TimeField',
+ 1114: 'DateTimeField',
+ 1184: 'DateTimeField',
+ 1266: 'TimeField',
+ 1700: 'FloatField',
+}
diff --git a/tests/testapp/__init__.py b/django/db/backends/sqlite3/__init__.py
similarity index 100%
rename from tests/testapp/__init__.py
rename to 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 "" % 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
similarity index 54%
rename from django/core/meta/fields.py
rename to 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
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):
+ def prepare_field_objs_and_params(self, manipulator, name_prefix):
+ params = {'validator_list': self.validator_list[:]}
+ if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
+ params['maxlength'] = self.maxlength
+
+ if self.choices:
+ if self.radio_admin:
+ field_objs = [forms.RadioSelectField]
+ params['ul_class'] = get_ul_class(self.radio_admin)
+ else:
+ 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 formfields.FormField instances for this field. It
+ 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.
"""
- 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.radio_admin:
- field_objs = [formfields.RadioSelectField]
- params['ul_class'] = get_ul_class(self.radio_admin)
- else:
- field_objs = [formfields.SelectField]
-
- params['choices'] = self.get_choices_default()
- else:
- field_objs = self.get_manipulator_field_objs()
+ 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)]
+ return [curry(forms.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', '')
+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 )
- 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')
+ def get_internal_type(self):
+ return "IntegerField"
- 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]
-
- 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 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_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 '' % 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 "" % (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 "
+__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
similarity index 86%
rename from django/core/formfields.py
rename to 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 '' % \
(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 '' % \
(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 '' % \
@@ -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
similarity index 97%
rename from django/utils/httpwrappers.py
rename to 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('
Forbidden
')
+ return http.HttpResponseForbidden('
Forbidden
')
# 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
similarity index 90%
rename from django/core/template/__init__.py
rename to 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
similarity index 97%
rename from django/core/template/defaultfilters.py
rename to 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
similarity index 97%
rename from django/core/template/defaulttags.py
rename to 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
similarity index 94%
rename from django/core/template/loader.py
rename to 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
similarity index 89%
rename from django/core/template/loader_tags.py
rename to 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
diff --git a/django/core/template/loaders/app_directories.py b/django/template/loaders/app_directories.py
similarity index 84%
rename from django/core/template/loaders/app_directories.py
rename to 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
similarity index 74%
rename from django/core/template/loaders/eggs.py
rename to 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
similarity index 74%
rename from django/core/template/loaders/filesystem.py
rename to 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('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
+ return html.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')
def linebreaks(value):
"Converts newlines into
and 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 = """
Of course, you haven't actually done any work yet. Here's what to do next:
-
Edit the DATABASE_* settings in {{ project_name }}/settings.py.
-
Start your first app by running {{ project_name }}/manage.py startapp [appname].
+
If you plan to use a database, edit the DATABASE_* settings in {{ project_name }}/settings.py.
+
Start your first app by running python {{ project_name }}/manage.py startapp [appname].
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: ``/_form``
+ Templates: ``/_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: ``/_form``
+ Templates: ``/_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: ``/_confirm_delete``
+ Templates: ``/_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: ``/_archive``
+ Templates: ``/_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: ``/_archive_year``
+ Templates: ``/_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: ``/_archive_month``
+ Templates: ``/_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: ``/_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: ``/_archive_day``
+ Templates: ``/_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: ``/_detail``
+ Templates: ``/_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: ``/_list``
+ Templates: ``/_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: ``/_detail``
+ Templates: ``/_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 @@
-import os
-import urllib
-import posixpath
+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 posixpath
import re
import rfc822
import stat
-from django.core import template_loader
-from django.core.exceptions import Http404, ImproperlyConfigured
-from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect, \
- HttpResponseNotModified
-from django.core.template import Template, Context, TemplateDoesNotExist
+import urllib
def serve(request, path, document_root=None, show_indexes=False):
"""
@@ -81,7 +80,7 @@ DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
def directory_index(path, fullpath):
try:
- t = template_loader.get_template('static/directory_index')
+ t = loader.get_template('static/directory_index')
except TemplateDoesNotExist:
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE)
files = []
diff --git a/docs/add_ons.txt b/docs/add_ons.txt
index 44354d9c82..e602e429ad 100644
--- a/docs/add_ons.txt
+++ b/docs/add_ons.txt
@@ -12,9 +12,9 @@ admin
=====
The automatic Django administrative interface. For more information, see
-`Tutorial 3`_.
+`Tutorial 2`_.
-.. _Tutorial 3: http://www.djangoproject.com/documentation/tutorial2/
+.. _Tutorial 2: http://www.djangoproject.com/documentation/tutorial2/
comments
========
diff --git a/docs/admin_css.txt b/docs/admin_css.txt
index 419e0bcd42..069012a84b 100644
--- a/docs/admin_css.txt
+++ b/docs/admin_css.txt
@@ -28,11 +28,13 @@ Column Types
.. admonition:: Note
- In the Django development version, all admin pages (except the dashboard) are fluid-width. All fixed-width classes have been removed.
+ All admin pages (except the dashboard) are fluid-width. All fixed-width
+ classes from previous Django versions have been removed.
The base template for each admin page has a block that defines the column
structure for the page. This sets a class on the page content area
-(``div#content``) so everything on the page knows how wide it should be. There are three column types available.
+(``div#content``) so everything on the page knows how wide it should be. There
+are three column types available.
colM
This is the default column setting for all pages. The "M" stands for "main".
@@ -46,39 +48,12 @@ colMS
colSM
Same as above, with the sidebar on the left. The source order of the columns
doesn't matter.
-colM superwide (removed in Django development version)
- This is for ridiculously wide pages. Doesn't really work very well for
- anything but colM. With superwide, you've got 1000px to work with. Don't
- waste them.
-flex (removed in Django development version)
- This is for liquid-width pages, such as changelists. Currently only works
- with single-column pages (does not combine with ``.colMS`` or ``.colSM``).
- Form pages should never use ``.flex``.
-For instance, you could stick this in a template to make a two-column page with the sidebar on the right::
+For instance, you could stick this in a template to make a two-column page with
+the sidebar on the right::
{% block coltype %}colMS{% endblock %}
-
-Widths
-======
-
-**Removed in Django development version (see note above).**
-
-There's a whole mess of classes in the stylesheet for custom pixel widths on
-objects. They come in handy for tables and table cells, if you want to avoid
-using the ``width`` attribute. Each class sets the width to the number of pixels
-in the class, except ``.xfull`` which will always be the width of the column
-it's in. (This helps with tables that you want to always fill the horizontal
-width, without using ``width="100%"`` which makes IE 5's box model cry.)
-
-**Note:** Within a ``.flex`` page, the ``.xfull`` class will ``usually`` set
-to 100%, but there are exceptions and still some untested cases.
-
-Available width classes::
-
- .x50 .x75 .x100 .x150 .x200 .x250 .x300 .x400 .x500 .xfull
-
Text Styles
===========
@@ -107,17 +82,18 @@ There are also a few styles for styling text.
.help
This is a custom class for blocks of inline help text explaining the
function of form elements. It makes text smaller and gray, and when applied
- to ``p`` elements withing ``.form-row`` elements (see Form Styles below), it will
- offset the text to align with the form field. Use this for help text,
- instead of ``small quiet``. It works on other elements, but try to put the class
- on a ``p`` whenever you can.
+ to ``p`` elements withing ``.form-row`` elements (see Form Styles below),
+ it will offset the text to align with the form field. Use this for help
+ text, instead of ``small quiet``. It works on other elements, but try to
+ put the class on a ``p`` whenever you can.
.align-left
- It aligns the text left. Only works on block elements containing inline elements.
+ It aligns the text left. Only works on block elements containing inline
+ elements.
.align-right
Are you paying attention?
.nowrap
- Keeps text and inline objects from wrapping. Comes in handy for table headers you want to stay
- on one line.
+ Keeps text and inline objects from wrapping. Comes in handy for table
+ headers you want to stay on one line.
Floats and Clears
-----------------
@@ -173,9 +149,10 @@ Each fieldset can also take extra classes in addition to ``.module`` to apply
appropriate formatting to the group of fields.
.aligned
- this will align the labels and inputs side by side on the same line.
+ This will align the labels and inputs side by side on the same line.
.wide
- used in combination with ``.aligned`` to widen the space available for the labels.
+ Used in combination with ``.aligned`` to widen the space available for the
+ labels.
Form Rows
---------
diff --git a/docs/authentication.txt b/docs/authentication.txt
index 4c45ec6759..8f618f8a20 100644
--- a/docs/authentication.txt
+++ b/docs/authentication.txt
@@ -6,13 +6,8 @@ Django comes with a user authentication system. It handles user accounts,
groups, permissions and cookie-based user sessions. This document explains how
things work.
-The basics
-==========
-
-Django supports authentication out of the box. The ``django-admin.py init``
-command, used to initialize a database with Django's core database tables,
-creates the infrastructure for the auth system. You don't have to do anything
-else to use authentication.
+Overview
+========
The auth system consists of:
@@ -23,13 +18,35 @@ The auth system consists of:
user.
* Messages: A simple way to queue messages for given users.
+Installation
+============
+
+Authentication support is bundled as a Django application in
+``django.contrib.auth``. To install it, do the following:
+
+ 1. Put ``'django.contrib.auth'`` in your ``INSTALLED_APPS`` setting.
+ 2. Run the command ``manage.py syncdb``.
+
+Note that the default ``settings.py`` file created by
+``django-admin.py startproject`` includes ``'django.contrib.auth'`` in
+``INSTALLED_APPS`` for convenience. If your ``INSTALLED_APPS`` already contains
+``'django.contrib.auth'``, feel free to run ``manage.py syncdb`` again; you
+can run that command as many times as you'd like, and each time it'll only
+install what's needed.
+
+The ``syncdb`` command creates the necessary database tables, creates
+permission objects for all installed apps that need 'em, and prompts you to
+create a superuser account.
+
+Once you've taken those steps, that's it.
+
Users
=====
Users are represented by a standard Django model, which lives in
-`django/models/auth.py`_.
+`django/contrib/auth/models.py`_.
-.. _django/models/auth.py: http://code.djangoproject.com/browser/django/trunk/django/models/auth.py
+.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py
API reference
-------------
@@ -62,16 +79,20 @@ Methods
~~~~~~~
``User`` objects have two many-to-many fields: ``groups`` and
-``user_permissions``. Because of those relationships, ``User`` objects get
-data-access methods like any other `Django model`_:
+``user_permissions``. ``User`` objects can access their related
+objects in the same way as any other `Django model`_::
- * ``get_group_list(**kwargs)``
- * ``set_groups(id_list)``
- * ``get_permission_list(**kwargs)``
- * ``set_user_permissions(id_list)``
+ ``myuser.objects.groups = [group_list]``
+ ``myuser.objects.groups.add(group, group,...)``
+ ``myuser.objects.groups.remove(group, group,...)``
+ ``myuser.objects.groups.clear()``
+ ``myuser.objects.permissions = [permission_list]``
+ ``myuser.objects.permissions.add(permission, permission, ...)``
+ ``myuser.objects.permissions.remove(permission, permission, ...]``
+ ``myuser.objects.permissions.clear()``
In addition to those automatic API methods, ``User`` objects have the following
-methods:
+custom methods:
* ``is_anonymous()`` -- Always returns ``False``. This is a way of
comparing ``User`` objects to anonymous users.
@@ -80,11 +101,12 @@ methods:
with a space in between.
* ``set_password(raw_password)`` -- Sets the user's password to the given
- raw string, taking care of the MD5 hashing. Doesn't save the ``User``
- object.
+ raw string, taking care of the password hashing. Doesn't save the
+ ``User`` object.
* ``check_password(raw_password)`` -- Returns ``True`` if the given raw
- string is the correct password for the user.
+ string is the correct password for the user. (This takes care of the
+ password hashing in making the comparison.)
* ``get_group_permissions()`` -- Returns a list of permission strings that
the user has, through his/her groups.
@@ -110,23 +132,25 @@ methods:
`DEFAULT_FROM_EMAIL`_ setting.
* ``get_profile()`` -- Returns a site-specific profile for this user.
- Raises ``django.models.auth.SiteProfileNotAvailable`` if the current site
+ Raises ``django.contrib.auth.models.SiteProfileNotAvailable`` if the current site
doesn't allow profiles.
.. _Django model: http://www.djangoproject.com/documentation/model_api/
.. _DEFAULT_FROM_EMAIL: http://www.djangoproject.com/documentation/settings/#default-from-email
-Module functions
-~~~~~~~~~~~~~~~~
+Manager functions
+~~~~~~~~~~~~~~~~~
-The ``django.models.auth.users`` module has the following helper functions:
+The ``User`` model has a custom manager that has the following helper functions:
* ``create_user(username, email, password)`` -- Creates, saves and returns
a ``User``. The ``username``, ``email`` and ``password`` are set as
given, and the ``User`` gets ``is_active=True``.
+ See _`Creating users` for example usage.
+
* ``make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')``
- -- Returns a random password with the given length and given string of
+ Returns a random password with the given length and given string of
allowed characters. (Note that the default value of ``allowed_chars``
doesn't contain ``"I"`` or letters that look like it, to avoid user
confusion.
@@ -140,11 +164,12 @@ Creating users
The most basic way to create users is to use the ``create_user`` helper
function that comes with Django::
- >>> from django.models.auth import users
- >>> user = users.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
+ >>> from django.contrib.auth.models import User
+ >>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
- # Now, user is a User object already saved to the database.
- # You can continue to change its attributes if you want to change other fields.
+ # At this point, user is a User object ready to be saved
+ # to the database. You can continue to change its attributes
+ # if you want to change other fields.
>>> user.is_staff = True
>>> user.save()
@@ -153,28 +178,26 @@ Changing passwords
Change a password with ``set_password()``::
- >>> from django.models.auth import users
- >>> u = users.get_object(username__exact='john')
+ >>> from django.contrib.auth.models import User
+ >>> u = User.objects.get(username__exact='john')
>>> u.set_password('new password')
>>> u.save()
-Don't set the password field directly unless you know what you're doing. This
-is explained in the next section.
+Don't set the ``password`` attribute directly unless you know what you're
+doing. This is explained in the next section.
Passwords
---------
-Previous versions, such as Django 0.90, used simple MD5 hashes without password
-salts.
-
-The ``password`` field of a ``User`` object is a string in this format::
+The ``password`` attribute of a ``User`` object is a string in this format::
hashtype$salt$hash
That's hashtype, salt and hash, separated by the dollar-sign character.
-Hashtype is either ``sha1`` (default) or ``md5``. Salt is a random string
-used to salt the raw password to create the hash.
+Hashtype is either ``sha1`` (default) or ``md5`` -- the algorithm used to
+perform a one-way hash of the password. Salt is a random string used to salt
+the raw password to create the hash.
For example::
@@ -183,17 +206,22 @@ For example::
The ``User.set_password()`` and ``User.check_password()`` functions handle
the setting and checking of these values behind the scenes.
+Previous Django versions, such as 0.90, used simple MD5 hashes without password
+salts. For backwards compatibility, those are still supported; they'll be
+converted automatically to the new style the first time ``check_password()``
+works correctly for a given user.
+
Anonymous users
---------------
-``django.parts.auth.anonymoususers.AnonymousUser`` is a class that implements
-the ``django.models.auth.users.User`` interface, with these differences:
+``django.contrib.auth.models.AnonymousUser`` is a class that implements
+the ``django.contirb.auth.models.User`` interface, with these differences:
* ``id`` is always ``None``.
* ``is_anonymous()`` returns ``True`` instead of ``False``.
* ``has_perm()`` always returns ``False``.
- * ``set_password()``, ``check_password()``, ``set_groups()`` and
- ``set_permissions()`` raise ``NotImplementedError``.
+ * ``set_password()``, ``check_password()``, ``save()``, ``delete()``,
+ ``set_groups()`` and ``set_permissions()`` raise ``NotImplementedError``.
In practice, you probably won't need to use ``AnonymousUser`` objects on your
own, but they're used by Web requests, as explained in the next section.
@@ -202,10 +230,15 @@ Authentication in Web requests
==============================
Until now, this document has dealt with the low-level APIs for manipulating
-authentication-related objects. On a higher level, Django hooks this
+authentication-related objects. On a higher level, Django can hook this
authentication framework into its system of `request objects`_.
-In any Django view, ``request.user`` will give you a ``User`` object
+First, install the ``SessionMiddleware`` and ``AuthenticationMiddleware``
+middlewares by adding them to your ``MIDDLEWARE_CLASSES`` setting. See the
+`session documentation`_ for more information.
+
+Once you have those middlewares installed, you'll be able to access
+``request.user`` in views. ``request.user`` will give you a ``User`` object
representing the currently logged-in user. If a user isn't currently logged in,
``request.user`` will be set to an instance of ``AnonymousUser`` (see the
previous section). You can tell them apart with ``is_anonymous()``, like so::
@@ -215,10 +248,6 @@ previous section). You can tell them apart with ``is_anonymous()``, like so::
else:
# Do something for logged-in users.
-If you want to use ``request.user`` in your view code, make sure you have
-``SessionMiddleware`` enabled. See the `session documentation`_ for more
-information.
-
.. _request objects: http://www.djangoproject.com/documentation/request_response/#httprequest-objects
.. _session documentation: http://www.djangoproject.com/documentation/sessions/
@@ -227,8 +256,8 @@ How to log a user in
To log a user in, do the following within a view::
- from django.models.auth import users
- request.session[users.SESSION_KEY] = some_user.id
+ from django.contrib.auth.models import SESSION_KEY
+ request.session[SESSION_KEY] = some_user.id
Because this uses sessions, you'll need to make sure you have
``SessionMiddleware`` enabled. See the `session documentation`_ for more
@@ -246,7 +275,7 @@ The raw way
The simple, raw way to limit access to pages is to check
``request.user.is_anonymous()`` and either redirect to a login page::
- from django.utils.httpwrappers import HttpResponseRedirect
+ from django.http import HttpResponseRedirect
def my_view(request):
if request.user.is_anonymous():
@@ -257,7 +286,7 @@ The simple, raw way to limit access to pages is to check
def my_view(request):
if request.user.is_anonymous():
- return render_to_response('myapp/login_error')
+ return render_to_response('myapp/login_error.html')
# ...
The login_required decorator
@@ -265,15 +294,16 @@ The login_required decorator
As a shortcut, you can use the convenient ``login_required`` decorator::
- from django.views.decorators.auth import login_required
+ from django.contrib.auth.decorators import login_required
def my_view(request):
# ...
my_view = login_required(my_view)
-Here's the same thing, using Python 2.4's decorator syntax::
+Here's an equivalent example, using the more compact decorator syntax
+introduced in Python 2.4::
- from django.views.decorators.auth import login_required
+ from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
@@ -304,7 +334,7 @@ permission ``polls.can_vote``::
As a shortcut, you can use the convenient ``user_passes_test`` decorator::
- from django.views.decorators.auth import user_passes_test
+ from django.contrib.auth.decorators import user_passes_test
def my_view(request):
# ...
@@ -312,7 +342,7 @@ As a shortcut, you can use the convenient ``user_passes_test`` decorator::
Here's the same thing, using Python 2.4's decorator syntax::
- from django.views.decorators.auth import user_passes_test
+ from django.contrib.auth.decorators import user_passes_test
@user_passes_test(lambda u: u.has_perm('polls.can_vote'))
def my_view(request):
@@ -328,7 +358,7 @@ specify the URL for your login page (``/accounts/login/`` by default).
Example in Python 2.3 syntax::
- from django.views.decorators.auth import user_passes_test
+ from django.contrib.auth.decorators import user_passes_test
def my_view(request):
# ...
@@ -336,7 +366,7 @@ Example in Python 2.3 syntax::
Example in Python 2.4 syntax::
- from django.views.decorators.auth import user_passes_test
+ from django.contrib.auth.decorators import user_passes_test
@user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')
def my_view(request):
@@ -380,49 +410,52 @@ Permissions are set globally per type of object, not per specific object
instance. For example, it's possible to say "Mary may change news stories," but
it's not currently possible to say "Mary may change news stories, but only the
ones she created herself" or "Mary may only change news stories that have a
-certain status or publication date." The latter functionality is something
+certain status, publication date or ID." The latter functionality is something
Django developers are currently discussing.
Default permissions
-------------------
Three basic permissions -- add, create and delete -- are automatically created
-for each Django model that has ``admin`` set. Behind the scenes, these
-permissions are added to the ``auth_permissions`` database table when you run
-``django-admin.py install [app]``. You can view the exact SQL ``INSERT``
-statements by running ``django-admin.py sqlinitialdata [app]``.
+for each Django model that has a ``class Admin`` set. Behind the scenes, these
+permissions are added to the ``auth_permission`` database table when you run
+``manage.py syncdb``.
-Note that if your model doesn't have ``admin`` set when you run
-``django-admin.py install``, the permissions won't be created. If you
-initialize your database and add ``admin`` to models after the fact, you'll
-need to add the permissions to the database manually. Do this by running
-``django-admin.py installperms [app]``, which creates any missing permissions
-for the given app.
+Note that if your model doesn't have ``class Admin`` set when you run
+``syncdb``, the permissions won't be created. If you initialize your database
+and add ``class Admin`` to models after the fact, you'll need to run
+``django-admin.py syncdb`` again. It will create any missing permissions for
+all of your installed apps.
Custom permissions
------------------
To create custom permissions for a given model object, use the ``permissions``
-`model META attribute`_.
+`model Meta attribute`_.
This example model creates three custom permissions::
- class USCitizen(meta.Model):
+ class USCitizen(models.Model):
# ...
- class META:
+ class Meta:
permissions = (
("can_drive", "Can drive"),
("can_vote", "Can vote in elections"),
("can_drink", "Can drink alcohol"),
)
-.. _model META attribute: http://www.djangoproject.com/documentation/model_api/#meta-options
+The only thing this does is create those extra permissions when you run
+``syncdb``.
+
+.. _model Meta attribute: http://www.djangoproject.com/documentation/model_api/#meta-options
API reference
-------------
Just like users, permissions are implemented in a Django model that lives in
-`django/models/auth.py`_.
+`django/contrib/auth/models.py`_.
+
+.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py
Fields
~~~~~~
@@ -430,8 +463,8 @@ Fields
``Permission`` objects have the following fields:
* ``name`` -- Required. 50 characters or fewer. Example: ``'Can vote'``.
- * ``package`` -- Required. A reference to the ``packages`` database table,
- which contains a record for each installed Django application.
+ * ``content_type`` -- Required. A reference to the ``django_content_type``
+ database table, which contains a record for each installed Django model.
* ``codename`` -- Required. 100 characters or fewer. Example: ``'can_vote'``.
Methods
@@ -444,21 +477,21 @@ Authentication data in templates
================================
The currently logged-in user and his/her permissions are made available in the
-`template context`_ when you use ``DjangoContext``.
+`template context`_ when you use ``RequestContext``.
.. admonition:: Technicality
Technically, these variables are only made available in the template context
- if you use ``DjangoContext`` *and* your ``TEMPLATE_CONTEXT_PROCESSORS``
+ if you use ``RequestContext`` *and* your ``TEMPLATE_CONTEXT_PROCESSORS``
setting contains ``"django.core.context_processors.auth"``, which is default.
- For more, see the `DjangoContext docs`_.
+ For more, see the `RequestContext docs`_.
- .. _DjangoContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
+ .. _RequestContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
Users
-----
-The currently logged-in user, either a ``User`` object or an``AnonymousUser``
+The currently logged-in user, either a ``User`` instance or an``AnonymousUser``
instance, is stored in the template variable ``{{ user }}``::
{% if user.is_anonymous %}
@@ -504,25 +537,25 @@ Thus, you can check permissions in template ``{% if %}`` statements::
Groups
======
-Groups are a generic way of categorizing users to apply permissions, or some
-other label, to those users. A user can belong to any number of groups.
+Groups are a generic way of categorizing users so you can apply permissions, or
+some other label, to those users. A user can belong to any number of groups.
A user in a group automatically has the permissions granted to that group. For
example, if the group ``Site editors`` has the permission
``can_edit_home_page``, any user in that group will have that permission.
-Beyond permissions, groups are a convenient way to categorize users to apply
-some label, or extended functionality, to them. For example, you could create
-a group ``'Special users'``, and you could write code that would do special
-things to those users -- such as giving them access to a members-only portion
-of your site, or sending them members-only e-mail messages.
+Beyond permissions, groups are a convenient way to categorize users to give
+them some label, or extended functionality. For example, you could create a
+group ``'Special users'``, and you could write code that could, say, give them
+access to a members-only portion of your site, or send them members-only e-mail
+messages.
Messages
========
The message system is a lightweight way to queue messages for given users.
-A message is associated with a User. There's no concept of expiration or
+A message is associated with a ``User``. There's no concept of expiration or
timestamps.
Messages are used by the Django admin after successful actions. For example,
@@ -530,8 +563,9 @@ Messages are used by the Django admin after successful actions. For example,
The API is simple::
- * To add messages, use ``user.add_message(message_text)``.
- * To retrieve/delete messages, use ``user.get_and_delete_messages()``,
+ * To create a new message, use
+ ``user_obj.message_set.create(message='message_text')``.
+ * To retrieve/delete messages, use ``user_obj.get_and_delete_messages()``,
which returns a list of ``Message`` objects in the user's queue (if any)
and deletes the messages from the queue.
@@ -541,10 +575,11 @@ a playlist::
def create_playlist(request, songs):
# Create the playlist with the given songs.
# ...
- request.user.add_message("Your playlist was added successfully.")
- return render_to_response("playlists/create", context_instance=DjangoContext(request))
+ request.user.message_set.create(message="Your playlist was added successfully.")
+ return render_to_response("playlists/create.html",
+ context_instance=RequestContext(request))
-When you use ``DjangoContext``, the currently logged-in user and his/her
+When you use ``RequestContext``, the currently logged-in user and his/her
messages are made available in the `template context`_ as the template variable
``{{ messages }}``. Here's an example of template code that displays messages::
@@ -556,7 +591,7 @@ messages are made available in the `template context`_ as the template variable
{% endif %}
-Note that ``DjangoContext`` calls ``get_and_delete_messages`` behind the
+Note that ``RequestContext`` calls ``get_and_delete_messages`` behind the
scenes, so any messages will be deleted even if you don't display them.
Finally, note that this messages framework only works with users in the user
diff --git a/docs/cache.txt b/docs/cache.txt
index f1f5668137..4fecdc6372 100644
--- a/docs/cache.txt
+++ b/docs/cache.txt
@@ -2,63 +2,180 @@
Django's cache framework
========================
-So, you got slashdotted_. Now what?
+A fundamental tradeoff in dynamic Web sites is, well, they're dynamic. Each
+time a user requests a page, the Web server makes all sorts of calculations --
+from database queries to template rendering to business logic -- to create the
+page that your site's visitor sees. This is a lot more expensive, from a
+processing-overhead perspective, than your standard read-a-file-off-the-filesystem
+server arrangement.
-Django's cache framework gives you three methods of caching dynamic pages in
-memory or in a database. You can cache the output of specific views, you can
-cache only the pieces that are difficult to produce, or you can cache your
-entire site.
+For most Web applications, this overhead isn't a big deal. Most Web
+applications aren't washingtonpost.com or slashdot.org; they're simply small-
+to medium-sized sites with so-so traffic. But for medium- to high-traffic
+sites, it's essential to cut as much overhead as possible.
-.. _slashdotted: http://en.wikipedia.org/wiki/Slashdot_effect
+That's where caching comes in.
+
+To cache something is to save the result of an expensive calculation so that
+you don't have to perform the calculation next time. Here's some pseudocode
+explaining how this would work for a dynamically generated Web page:
+
+ given a URL, try finding that page in the cache
+ if the page is in the cache:
+ return the cached page
+ else:
+ generate the page
+ save the generated page in the cache (for next time)
+ return the generated page
+
+Django comes with a robust cache system that lets you save dynamic pages so
+they don't have to be calculated for each request. For convenience, Django
+offers different levels of cache granularity: You can cache the output of
+specific views, you can cache only the pieces that are difficult to produce, or
+you can cache your entire site.
+
+Django also works well with "upstream" caches, such as Squid
+(http://www.squid-cache.org/) and browser-based caches. These are the types of
+caches that you don't directly control but to which you can provide hints (via
+HTTP headers) about which parts of your site should be cached, and how.
Setting up the cache
====================
-The cache framework allows for different "backends" -- different methods of
-caching data. There's a simple single-process memory cache (mostly useful as a
-fallback) and a memcached_ backend (the fastest option, by far, if you've got
-the RAM).
+The cache system requires a small amount of setup. Namely, you have to tell it
+where your cached data should live -- whether in a database, on the filesystem
+or directly in memory. This is an important decision that affects your cache's
+performance; yes, some cache types are faster than others.
-Before using the cache, you'll need to tell Django which cache backend you'd
-like to use. Do this by setting the ``CACHE_BACKEND`` in your settings file.
+Your cache preference goes in the ``CACHE_BACKEND`` setting in your settings
+file. Here's an explanation of all available values for CACHE_BACKEND.
-The ``CACHE_BACKEND`` setting is a "fake" URI (really an unregistered scheme).
-Examples:
+Memcached
+---------
- ============================== ===========================================
- CACHE_BACKEND Explanation
- ============================== ===========================================
- memcached://127.0.0.1:11211/ A memcached backend; the server is running
- on localhost port 11211. You can use
- multiple memcached servers by separating
- them with semicolons.
+By far the fastest, most efficient type of cache available to Django, Memcached
+is an entirely memory-based cache framework originally developed to handle high
+loads at LiveJournal.com and subsequently open-sourced by Danga Interactive.
+It's used by sites such as Slashdot and Wikipedia to reduce database access and
+dramatically increase site performance.
- This backend requires the
- `Python memcached bindings`_.
+Memcached is available for free at http://danga.com/memcached/ . It runs as a
+daemon and is allotted a specified amount of RAM. All it does is provide an
+interface -- a *super-lightning-fast* interface -- for adding, retrieving and
+deleting arbitrary data in the cache. All data is stored directly in memory,
+so there's no overhead of database or filesystem usage.
- db://tablename/ A database backend in a table named
- "tablename". This table should be created
- with "django-admin createcachetable".
+After installing Memcached itself, you'll need to install the Memcached Python
+bindings. They're in a single Python module, memcache.py, available at
+ftp://ftp.tummy.com/pub/python-memcached/ . If that URL is no longer valid,
+just go to the Memcached Web site (http://www.danga.com/memcached/) and get the
+Python bindings from the "Client APIs" section.
- file:///var/tmp/django_cache/ A file-based cache stored in the directory
- /var/tmp/django_cache/.
+To use Memcached with Django, set ``CACHE_BACKEND`` to
+``memcached://ip:port/``, where ``ip`` is the IP address of the Memcached
+daemon and ``port`` is the port on which Memcached is running.
- simple:/// A simple single-process memory cache; you
- probably don't want to use this except for
- testing. Note that this cache backend is
- NOT thread-safe!
+In this example, Memcached is running on localhost (127.0.0.1) port 11211::
- locmem:/// A more sophisticated local memory cache;
- this is multi-process- and thread-safe.
+ CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
- dummy:/// Doesn't actually cache; just implements the
- cache backend interface and doesn't do
- anything. This is an easy way to turn off
- caching for a test environment.
- ============================== ===========================================
+One excellent feature of Memcached is its ability to share cache over multiple
+servers. To take advantage of this feature, include all server addresses in
+``CACHE_BACKEND``, separated by semicolons. In this example, the cache is
+shared over Memcached instances running on IP address 172.19.26.240 and
+172.19.26.242, both on port 11211::
-All caches may take arguments -- they're given in query-string style. Valid
-arguments are:
+ CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'
+
+Memory-based caching has one disadvantage: Because the cached data is stored in
+memory, the data will be lost if your server crashes. Clearly, memory isn't
+intended for permanent data storage, so don't rely on memory-based caching as
+your only data storage. Actually, none of the Django caching backends should be
+used for permanent storage -- they're all intended to be solutions for caching,
+not storage -- but we point this out here because memory-based caching is
+particularly temporary.
+
+Database caching
+----------------
+
+To use a database table as your cache backend, first create a cache table in
+your database by running this command::
+
+ python manage.py createcachetable [cache_table_name]
+
+...where ``[cache_table_name]`` is the name of the database table to create.
+(This name can be whatever you want, as long as it's a valid table name that's
+not already being used in your database.) This command creates a single table
+in your database that is in the proper format that Django's database-cache
+system expects.
+
+Once you've created that database table, set your ``CACHE_BACKEND`` setting to
+``"db://tablename/"``, where ``tablename`` is the name of the database table.
+In this example, the cache table's name is ``my_cache_table``:
+
+ CACHE_BACKEND = 'db://my_cache_table'
+
+Database caching works best if you've got a fast, well-indexed database server.
+
+Filesystem caching
+------------------
+
+To store cached items on a filesystem, use the ``"file://"`` cache type for
+``CACHE_BACKEND``. For example, to store cached data in ``/var/tmp/django_cache``,
+use this setting::
+
+ CACHE_BACKEND = 'file:///var/tmp/django_cache'
+
+Note that there are three forward slashes toward the beginning of that example.
+The first two are for ``file://``, and the third is the first character of the
+directory path, ``/var/tmp/django_cache``.
+
+The directory path should be absolute -- that is, it should start at the root
+of your filesystem. It doesn't matter whether you put a slash at the end of the
+setting.
+
+Make sure the directory pointed-to by this setting exists and is readable and
+writable by the system user under which your Web server runs. Continuing the
+above example, if your server runs as the user ``apache``, make sure the
+directory ``/var/tmp/django_cache`` exists and is readable and writable by the
+user ``apache``.
+
+Local-memory caching
+--------------------
+
+If you want the speed advantages of in-memory caching but don't have the
+capability of running Memcached, consider the local-memory cache backend. This
+cache is multi-process and thread-safe. To use it, set ``CACHE_BACKEND`` to
+``"locmem:///"``. For example::
+
+ CACHE_BACKEND = 'locmem:///'
+
+Simple caching (for development)
+--------------------------------
+
+A simple, single-process memory cache is available as ``"simple:///"``. This
+merely saves cached data in-process, which means it should only be used in
+development or testing environments. For example::
+
+ CACHE_BACKEND = 'simple:///'
+
+Dummy caching (for development)
+-------------------------------
+
+Finally, Django comes with a "dummy" cache that doesn't actually cache -- it
+just implements the cache interface without doing anything.
+
+This is useful if you have a production site that uses heavy-duty caching in
+various places but a development/test environment on which you don't want to
+cache. In that case, set ``CACHE_BACKEND`` to ``"dummy:///"`` in the settings
+file for your development environment. As a result, your development
+environment won't use caching and your production environment still will.
+
+CACHE_BACKEND arguments
+-----------------------
+
+All caches may take arguments. They're given in query-string style on the
+``CACHE_BACKEND`` setting. Valid arguments are:
timeout
Default timeout, in seconds, to use for the cache. Defaults to 5
@@ -66,7 +183,7 @@ arguments are:
max_entries
For the simple and database backends, the maximum number of entries
- allowed in the cache before it is cleaned. Defaults to 300.
+ allowed in the cache before it is cleaned. Defaults to 300.
cull_percentage
The percentage of entries that are culled when max_entries is reached.
@@ -77,20 +194,21 @@ arguments are:
dumped when max_entries is reached. This makes culling *much* faster
at the expense of more cache misses.
-For example::
+In this example, ``timeout`` is set to ``60``::
CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=60"
+In this example, ``timeout`` is ``30`` and ``max_entries`` is ``400``::
+
+ CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=30&max_entries=400"
+
Invalid arguments are silently ignored, as are invalid values of known
arguments.
-.. _memcached: http://www.danga.com/memcached/
-.. _Python memcached bindings: ftp://ftp.tummy.com/pub/python-memcached/
-
The per-site cache
==================
-Once the cache is set up, the simplest way to use the cache is to cache your
+Once the cache is set up, the simplest way to use caching is to cache your
entire site. Just add ``django.middleware.cache.CacheMiddleware`` to your
``MIDDLEWARE_CLASSES`` setting, as in this example::
@@ -159,52 +277,100 @@ For example, you may find it's only necessary to cache the result of an
intensive database query. In cases like this, you can use the low-level cache
API to store objects in the cache with any level of granularity you like.
-The cache API is simple::
+The cache API is simple. The cache module, ``django.core.cache``, exports a
+``cache`` object that's automatically created from the ``CACHE_BACKEND``
+setting::
- # The cache module exports a cache object that's automatically
- # created from the CACHE_BACKEND setting.
>>> from django.core.cache import cache
- # The basic interface is set(key, value, timeout_seconds) and get(key).
+The basic interface is ``set(key, value, timeout_seconds)`` and ``get(key)``::
+
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'
- # (Wait 30 seconds...)
+The ``timeout_seconds`` argument is optional and defaults to the ``timeout``
+argument in the ``CACHE_BACKEND`` setting (explained above).
+
+If the object doesn't exist in the cache, ``cache.get()`` returns ``None``::
+
+ >>> cache.get('some_other_key')
+ None
+
+ # Wait 30 seconds for 'my_key' to expire...
+
>>> cache.get('my_key')
None
- # get() can take a default argument.
- >>> cache.get('my_key', 'has_expired')
- 'has_expired'
+get() can take a ``default`` argument::
+
+ >>> cache.get('my_key', 'has expired')
+ 'has expired'
+
+There's also a get_many() interface that only hits the cache once. get_many()
+returns a dictionary with all the keys you asked for that actually exist in the
+cache (and haven't expired)::
- # There's also a get_many() interface that only hits the cache once.
- # Also, note that the timeout argument is optional and defaults to what
- # you've given in the settings file.
>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
-
- # get_many() returns a dictionary with all the keys you asked for that
- # actually exist in the cache (and haven't expired).
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
- # There's also a way to delete keys explicitly.
+Finally, you can delete keys explicitly with ``delete()``. This is an easy way
+of clearing the cache for a particular object::
+
>>> cache.delete('a')
That's it. The cache has very few restrictions: You can cache any object that
can be pickled safely, although keys must be strings.
-Controlling cache: Using Vary headers
-=====================================
+Upstream caches
+===============
-The Django cache framework works with `HTTP Vary headers`_ to allow developers
-to instruct caching mechanisms to differ their cache contents depending on
-request HTTP headers.
+So far, this document has focused on caching your *own* data. But another type
+of caching is relevant to Web development, too: caching performed by "upstream"
+caches. These are systems that cache pages for users even before the request
+reaches your Web site.
-Essentially, the ``Vary`` response HTTP header defines which request headers a
-cache mechanism should take into account when building its cache key.
+Here are a few examples of upstream caches:
+
+ * Your ISP may cache certain pages, so if you requested a page from
+ somedomain.com, your ISP would send you the page without having to access
+ somedomain.com directly.
+
+ * Your Django Web site may site behind a Squid Web proxy
+ (http://www.squid-cache.org/) that caches pages for performance. In this
+ case, each request first would be handled by Squid, and it'd only be
+ passed to your application if needed.
+
+ * Your Web browser caches pages, too. If a Web page sends out the right
+ headers, your browser will use the local (cached) copy for subsequent
+ requests to that page.
+
+Upstream caching is a nice efficiency boost, but there's a danger to it:
+Many Web pages' contents differ based on authentication and a host of other
+variables, and cache systems that blindly save pages based purely on URLs could
+expose incorrect or sensitive data to subsequent visitors to those pages.
+
+For example, say you operate a Web e-mail system, and the contents of the
+"inbox" page obviously depend on which user is logged in. If an ISP blindly
+cached your site, then the first user who logged in through that ISP would have
+his user-specific inbox page cached for subsequent visitors to the site. That's
+not cool.
+
+Fortunately, HTTP provides a solution to this problem: A set of HTTP headers
+exist to instruct caching mechanisms to differ their cache contents depending
+on designated variables, and to tell caching mechanisms not to cache particular
+pages.
+
+Using Vary headers
+==================
+
+One of these headers is ``Vary``. It defines which request headers a cache
+mechanism should take into account when building its cache key. For example, if
+the contents of a Web page depend on a user's language preference, the page is
+said to "vary on language."
By default, Django's cache system creates its cache keys using the requested
path -- e.g., ``"/stories/2005/jun/23/bank_robbed/"``. This means every request
@@ -241,7 +407,7 @@ setting the ``Vary`` header (using something like
``response['Vary'] = 'user-agent'``) is that the decorator adds to the ``Vary``
header (which may already exist) rather than setting it from scratch.
-Note that you can pass multiple headers to ``vary_on_headers()``::
+You can pass multiple headers to ``vary_on_headers()``::
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
@@ -261,7 +427,8 @@ decorator. These two views are equivalent::
Also note that the headers you pass to ``vary_on_headers`` are not case
sensitive. ``"User-Agent"`` is the same thing as ``"user-agent"``.
-You can also use a helper function, ``patch_vary_headers()``, directly::
+You can also use a helper function, ``django.utils.cache.patch_vary_headers``,
+directly::
from django.utils.cache import patch_vary_headers
def my_view(request):
@@ -273,7 +440,9 @@ You can also use a helper function, ``patch_vary_headers()``, directly::
``patch_vary_headers`` takes an ``HttpResponse`` instance as its first argument
and a list/tuple of header names as its second argument.
-.. _`HTTP Vary headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
+For more on Vary headers, see the `official Vary spec`_.
+
+.. _`official Vary spec`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
Controlling cache: Using other headers
======================================
@@ -317,15 +486,19 @@ cache on every access and to store cached versions for, at most, 3600 seconds::
def my_view(request):
...
-Any valid ``Cache-Control`` directive is valid in ``cache_control()``. For a
-full list, see the `Cache-Control spec`_. Just pass the directives as keyword
-arguments to ``cache_control()``, substituting underscores for hyphens. For
-directives that don't take an argument, set the argument to ``True``.
+Any valid ``Cache-Control`` HTTP directive is valid in ``cache_control()``.
+Here's a full list:
-Examples:
+ * ``public=True``
+ * ``private=True``
+ * ``no_cache=True``
+ * ``no_transform=True``
+ * ``must_revalidate=True``
+ * ``proxy_revalidate=True``
+ * ``max_age=num_seconds``
+ * ``s_maxage=num_seconds``
- * ``@cache_control(max_age=3600)`` turns into ``max-age=3600``.
- * ``@cache_control(public=True)`` turns into ``public``.
+For explanation of Cache-Control HTTP directives, see the `Cache-Control spec`_.
(Note that the caching middleware already sets the cache header's max-age with
the value of the ``CACHE_MIDDLEWARE_SETTINGS`` setting. If you use a custom
diff --git a/docs/db-api.txt b/docs/db-api.txt
index e37645e601..8da6ebbac2 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -2,509 +2,1366 @@
Database API reference
======================
-Once you've created your `data models`_, you'll need to retrieve data from the
-database. This document explains the database abstraction API derived from the
-models, and how to create, retrieve and update objects.
+Once you've created your `data models`_, Django automatically gives you a
+database-abstraction API that lets you create, retrieve, update and delete
+objects. This document explains that API.
.. _`data models`: http://www.djangoproject.com/documentation/model_api/
-Throughout this reference, we'll refer to the following Poll application::
+Throughout this reference, we'll refer to the following models, which comprise
+a weblog application::
- class Poll(meta.Model):
- slug = meta.SlugField(unique_for_month='pub_date')
- question = meta.CharField(maxlength=255)
- pub_date = meta.DateTimeField()
- expire_date = meta.DateTimeField()
+ class Blog(models.Model):
+ name = models.CharField(maxlength=100)
+ tagline = models.TextField()
- def __repr__(self):
- return self.question
+ def __str__(self):
+ return self.name
- class Choice(meta.Model):
- poll = meta.ForeignKey(Poll, edit_inline=meta.TABULAR,
- num_in_admin=10, min_num_in_admin=5)
- choice = meta.CharField(maxlength=255, core=True)
- votes = meta.IntegerField(editable=False, default=0)
+ class Author(models.Model):
+ name = models.CharField(maxlength=50)
+ email = models.URLField()
- def __repr__(self):
- return self.choice
+ class __str__(self):
+ return self.name
-Basic lookup functions
-======================
+ class Entry(models.Model):
+ blog = models.ForeignKey(Blog)
+ headline = models.CharField(maxlength=255)
+ body_text = models.TextField()
+ pub_date = models.DateTimeField()
+ authors = models.ManyToManyField(Author)
-Each model exposes these module-level functions for lookups:
+ def __str__(self):
+ return self.headline
-get_object(\**kwargs)
----------------------
+Creating objects
+================
-Returns the object matching the given lookup parameters, which should be in
-the format described in "Field lookups" below. Raises a module-level
-``*DoesNotExist`` exception if an object wasn't found for the given parameters.
-Raises ``AssertionError`` if more than one object was found.
+To represent database-table data in Python objects, Django uses an intuitive
+system: A model class represents a database table, and an instance of that
+class represents a particular record in the database table.
-get_list(\**kwargs)
--------------------
+To create an object, instantiate it using keyword arguments to the model class,
+then call ``save()`` to save it to the database.
-Returns a list of objects matching the given lookup parameters, which should be
-in the format described in "Field lookups" below. If no objects match the given
-parameters, it returns an empty list. ``get_list()`` will always return a list.
+You import the model class from wherever it lives on the Python path, as you
+may expect. (We point this out here because previous Django versions required
+funky model importing.)
-get_iterator(\**kwargs)
------------------------
+Assuming models live in a file ``mysite/blog/models.py``, here's an example::
-Just like ``get_list()``, except it returns an iterator instead of a list. This
-is more efficient for large result sets. This example shows the difference::
+ from mysite.blog.models import Blog
+ b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
+ b.save()
- # get_list() loads all objects into memory.
- for obj in foos.get_list():
- print repr(obj)
+This performs an ``INSERT`` SQL statement behind the scenes. Django doesn't hit
+the database until you explicitly call ``save()``.
- # get_iterator() only loads a number of objects into memory at a time.
- for obj in foos.get_iterator():
- print repr(obj)
+The ``save()`` method has no return value.
-get_count(\**kwargs)
---------------------
-
-Returns an integer representing the number of objects in the database matching
-the given lookup parameters, which should be in the format described in
-"Field lookups" below. ``get_count()`` never raises exceptions
-
-Depending on which database you're using (e.g. PostgreSQL vs. MySQL), this may
-return a long integer instead of a normal Python integer.
-
-get_values(\**kwargs)
----------------------
-
-Just like ``get_list()``, except it returns a list of dictionaries instead of
-model-instance objects.
-
-It accepts an optional parameter, ``fields``, which should be a list or tuple
-of field names. If you don't specify ``fields``, each dictionary in the list
-returned by ``get_values()`` will have a key and value for each field in the
-database table. If you specify ``fields``, each dictionary will have only the
-field keys/values for the fields you specify. Here's an example, using the
-``Poll`` model defined above::
-
- >>> from datetime import datetime
- >>> p1 = polls.Poll(slug='whatsup', question="What's up?",
- ... pub_date=datetime(2005, 2, 20), expire_date=datetime(2005, 3, 20))
- >>> p1.save()
- >>> p2 = polls.Poll(slug='name', question="What's your name?",
- ... pub_date=datetime(2005, 3, 20), expire_date=datetime(2005, 4, 20))
- >>> p2.save()
- >>> polls.get_list()
- [What's up?, What's your name?]
- >>> polls.get_values()
- [{'id': 1, 'slug': 'whatsup', 'question': "What's up?", 'pub_date': datetime.datetime(2005, 2, 20), 'expire_date': datetime.datetime(2005, 3, 20)},
- {'id': 2, 'slug': 'name', 'question': "What's your name?", 'pub_date': datetime.datetime(2005, 3, 20), 'expire_date': datetime.datetime(2005, 4, 20)}]
- >>> polls.get_values(fields=['id', 'slug'])
- [{'id': 1, 'slug': 'whatsup'}, {'id': 2, 'slug': 'name'}]
-
-Use ``get_values()`` when you know you're only going to need a couple of field
-values and you won't need the functionality of a model instance object. It's
-more efficient to select only the fields you need to use.
-
-get_values_iterator(\**kwargs)
+Auto-incrementing primary keys
------------------------------
-Just like ``get_values()``, except it returns an iterator instead of a list.
-See the section on ``get_iterator()`` above.
+If a model has an ``AutoField`` -- an auto-incrementing primary key -- then
+that auto-incremented value will be calculated and saved as an attribute on
+your object the first time you call ``save()``.
-get_in_bulk(id_list, \**kwargs)
--------------------------------
-
-Takes a list of IDs and returns a dictionary mapping each ID to an instance of
-the object with the given ID. Also takes optional keyword lookup arguments,
-which should be in the format described in "Field lookups" below. Here's an
-example, using the ``Poll`` model defined above::
-
- >>> from datetime import datetime
- >>> p1 = polls.Poll(slug='whatsup', question="What's up?",
- ... pub_date=datetime(2005, 2, 20), expire_date=datetime(2005, 3, 20))
- >>> p1.save()
- >>> p2 = polls.Poll(slug='name', question="What's your name?",
- ... pub_date=datetime(2005, 3, 20), expire_date=datetime(2005, 4, 20))
- >>> p2.save()
- >>> polls.get_list()
- [What's up?, What's your name?]
- >>> polls.get_in_bulk([1])
- {1: What's up?}
- >>> polls.get_in_bulk([1, 2])
- {1: What's up?, 2: What's your name?}
-
-Field lookups
-=============
-
-Basic field lookups take the form ``field__lookuptype`` (that's a
-double-underscore). For example::
-
- polls.get_list(pub_date__lte=datetime.datetime.now())
-
-translates (roughly) into the following SQL::
-
- SELECT * FROM polls_polls WHERE pub_date <= NOW();
-
-.. admonition:: How this is possible
-
- Python has the ability to define functions that accept arbitrary name-value
- arguments whose names and values are evaluated at run time. For more
- information, see `Keyword Arguments`_ in the official Python tutorial.
-
-The DB API supports the following lookup types:
-
- =========== ==============================================================
- Type Description
- =========== ==============================================================
- exact Exact match: ``polls.get_object(id__exact=14)``.
- iexact Case-insensitive exact match:
- ``polls.get_list(slug__iexact="foo")`` matches a slug of
- ``foo``, ``FOO``, ``fOo``, etc.
- contains Case-sensitive containment test:
- ``polls.get_list(question__contains="spam")`` returns all polls
- that contain "spam" in the question. (PostgreSQL and MySQL
- only. SQLite doesn't support case-sensitive LIKE statements;
- ``contains`` will act like ``icontains`` for SQLite.)
- icontains Case-insensitive containment test.
- gt Greater than: ``polls.get_list(id__gt=4)``.
- gte Greater than or equal to.
- lt Less than.
- lte Less than or equal to.
- ne Not equal to.
- in In a given list: ``polls.get_list(id__in=[1, 3, 4])`` returns
- a list of polls whose IDs are either 1, 3 or 4.
- startswith Case-sensitive starts-with:
- ``polls.get_list(question__startswith="Would")``. (PostgreSQL
- and MySQL only. SQLite doesn't support case-sensitive LIKE
- statements; ``startswith`` will act like ``istartswith`` for
- SQLite.)
- endswith Case-sensitive ends-with. (PostgreSQL and MySQL only.)
- istartswith Case-insensitive starts-with.
- iendswith Case-insensitive ends-with.
- range Range test:
- ``polls.get_list(pub_date__range=(start_date, end_date))``
- returns all polls with a pub_date between ``start_date``
- and ``end_date`` (inclusive).
- year For date/datetime fields, exact year match:
- ``polls.get_count(pub_date__year=2005)``.
- month For date/datetime fields, exact month match.
- day For date/datetime fields, exact day match.
- isnull True/False; does is IF NULL/IF NOT NULL lookup:
- ``polls.get_list(expire_date__isnull=True)``.
- =========== ==============================================================
-
-Multiple lookups are allowed, of course, and are translated as "AND"s::
-
- polls.get_list(
- pub_date__year=2005,
- pub_date__month=1,
- question__startswith="Would",
- )
-
-...retrieves all polls published in January 2005 that have a question starting with "Would."
-
-For convenience, there's a ``pk`` lookup type, which translates into
-``(primary_key)__exact``. In the polls example, these two statements are
-equivalent::
-
- polls.get_object(id__exact=3)
- polls.get_object(pk=3)
-
-``pk`` lookups also work across joins. In the polls example, these two
-statements are equivalent::
-
- choices.get_list(poll__id__exact=3)
- choices.get_list(poll__pk=3)
-
-If you pass an invalid keyword argument, the function will raise ``TypeError``.
-
-.. _`Keyword Arguments`: http://docs.python.org/tut/node6.html#SECTION006720000000000000000
-
-OR lookups
-----------
-
-By default, multiple lookups are "AND"ed together. If you'd like to use ``OR``
-statements in your queries, use the ``complex`` lookup type.
-
-``complex`` takes an expression of clauses, each of which is an instance of
-``django.core.meta.Q``. ``Q`` takes an arbitrary number of keyword arguments in
-the standard Django lookup format. And you can use Python's "and" (``&``) and
-"or" (``|``) operators to combine ``Q`` instances. For example::
-
- from django.core.meta import Q
- polls.get_object(complex=(Q(question__startswith='Who') | Q(question__startswith='What')))
-
-The ``|`` symbol signifies an "OR", so this (roughly) translates into::
-
- SELECT * FROM polls
- WHERE question LIKE 'Who%' OR question LIKE 'What%';
-
-You can use ``&`` and ``|`` operators together, and use parenthetical grouping.
Example::
- polls.get_object(complex=(Q(question__startswith='Who') & (Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6))))
+ b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
+ b2.id # Returns None, because b doesn't have an ID yet.
+ b2.save()
+ b2.id # Returns the ID of your new object.
-This roughly translates into::
+There's no way to tell what the value of an ID will be before you call
+``save()``, because that value is calculated by your database, not by Django.
- SELECT * FROM polls
- WHERE question LIKE 'Who%'
- AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06');
+(For convenience, each model has an ``AutoField`` named ``id`` by default
+unless you explicitly specify ``primary_key=True`` on a field. See the
+`AutoField documentation`_.)
-See the `OR lookups examples page`_ for more examples.
+.. _AutoField documentation: TODO: Link
-.. _OR lookups examples page: http://www.djangoproject.com/documentation/models/or_lookups/
+Explicitly specifying auto-primary-key values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Ordering
-========
+If a model has an ``AutoField`` but you want to define a new object's ID
+explicitly when saving, just define it explicitly before saving, rather than
+relying on the auto-assignment of the ID.
-The results are automatically ordered by the ordering tuple given by the
-``ordering`` key in the model, but the ordering may be explicitly
-provided by the ``order_by`` argument to a lookup::
+Example::
- polls.get_list(
- pub_date__year=2005,
- pub_date__month=1,
- order_by=('-pub_date', 'question'),
- )
+ b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
+ b3.id # Returns 3.
+ b3.save()
+ b3.id # Returns 3.
-The result set above will be ordered by ``pub_date`` descending, then
-by ``question`` ascending. The negative sign in front of "-pub_date" indicates
-descending order. Ascending order is implied. To order randomly, use "?", like
-so::
+If you assign auto-primary-key values manually, make sure not to use an
+already-existing primary-key value! If you create a new object with an explicit
+primary-key value that already exists in the database, Django will assume
+you're changing the existing record rather than creating a new one.
- polls.get_list(order_by=['?'])
+Given the above ``'Cheddar Talk'`` blog example, this example would override
+the previous record in the database::
+
+ b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
+ b4.save() # Overrides the previous blog with ID=3!
+
+See _`How Django knows to UPDATE vs. INSERT`, below, for the reason this
+happens.
+
+Explicitly specifying auto-primary-key values is mostly useful for bulk-saving
+objects, when you're confident you won't have primary-key collision.
+
+Saving changes to objects
+=========================
+
+To save changes to an object that's already in the database, use ``save()``.
+
+Given a ``Blog`` instance ``b5`` that has already been saved to the database,
+this example changes its name and updates its record in the database::
+
+ b5.name = 'New name'
+ b5.save()
+
+This performs an ``UPDATE`` SQL statement behind the scenes. Django doesn't hit
+the database until you explicitly call ``save()``.
+
+The ``save()`` method has no return value.
+
+How Django knows to UPDATE vs. INSERT
+-------------------------------------
+
+You may have noticed Django database objects use the same ``save()`` method
+for creating and changing objects. Django abstracts the need to use ``INSERT``
+or ``UPDATE`` SQL statements. Specifically, when you call ``save()``, Django
+follows this algorithm:
+
+ * If the object's primary key attribute is set to a value that evaluates to
+ ``False`` (such as ``None`` or the empty string), Django executes a
+ ``SELECT`` query to determine whether a record with the given primary key
+ already exists.
+ * If the record with the given primary key does already exist, Django
+ executes an ``UPDATE`` query.
+ * If the object's primary key attribute is *not* set, or if it's set but a
+ record doesn't exist, Django executes an ``INSERT``.
+
+The one gotcha here is that you should be careful not to specify a primary-key
+value explicitly when saving new objects, if you cannot guarantee the
+primary-key value is unused. For more on this nuance, see
+"Explicitly specifying auto-primary-key values" above.
+
+Retrieving objects
+==================
+
+To retrieve objects from your database, you construct a ``QuerySet`` via a
+``Manager`` on your model class.
+
+A ``QuerySet`` represents a collection of objects from your database. It can
+have zero, one or many *filters* -- criteria that narrow down the collection
+based on given parameters. In SQL terms, a ``QuerySet`` equates to a ``SELECT``
+statement, and a filter is a limiting clause such as ``WHERE`` or ``LIMIT``.
+
+You get a ``QuerySet`` by using your model's ``Manager``. Each model has at
+least one ``Manager``, and it's called ``objects`` by default. Access it
+directly via the model class, like so::
+
+ Blog.objects #
+ b = Blog(name='Foo', tagline='Bar')
+ b.objects # AttributeError: "Manager isn't accessible via Blog instances."
+
+(``Managers`` are accessible only via model classes, rather than from model
+instances, to enforce a separation between "table-level" operations and
+"record-level" operations.)
+
+The ``Manager`` is the main source of ``QuerySets`` for a model. It acts as a
+"root" ``QuerySet`` that describes all objects in the model's database table.
+For example, ``Blog.objects`` is the initial ``QuerySet`` that contains all
+``Blog`` objects in the database.
+
+Retrieving all objects
+----------------------
+
+The simplest way to retrieve objects from a table is to get all of them.
+To do this, use the ``all()`` method on a ``Manager``.
+
+Example::
+
+ all_entries = Entry.objects.all()
+
+The ``all()`` method returns a ``QuerySet`` of all the objects in the database.
+
+(If ``Entry.objects`` is a ``QuerySet``, why can't we just do ``Entry.objects``?
+That's because ``Entry.objects``, the root ``QuerySet``, is a special case
+that cannot be evaluated. The ``all()`` method returns a ``QuerySet`` that
+*can* be evaluated.)
+
+Filtering objects
+-----------------
+
+The root ``QuerySet`` provided by the ``Manager`` describes all objects in the
+database table. Usually, though, you'll need to select only a subset of the
+complete set of objects.
+
+To create such a subset, you refine the initial ``QuerySet``, adding filter
+conditions. The two most common ways to refine a ``QuerySet`` are:
+
+``filter(**kwargs)``
+ Returns a new ``QuerySet`` containing objects that match the given lookup
+ parameters.
+
+``exclude(**kwargs)``
+ Returns a new ``QuerySet`` containing objects that do *not* match the given
+ lookup parameters.
+
+The lookup parameters (``**kwargs`` in the above function definitions) should
+be in the format described in _`Field lookups` below.
+
+For example, to get a ``QuerySet`` of blog entries from the year 2006, use
+``filter()`` like so::
+
+ Entry.objects.filter(pub_date__year=2006)
+
+(Note we don't have to add an ``all()`` -- ``Entry.objects.all().filter(...)``.
+That would still work, but you only need ``all()`` when you want all objects
+from the root ``QuerySet``.)
+
+Chaining filters
+~~~~~~~~~~~~~~~~
+
+The result of refining a ``QuerySet`` is itself a ``QuerySet``, so it's
+possible to chain refinements together. For example::
+
+ Entry.objects.filter(
+ headline__startswith='What').exclude(
+ pub_date__gte=datetime.now()).filter(
+ pub_date__gte=datetime(2005, 1, 1))
+
+...takes the initial ``QuerySet`` of all entries in the database, adds a
+filter, then an exclusion, then another filter. The final result is a
+``QuerySet`` containing all entries with a headline that starts with "What",
+that were published between January 1, 2005, and the current day.
+
+Filtered QuerySets are unique
+-----------------------------
+
+Each time you refine a ``QuerySet``, you get a brand-new ``QuerySet`` that is
+in no way bound to the previous ``QuerySet``. Each refinement creates a
+separate and distinct ``QuerySet`` that can be stored, used and reused.
+
+Example::
+
+ q1 = Entry.objects.filter(headline__startswith="What")
+ q2 = q1.exclude(pub_date__gte=datetime.now())
+ q3 = q1.filter(pub_date__gte=datetime.now())
+
+These three ``QuerySets`` are separate. The first is a base ``QuerySet``
+containing all entries that contain a headline starting with "What". The second
+is a subset of the first, with an additional criteria that excludes records
+whose ``pub_date`` is greater than now. The third is a subset of the first,
+with an additional criteria that selects only the records whose ``pub_date`` is
+greater than now. The initial ``QuerySet`` (``q1``) is unaffected by the
+refinement process.
+
+QuerySets are lazy
+------------------
+
+``QuerySets`` are lazy -- the act of creating a ``QuerySet`` doesn't involve
+any database activity. You can stack filters together all day long, and Django
+won't actually run the query until the ``QuerySet`` is *evaluated*.
+
+When QuerySets are evaluated
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can evaluate a ``QuerySet`` in the following ways:
+
+ * **Iteration.** A ``QuerySet`` is iterable, and it executes its database
+ query the first time you iterate over it. For example, this will print
+ the headline of all entries in the database::
+
+ for e in Entry.objects.all():
+ print e.headline
+
+ * **Slicing.** A ``QuerySet`` can be sliced, using Python's array-slicing
+ syntax, and it executes its database query the first time you slice it.
+ Examples::
+
+ fifth_entry = Entry.objects.all()[4]
+ all_entries_but_the_first_two = Entry.objects.all()[2:]
+ every_second_entry = Entry.objects.all()[::2]
+
+ * **repr().** A ``QuerySet`` is evaluated when you call ``repr()`` on it.
+ This is for convenience in the Python interactive interpreter, so you can
+ immediately see your results when using the API interactively.
+
+ * **len().** A ``QuerySet`` is evaluated when you call ``len()`` on it.
+ This, as you might expect, returns the length of the result list.
+
+ Note: *Don't* use ``len()`` on ``QuerySet``s if all you want to do is
+ determine the number of records in the set. It's much more efficient to
+ handle a count at the database level, using SQL's ``SELECT COUNT(*)``,
+ and Django provides a ``count()`` method for precisely this reason. See
+ ``count()`` below.
+
+ * **list().** Force evaluation of a ``QuerySet`` by calling ``list()`` on
+ it. For example::
+
+ entry_list = list(Entry.objects.all())
+
+ Be warned, though, that this could have a large memory overhead, because
+ Django will load each element of the list into memory. In contrast,
+ iterating over a ``QuerySet`` will take advantage of your database to
+ load data and instantiate objects only as you need them.
+
+QuerySet methods that return new QuerySets
+------------------------------------------
+
+Django provides a range of ``QuerySet`` refinement methods that modify either
+the types of results returned by the ``QuerySet`` or the way its SQL query is
+executed.
+
+filter(**kwargs)
+~~~~~~~~~~~~~~~~
+
+Returns a new ``QuerySet`` containing objects that match the given lookup
+parameters.
+
+The lookup parameters (``**kwargs``) should be in the format described in
+_`Field lookups` below. Multiple parameters are joined via ``AND`` in the
+underlying SQL statement.
+
+exclude(**kwargs)
+~~~~~~~~~~~~~~~~~
+
+Returns a new ``QuerySet`` containing objects that do *not* match the given
+lookup parameters.
+
+The lookup parameters (``**kwargs``) should be in the format described in
+_`Field lookups` below. Multiple parameters are joined via ``AND`` in the
+underlying SQL statement, and the whole thing is enclosed in a ``NOT()``.
+
+This example excludes all entries whose ``pub_date`` is the current date/time
+AND whose ``headline`` is "Hello"::
+
+ Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
+
+In SQL terms, that evaluates to::
+
+ SELECT ...
+ WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
+
+This example excludes all entries whose ``pub_date`` is the current date/time
+OR whose ``headline`` is "Hello"::
+
+ Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')
+
+In SQL terms, that evaluates to::
+
+ SELECT ...
+ WHERE NOT pub_date > '2005-1-3'
+ AND NOT headline = 'Hello'
+
+Note the second example is more restrictive.
+
+order_by(*fields)
+~~~~~~~~~~~~~~~~~
+
+By default, results returned by a ``QuerySet`` are ordered by the ordering
+tuple given by the ``ordering`` option in the model's ``Meta``. You can
+override this on a per-``QuerySet`` basis by using the ``order_by`` method.
+
+Example::
+
+ Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')
+
+The result above will be ordered by ``pub_date`` descending, then by
+``headline`` ascending. The negative sign in front of ``"-pub_date"`` indicates
+*descending* order. Ascending order is implied. To order randomly, use ``"?"``,
+like so::
+
+ Entry.objects.order_by('?')
To order by a field in a different table, add the other table's name and a dot,
like so::
- choices.get_list(order_by=('polls.pub_date', 'choice'))
+ Entry.objects.order_by('blogs_blog.name', 'headline')
There's no way to specify whether ordering should be case sensitive. With
respect to case-sensitivity, Django will order results however your database
backend normally orders them.
+distinct()
+~~~~~~~~~~
+
+Returns a new ``QuerySet`` that uses ``SELECT DISTINCT`` in its SQL query. This
+eliminates duplicate rows from the query results.
+
+By default, a ``QuerySet`` will not eliminate duplicate rows. In practice, this
+is rarely a problem, because simple queries such as ``Blog.objects.all()``
+don't introduce the possibility of duplicate result rows.
+
+However, if your query spans multiple tables, it's possible to get duplicate
+results when a ``QuerySet`` is evaluated. That's when you'd use ``distinct()``.
+
+values(*fields)
+~~~~~~~~~~~~~~~
+
+Returns a ``ValuesQuerySet`` -- a ``QuerySet`` that evaluates to a list of
+dictionaries instead of model-instance objects.
+
+Each of those dictionaries represents an object, with the keys corresponding to
+the attribute names of model objects.
+
+This example compares the dictionaries of ``values()`` with the normal model
+objects::
+
+ # This list contains a Blog object.
+ >>> Blog.objects.filter(name__startswith='Beatles')
+ [Beatles Blog]
+
+ # This list contains a dictionary.
+ >>> Blog.objects.filter(name__startswith='Beatles').values()
+ [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]
+
+``values()`` takes optional positional arguments, ``*fields``, which specify
+field names to which the ``SELECT`` should be limited. If you specify the
+fields, each dictionary will contain only the field keys/values for the fields
+you specify. If you don't specify the fields, each dictionary will contain a
+key and value for every field in the database table.
+
+Example::
+
+ >>> Blog.objects.values()
+ [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}],
+ >>> Blog.objects.values('id', 'name')
+ [{'id': 1, 'name': 'Beatles Blog'}]
+
+A ``ValuesQuerySet`` is useful when you know you're only going to need values
+from a small number of the available fields and you won't need the
+functionality of a model instance object. It's more efficient to select only
+the fields you need to use.
+
+Finally, note a ``ValuesQuerySet`` is a subclass of ``QuerySet``, so it has all
+methods of ``QuerySet``. You can call ``filter()`` on it, or ``order_by()``, or
+whatever. Yes, that means these two calls are identical::
+
+ Blog.objects.values().order_by('id')
+ Blog.objects.order_by('id').values()
+
+The people who made Django prefer to put all the SQL-affecting methods first,
+followed (optionally) by any output-affecting methods (such as ``values()``),
+but it doesn't really matter. This is your chance to really flaunt your
+individualism.
+
+dates(field, kind, order='ASC')
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Returns a ``DateQuerySet`` -- a ``QuerySet`` that evaluates to a list of
+``datetime.datetime`` objects representing all available dates of a particular
+kind within the contents of the ``QuerySet``.
+
+``field`` should be the name of a ``DateField`` or ``DateTimeField`` of your
+model.
+
+``kind`` should be either ``"year"``, ``"month"`` or ``"day"``. Each
+``datetime.datetime`` object in the result list is "truncated" to the given
+``type``.
+
+ * ``"year"`` returns a list of all distinct year values for the field.
+ * ``"month"`` returns a list of all distinct year/month values for the field.
+ * ``"day"`` returns a list of all distinct year/month/day values for the field.
+
+``order``, which defaults to ``'ASC'``, should be either ``'ASC'`` or
+``'DESC'``. This specifies how to order the results.
+
+Examples::
+
+ >>> Entry.objects.dates('pub_date', 'year')
+ [datetime.datetime(2005, 1, 1)]
+ >>> Entry.objects.dates('pub_date', 'month')
+ [datetime.datetime(2005, 2, 1), datetime.datetime(2005, 3, 1)]
+ >>> Entry.objects.dates('pub_date', 'day')
+ [datetime.datetime(2005, 2, 20), datetime.datetime(2005, 3, 20)]
+ >>> Entry.objects.dates('pub_date', 'day', order='DESC')
+ [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)]
+ >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
+ [datetime.datetime(2005, 3, 20)]
+
+select_related()
+~~~~~~~~~~~~~~~~
+
+Returns a ``QuerySet`` that will automatically "follow" foreign-key
+relationships, selecting that additional related-object data when it executes
+its query. This is a performance booster which results in (sometimes much)
+larger queries but means later use of foreign-key relationships won't require
+database queries.
+
+The following examples illustrate the difference between plain lookups and
+``select_related()`` lookups. Here's standard lookup::
+
+ # Hits the database.
+ e = Entry.objects.get(id=5)
+
+ # Hits the database again to get the related Blog object.
+ b = e.blog
+
+And here's ``select_related`` lookup::
+
+ # Hits the database.
+ e = Entry.objects.select_related().get(id=5)
+
+ # Doesn't hit the database, because e.blog has been prepopulated
+ # in the previous query.
+ b = e.blog
+
+``select_related()`` follows foreign keys as far as possible. If you have the
+following models::
+
+ class City(models.Model):
+ # ...
+
+ class Person(models.Model):
+ # ...
+ hometown = models.ForeignKey(City)
+
+ class Book(meta.Model):
+ # ...
+ author = models.ForeignKey(Person)
+
+...then a call to ``Book.objects.select_related().get(id=4)`` will cache the
+related ``Person`` *and* the related ``City``::
+
+ b = Book.objects.select_related().get(id=4)
+ p = b.author # Doesn't hit the database.
+ c = p.hometown # Doesn't hit the database.
+
+ sv = Book.objects.get(id=4) # No select_related() in this example.
+ p = b.author # Hits the database.
+ c = p.hometown # Hits the database.
+
+extra(select=None, where=None, params=None, tables=None)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, the Django query syntax by itself can't easily express a complex
+``WHERE`` clause. For these edge cases, Django provides the ``extra()``
+``QuerySet`` modifier -- a hook for injecting specific clauses into the SQL
+generated by a ``QuerySet``.
+
+By definition, these extra lookups may not be portable to different database
+engines (because you're explicitly writing SQL code) and violate the DRY
+principle, so you should avoid them if possible.
+
+Specify one or more of ``params``, ``select``, ``where`` or ``tables``. None
+of the arguments is required, but you should use at least one of them.
+
+``select``
+ The ``select`` argument lets you put extra fields in the ``SELECT`` clause.
+ It should be a dictionary mapping attribute names to SQL clauses to use to
+ calculate that attribute.
+
+ Example::
+
+ Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
+
+ As a result, each ``Entry`` object will have an extra attribute,
+ ``is_recent``, a boolean representing whether the entry's ``pub_date`` is
+ greater than Jan. 1, 2006.
+
+ Django inserts the given SQL snippet directly into the ``SELECT``
+ statement, so the resulting SQL of the above example would be::
+
+ SELECT blog_entry.*, (pub_date > '2006-01-01')
+ FROM blog_entry;
+
+
+ The next example is more advanced; it does a subquery to give each
+ resulting ``Blog`` object an ``entry_count`` attribute, an integer count
+ of associated ``Entry`` objects.
+
+ Blog.objects.extra(
+ select={
+ 'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'
+ },
+ )
+
+ (In this particular case, we're exploiting the fact that the query will
+ already contain the ``blog_blog`` table in its ``FROM`` clause.)
+
+ The resulting SQL of the above example would be::
+
+ SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id)
+ FROM blog_blog;
+
+ Note that the parenthesis required by most database engines around
+ subqueries are not required in Django's ``select`` clauses. Also note that
+ some database backends, such as some MySQL versions, don't support
+ subqueries.
+
+``where`` / ``tables``
+ You can define explicit SQL ``WHERE`` clauses -- perhaps to perform
+ non-explicit joins -- by using ``where``. You can manually add tables to
+ the SQL ``FROM`` clause by using ``tables``.
+
+ ``where`` and ``tables`` both take a list of strings. All ``where``
+ parameters are "AND"ed to any other search criteria.
+
+ Example::
+
+ Entry.objects.extra(where=['id IN (3, 4, 5, 20)'])
+
+ ...translates (roughly) into the following SQL::
+
+ SELECT * FROM blog_entry WHERE id IN (3, 4, 5, 20);
+
+``params``
+ The ``select`` and ``where`` parameters described above may use standard
+ Python database string placeholders -- ``'%s'`` to indicate parameters the
+ database engine should automatically quote. The ``params`` argument is a
+ list of any extra parameters to be substituted.
+
+ Example::
+
+ Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
+
+ Always use ``params`` instead of embedding values directly into ``select``
+ or ``where`` because ``params`` will ensure values are quoted correctly
+ according to your particular backend. (For example, quotes will be escaped
+ correctly.)
+
+ Bad::
+
+ Entry.objects.extra(where=["headline='Lennon'"])
+
+ Good::
+
+ Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
+
+QuerySet methods that do not return QuerySets
+---------------------------------------------
+
+The following ``QuerySet`` methods evaluate the ``QuerySet`` and return
+something *other than* a ``QuerySet``.
+
+These methods do not use a cache (see _`Caching and QuerySets` below). Rather,
+they query the database each time they're called.
+
+get(**kwargs)
+~~~~~~~~~~~~~
+
+Returns the object matching the given lookup parameters, which should be in
+the format described in _`Field lookups`.
+
+``get()`` raises ``AssertionError`` if more than one object was found.
+
+``get()`` raises a ``DoesNotExist`` exception if an object wasn't found for the
+given parameters. The ``DoesNotExist`` exception is an attribute of the model
+class. Example::
+
+ Entry.objects.get(id='foo') # raises Entry.DoesNotExist
+
+The ``DoesNotExist`` exception inherits from
+``django.core.exceptions.ObjectDoesNotExist``, so you can target multiple
+``DoesNotExist`` exceptions. Example::
+
+ from django.core.exceptions import ObjectDoesNotExist
+ try:
+ e = Entry.objects.get(id=3)
+ b = Blog.objects.get(id=1)
+ except ObjectDoesNotExist:
+ print "Either the entry or blog doesn't exist."
+
+count()
+~~~~~~~
+
+Returns an integer representing the number of objects in the database matching
+the ``QuerySet``. ``count()`` never raises exceptions.
+
+Example::
+
+ # Returns the total number of entries in the database.
+ Entry.objects.count()
+
+ # Returns the number of entries whose headline contains 'Lennon'
+ Entry.objects.filter(headline__contains='Lennon').count()
+
+``count()`` performs a ``SELECT COUNT(*)`` behind the scenes, so you should
+always use ``count()`` rather than loading all of the record into Python
+objects and calling ``len()`` on the result.
+
+Depending on which database you're using (e.g. PostgreSQL vs. MySQL),
+``count()`` may return a long integer instead of a normal Python integer. This
+is an underlying implementation quirk that shouldn't pose any real-world
+problems.
+
+in_bulk(id_list)
+~~~~~~~~~~~~~~~~
+
+Takes a list of primary-key values and returns a dictionary mapping each
+primary-key value to an instance of the object with the given ID.
+
+Example::
+
+ >>> Blog.objects.in_bulk([1])
+ {1: Beatles Blog}
+ >>> Blog.objects.in_bulk([1, 2])
+ {1: Beatles Blog, 2: Cheddar Talk}
+ >>> Blog.objects.in_bulk([])
+ {}
+
+If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary.
+
+latest(field_name=None)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Returns the latest object in the table, by date, using the ``field_name``
+provided as the date field.
+
+This example returns the latest ``Entry`` in the table, according to the
+``pub_date`` field::
+
+ Entry.objects.latest('pub_date')
+
+If your model's ``Meta`` specifies ``get_latest_by``, you can leave off the
+``field_name`` argument to ``latest()``. Django will use the field specified in
+``get_latest_by`` by default.
+
+Like ``get()``, ``latest()`` raises ``DoesNotExist`` if an object doesn't
+exist with the given parameters.
+
+Note ``latest()`` exists purely for convenience and readability.
+
+Field lookups
+-------------
+
+Field lookups are how you specify the meat of an SQL ``WHERE`` clause. They're
+specified as keyword arguments to the ``QuerySet`` methods ``filter()``,
+``exclude()`` and ``get()``.
+
+Basic lookups keyword arguments take the form ``field__lookuptype=value``.
+(That's a double-underscore). For example::
+
+ Entry.objects.filter(pub_date__lte='2006-01-01')
+
+translates (roughly) into the following SQL::
+
+ SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
+
+.. admonition:: How this is possible
+
+ Python has the ability to define functions that accept arbitrary name-value
+ arguments whose names and values are evaluated at runtime. For more
+ information, see `Keyword Arguments`_ in the official Python tutorial.
+
+ .. _`Keyword Arguments`: http://docs.python.org/tut/node6.html#SECTION006720000000000000000
+
+If you pass an invalid keyword argument, a lookup function will raise
+``TypeError``.
+
+The database API supports the following lookup types:
+
+exact
+~~~~~
+
+Exact match.
+
+Example::
+
+ Entry.objects.get(id__exact=14)
+
+SQL equivalent::
+
+ SELECT ... WHERE id = 14;
+
+iexact
+~~~~~~
+
+Case-insensitive exact match.
+
+Example::
+
+ Blog.objects.get(name__iexact='beatles blog')
+
+SQL equivalent::
+
+ SELECT ... WHERE name ILIKE 'beatles blog';
+
+Note this will match ``'Beatles Blog'``, ``'beatles blog'``,
+``'BeAtLes BLoG'``, etc.
+
+contains
+~~~~~~~~
+
+Case-sensitive containment test.
+
+Example::
+
+ Entry.objects.get(headline__contains='Lennon')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline LIKE '%Lennon%';
+
+Note this will match the headline ``'Today Lennon honored'`` but not
+``'today lennon honored'``.
+
+SQLite doesn't support case-sensitive ``LIKE`` statements; ``contains`` acts
+like ``icontains`` for SQLite.
+
+icontains
+~~~~~~~~~
+
+Case-insensitive containment test.
+
+Example::
+
+ Entry.objects.get(headline__icontains='Lennon')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline ILIKE '%Lennon%';
+
+gt
+~~
+
+Greater than.
+
+Example::
+
+ Entry.objects.filter(id__gt=4)
+
+SQL equivalent::
+
+ SELECT ... WHERE id > 4;
+
+gte
+~~~
+
+Greater than or equal to.
+
+lt
+~~
+
+Less than.
+
+lte
+~~~
+
+Less than or equal to.
+
+in
+~~
+
+In a given list.
+
+Example::
+
+ Entry.objects.filter(id__in=[1, 3, 4])
+
+SQL equivalent::
+
+ SELECT ... WHERE id IN (1, 3, 4);
+
+startswith
+~~~~~~~~~~
+
+Case-sensitive starts-with.
+
+Example::
+
+ Entry.objects.filter(headline__startswith='Will')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline LIKE 'Will%';
+
+SQLite doesn't support case-sensitive ``LIKE`` statements; ``startswith`` acts
+like ``istartswith`` for SQLite.
+
+istartswith
+~~~~~~~~~~~
+
+Case-insensitive starts-with.
+
+Example::
+
+ Entry.objects.filter(headline__istartswith='will')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline ILIKE 'Will%';
+
+endswith
+~~~~~~~~
+
+Case-sensitive ends-with.
+
+Example::
+
+ Entry.objects.filter(headline__endswith='cats')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline LIKE '%cats';
+
+SQLite doesn't support case-sensitive ``LIKE`` statements; ``endswith`` acts
+like ``iendswith`` for SQLite.
+
+iendswith
+~~~~~~~~~
+
+Case-insensitive ends-with.
+
+Example::
+
+ Entry.objects.filter(headline__iendswith='will')
+
+SQL equivalent::
+
+ SELECT ... WHERE headline ILIKE '%will'
+
+range
+~~~~~
+
+Range test (inclusive).
+
+Example::
+
+ start_date = datetime.date(2005, 1, 1)
+ end_date = datetime.date(2005, 3, 31)
+ Entry.objects.filter(pub_date__range=(start_date, end_date))
+
+SQL equivalent::
+
+ SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';
+
+You can use ``range`` anywhere you can use ``BETWEEN`` in SQL -- for dates,
+numbers and even characters.
+
+year
+~~~~
+
+For date/datetime fields, exact year match. Takes a four-digit year.
+
+Example::
+
+ Entry.objects.filter(pub_date__year=2005)
+
+SQL equivalent::
+
+ SELECT ... WHERE EXTRACT('year' FROM pub_date) = '2005';
+
+(The exact SQL syntax varies for each database engine.)
+
+month
+~~~~~
+
+For date/datetime fields, exact month match. Takes an integer 1 (January)
+through 12 (December).
+
+Example::
+
+ Entry.objects.filter(pub_date__month=12)
+
+SQL equivalent::
+
+ SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';
+
+(The exact SQL syntax varies for each database engine.)
+
+day
+~~~
+
+For date/datetime fields, exact day match.
+
+Example::
+
+ Entry.objects.filter(pub_date__day=3)
+
+SQL equivalent::
+
+ SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';
+
+(The exact SQL syntax varies for each database engine.)
+
+Note this will match any record with a pub_date on the third day of the month,
+such as January 3, July 3, etc.
+
+isnull
+~~~~~~
+
+``NULL`` or ``IS NOT NULL`` match. Takes either ``True`` or ``False``, which
+correspond to ``IS NULL`` and ``IS NOT NULL``, respectively.
+
+Example::
+
+ Entry.objects.filter(pub_date__isnull=True)
+
+SQL equivalent::
+
+ SELECT ... WHERE pub_date IS NULL;
+
+Default lookups are exact
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you don't provide a lookup type -- that is, if your keyword argument doesn't
+contain a double underscore -- the lookup type is assumed to be ``exact``.
+
+For example, the following two statements are equivalent::
+
+ Blog.objects.get(id=14)
+ Blog.objects.get(id__exact=14)
+
+This is for convenience, because ``exact`` lookups are the common case.
+
+The pk lookup shortcut
+~~~~~~~~~~~~~~~~~~~~~~
+
+For convenience, Django provides a ``pk`` lookup type, which stands for
+"primary_key". This is shorthand for "an exact lookup on the primary-key."
+
+In the example ``Blog`` model, the primary key is the ``id`` field, so these
+two statements are equivalent::
+
+ Blog.objects.get(id__exact=14)
+ Blog.objects.get(pk=14)
+
+``pk`` lookups also work across joins. For example, these two statements are
+equivalent::
+
+ Entry.objects.filter(blog__id__exact=3)
+ Entry.objects.filter(blog__pk=3)
+
+Escaping parenthesis and underscores in LIKE statements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
+``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``
+and ``iendswith``) will automatically escape the two special characters used in
+``LIKE`` statements -- the percent sign and the underscore. (In a ``LIKE``
+statement, the percent sign signifies a multiple-character wildcard and the
+underscore signifies a single-character wildcard.)
+
+This means things should work intuitively, so the abstraction doesn't leak.
+For example, to retrieve all the entries that contain a percent sign, just use
+the percent sign as any other character::
+
+ Entry.objects.filter(headline__contains='%')
+
+Django takes care of the quoting for you; the resulting SQL will look something
+like this::
+
+ SELECT ... WHERE headline LIKE '%\%%';
+
+Same goes for underscores. Both percentage signs and underscores are handled
+for you transparently.
+
+Caching and QuerySets
+---------------------
+
+Each ``QuerySet`` contains a cache, to minimize database access. It's important
+to understand how it works, in order to write the most efficient code.
+
+In a newly created ``QuerySet``, the cache is empty. The first time a
+``QuerySet`` is evaluated -- and, hence, a database query happens -- Django
+saves the query results in the ``QuerySet``'s cache and returns the results
+that have been explicitly requested (e.g., the next element, if the
+``QuerySet`` is being iterated over). Subsequent evaluations of the
+``QuerySet`` reuse the cached results.
+
+Keep this caching behavior in mind, because it may bite you if you don't use
+your ``QuerySet``s correctly. For example, the following will create two
+``QuerySet``s, evaluate them, and throw them away::
+
+ print [e.headline for e in Entry.objects.all()]
+ print [e.pub_date for e in Entry.objects.all()]
+
+That means the same database query will be executed twice, effectively doubling
+your database load. Also, there's a possibility the two lists may not include
+the same database records, because an ``Entry`` may have been added or deleted
+in the split second between the two requests.
+
+To avoid this problem, simply save the ``QuerySet`` and reuse it::
+
+ queryset = Poll.objects.all()
+ print [p.headline for p in queryset] # Evaluate the query set.
+ print [p.pub_date for p in queryset] # Re-use the cache from the evaluation.
+
+Comparing objects
+=================
+
+To compare two model instances, just use the standard Python comparison operator,
+the double equals sign: ``==``. Behind the scenes, that compares the primary
+key values of two models.
+
+Using the ``Entry`` example above, the following two statements are equivalent::
+
+ some_entry == other_entry
+ some_entry.id == other_entry.id
+
+If a model's primary key isn't called ``id``, no problem. Comparisons will
+always use the primary key, whatever it's called. For example, if a model's
+primary key field is called ``name``, these two statements are equivalent::
+
+ some_obj == other_obj
+ some_obj.name == other_obj.name
+
+
+
+
+========================================
+THE REST OF THIS HAS NOT YET BEEN EDITED
+========================================
+
+
+OR lookups
+==========
+
+Keyword argument queries are "AND"ed together. If you have more
+complex query requirements (for example, you need to include an ``OR``
+statement in your query), you need to use ``Q`` objects.
+
+A ``Q`` object (``django.db.models.Q``) is an object used to encapsulate a
+collection of keyword arguments. These keyword arguments are specified in
+the same way as keyword arguments to the basic lookup functions like get()
+and filter(). For example::
+
+ Q(question__startswith='What')
+
+is a ``Q`` object encapsulating a single ``LIKE`` query. ``Q`` objects can be
+combined using the ``&`` and ``|`` operators. When an operator is used on two
+``Q`` objects, it yields a new ``Q`` object. For example the statement::
+
+ Q(question__startswith='Who') | Q(question__startswith='What')
+
+... yields a single ``Q`` object that represents the "OR" of two
+"question__startswith" queries, equivalent to the SQL WHERE clause::
+
+ ... WHERE question LIKE 'Who%' OR question LIKE 'What%'
+
+You can compose statements of arbitrary complexity by combining ``Q`` objects
+with the ``&`` and ``|`` operators. Parenthetical grouping can also be used.
+
+One or more ``Q`` objects can then provided as arguments to the lookup
+functions. If multiple ``Q`` object arguments are provided to a lookup
+function, they will be "AND"ed together. For example::
+
+ Poll.objects.get(
+ Q(question__startswith='Who'),
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
+ )
+
+... roughly translates into the SQL::
+
+ SELECT * from polls WHERE question LIKE 'Who%'
+ AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
+
+If necessary, lookup functions can mix the use of ``Q`` objects and keyword
+arguments. All arguments provided to a lookup function (be they keyword
+argument or ``Q`` object) are "AND"ed together. However, if a ``Q`` object is
+provided, it must precede the definition of any keyword arguments. For
+example::
+
+ Poll.objects.get(
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
+ question__startswith='Who')
+
+... would be a valid query, equivalent to the previous example; but::
+
+ # INVALID QUERY
+ Poll.objects.get(
+ question__startswith='Who',
+ Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
+
+... would not be valid.
+
+A ``Q`` objects can also be provided to the ``complex`` keyword argument. For example::
+
+ Poll.objects.get(
+ complex=Q(question__startswith='Who') &
+ (Q(pub_date=date(2005, 5, 2)) |
+ Q(pub_date=date(2005, 5, 6))
+ )
+ )
+
+See the `OR lookups examples page`_ for more examples.
+
+.. _OR lookups examples page: http://www.djangoproject.com/documentation/models/or_lookups/
+
+
Relationships (joins)
=====================
-Joins may implicitly be performed by following relationships:
-``choices.get_list(poll__slug__exact="eggs")`` fetches a list of ``Choice``
-objects where the associated ``Poll`` has a slug of ``eggs``. Multiple levels
-of joins are allowed.
+When you define a relationship in a model (i.e., a ForeignKey,
+OneToOneField, or ManyToManyField), Django uses the name of the
+relationship to add a descriptor_ on every instance of the model.
+This descriptor behaves just like a normal attribute, providing
+access to the related object or objects. For example,
+``mychoice.poll`` will return the poll object associated with a specific
+instance of ``Choice``.
-Given an instance of an object, related objects can be looked-up directly using
-convenience functions. For example, if ``p`` is a ``Poll`` instance,
-``p.get_choice_list()`` will return a list of all associated choices. Astute
-readers will note that this is the same as
-``choices.get_list(poll__id__exact=p.id)``, except clearer.
+.. _descriptor: http://users.rcn.com/python/download/Descriptor.htm
-Each type of relationship creates a set of methods on each object in the
-relationship. These methods are created in both directions, so objects that are
-"related-to" need not explicitly define reverse relationships; that happens
-automatically.
+Django also adds a descriptor for the 'other' side of the relationship -
+the link from the related model to the model that defines the relationship.
+Since the related model has no explicit reference to the source model,
+Django will automatically derive a name for this descriptor. The name that
+Django chooses depends on the type of relation that is represented. However,
+if the definition of the relation has a `related_name` parameter, Django
+will use this name in preference to deriving a name.
-One-to-one relations
---------------------
+There are two types of descriptor that can be employed: Single Object
+Descriptors and Object Set Descriptors. The following table describes
+when each descriptor type is employed. The local model is the model on
+which the relation is defined; the related model is the model referred
+to by the relation.
-Each object in a one-to-one relationship will have a ``get_relatedobjectname()``
-method. For example::
+ =============== ============= =============
+ Relation Type Local Model Related Model
+ =============== ============= =============
+ OneToOneField Single Object Single Object
- class Place(meta.Model):
- # ...
+ ForeignKey Single Object Object Set
- class Restaurant(meta.Model):
- # ...
- the_place = meta.OneToOneField(places.Place)
+ ManyToManyField Object Set Object Set
+ =============== ============= =============
-In the above example, each ``Place`` will have a ``get_restaurant()`` method,
-and each ``Restaurant`` will have a ``get_the_place()`` method.
+Single object descriptor
+------------------------
-Many-to-one relations
+If the related object is a single object, the descriptor acts
+just as if the related object were an attribute::
+
+ # Obtain the existing poll
+ old_poll = mychoice.poll
+ # Set a new poll
+ mychoice.poll = new_poll
+ # Save the change
+ mychoice.save()
+
+Whenever a change is made to a Single Object Descriptor, save()
+must be called to commit the change to the database.
+
+If no `related_name` parameter is defined, Django will use the
+lower case version of the source model name as the name for the
+related descriptor. For example, if the ``Choice`` model had
+a field::
+
+ coordinator = models.OneToOneField(User)
+
+... instances of the model ``User`` would be able to call:
+
+ old_choice = myuser.choice
+ myuser.choice = new_choice
+
+By default, relations do not allow values of None; if you attempt
+to assign None to a Single Object Descriptor, an AttributeError
+will be thrown. However, if the relation has 'null=True' set
+(i.e., the database will allow NULLs for the relation), None can
+be assigned and returned by the descriptor to represent empty
+relations.
+
+Access to Single Object Descriptors is cached. The first time
+a descriptor on an instance is accessed, the database will be
+queried, and the result stored. Subsequent attempts to access
+the descriptor on the same instance will use the cached value.
+
+Object set descriptor
---------------------
-In each many-to-one relationship, the related object will have a
-``get_relatedobject()`` method, and the related-to object will have
-``get_relatedobject()``, ``get_relatedobject_list()``, and
-``get_relatedobject_count()`` methods (the same as the module-level
-``get_object()``, ``get_list()``, and ``get_count()`` methods).
+An Object Set Descriptor acts just like the Manager - as an initial Query
+Set describing the set of objects related to an instance. As such, any
+query refining technique (filter, exclude, etc) can be used on the Object
+Set descriptor. This also means that Object Set Descriptor cannot be evaluated
+directly - the ``all()`` method must be used to produce a Query Set that
+can be evaluated.
-In the poll example above, here are the available choice methods on a ``Poll`` object ``p``::
+If no ``related_name`` parameter is defined, Django will use the lower case
+version of the source model name appended with `_set` as the name for the
+related descriptor. For example, every ``Poll`` object has a ``choice_set``
+descriptor.
- p.get_choice()
- p.get_choice_list()
- p.get_choice_count()
+The Object Set Descriptor has utility methods to add objects to the
+related object set:
-And a ``Choice`` object ``c`` has the following method::
+``add(obj1, obj2, ...)``
+ Add the specified objects to the related object set.
- c.get_poll()
+``create(\**kwargs)``
+ Create a new object, and put it in the related object set. See
+ _`Creating new objects`
-Many-to-many relations
-----------------------
+The Object Set Descriptor may also have utility methods to remove objects
+from the related object set:
-Many-to-many relations result in the same set of methods as `Many-to-one relations`_,
-except that the ``get_relatedobject_list()`` function on the related object will
-return a list of instances instead of a single instance. So, if the relationship
-between ``Poll`` and ``Choice`` was many-to-many, ``choice.get_poll_list()`` would
-return a list.
+``remove(obj1, obj2, ...)``
+ Remove the specified objects from the related object set.
-Relationships across applications
----------------------------------
+``clear()``
+ Remove all objects from the related object set.
-If a relation spans applications -- if ``Place`` was had a ManyToOne relation to
-a ``geo.City`` object, for example -- the name of the other application will be
-added to the method, i.e. ``place.get_geo_city()`` and
-``city.get_places_place_list()``.
-
-Selecting related objects
--------------------------
-
-Relations are the bread and butter of databases, so there's an option to "follow"
-all relationships and pre-fill them in a simple cache so that later calls to
-objects with a one-to-many relationship don't have to hit the database. Do this by
-passing ``select_related=True`` to a lookup. This results in (sometimes much) larger
-queries, but it means that later use of relationships is much faster.
-
-For example, using the Poll and Choice models from above, if you do the following::
-
- c = choices.get_object(id__exact=5, select_related=True)
-
-Then subsequent calls to ``c.get_poll()`` won't hit the database.
-
-Note that ``select_related`` follows foreign keys as far as possible. If you have the
-following models::
-
- class Poll(meta.Model):
- # ...
-
- class Choice(meta.Model):
- # ...
- poll = meta.ForeignKey(Poll)
-
- class SingleVote(meta.Model):
- # ...
- choice = meta.ForeignKey(Choice)
-
-then a call to ``singlevotes.get_object(id__exact=4, select_related=True)`` will
-cache the related choice *and* the related poll::
-
- >>> sv = singlevotes.get_object(id__exact=4, select_related=True)
- >>> c = sv.get_choice() # Doesn't hit the database.
- >>> p = c.get_poll() # Doesn't hit the database.
-
- >>> sv = singlevotes.get_object(id__exact=4) # Note no "select_related".
- >>> c = sv.get_choice() # Hits the database.
- >>> p = c.get_poll() # Hits the database.
-
-Limiting selected rows
-======================
-
-The ``limit``, ``offset``, and ``distinct`` keywords can be used to control
-which rows are returned. Both ``limit`` and ``offset`` should be integers which
-will be directly passed to the SQL ``LIMIT``/``OFFSET`` commands.
-
-If ``distinct`` is True, only distinct rows will be returned. This is equivalent
-to a ``SELECT DISTINCT`` SQL clause. You can use this with ``get_values()`` to
-get distinct values. For example, this returns the distinct first_names::
-
- >>> people.get_values(fields=['first_name'], distinct=True)
- [{'first_name': 'Adrian'}, {'first_name': 'Jacob'}, {'first_name': 'Simon'}]
-
-Other lookup options
-====================
-
-There are a few other ways of more directly controlling the generated SQL
-for the lookup. Note that by definition these extra lookups may not be
-portable to different database engines (because you're explicitly writing
-SQL code) and should be avoided if possible.:
-
-``params``
-----------
-
-All the extra-SQL params described below may use standard Python string
-formatting codes to indicate parameters that the database engine will
-automatically quote. The ``params`` argument can contain any extra
-parameters to be substituted.
-
-``select``
-----------
-
-The ``select`` keyword allows you to select extra fields. This should be a
-dictionary mapping attribute names to a SQL clause to use to calculate that
-attribute. For example::
-
- polls.get_list(
- select={
- 'choice_count': 'SELECT COUNT(*) FROM choices WHERE poll_id = polls.id'
- }
- )
-
-Each of the resulting ``Poll`` objects will have an extra attribute, ``choice_count``,
-an integer count of associated ``Choice`` objects. Note that the parenthesis required by
-most database engines around sub-selects are not required in Django's ``select``
-clauses.
-
-``where`` / ``tables``
-----------------------
-
-If you need to explicitly pass extra ``WHERE`` clauses -- perhaps to perform
-non-explicit joins -- use the ``where`` keyword. If you need to
-join other tables into your query, you can pass their names to ``tables``.
-
-``where`` and ``tables`` both take a list of strings. All ``where`` parameters
-are "AND"ed to any other search criteria.
+These two removal methods will not exist on ForeignKeys where ``Null=False``
+(such as in the Poll example). This is to prevent database inconsistency - if
+the related field cannot be set to None, then an object cannot be removed
+from one relation without adding it to another.
+The members of a related object set can be assigned from any iterable object.
For example::
- polls.get_list(question__startswith='Who', where=['id IN (3, 4, 5, 20)'])
+ mypoll.choice_set = [choice1, choice2]
-...translates (roughly) into the following SQL:
+If the ``clear()`` method is available, any pre-existing objects will be removed
+from the Object Set before all objects in the iterable (in this case, a list)
+are added to the choice set. If the ``clear()`` method is not available, all
+objects in the iterable will be added without removing any existing elements.
- SELECT * FROM polls_polls WHERE question LIKE 'Who%' AND id IN (3, 4, 5, 20);
+Each of these operations on the Object Set Descriptor has immediate effect
+on the database - every add, create and remove is immediately and
+automatically saved to the database.
-Changing objects
-================
+Relationships and queries
+=========================
-Once you've retrieved an object from the database using any of the above
-options, changing it is extremely easy. Make changes directly to the
-objects fields, then call the object's ``save()`` method::
+When composing a ``filter`` or ``exclude`` refinement, it may be necessary to
+include conditions that span relationships. Relations can be followed as deep
+as required - just add descriptor names, separated by double underscores, to
+describe the full path to the query attribute. The query::
- >>> p = polls.get_object(id__exact=15)
- >>> p.slug = "new_slug"
- >>> p.pub_date = datetime.datetime.now()
- >>> p.save()
+ Foo.objects.filter(name1__name2__name3__attribute__lookup=value)
-Creating new objects
-====================
+... is interpreted as 'get every Foo that has a name1 that has a name2 that
+has a name3 that has an attribute with lookup matching value'. In the Poll
+example::
-Creating new objects (i.e. ``INSERT``) is done by creating new instances
-of objects then calling save() on them::
+ Choice.objects.filter(poll__slug__startswith="eggs")
- >>> p = polls.Poll(slug="eggs",
- ... question="How do you like your eggs?",
- ... pub_date=datetime.datetime.now(),
- ... expire_date=some_future_date)
- >>> p.save()
+... describes the set of choices for which the related poll has a slug
+attribute that starts with "eggs". Django automatically composes the joins
+and conditions required for the SQL query.
-Calling ``save()`` on an object with a primary key whose value is ``None``
-signifies to Django that the object is new and should be inserted.
+Creating new related objects
+============================
-Related objects (e.g. ``Choices``) are created using convenience functions::
+Related objects are created using the ``create()`` convenience function on
+the descriptor Manager for relation::
- >>> p.add_choice(choice="Over easy", votes=0)
- >>> p.add_choice(choice="Scrambled", votes=0)
- >>> p.add_choice(choice="Fertilized", votes=0)
- >>> p.add_choice(choice="Poached", votes=0)
- >>> p.get_choice_count()
+ >>> p.choice_set.create(choice="Over easy", votes=0)
+ >>> p.choice_set.create(choice="Scrambled", votes=0)
+ >>> p.choice_set.create(choice="Fertilized", votes=0)
+ >>> p.choice_set.create(choice="Poached", votes=0)
+ >>> p.choice_set.count()
4
-Each of those ``add_choice`` methods is equivalent to (but much simpler than)::
+Each of those ``create()`` methods is equivalent to (but much simpler than)::
- >>> c = polls.Choice(poll_id=p.id, choice="Over easy", votes=0)
+ >>> c = Choice(poll_id=p.id, choice="Over easy", votes=0)
>>> c.save()
-Note that when using the `add_foo()`` methods, you do not give any value
+Note that when using the `create()`` method, you do not give any value
for the ``id`` field, nor do you give a value for the field that stores
the relation (``poll_id`` in this case).
-The ``add_FOO()`` method always returns the newly created object.
+The ``create()`` method always returns the newly created object.
Deleting objects
================
@@ -514,31 +1371,27 @@ deletes the object and has no return value. Example::
>>> c.delete()
-Comparing objects
-=================
+Objects can also be deleted in bulk. Every Query Set has a ``delete()`` method
+that will delete all members of the query set. For example::
-To compare two model objects, just use the standard Python comparison operator,
-the double equals sign: ``==``. Behind the scenes, that compares the primary
-key values of two models.
+ >>> Polls.objects.filter(pub_date__year=2005).delete()
-Using the ``Poll`` example above, the following two statements are equivalent::
+would bulk delete all Polls with a year of 2005. Note that ``delete()`` is the
+only Query Set method that is not exposed on the Manager itself.
- some_poll == other_poll
- some_poll.id == other_poll.id
+This is a safety mechanism to prevent you from accidentally requesting
+``Polls.objects.delete()``, and deleting *all* the polls.
-If a model's primary key isn't called ID, no problem. Comparisons will always
-use the primary key, whatever it's called. For example, if a model's primary
-key field is called ``name``, these two statements are equivalent::
+If you *actually* want to delete all the objects, then you have to explicitly
+request a complete query set::
- some_obj == other_obj
- some_obj.name == other_obj.name
+ Polls.objects.all().delete()
Extra instance methods
======================
-In addition to ``save()``, ``delete()`` and all of the ``add_*`` and ``get_*``
-related-object methods, a model object might get any or all of the following
-methods:
+In addition to ``save()``, ``delete()``, a model object might get any or all
+of the following methods:
get_FOO_display()
-----------------
@@ -572,10 +1425,10 @@ For every ``DateField`` and ``DateTimeField`` that does not have ``null=True``,
the object will have ``get_next_by_FOO()`` and ``get_previous_by_FOO()``
methods, where ``FOO`` is the name of the field. This returns the next and
previous object with respect to the date field, raising the appropriate
-``*DoesNotExist`` exception when appropriate.
+``DoesNotExist`` exception when appropriate.
Both methods accept optional keyword arguments, which should be in the format
-described in "Field lookups" above.
+described in _`Field lookups` above.
Note that in the case of identical date values, these methods will use the ID
as a fallback check. This guarantees that no records are skipped or duplicated.
@@ -604,14 +1457,14 @@ returns an empty string.
get_FOO_size()
--------------
-For every ``FileField``, the object will have a ``get_FOO_size()`` method,
+For every ``FileField``, the object will have a ``get_FOO_filename()`` method,
where ``FOO`` is the name of the field. This returns the size of the file, in
bytes. (Behind the scenes, it uses ``os.path.getsize``.)
save_FOO_file(filename, raw_contents)
-------------------------------------
-For every ``FileField``, the object will have a ``save_FOO_file()`` method,
+For every ``FileField``, the object will have a ``get_FOO_filename()`` method,
where ``FOO`` is the name of the field. This saves the given file to the
filesystem, using the given filename. If a file with the given filename already
exists, Django adds an underscore to the end of the filename (but before the
@@ -623,48 +1476,3 @@ get_FOO_height() and get_FOO_width()
For every ``ImageField``, the object will have ``get_FOO_height()`` and
``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This
returns the height (or width) of the image, as an integer, in pixels.
-
-Extra module functions
-======================
-
-In addition to every function described in "Basic lookup functions" above, a
-model module might get any or all of the following methods:
-
-get_FOO_list(kind, \**kwargs)
------------------------------
-
-For every ``DateField`` and ``DateTimeField``, the model module will have a
-``get_FOO_list()`` function, where ``FOO`` is the name of the field. This
-returns a list of ``datetime.datetime`` objects representing all available
-dates of the given scope, as defined by the ``kind`` argument. ``kind`` should
-be either ``"year"``, ``"month"`` or ``"day"``. Each ``datetime.datetime``
-object in the result list is "truncated" to the given ``type``.
-
- * ``"year"`` returns a list of all distinct year values for the field.
- * ``"month"`` returns a list of all distinct year/month values for the field.
- * ``"day"`` returns a list of all distinct year/month/day values for the field.
-
-Additional, optional keyword arguments, in the format described in
-"Field lookups" above, are also accepted.
-
-Here's an example, using the ``Poll`` model defined above::
-
- >>> from datetime import datetime
- >>> p1 = polls.Poll(slug='whatsup', question="What's up?",
- ... pub_date=datetime(2005, 2, 20), expire_date=datetime(2005, 3, 20))
- >>> p1.save()
- >>> p2 = polls.Poll(slug='name', question="What's your name?",
- ... pub_date=datetime(2005, 3, 20), expire_date=datetime(2005, 4, 20))
- >>> p2.save()
- >>> polls.get_pub_date_list('year')
- [datetime.datetime(2005, 1, 1)]
- >>> polls.get_pub_date_list('month')
- [datetime.datetime(2005, 2, 1), datetime.datetime(2005, 3, 1)]
- >>> polls.get_pub_date_list('day')
- [datetime.datetime(2005, 2, 20), datetime.datetime(2005, 3, 20)]
- >>> polls.get_pub_date_list('day', question__contains='name')
- [datetime.datetime(2005, 3, 20)]
-
-``get_FOO_list()`` also accepts an optional keyword argument ``order``, which
-should be either ``"ASC"`` or ``"DESC"``. This specifies how to order the
-results. Default is ``"ASC"``.
diff --git a/docs/design_philosophies.txt b/docs/design_philosophies.txt
index 89a537da17..17ed3ad6da 100644
--- a/docs/design_philosophies.txt
+++ b/docs/design_philosophies.txt
@@ -20,6 +20,9 @@ For example, the template system knows nothing about Web requests, the database
layer knows nothing about data display and the view system doesn't care which
template system a programmer uses.
+Although Django comes with a full stack for convenience, the pieces of the
+stack are independent of another wherever possible.
+
.. _`loose coupling and tight cohesion`: http://c2.com/cgi/wiki?CouplingAndCohesion
Less code
@@ -49,7 +52,10 @@ Explicit is better than implicit
--------------------------------
This, a `core Python principle`_, means Django shouldn't do too much "magic."
-Magic shouldn't happen unless there's a really good reason for it.
+Magic shouldn't happen unless there's a really good reason for it. Magic is
+worth using only if it creates a huge convenience unattainable in other ways,
+and it isn't implemented in a way that confuses developers who are trying to
+learn how to use the feature.
.. _`core Python principle`: http://www.python.org/doc/Humor.html#zen
@@ -96,8 +102,9 @@ optimize statements internally.
This is why developers need to call ``save()`` explicitly, rather than the
framework saving things behind the scenes silently.
-This is also why the ``select_related`` argument exists. It's an optional
-performance booster for the common case of selecting "every related object."
+This is also why the ``select_related()`` ``QuerySet`` method exists. It's an
+optional performance booster for the common case of selecting "every related
+object."
Terse, powerful syntax
----------------------
@@ -144,6 +151,8 @@ design pretty URLs than ugly ones.
File extensions in Web-page URLs should be avoided.
+Vignette-style commas in URLs deserve severe punishment.
+
Definitive URLs
---------------
@@ -186,6 +195,13 @@ The template system shouldn't be designed so that it only outputs HTML. It
should be equally good at generating other text-based formats, or just plain
text.
+XML should not be used for template languages
+---------------------------------------------
+
+Using an XML engine to parse templates introduces a whole new world of human
+error in editing templates -- and incurs an unacceptable level of overhead in
+template processing.
+
Assume designer competence
--------------------------
diff --git a/docs/django-admin.txt b/docs/django-admin.txt
index 45cc2363dc..b314366fee 100644
--- a/docs/django-admin.txt
+++ b/docs/django-admin.txt
@@ -1,15 +1,10 @@
-===========================
-The django-admin.py utility
-===========================
+=============================
+django-admin.py and manage.py
+=============================
``django-admin.py`` is Django's command-line utility for administrative tasks.
This document outlines all it can do.
-The ``django-admin.py`` script should be on your system path if you installed
-Django via its ``setup.py`` utility. If it's not on your path, you can find it in
-``site-packages/django/bin`` within your Python installation. Consider
-symlinking to it from some place on your path, such as ``/usr/local/bin``.
-
In addition, ``manage.py`` is automatically created in each Django project.
``manage.py`` is a thin wrapper around ``django-admin.py`` that takes care of
two things for you before delegating to ``django-admin.py``:
@@ -19,6 +14,11 @@ two things for you before delegating to ``django-admin.py``:
* It sets the ``DJANGO_SETTINGS_MODULE`` environment variable so that it
points to your project's ``settings.py`` file.
+The ``django-admin.py`` script should be on your system path if you installed
+Django via its ``setup.py`` utility. If it's not on your path, you can find it in
+``site-packages/django/bin`` within your Python installation. Consider
+symlinking to it from some place on your path, such as ``/usr/local/bin``.
+
Generally, when working on a single Django project, it's easier to use
``manage.py``. Use ``django-admin.py`` with ``DJANGO_SETTINGS_MODULE``, or the
``--settings`` command line option, if you need to switch between multiple
@@ -38,18 +38,17 @@ document.
Run ``django-admin.py --help`` to display a help message that includes a terse
list of all available actions and options.
-Most actions take a list of "modelmodule"s. A "modelmodule," in this case, is
-the name of a file containing Django models. For example, if you have a model
-module called ``myproject/apps/polls/pollmodels.py``, the "modelmodule" in this
-case would be ``"pollmodels"``.
+Most actions take a list of ``appname``s. An ``appname`` is the basename of the
+package containing your models. For example, if your ``INSTALLED_APPS``
+contains the string ``'mysite.blog'``, the ``appname`` is ``blog``.
Available actions
=================
-adminindex [modelmodule modelmodule ...]
-----------------------------------------
+adminindex [appname appname ...]
+--------------------------------
-Prints the admin-index template snippet for the given model module(s).
+Prints the admin-index template snippet for the given appnames.
Use admin-index template snippets if you want to customize the look and feel of
your admin's index page. See `Tutorial 2`_ for more information.
@@ -64,29 +63,41 @@ backend. See the `cache documentation`_ for more information.
.. _cache documentation: http://www.djangoproject.com/documentation/cache/
-createsuperuser
----------------
+dbshell
+-------
-Creates a superuser account interactively. It asks you for a username, e-mail
-address and password.
+Runs the command-line client for the database engine specified in your
+``DATABASE_ENGINE`` setting, with the connection parameters specified in your
+``DATABASE_USER``, ``DATABASE_PASSWORD``, etc., settings.
-You can specify ``username email password`` on the command line, for convenient
-use in shell scripts. Example::
+ * For PostgreSQL, this runs the ``psql`` command-line client.
+ * For MySQL, this runs the ``mysql`` command-line client.
+ * For SQLite, this runs the ``sqlite3`` command-line client.
- django-admin.py createsuperuser john john@example.com mypassword
+This command assumes the programs are on your ``PATH`` so that a simple call to
+the program name (``psql``, ``mysql``, ``sqlite3``) will find the program in
+the right place. There's no way to specify the location of the program
+manually.
-init
-----
+diffsettings
+------------
-Initializes the database with the tables and data Django needs by default.
-Specifically, these are the database tables from the ``auth`` and ``core``
-models.
+Displays differences between the current settings file and Django's default
+settings.
-inspectdb [dbname]
-------------------
+Settings that don't appear in the defaults are followed by ``"###"``. For
+example, the default settings don't define ``ROOT_URLCONF``, so
+``ROOT_URLCONF`` is followed by ``"###"`` in the output of ``diffsettings``.
-Introspects the database tables in the given database and outputs a Django
-model module to standard output.
+Note that Django's default settings live in ``django/conf/global_settings.py``,
+if you're ever curious to see the full list of defaults.
+
+inspectdb
+---------
+
+Introspects the database tables in the database pointed-to by the
+``DATABASE_NAME`` setting and outputs a Django model module (a ``models.py``
+file) to standard output.
Use this if you have a legacy database with which you'd like to use Django.
The script will inspect the database and create a model for each table within
@@ -101,12 +112,12 @@ output:
``'This field type is a guess.'`` next to the field in the generated
model.
- * **New in Django development version.** If the database column name is a
- Python reserved word (such as ``'pass'``, ``'class'`` or ``'for'``),
- ``inspectdb`` will append ``'_field'`` to the attribute name. For
- example, if a table has a column ``'for'``, the generated model will have
- a field ``'for_field'``, with the ``db_column`` attribute set to
- ``'for'``. ``inspectdb`` will insert the Python comment
+ * If the database column name is a Python reserved word (such as
+ ``'pass'``, ``'class'`` or ``'for'``), ``inspectdb`` will append
+ ``'_field'`` to the attribute name. For example, if a table has a column
+ ``'for'``, the generated model will have a field ``'for_field'``, with
+ the ``db_column`` attribute set to ``'for'``. ``inspectdb`` will insert
+ the Python comment
``'Field renamed because it was a Python reserved word.'`` next to the
field.
@@ -115,25 +126,16 @@ you run it, you'll want to look over the generated models yourself to make
customizations. In particular, you'll need to rearrange models' order, so that
models that refer to other models are ordered properly.
-If you're using Django 0.90 or 0.91, you'll need to add ``primary_key=True`` to
-one field in each model. In the Django development version, primary keys are
-automatically introspected for PostgreSQL and MySQL, and Django puts in the
-``primary_key=True`` where needed.
+Primary keys are automatically introspected for PostgreSQL and MySQL, in which
+case Django puts in the ``primary_key=True`` where needed.
``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection
-only works in PostgreSQL.
+only works in PostgreSQL and with certain types of MySQL tables.
-install [modelmodule modelmodule ...]
--------------------------------------
+install [appname appname ...]
+-----------------------------
-Executes the equivalent of ``sqlall`` for the given model module(s).
-
-installperms [modelmodule modelmodule ...]
-------------------------------------------
-
-Installs any admin permissions for the given model module(s) that aren't
-already installed in the database. Outputs a message telling how many
-permissions were added, if any.
+Executes the equivalent of ``sqlall`` for the given appnames.
runserver [optional port number, or ipaddr:port]
------------------------------------------------
@@ -144,7 +146,7 @@ IP address and port number explicitly.
If you run this script as a user with normal privileges (recommended), you
might not have access to start a port on a low port number. Low port numbers
-are reserved for superusers (root).
+are reserved for the superuser (root).
DO NOT USE THIS SERVER IN A PRODUCTION SETTING.
@@ -153,7 +155,7 @@ needed. You don't need to restart the server for code changes to take effect.
When you start the server, and each time you change Python code while the
server is running, the server will validate all of your installed models. (See
-the "validate" option below.) If the validator finds errors, it will print
+the ``validate`` command below.) If the validator finds errors, it will print
them to standard output, but it won't stop the server.
You can run as many servers as you want, as long as they're on separate ports.
@@ -180,49 +182,49 @@ shell
Starts the Python interactive interpreter.
-**New in Django development version:** Uses IPython_, if it's installed. If you
-have IPython installed and want to force use of the "plain" Python interpreter,
-use the ``--plain`` option, like so::
+Django will use IPython_, if it's installed. If you have IPython installed and
+want to force use of the "plain" Python interpreter, use the ``--plain``
+option, like so::
django-admin.py shell --plain
.. _IPython: http://ipython.scipy.org/
-sql [modelmodule modelmodule ...]
----------------------------------
+sql [appname appname ...]
+-------------------------
-Prints the CREATE TABLE SQL statements for the given model module(s).
+Prints the CREATE TABLE SQL statements for the given appnames.
-sqlall [modelmodule modelmodule ...]
-------------------------------------
+sqlall [appname appname ...]
+----------------------------
-Prints the CREATE TABLE and initial-data SQL statements for the given model module(s).
+Prints the CREATE TABLE and initial-data SQL statements for the given appnames.
-sqlclear [modelmodule modelmodule ...]
+sqlclear [appname appname ...]
--------------------------------------
-Prints the DROP TABLE SQL statements for the given model module(s).
+Prints the DROP TABLE SQL statements for the given appnames.
-sqlindexes [modelmodule modelmodule ...]
+sqlindexes [appname appname ...]
----------------------------------------
-Prints the CREATE INDEX SQL statements for the given model module(s).
+Prints the CREATE INDEX SQL statements for the given appnames.
-sqlinitialdata [modelmodule modelmodule ...]
+sqlinitialdata [appname appname ...]
--------------------------------------------
-Prints the initial INSERT SQL statements for the given model module(s).
+Prints the initial INSERT SQL statements for the given appnames.
-sqlreset [modelmodule modelmodule ...]
+sqlreset [appname appname ...]
--------------------------------------
-Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given model module(s).
+Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given appnames.
-sqlsequencereset [modelmodule modelmodule ...]
+sqlsequencereset [appname appname ...]
----------------------------------------------
Prints the SQL statements for resetting PostgreSQL sequences for the given
-model module(s).
+appnames.
See http://simon.incutio.com/archive/2004/04/21/postgres for more information.
@@ -252,11 +254,12 @@ Available options
Example usage::
- django-admin.py init --settings=myproject.settings
+ django-admin.py syncdb --settings=mysite.settings
Explicitly specifies the settings module to use. The settings module should be
-in Python path syntax, e.g. "myproject.settings". If this isn't provided,
-``django-admin.py`` will use the DJANGO_SETTINGS_MODULE environment variable.
+in Python package syntax, e.g. ``mysite.settings``. If this isn't provided,
+``django-admin.py`` will use the ``DJANGO_SETTINGS_MODULE`` environment
+variable.
Note that this option is unnecessary in ``manage.py``, because it takes care of
setting ``DJANGO_SETTINGS_MODULE`` for you.
@@ -266,7 +269,7 @@ setting ``DJANGO_SETTINGS_MODULE`` for you.
Example usage::
- django-admin.py init --pythonpath='/home/djangoprojects/myproject'
+ django-admin.py syncdb --pythonpath='/home/djangoprojects/myproject'
Adds the given filesystem path to the Python `import search path`_. If this
isn't provided, ``django-admin.py`` will use the ``PYTHONPATH`` environment
@@ -282,3 +285,27 @@ setting the Python path for you.
Displays a help message that includes a terse list of all available actions and
options.
+
+Extra niceties
+==============
+
+Syntax coloring
+---------------
+
+The ``django-admin.py`` / ``manage.py`` commands that output SQL to standard
+output will use pretty color-coded output if your terminal supports
+ANSI-colored output. It won't use the color codes if you're piping the
+command's output to another program.
+
+Bash completion
+---------------
+
+If you use the Bash shell, consider installing the Django bash completion
+script, which lives in ``extras/django_bash_completion`` in the Django
+distribution. It enables tab-completion of ``django-admin.py`` and
+``manage.py`` commands, so you can, for instance...
+
+ * Type ``django-admin.py``.
+ * Press [TAB] to see all available options.
+ * Type ``sql``, then [TAB], to see all available options whose names start
+ with ``sql``.
diff --git a/docs/email.txt b/docs/email.txt
index ae55a51a14..b38b855cb3 100644
--- a/docs/email.txt
+++ b/docs/email.txt
@@ -20,11 +20,11 @@ In two lines::
send_mail('Subject here', 'Here is the message.', 'from@example.com',
['to@example.com'], fail_silently=False)
-The send_mail function
-======================
+send_mail()
+===========
The simplest way to send e-mail is using the function
-``django.core.mail.send_mail``. Here's its definition::
+``django.core.mail.send_mail()``. Here's its definition::
send_mail(subject, message, from_email, recipient_list,
fail_silently=False, auth_user=EMAIL_HOST_USER,
@@ -42,20 +42,19 @@ are required.
* ``fail_silently``: A boolean. If it's ``False``, ``send_mail`` will raise
an ``smtplib.SMTPException``. See the `smtplib docs`_ for a list of
possible exceptions, all of which are subclasses of ``SMTPException``.
- * ``auth_user``: **New in Django development version.** The optional
- username to use to authenticate to the SMTP server. If this isn't
- provided, Django will use the value of the ``EMAIL_HOST_USER`` setting.
- * ``auth_password``: **New in Django development version.** The optional
- password to use to authenticate to the SMTP server. If this isn't
- provided, Django will use the value of the ``EMAIL_HOST_PASSWORD``
- setting.
+ * ``auth_user``: The optional username to use to authenticate to the SMTP
+ server. If this isn't provided, Django will use the value of the
+ ``EMAIL_HOST_USER`` setting.
+ * ``auth_password``: The optional password to use to authenticate to the
+ SMTP server. If this isn't provided, Django will use the value of the
+ ``EMAIL_HOST_PASSWORD`` setting.
.. _smtplib docs: http://www.python.org/doc/current/lib/module-smtplib.html
-The send_mass_mail function
-===========================
+send_mass_mail()
+================
-``django.core.mail.send_mass_mail`` is intended to handle mass e-mailing.
+``django.core.mail.send_mass_mail()`` is intended to handle mass e-mailing.
Here's the definition::
send_mass_mail(datatuple, fail_silently=False,
@@ -66,25 +65,24 @@ Here's the definition::
(subject, message, from_email, recipient_list)
``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
-as in ``send_mail()``. Note that ``auth_user`` and ``auth_password`` are only
-available in the Django development version.
+as in ``send_mail()``.
Each separate element of ``datatuple`` results in a separate e-mail message.
As in ``send_mail()``, recipients in the same ``recipient_list`` will all see
the other addresses in the e-mail messages's "To:" field.
-send_mass_mail vs. send_mail
-----------------------------
+send_mass_mail() vs. send_mail()
+--------------------------------
The main difference between ``send_mass_mail()`` and ``send_mail()`` is that
``send_mail()`` opens a connection to the mail server each time it's executed,
while ``send_mass_mail()`` uses a single connection for all of its messages.
This makes ``send_mass_mail()`` slightly more efficient.
-The mail_admins function
-========================
+mail_admins()
+=============
-``django.core.mail.mail_admins`` is a shortcut for sending an e-mail to the
+``django.core.mail.mail_admins()`` is a shortcut for sending an e-mail to the
site admins, as defined in the `ADMINS setting`_. Here's the definition::
mail_admins(subject, message, fail_silently=False)
@@ -94,14 +92,16 @@ site admins, as defined in the `ADMINS setting`_. Here's the definition::
The "From:" header of the e-mail will be the value of the `SERVER_EMAIL setting`_.
+This method exists for convenience and readability.
+
.. _ADMINS setting: http://www.djangoproject.com/documentation/settings/#admins
.. _EMAIL_SUBJECT_PREFIX setting: http://www.djangoproject.com/documentation/settings/#email-subject-prefix
.. _SERVER_EMAIL setting: http://www.djangoproject.com/documentation/settings/#server-email
-The mail_managers function
-==========================
+mail_managers() function
+========================
-``django.core.mail.mail_managers`` is just like ``mail_admins``, except it
+``django.core.mail.mail_managers()`` is just like ``mail_admins()``, except it
sends an e-mail to the site managers, as defined in the `MANAGERS setting`_.
Here's the definition::
diff --git a/docs/faq.txt b/docs/faq.txt
index 977a3644fe..e8bf09a7d3 100644
--- a/docs/faq.txt
+++ b/docs/faq.txt
@@ -8,14 +8,20 @@ General questions
Why does this project exist?
----------------------------
-Django grew from a very practical need: in our fast-paced newsroom, we often
-have only a matter of hours to take a complicated Web application from
-concept to public launch. Django was designed to not only allow us to
-build Web applications quickly, but to allow us to build them right.
+Django grew from a very practical need: World Online, a newspaper Web
+operation, is responsible for building intensive Web applications on journalism
+deadlines. In the fast-paced newsroom, World Online often has only a matter of
+hours to take a complicated Web application from concept to public launch.
+
+At the same time, the World Online Web developers have consistently been
+perfectionists when it comes to following best practices of Web development.
+
+Thus, Django was designed not only to allow fast Web development, but
+*best-practice* Web development.
Django would not be possible without a whole host of open-source projects --
-`Apache`_, `Python`_, and `PostgreSQL`_ to name a few -- and we're thrilled to be
-able to give something back to the open-source community.
+`Apache`_, `Python`_, and `PostgreSQL`_ to name a few -- and we're thrilled to
+be able to give something back to the open-source community.
.. _Apache: http://httpd.apache.org/
.. _Python: http://www.python.org/
@@ -29,25 +35,28 @@ to early 1950s. To this day, he's considered one of the best guitarists of all t
Listen to his music. You'll like it.
-According to Wikipedia_, "Django is pronounced **zhane**-go (with a long 'a')."
+Django is pronounced **JANG**-oh. Rhymes with FANG-oh.
.. _Django Reinhardt: http://en.wikipedia.org/wiki/Django_Reinhardt
-.. _Wikipedia: http://en.wikipedia.org/wiki/Django_Reinhardt
Is Django stable?
-----------------
-We've been using Django for almost two years. Sites built on Django have
-weathered traffic spikes of over one million hits an hour, and at least
-one Slashdotting. Yes, it's quite stable.
+Yes. World Online has been using Django for more than two years. Sites built on
+Django have weathered traffic spikes of over one million hits an hour and at
+least one Slashdotting. Yes, it's quite stable.
Does Django scale?
------------------
Yes. Compared to development time, hardware is cheap, and so Django is
designed to take advantage of as much hardware as you can throw at it.
-Django ships with clean separation of the database layer from the
-application layer and a simple-yet-powerful `cache framework`_.
+
+Django uses a "shared-nothing" architecture, which means you can add hardware
+at any level -- database servers, caching servers or Web/application servers.
+
+The framework cleanly separates components such as its database layer and
+application layer. And it ships with a simple-yet-powerful `cache framework`_.
.. _`cache framework`: http://www.djangoproject.com/documentation/cache/
@@ -60,32 +69,34 @@ Lawrence, Kansas, USA.
`Adrian Holovaty`_
Adrian is a Web developer with a background in journalism. He was lead
developer at World Online for 2.5 years, during which time Django was
- developed and implemented on World Online's sites. Now he's editor of
- editorial innovations at washingtonpost.com, and he continues to oversee
- Django development. He likes playing guitar (Django Reinhardt style) and
- hacking on side projects such as `chicagocrime.org`_. He lives in Chicago.
+ developed and implemented on World Online's sites. Now he works for
+ washingtonpost.com building rich, database-backed information sites, and
+ continues to oversee Django development. He likes playing guitar (Django
+ Reinhardt style) and hacking on side projects such as `chicagocrime.org`_.
+ He lives in Chicago.
On IRC, Adrian goes by ``adrian_h``.
+`Jacob Kaplan-Moss`_
+ Jacob is a whipper-snapper from California who spends equal time coding and
+ cooking. He's lead developer at World Online and actively hacks on various
+ cool side projects. He's contributed to the Python-ObjC bindings and was
+ the first guy to figure out how to write Tivo apps in Python. Lately he's
+ been messing with Python on the PSP. He lives in Lawrence, Kansas.
+
+ On IRC, Jacob goes by ``jacobkm``.
+
`Simon Willison`_
Simon is a well-respected Web developer from England. He had a one-year
internship at World Online, during which time he and Adrian developed
- Django from scratch. He's enthusiastic, he's passionate about best
- practices in Web development, and he really likes squirrels. Probably to a
- fault. He went back to university to finish his degree and is poised to
- continue doing big, exciting things on the Web. He lives in England.
+ Django from scratch. The most enthusiastic Brit you'll ever meet, he's
+ passionate about best practices in Web development and has maintained a
+ well-read Web-development blog for years at http://simon.incutio.com.
+ He works for Yahoo UK, where he managed to score the title "Hacker Liason."
+ He lives in London.
On IRC, Simon goes by ``SimonW``.
-`Jacob Kaplan-Moss`_
- Jacob is a whipper-snapper from California who spends equal time coding and
- cooking. He does Web development for World Online and actively hacks on
- various cool side projects. He's contributed to the Python-ObjC bindings and
- was the first guy to figure out how to write Tivo apps in Python. Lately
- he's been messing with Python on the PSP. He lives in Lawrence, Kansas.
-
- On IRC, Jacob goes by ``jacobkm``.
-
`Wilson Miner`_
Wilson's design-fu makes us all look like rock stars. When not sneaking
into apartment complex swimming pools, he's the Commercial Development
@@ -106,18 +117,17 @@ Lawrence, Kansas, USA.
Which sites use Django?
-----------------------
-The Django wiki features a `list of Django-powered sites`_. Feel free to add
-your Django-powered site to the list.
+The Django wiki features a consistently growing `list of Django-powered sites`_.
+Feel free to add your Django-powered site to the list.
.. _list of Django-powered sites: http://code.djangoproject.com/wiki/DjangoPoweredSites
Django appears to be a MVC framework, but you call the Controller the "view", and the View the "template". How come you don't use the standard names?
-----------------------------------------------------------------------------------------------------------------------------------------------------
-That's because Django isn't strictly a MVC framework. We don't really believe in
-any capital-M Methodologies; we do what "feels" right. If you squint the right
-way, you can call Django's ORM the "Model", the view functions the "View", and
-the dynamically-generated API the "Controller" -- but not really.
+That's because Django isn't strictly a MVC framework. If you squint the right
+way, you can call Django's database layer the "Model", the view functions the
+"View", and the URL dispatcher the "Controller" -- but not really.
In fact, you might say that Django is a "MTV" framework -- that is, Model,
Template, and View make much more sense to us.
@@ -183,9 +193,13 @@ begin maintaining backwards compatibility. This should happen in a couple of
months or so, although it's entirely possible that it could happen earlier.
That translates into summer 2006.
+The merging of Django's `magic-removal branch`_ went a long way toward Django
+1.0.
+
Of course, you should note that `quite a few production sites`_ use Django in
its current status. Don't let the lack of a 1.0 turn you off.
+.. _magic-removal branch: http://code.djangoproject.com/wiki/RemovingTheMagic
.. _quite a few production sites: http://code.djangoproject.com/wiki/DjangoPoweredSites
How can I download the Django documentation to read it offline?
@@ -225,12 +239,12 @@ How do I get started?
How do I fix the "install a later version of setuptools" error?
---------------------------------------------------------------
-Just run the ``ex_setup.py`` script in the Django distribution.
+Just run the ``ez_setup.py`` script in the Django distribution.
What are Django's prerequisites?
--------------------------------
-Django requires Python_ 2.3 or later.
+Django requires Python_ 2.3 or later. No other Python libraries are required.
For a development environment -- if you just want to experiment with Django --
you don't need to have a separate Web server installed; Django comes with its
@@ -256,7 +270,7 @@ Not if you just want to play around and develop things on your local computer.
Django comes with its own Web server, and things should Just Work.
For production use, though, we recommend mod_python. The Django developers have
-been running it on mod_python for about two years, and it's quite stable.
+been running it on mod_python for more than two years, and it's quite stable.
However, if you don't want to use mod_python, you can use a different server,
as long as that server has WSGI_ hooks. See the `server arrangements wiki page`_.
@@ -325,6 +339,14 @@ but we recognize that choosing a template language runs close to religion.
There's nothing about Django that requires using the template language, so
if you're attached to ZPT, Cheetah, or whatever, feel free to use those.
+Do I have to use your model/database layer?
+-------------------------------------------
+
+Nope. Just like the template system, the model/database layer is decoupled from
+the rest of the framework. The one exception is: If you use a different
+database library, you won't get to use Django's automatically-generated admin
+site. That app is coupled to the Django database layer.
+
How do I use image and file fields?
-----------------------------------
@@ -367,9 +389,8 @@ input.
If you do care about deleting data, you'll have to execute the ``ALTER TABLE``
statements manually in your database. That's the way we've always done it,
because dealing with data is a very sensitive operation that we've wanted to
-avoid automating. That said, there's some work being done to add a
-``django-admin.py updatedb`` command, which would output the necessary
-``ALTER TABLE`` statements, if any.
+avoid automating. That said, there's some work being done to add partially
+automated database-upgrade functionality.
Do Django models support multiple-column primary keys?
------------------------------------------------------
@@ -392,19 +413,19 @@ How can I see the raw SQL queries Django is running?
Make sure your Django ``DEBUG`` setting is set to ``True``. Then, just do
this::
- >>> from django.core.db import db
- >>> db.queries
+ >>> from django.db import connection
+ >>> connection.queries
[{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',
'time': '0.002'}]
-``db.queries`` is only available if ``DEBUG`` is ``True``. It's a list of
-dictionaries in order of query execution. Each dictionary has the following::
+``connection.queries`` is only available if ``DEBUG`` is ``True``. It's a list
+of dictionaries in order of query execution. Each dictionary has the following::
``sql`` -- The raw SQL statement
``time`` -- How long the statement took to execute, in seconds.
-``db.queries`` includes all SQL statements -- INSERTs, UPDATES, SELECTs, etc.
-Each time your app hits the database, the query will be recorded.
+``connection.queries`` includes all SQL statements -- INSERTs, UPDATES,
+SELECTs, etc. Each time your app hits the database, the query will be recorded.
Can I use Django with a pre-existing database?
----------------------------------------------
@@ -465,8 +486,8 @@ documentation.
My "list_filter" contains a ManyToManyField, but the filter doesn't display.
----------------------------------------------------------------------------
-Django won't bother displaying the filter for a ManyToManyField if there are
-fewer than two related objects.
+Django won't bother displaying the filter for a ``ManyToManyField`` if there
+are fewer than two related objects.
For example, if your ``list_filter`` includes ``sites``, and there's only one
site in your database, it won't display a "Site" filter. In that case,
@@ -477,9 +498,9 @@ How can I customize the functionality of the admin interface?
You've got several options. If you want to piggyback on top of an add/change
form that Django automatically generates, you can attach arbitrary JavaScript
-modules to the page via the model's ``admin.js`` parameter. That parameter is
-a list of URLs, as strings, pointing to JavaScript modules that will be
-included within the admin form via a disallowed tags', 'script img')
@@ -133,19 +133,15 @@ u'\\xcb'
>>> striptags('some html with disallowed tags')
'some html with alert("You smell") disallowed tags'
->>> dictsort([{'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 63, 'name': 'Ra Ra Rasputin'},\
- {'name': 'Jonny B Goode', 'age': 18}], 'age')
-[{'age': 18, 'name': 'Jonny B Goode'},\
- {'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 63, 'name': 'Ra Ra Rasputin'}]
+>>> dictsort([{'age': 23, 'name': 'Barbara-Ann'},
+... {'age': 63, 'name': 'Ra Ra Rasputin'},
+... {'name': 'Jonny B Goode', 'age': 18}], 'age')
+[{'age': 18, 'name': 'Jonny B Goode'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 63, 'name': 'Ra Ra Rasputin'}]
->>> dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 63, 'name': 'Ra Ra Rasputin'},\
- {'name': 'Jonny B Goode', 'age': 18}], 'age')
-[{'age': 63, 'name': 'Ra Ra Rasputin'},\
- {'age': 23, 'name': 'Barbara-Ann'},\
- {'age': 18, 'name': 'Jonny B Goode'}]
+>>> dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},
+... {'age': 63, 'name': 'Ra Ra Rasputin'},
+... {'name': 'Jonny B Goode', 'age': 18}], 'age')
+[{'age': 63, 'name': 'Ra Ra Rasputin'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 18, 'name': 'Jonny B Goode'}]
>>> first([0,1,2])
0
@@ -196,13 +192,13 @@ False
'aceg'
>>> unordered_list(['item 1', []])
-'\\t