
-
11725
at 2009-11-07 17:09:09
by Honza.Kral@gmail.com
Index: /django/branches/soc2009/model-validation/django/test/client.py
===================================================================
--- /django/branches/soc2009/model-validation/django/test/client.py (revision 10513)
+++ /django/branches/soc2009/model-validation/django/test/client.py (revision 11725)
@@ -67,4 +67,9 @@
try:
request = WSGIRequest(environ)
+ # sneaky little hack so that we can easily get round
+ # CsrfViewMiddleware. This makes life easier, and is probably
+ # required for backwards compatibility with external tests against
+ # admin views.
+ request._dont_enforce_csrf_checks = True
response = self.get_response(request)
@@ -363,4 +368,10 @@
post_data = data
+ # Make `data` into a querystring only if it's not already a string. If
+ # it is a string, we'll assume that the caller has already encoded it.
+ query_string = None
+ if not isinstance(data, basestring):
+ query_string = urlencode(data, doseq=True)
+
parsed = urlparse(path)
r = {
@@ -368,5 +379,5 @@
'CONTENT_TYPE': content_type,
'PATH_INFO': urllib.unquote(parsed[2]),
- 'QUERY_STRING': urlencode(data, doseq=True) or parsed[4],
+ 'QUERY_STRING': query_string or parsed[4],
'REQUEST_METHOD': 'PUT',
'wsgi.input': FakePayload(post_data),
Index: /django/branches/soc2009/model-validation/django/test/utils.py
===================================================================
--- /django/branches/soc2009/model-validation/django/test/utils.py (revision 10084)
+++ /django/branches/soc2009/model-validation/django/test/utils.py (revision 11725)
@@ -3,4 +3,5 @@
from django.db import connection
from django.core import mail
+from django.core.mail.backends import locmem
from django.test import signals
from django.template import Template
@@ -29,22 +30,4 @@
return self.nodelist.render(context)
-class TestSMTPConnection(object):
- """A substitute SMTP connection for use during test sessions.
- The test connection stores email messages in a dummy outbox,
- rather than sending them out on the wire.
-
- """
- def __init__(*args, **kwargs):
- pass
- def open(self):
- "Mock the SMTPConnection open() interface"
- pass
- def close(self):
- "Mock the SMTPConnection close() interface"
- pass
- def send_messages(self, messages):
- "Redirect messages to the dummy outbox"
- mail.outbox.extend(messages)
- return len(messages)
def setup_test_environment():
@@ -52,5 +35,5 @@
- Installing the instrumented test renderer
- - Diverting the email sending functions to a test buffer
+ - Set the email backend to the locmem email backend.
- Setting the active locale to match the LANGUAGE_CODE setting.
"""
@@ -59,5 +42,8 @@
mail.original_SMTPConnection = mail.SMTPConnection
- mail.SMTPConnection = TestSMTPConnection
+ mail.SMTPConnection = locmem.EmailBackend
+
+ mail.original_email_backend = settings.EMAIL_BACKEND
+ settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem'
mail.outbox = []
@@ -78,6 +64,8 @@
del mail.original_SMTPConnection
+ settings.EMAIL_BACKEND = mail.original_email_backend
+ del mail.original_email_backend
+
del mail.outbox
-
def get_runner(settings):
Index: /django/branches/soc2009/model-validation/django/db/models/sql/query.py
===================================================================
--- /django/branches/soc2009/model-validation/django/db/models/sql/query.py (revision 10944)
+++ /django/branches/soc2009/model-validation/django/db/models/sql/query.py (revision 11725)
@@ -9,5 +9,4 @@
from copy import deepcopy
-
from django.utils.tree import Node
from django.utils.datastructures import SortedDict
@@ -24,9 +23,4 @@
from datastructures import EmptyResultSet, Empty, MultiJoin
from constants import *
-
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
__all__ = ['Query', 'BaseQuery']
@@ -384,4 +378,14 @@
return number
+
+ def has_results(self):
+ q = self.clone()
+ q.add_extra({'a': 1}, None, None, None, None, None)
+ q.add_fields(())
+ q.set_extra_mask(('a',))
+ q.set_aggregate_mask(())
+ q.clear_ordering()
+ q.set_limits(high=1)
+ return bool(q.execute_sql(SINGLE))
def as_sql(self, with_limits=True, with_col_aliases=False):
Index: /django/branches/soc2009/model-validation/django/db/models/base.py
===================================================================
--- /django/branches/soc2009/model-validation/django/db/models/base.py (revision 11437)
+++ /django/branches/soc2009/model-validation/django/db/models/base.py (revision 11725)
@@ -4,9 +4,4 @@
import os
from itertools import izip
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback.
-
import django.db.models.manager # Imported to register signal handler.
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS
@@ -25,5 +20,4 @@
from django.utils.text import get_text_list, capfirst
from django.conf import settings
-
class ModelBase(type):
@@ -239,5 +233,4 @@
signals.class_prepared.send(sender=cls)
-
class Model(object):
@@ -304,5 +297,12 @@
val = None
else:
- val = kwargs.pop(field.attname, field.get_default())
+ try:
+ val = kwargs.pop(field.attname)
+ except KeyError:
+ # This is done with an exception rather than the
+ # default argument on pop because we don't want
+ # get_default() to be evaluated, and then not used.
+ # Refs #12057.
+ val = field.get_default()
else:
val = field.get_default()
@@ -356,18 +356,24 @@
"""
data = self.__dict__
- if not self._deferred:
- return (self.__class__, (), data)
+ model = self.__class__
+ # The obvious thing to do here is to invoke super().__reduce__()
+ # for the non-deferred case. Don't do that.
+ # On Python 2.4, there is something wierd with __reduce__,
+ # and as a result, the super call will cause an infinite recursion.
+ # See #10547 and #12121.
defers = []
pk_val = None
- for field in self._meta.fields:
- if isinstance(self.__class__.__dict__.get(field.attname),
- DeferredAttribute):
- defers.append(field.attname)
- if pk_val is None:
- # The pk_val and model values are the same for all
- # DeferredAttribute classes, so we only need to do this
- # once.
- obj = self.__class__.__dict__[field.attname]
- model = obj.model_ref()
+ if self._deferred:
+ for field in self._meta.fields:
+ if isinstance(self.__class__.__dict__.get(field.attname),
+ DeferredAttribute):
+ defers.append(field.attname)
+ if pk_val is None:
+ # The pk_val and model values are the same for all
+ # DeferredAttribute classes, so we only need to do this
+ # once.
+ obj = self.__class__.__dict__[field.attname]
+ model = obj.model_ref()
+
return (model_unpickle, (model, defers), data)
@@ -432,5 +438,5 @@
meta = cls._meta
- if origin:
+ if origin and not meta.auto_created:
signals.pre_save.send(sender=origin, instance=self, raw=raw)
@@ -471,5 +477,5 @@
# Determine whether a record with the primary key already exists.
if (force_update or (not force_insert and
- manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
+ manager.filter(pk=pk_val).exists())):
# It does already exist, so do an UPDATE.
if force_update or non_pks:
@@ -505,5 +511,5 @@
transaction.commit_unless_managed()
- if origin:
+ if origin and not meta.auto_created:
signals.post_save.send(sender=origin, instance=self,
created=(not record_exists), raw=raw)
@@ -542,5 +548,10 @@
break
else:
- raise AssertionError("Should never get here.")
+ # in the case of a hidden fkey just skip it, it'll get
+ # processed as an m2m
+ if not related.field.rel.is_hidden():
+ raise AssertionError("Should never get here.")
+ else:
+ continue
delete_qs = rel_descriptor.delete_manager(self).all()
for sub_obj in delete_qs:
Index: /django/branches/soc2009/model-validation/django/db/models/manager.py
===================================================================
--- /django/branches/soc2009/model-validation/django/db/models/manager.py (revision 10738)
+++ /django/branches/soc2009/model-validation/django/db/models/manager.py (revision 11725)
@@ -1,4 +1,3 @@
import copy
-
from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
from django.db.models import signals
@@ -174,4 +173,7 @@
return self.get_query_set().only(*args, **kwargs)
+ def exists(self, *args, **kwargs):
+ return self.get_query_set().exists(*args, **kwargs)
+
def _insert(self, values, **kwargs):
return insert_query(self.model, values, **kwargs)
Index: /django/branches/soc2009/model-validation/django/db/models/options.py
===================================================================
--- /django/branches/soc2009/model-validation/django/db/models/options.py (revision 10738)
+++ /django/branches/soc2009/model-validation/django/db/models/options.py (revision 11725)
@@ -22,5 +22,5 @@
'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace',
- 'abstract', 'managed', 'proxy')
+ 'abstract', 'managed', 'proxy', 'auto_created')
class Options(object):
@@ -48,4 +48,5 @@
self.parents = SortedDict()
self.duplicate_targets = {}
+ self.auto_created = False
# To handle various inheritance situations, we need to track where
@@ -488,3 +489,2 @@
"""
return self.fields.index(self.pk)
-
Index: /django/branches/soc2009/model-validation/django/db/models/loading.py
===================================================================
--- /django/branches/soc2009/model-validation/django/db/models/loading.py (revision 10088)
+++ /django/branches/soc2009/model-validation/django/db/models/loading.py (revision 11725)
@@ -132,17 +132,23 @@
return self.app_errors
- def get_models(self, app_mod=None):
+ def get_models(self, app_mod=None, include_auto_created=False):
"""
Given a module containing models, returns a list of the models.
Otherwise returns a list of all installed models.
+
+ By default, auto-created models (i.e., m2m models without an
+ explicit intermediate table) are not included. However, if you
+ specify include_auto_created=True, they will be.
"""
self._populate()
if app_mod:
- return self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
+ model_list = self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
else:
model_list = []
for app_entry in self.app_models.itervalues():
model_list.extend(app_entry.values())
- return model_list
+ if not include_auto_created:
+ return filter(lambda o: not o._meta.auto_created, model_list)
+ return model_list
def get_model(self, app_label, model_name, seed_cache=True):
Index: /django/branches/soc2009/model-validation/django/db/models/fields/related.py
===================================================================
--- /django/branches/soc2009/model-validation/django/db/models/fields/related.py (revision 11398)
+++ /django/branches/soc2009/model-validation/django/db/models/fields/related.py (revision 11725)
@@ -59,4 +59,8 @@
app_label = cls._meta.app_label
model_name = relation
+ except AttributeError:
+ # If it doesn't have a split it's actually a model class
+ app_label = relation._meta.app_label
+ model_name = relation._meta.object_name
# Try to look up the related model, and if it's already loaded resolve the
@@ -97,5 +101,5 @@
other = self.rel.to
- if isinstance(other, basestring):
+ if isinstance(other, basestring) or other._meta.pk is None:
def resolve_related_class(field, model, cls):
field.rel.to = model
@@ -402,10 +406,11 @@
return manager
-def create_many_related_manager(superclass, through=False):
+def create_many_related_manager(superclass, rel=False):
"""Creates a manager that subclasses 'superclass' (which is a Manager)
and adds behavior for many-to-many related objects."""
+ through = rel.through
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):
+ join_table=None, source_field_name=None, target_field_name=None):
super(ManyRelatedManager, self).__init__()
self.core_filters = core_filters
@@ -413,9 +418,8 @@
self.symmetrical = symmetrical
self.instance = instance
- self.join_table = join_table
- self.source_col_name = source_col_name
- self.target_col_name = target_col_name
+ self.source_field_name = source_field_name
+ self.target_field_name = target_field_name
self.through = through
- self._pk_val = self.instance._get_pk_val()
+ self._pk_val = self.instance.pk
if self._pk_val is None:
raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__)
@@ -426,27 +430,27 @@
# If the ManyToMany relation has an intermediary model,
# the add and remove methods do not exist.
- if through is None:
+ if rel.through._meta.auto_created:
def add(self, *objs):
- self._add_items(self.source_col_name, self.target_col_name, *objs)
+ self._add_items(self.source_field_name, self.target_field_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)
+ self._add_items(self.target_field_name, self.source_field_name, *objs)
add.alters_data = True
def remove(self, *objs):
- self._remove_items(self.source_col_name, self.target_col_name, *objs)
+ self._remove_items(self.source_field_name, self.target_field_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)
+ self._remove_items(self.target_field_name, self.source_field_name, *objs)
remove.alters_data = True
def clear(self):
- self._clear_items(self.source_col_name)
+ self._clear_items(self.source_field_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)
+ self._clear_items(self.target_field_name)
clear.alters_data = True
@@ -454,6 +458,7 @@
# This check needs to be done here, since we can't later remove this
# from the method lookup table, as we do with add and remove.
- if through is not None:
- raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
+ if not rel.through._meta.auto_created:
+ opts = through._meta
+ raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
new_obj = super(ManyRelatedManager, self).create(**kwargs)
self.add(new_obj)
@@ -471,39 +476,36 @@
get_or_create.alters_data = True
- def _add_items(self, source_col_name, target_col_name, *objs):
+ def _add_items(self, source_field_name, target_field_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
+ # source_field_name: the PK fieldname in join_table for the source object
+ # target_col_name: the PK fieldname in join_table for the target object
# *objs - objects to add. Either object instances, or primary keys of object instances.
# If there aren't any objects, there is nothing to do.
+ from django.db.models import Model
if objs:
- from django.db.models.base import Model
- # Check that all the objects are of the right type
new_ids = set()
for obj in objs:
if isinstance(obj, self.model):
- new_ids.add(obj._get_pk_val())
+ new_ids.add(obj.pk)
elif isinstance(obj, Model):
raise TypeError, "'%s' instance expected" % self.model._meta.object_name
else:
new_ids.add(obj)
- # Add the newly created or already existing objects to the join table.
- # First find out which items are already added, to avoid adding them twice
- cursor = connection.cursor()
- cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
- (target_col_name, self.join_table, source_col_name,
- target_col_name, ",".join(['%s'] * len(new_ids))),
- [self._pk_val] + list(new_ids))
- existing_ids = set([row[0] for row in cursor.fetchall()])
+ vals = self.through._default_manager.values_list(target_field_name, flat=True)
+ vals = vals.filter(**{
+ source_field_name: self._pk_val,
+ '%s__in' % target_field_name: new_ids,
+ })
+ vals = set(vals)
# 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):
+ for obj_id in (new_ids - vals):
+ self.through._default_manager.create(**{
+ '%s_id' % source_field_name: self._pk_val,
+ '%s_id' % target_field_name: obj_id,
+ })
+
+ def _remove_items(self, source_field_name, target_field_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
@@ -516,22 +518,18 @@
for obj in objs:
if isinstance(obj, self.model):
- old_ids.add(obj._get_pk_val())
+ old_ids.add(obj.pk)
else:
old_ids.add(obj)
# Remove the specified objects from the join table
- cursor = connection.cursor()
- cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
- (self.join_table, source_col_name,
- target_col_name, ",".join(['%s'] * len(old_ids))),
- [self._pk_val] + list(old_ids))
- transaction.commit_unless_managed()
-
- def _clear_items(self, source_col_name):
+ self.through._default_manager.filter(**{
+ source_field_name: self._pk_val,
+ '%s__in' % target_field_name: old_ids
+ }).delete()
+
+ def _clear_items(self, source_field_name):
# source_col_name: the PK colname in join_table for the source object
- cursor = connection.cursor()
- cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
- (self.join_table, source_col_name),
- [self._pk_val])
- transaction.commit_unless_managed()
+ self.through._default_manager.filter(**{
+ source_field_name: self._pk_val
+ }).delete()
return ManyRelatedManager
@@ -555,7 +553,6 @@
rel_model = self.related.model
superclass = rel_model._default_manager.__class__
- RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through)
-
- qn = connection.ops.quote_name
+ RelatedManager = create_many_related_manager(superclass, self.related.field.rel)
+
manager = RelatedManager(
model=rel_model,
@@ -563,7 +560,6 @@
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())
+ source_field_name=self.related.field.m2m_reverse_field_name(),
+ target_field_name=self.related.field.m2m_field_name()
)
@@ -574,7 +570,7 @@
raise AttributeError, "Manager must be accessed via instance"
- through = getattr(self.related.field.rel, 'through', None)
- if through is not None:
- raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
+ if not self.related.field.rel.through._meta.auto_created:
+ opts = self.related.field.rel.through._meta
+ raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
manager = self.__get__(instance)
@@ -591,4 +587,7 @@
def __init__(self, m2m_field):
self.field = m2m_field
+ # through is provided so that you have easy access to the through
+ # model (Book.authors.through) for inlines, etc.
+ self.through = m2m_field.rel.through
def __get__(self, instance, instance_type=None):
@@ -600,7 +599,6 @@
rel_model=self.field.rel.to
superclass = rel_model._default_manager.__class__
- RelatedManager = create_many_related_manager(superclass, self.field.rel.through)
-
- qn = connection.ops.quote_name
+ RelatedManager = create_many_related_manager(superclass, self.field.rel)
+
manager = RelatedManager(
model=rel_model,
@@ -608,7 +606,6 @@
instance=instance,
symmetrical=(self.field.rel.symmetrical and isinstance(instance, 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())
+ source_field_name=self.field.m2m_field_name(),
+ target_field_name=self.field.m2m_reverse_field_name()
)
@@ -619,7 +616,7 @@
raise AttributeError, "Manager must be accessed via instance"
- through = getattr(self.field.rel, 'through', None)
- if through is not None:
- raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
+ if not self.field.rel.through._meta.auto_created:
+ opts = self.field.rel.through._meta
+ raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
manager = self.__get__(instance)
@@ -643,4 +640,8 @@
self.parent_link = parent_link
+ def is_hidden(self):
+ "Should the related object be hidden?"
+ return self.related_name and self.related_name[-1] == '+'
+
def get_related_field(self):
"""
@@ -673,4 +674,8 @@
self.multiple = True
self.through = through
+
+ def is_hidden(self):
+ "Should the related object be hidden?"
+ return self.related_name and self.related_name[-1] == '+'
def get_related_field(self):
@@ -694,5 +699,4 @@
else:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
- to_field = to_field or to._meta.pk.name
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
@@ -759,5 +763,10 @@
def contribute_to_related_class(self, cls, related):
- setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
+ # Internal FK's - i.e., those with a related name ending with '+' -
+ # don't get a related descriptor.
+ if not self.rel.is_hidden():
+ setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
+ if self.rel.field_name is None:
+ self.rel.field_name = cls._meta.pk.name
def formfield(self, **kwargs):
@@ -813,4 +822,49 @@
setattr(instance, self.attname, data)
+def create_many_to_many_intermediary_model(field, klass):
+ from django.db import models
+ managed = True
+ if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
+ to = field.rel.to
+ to_model = field.rel.to
+ def set_managed(field, model, cls):
+ field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
+ add_lazy_relation(klass, field, to_model, set_managed)
+ elif isinstance(field.rel.to, basestring):
+ to = klass._meta.object_name
+ to_model = klass
+ managed = klass._meta.managed
+ else:
+ to = field.rel.to._meta.object_name
+ to_model = field.rel.to
+ managed = klass._meta.managed or to_model._meta.managed
+ name = '%s_%s' % (klass._meta.object_name, field.name)
+ if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or field.rel.to == klass._meta.object_name:
+ from_ = 'from_%s' % to.lower()
+ to = 'to_%s' % to.lower()
+ else:
+ from_ = klass._meta.object_name.lower()
+ to = to.lower()
+ meta = type('Meta', (object,), {
+ 'db_table': field._get_m2m_db_table(klass._meta),
+ 'managed': managed,
+ 'auto_created': klass,
+ 'unique_together': (from_, to)
+ })
+ # If the models have been split into subpackages, klass.__module__
+ # will be the subpackge, not the models module for the app. (See #12168)
+ # Compose the actual models module name by stripping the trailing parts
+ # of the namespace until we find .models
+ parts = klass.__module__.split('.')
+ while parts[-1] != 'models':
+ parts.pop()
+ module = '.'.join(parts)
+ # Construct and return the new class.
+ return type(name, (models.Model,), {
+ 'Meta': meta,
+ '__module__': module,
+ from_: models.ForeignKey(klass, related_name='%s+' % name),
+ to: models.ForeignKey(to_model, related_name='%s+' % name)
+ })
class ManyToManyField(RelatedField, Field):
@@ -830,8 +884,5 @@
self.db_table = kwargs.pop('db_table', None)
if kwargs['rel'].through is not None:
- self.creates_table = False
assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
- else:
- self.creates_table = True
Field.__init__(self, **kwargs)
@@ -846,5 +897,5 @@
"Function that can be curried to provide the m2m table name for this relation"
if self.rel.through is not None:
- return self.rel.through_model._meta.db_table
+ return self.rel.through._meta.db_table
elif self.db_table:
return self.db_table
@@ -853,53 +904,36 @@
connection.ops.max_name_length())
- def _get_m2m_column_name(self, related):
+ def _get_m2m_attr(self, related, attr):
"Function that can be curried to provide the source column name for the m2m table"
- try:
- return self._m2m_column_name_cache
- except:
- if self.rel.through is not None:
- for f in self.rel.through_model._meta.fields:
- if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
- self._m2m_column_name_cache = f.column
+ cache_attr = '_m2m_%s_cache' % attr
+ if hasattr(self, cache_attr):
+ return getattr(self, cache_attr)
+ for f in self.rel.through._meta.fields:
+ if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
+ setattr(self, cache_attr, getattr(f, attr))
+ return getattr(self, cache_attr)
+
+ def _get_m2m_reverse_attr(self, related, attr):
+ "Function that can be curried to provide the related column name for the m2m table"
+ cache_attr = '_m2m_reverse_%s_cache' % attr
+ if hasattr(self, cache_attr):
+ return getattr(self, cache_attr)
+ found = False
+ for f in self.rel.through._meta.fields:
+ if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
+ if related.model == related.parent_model:
+ # If this is an m2m-intermediate to self,
+ # the first foreign key you find will be
+ # the source column. Keep searching for
+ # the second foreign key.
+ if found:
+ setattr(self, cache_attr, getattr(f, attr))
break
- # If this is an m2m relation to self, avoid the inevitable name clash
- elif related.model == related.parent_model:
- self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id'
- else:
- self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id'
-
- # Return the newly cached value
- return self._m2m_column_name_cache
-
- def _get_m2m_reverse_name(self, related):
- "Function that can be curried to provide the related column name for the m2m table"
- try:
- return self._m2m_reverse_name_cache
- except:
- if self.rel.through is not None:
- found = False
- for f in self.rel.through_model._meta.fields:
- if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
- if related.model == related.parent_model:
- # If this is an m2m-intermediate to self,
- # the first foreign key you find will be
- # the source column. Keep searching for
- # the second foreign key.
- if found:
- self._m2m_reverse_name_cache = f.column
- break
- else:
- found = True
- else:
- self._m2m_reverse_name_cache = f.column
- break
- # If this is an m2m relation to self, avoid the inevitable name clash
- elif related.model == related.parent_model:
- self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id'
- else:
- self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id'
-
- # Return the newly cached value
- return self._m2m_reverse_name_cache
+ else:
+ found = True
+ else:
+ setattr(self, cache_attr, getattr(f, attr))
+ break
+ return getattr(self, cache_attr)
def isValidIDList(self, field_data, all_data):
@@ -943,8 +977,15 @@
# automatically. The funky name reduces the chance of an accidental
# clash.
- if self.rel.symmetrical and self.rel.to == "self" and self.rel.related_name is None:
+ if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name):
self.rel.related_name = "%s_rel_+" % name
super(ManyToManyField, self).contribute_to_class(cls, name)
+
+ # The intermediate m2m model is not auto created if:
+ # 1) There is a manually specified intermediate, or
+ # 2) The class owning the m2m field is abstract.
+ if not self.rel.through and not cls._meta.abstract:
+ self.rel.through = create_many_to_many_intermediary_model(self, cls)
+
# Add the descriptor for the m2m relation
setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
@@ -957,9 +998,6 @@
if isinstance(self.rel.through, basestring):
def resolve_through_model(field, model, cls):
- field.rel.through_model = model
+ field.rel.through = model
add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
- elif self.rel.through:
- self.rel.through_model = self.rel.through
- self.rel.through = self.rel.through._meta.object_name
if isinstance(self.rel.to, basestring):
@@ -970,13 +1008,15 @@
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
+ # Internal M2Ms (i.e., those with a related name ending with '+')
+ # don't get a related descriptor.
+ if not self.rel.is_hidden():
setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
# Set up the accessors for the column names on the m2m table
- self.m2m_column_name = curry(self._get_m2m_column_name, related)
- self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related)
+ self.m2m_column_name = curry(self._get_m2m_attr, related, 'column')
+ self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column')
+
+ self.m2m_field_name = curry(self._get_m2m_attr, related, 'name')
+ self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name')
def set_attributes_from_rel(self):
Index: /django/branches/soc2009/model-validation/django/db/models/query.py
===================================================================
--- /django/branches/soc2009/model-validation/django/db/models/query.py (revision 10944)
+++ /django/branches/soc2009/model-validation/django/db/models/query.py (revision 11725)
@@ -3,11 +3,5 @@
"""
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
-
from copy import deepcopy
-
from django.db import connection, transaction, IntegrityError
from django.db.models.aggregates import Aggregate
@@ -15,5 +9,4 @@
from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
from django.db.models import signals, sql
-
# Used to control how many objects are worked with at once in some cases (e.g.
@@ -445,4 +438,9 @@
_update.alters_data = True
+ def exists(self):
+ if self._result_cache is None:
+ return self.query.has_results()
+ return bool(self._result_cache)
+
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
@@ -1031,5 +1029,6 @@
# Pre-notify all instances to be deleted.
for pk_val, instance in items:
- signals.pre_delete.send(sender=cls, instance=instance)
+ if not cls._meta.auto_created:
+ signals.pre_delete.send(sender=cls, instance=instance)
pk_list = [pk for pk,instance in items]
@@ -1065,5 +1064,6 @@
setattr(instance, field.attname, None)
- signals.post_delete.send(sender=cls, instance=instance)
+ if not cls._meta.auto_created:
+ signals.post_delete.send(sender=cls, instance=instance)
setattr(instance, cls._meta.pk.attname, None)
Index: /django/branches/soc2009/model-validation/django/conf/locale/pl/LC_MESSAGES/django.po
===================================================================
--- /django/branches/soc2009/model-validation/django/conf/locale/pl/LC_MESSAGES/django.po (revision 11395)
+++ /django/branches/soc2009/model-validation/django/conf/locale/pl/LC_MESSAGES/django.po (revision 11725)
@@ -6,5 +6,5 @@
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-07-17 21:59+0200\n"
+"POT-Creation-Date: 2009-10-25 20:56+0100\n"
"PO-Revision-Date: 2008-02-25 15:53+0100\n"
"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
@@ -267,13 +267,13 @@
msgstr "Ten rok"
-#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "Yes"
msgstr "Tak"
-#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "No"
msgstr "Nie"
-#: contrib/admin/filterspecs.py:154 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:154 forms/widgets.py:435
msgid "Unknown"
msgstr "Nieznany"
@@ -321,6 +321,6 @@
#: contrib/admin/options.py:519 contrib/admin/options.py:529
-#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388
-#: forms/models.py:600
+#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
+#: forms/models.py:596
msgid "and"
msgstr "i"
@@ -418,9 +418,9 @@
"znaczenie."
-#: contrib/admin/sites.py:285 contrib/admin/views/decorators.py:40
+#: contrib/admin/sites.py:288 contrib/admin/views/decorators.py:40
msgid "Please log in again, because your session has expired."
[[BINARY DATA]]
-#: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:47
+#: contrib/admin/sites.py:295 contrib/admin/views/decorators.py:47
msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
@@ -430,19 +430,19 @@
[[BINARY DATA]]
-#: contrib/admin/sites.py:308 contrib/admin/sites.py:314
+#: contrib/admin/sites.py:311 contrib/admin/sites.py:317
#: contrib/admin/views/decorators.py:66
msgid "Usernames cannot contain the '@' character."
[[BINARY DATA]]
-#: contrib/admin/sites.py:311 contrib/admin/views/decorators.py:62
+#: contrib/admin/sites.py:314 contrib/admin/views/decorators.py:62
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
[[BINARY DATA]]
-#: contrib/admin/sites.py:367
+#: contrib/admin/sites.py:370
msgid "Site administration"
[[BINARY DATA]]
-#: contrib/admin/sites.py:381 contrib/admin/templates/admin/login.html:26
+#: contrib/admin/sites.py:384 contrib/admin/templates/admin/login.html:26
#: contrib/admin/templates/registration/password_reset_complete.html:14
#: contrib/admin/views/decorators.py:20
@@ -450,5 +450,5 @@
[[BINARY DATA]]
-#: contrib/admin/sites.py:426
+#: contrib/admin/sites.py:429
#, python-format
msgid "%s administration"
@@ -465,25 +465,25 @@
[[BINARY DATA]]
-#: contrib/admin/widgets.py:71
+#: contrib/admin/widgets.py:72
msgid "Date:"
msgstr "Data:"
-#: contrib/admin/widgets.py:71
+#: contrib/admin/widgets.py:72
msgid "Time:"
msgstr "Czas:"
-#: contrib/admin/widgets.py:95
+#: contrib/admin/widgets.py:96
msgid "Currently:"
msgstr "Teraz:"
-#: contrib/admin/widgets.py:95
+#: contrib/admin/widgets.py:96
msgid "Change:"
[[BINARY DATA]]
-#: contrib/admin/widgets.py:124
+#: contrib/admin/widgets.py:125
msgid "Lookup"
msgstr "Szukaj"
-#: contrib/admin/widgets.py:235
+#: contrib/admin/widgets.py:237
msgid "Add Another"
msgstr "Dodaj kolejny"
@@ -599,5 +599,5 @@
#: contrib/admin/templates/admin/change_form.html:28
#: contrib/admin/templates/admin/edit_inline/stacked.html:13
-#: contrib/admin/templates/admin/edit_inline/tabular.html:27
+#: contrib/admin/templates/admin/edit_inline/tabular.html:28
msgid "View on site"
[[BINARY DATA]]
@@ -669,8 +669,8 @@
msgid ""
"Are you sure you want to delete the selected %(object_name)s objects? All of "
-"the following objects and it's related items will be deleted:"
-msgstr ""
[[BINARY DATA]]
+"the following objects and their related items will be deleted:"
+msgstr ""
[[BINARY DATA]]
#: contrib/admin/templates/admin/filter.html:2
@@ -735,5 +735,4 @@
#: contrib/admin/templates/admin/object_history.html:24
-#: contrib/comments/templates/comments/moderation_queue.html:33
msgid "Action"
msgstr "Akcja"
@@ -1126,5 +1125,4 @@
#: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
-#: contrib/comments/templates/comments/moderation_queue.html:37
#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
msgid "URL"
@@ -1429,20 +1427,52 @@
[[BINARY DATA]]
-#: contrib/auth/views.py:56
+#: contrib/auth/views.py:58
msgid "Logged out"
msgstr "Wylogowany"
-#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:429
+#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:428
msgid "Enter a valid e-mail address."
[[BINARY DATA]]
-#: contrib/comments/admin.py:11
+#: contrib/comments/admin.py:12
msgid "Content"
[[BINARY DATA]]
-#: contrib/comments/admin.py:14
+#: contrib/comments/admin.py:15
msgid "Metadata"
msgstr "Metadane"
+#: contrib/comments/admin.py:39
+msgid "flagged"
+msgstr "oflagowany"
+
+#: contrib/comments/admin.py:40
+msgid "Flag selected comments"
+msgstr "Oflaguj wybrane komentarze"
+
+#: contrib/comments/admin.py:43
+msgid "approved"
+msgstr "zaakceptowany"
+
+#: contrib/comments/admin.py:44
+msgid "Approve selected comments"
+msgstr "Zaakceptuj wybrane komentarze"
+
+#: contrib/comments/admin.py:47
+msgid "removed"
[[BINARY DATA]]
+#: contrib/comments/admin.py:48
+msgid "Remove selected comments"
[[BINARY DATA]]
+#: contrib/comments/admin.py:60
+#, python-format
+msgid "1 comment was successfully %(action)s."
+msgid_plural "%(count)s comments were successfully %(action)s."
[[BINARY DATA]]
#: contrib/comments/feeds.py:13
#, python-format
@@ -1456,5 +1486,4 @@
#: contrib/comments/forms.py:93
-#: contrib/comments/templates/comments/moderation_queue.html:34
msgid "Name"
msgstr "Nazwa"
@@ -1465,5 +1494,4 @@
#: contrib/comments/forms.py:96
-#: contrib/comments/templates/comments/moderation_queue.html:35
msgid "Comment"
msgstr "Komentarz"
@@ -1593,5 +1621,4 @@
#: contrib/comments/templates/comments/approve.html:12
-#: contrib/comments/templates/comments/moderation_queue.html:49
msgid "Approve"
msgstr "Zaakceptuj"
@@ -1619,5 +1646,4 @@
#: contrib/comments/templates/comments/delete.html:12
-#: contrib/comments/templates/comments/moderation_queue.html:53
msgid "Remove"
[[BINARY DATA]]
@@ -1652,37 +1678,4 @@
msgid "Preview"
[[BINARY DATA]]
-#: contrib/comments/templates/comments/moderation_queue.html:4
-#: contrib/comments/templates/comments/moderation_queue.html:19
-msgid "Comment moderation queue"
-msgstr "Kolejka moderacji komentarzy"
-
-#: contrib/comments/templates/comments/moderation_queue.html:26
-msgid "No comments to moderate"
[[BINARY DATA]]
-#: contrib/comments/templates/comments/moderation_queue.html:36
-msgid "Email"
-msgstr "E-mail"
-
-#: contrib/comments/templates/comments/moderation_queue.html:38
-msgid "Authenticated?"
-msgstr "Zalogowany?"
-
-#: contrib/comments/templates/comments/moderation_queue.html:39
-msgid "IP Address"
-msgstr "Adres IP"
-
-#: contrib/comments/templates/comments/moderation_queue.html:40
-msgid "Date posted"
-msgstr "Data dodania"
-
-#: contrib/comments/templates/comments/moderation_queue.html:63
-msgid "yes"
-msgstr "tak"
-
-#: contrib/comments/templates/comments/moderation_queue.html:63
-msgid "no"
-msgstr "nie"
#: contrib/comments/templates/comments/posted.html:4
@@ -2599,4 +2592,8 @@
msgid "Enter a valid Finnish social security number."
[[BINARY DATA]]
+#: contrib/localflavor/fr/forms.py:30
+msgid "Phone numbers must be in 0X XX XX XX XX format."
[[BINARY DATA]]
#: contrib/localflavor/in_/forms.py:14
@@ -3945,13 +3942,13 @@
"niepoprawne."
-#: forms/fields.py:54
+#: forms/fields.py:53
msgid "This field is required."
msgstr "To pole jest wymagane."
-#: forms/fields.py:55
+#: forms/fields.py:54
msgid "Enter a valid value."
[[BINARY DATA]]
-#: forms/fields.py:138
+#: forms/fields.py:137
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
@@ -3960,5 +3957,5 @@
"(length)d)."
-#: forms/fields.py:139
+#: forms/fields.py:138
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
@@ -3967,62 +3964,62 @@
"(length)d)."
-#: forms/fields.py:166
+#: forms/fields.py:165
msgid "Enter a whole number."
[[BINARY DATA]]
-#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
+#: forms/fields.py:166 forms/fields.py:195 forms/fields.py:224
#, python-format
msgid "Ensure this value is less than or equal to %s."
[[BINARY DATA]]
-#: forms/fields.py:168 forms/fields.py:197 forms/fields.py:226
+#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
#, python-format
msgid "Ensure this value is greater than or equal to %s."
[[BINARY DATA]]
-#: forms/fields.py:195 forms/fields.py:224
+#: forms/fields.py:194 forms/fields.py:223
msgid "Enter a number."
[[BINARY DATA]]
-#: forms/fields.py:227
+#: forms/fields.py:226
#, python-format
msgid "Ensure that there are no more than %s digits in total."
[[BINARY DATA]]
-#: forms/fields.py:228
+#: forms/fields.py:227
#, python-format
msgid "Ensure that there are no more than %s decimal places."
[[BINARY DATA]]
-#: forms/fields.py:229
+#: forms/fields.py:228
#, python-format
msgid "Ensure that there are no more than %s digits before the decimal point."
[[BINARY DATA]]
-#: forms/fields.py:288 forms/fields.py:863
+#: forms/fields.py:287 forms/fields.py:862
msgid "Enter a valid date."
[[BINARY DATA]]
-#: forms/fields.py:322 forms/fields.py:864
+#: forms/fields.py:321 forms/fields.py:863
msgid "Enter a valid time."
[[BINARY DATA]]
-#: forms/fields.py:361
+#: forms/fields.py:360
msgid "Enter a valid date/time."
[[BINARY DATA]]
-#: forms/fields.py:447
+#: forms/fields.py:446
msgid "No file was submitted. Check the encoding type on the form."
[[BINARY DATA]]
-#: forms/fields.py:448
+#: forms/fields.py:447
msgid "No file was submitted."
[[BINARY DATA]]
-#: forms/fields.py:449
+#: forms/fields.py:448
msgid "The submitted file is empty."
[[BINARY DATA]]
-#: forms/fields.py:450
+#: forms/fields.py:449
#, python-format
msgid ""
@@ -4032,5 +4029,5 @@
"(length)d)."
-#: forms/fields.py:483
+#: forms/fields.py:482
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
@@ -4040,13 +4037,13 @@
"albo jest uszkodzony."
-#: forms/fields.py:544
+#: forms/fields.py:543
msgid "Enter a valid URL."
msgstr "Wpisz poprawny URL."
-#: forms/fields.py:545
+#: forms/fields.py:544
msgid "This URL appears to be a broken link."
[[BINARY DATA]]
-#: forms/fields.py:625 forms/fields.py:703
+#: forms/fields.py:624 forms/fields.py:702
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
@@ -4054,13 +4051,13 @@
[[BINARY DATA]]
-#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003
+#: forms/fields.py:703 forms/fields.py:764 forms/models.py:999
msgid "Enter a list of values."
[[BINARY DATA]]
-#: forms/fields.py:892
+#: forms/fields.py:891
msgid "Enter a valid IPv4 address."
[[BINARY DATA]]
-#: forms/fields.py:902
+#: forms/fields.py:901
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
@@ -4071,5 +4068,5 @@
[[BINARY DATA]]
-#: forms/models.py:367
+#: forms/models.py:363
#, python-format
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
@@ -4078,20 +4075,20 @@
"(date_field)s"
-#: forms/models.py:381 forms/models.py:389
+#: forms/models.py:377 forms/models.py:385
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
[[BINARY DATA]]
-#: forms/models.py:594
+#: forms/models.py:590
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr "Popraw zduplikowane dane w %(field)s."
-#: forms/models.py:598
+#: forms/models.py:594
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
[[BINARY DATA]]
-#: forms/models.py:604
+#: forms/models.py:600
#, python-format
msgid ""
@@ -4102,22 +4099,22 @@
"(lookup)s w polu %(date_field)s."
-#: forms/models.py:612
+#: forms/models.py:608
msgid "Please correct the duplicate values below."
[[BINARY DATA]]
-#: forms/models.py:867
+#: forms/models.py:863
msgid "The inline foreign key did not match the parent instance primary key."
[[BINARY DATA]]
-#: forms/models.py:930
+#: forms/models.py:926
msgid "Select a valid choice. That choice is not one of the available choices."
[[BINARY DATA]]
-#: forms/models.py:1004
+#: forms/models.py:1000
#, python-format
msgid "Select a valid choice. %s is not one of the available choices."
[[BINARY DATA]]
-#: forms/models.py:1006
+#: forms/models.py:1002
#, python-format
msgid "\"%s\" is not a valid value for a primary key."
@@ -4445,2 +4442,26 @@
msgid "The %(verbose_name)s was deleted."
[[BINARY DATA]]
+#~ msgid "Comment moderation queue"
+#~ msgstr "Kolejka moderacji komentarzy"
+
+#~ msgid "No comments to moderate"
[[BINARY DATA]]
+#~ msgid "Email"
+#~ msgstr "E-mail"
+
+#~ msgid "Authenticated?"
+#~ msgstr "Zalogowany?"
+
+#~ msgid "IP Address"
+#~ msgstr "Adres IP"
+
+#~ msgid "Date posted"
+#~ msgstr "Data dodania"
+
+#~ msgid "yes"
+#~ msgstr "tak"
+
+#~ msgid "no"
+#~ msgstr "nie"
Index: /django/branches/soc2009/model-validation/django/conf/project_template/settings.py
===================================================================
--- /django/branches/soc2009/model-validation/django/conf/project_template/settings.py (revision 10128)
+++ /django/branches/soc2009/model-validation/django/conf/project_template/settings.py (revision 11725)
@@ -61,4 +61,5 @@
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
Index: /django/branches/soc2009/model-validation/django/conf/__init__.py
===================================================================
--- /django/branches/soc2009/model-validation/django/conf/__init__.py (revision 10088)
+++ /django/branches/soc2009/model-validation/django/conf/__init__.py (revision 11725)
@@ -109,7 +109,4 @@
time.tzset()
- def get_all_members(self):
- return dir(self)
-
class UserSettingsHolder(object):
"""
@@ -130,6 +127,9 @@
return getattr(self.default_settings, name)
- def get_all_members(self):
+ def __dir__(self):
return dir(self) + dir(self.default_settings)
+
+ # For Python < 2.6:
+ __members__ = property(lambda self: self.__dir__())
settings = LazySettings()
Index: /django/branches/soc2009/model-validation/django/conf/global_settings.py
===================================================================
--- /django/branches/soc2009/model-validation/django/conf/global_settings.py (revision 10840)
+++ /django/branches/soc2009/model-validation/django/conf/global_settings.py (revision 11725)
@@ -132,4 +132,10 @@
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
+# The email backend to use. For possible shortcuts see django.core.mail.
+# The default is to use the SMTP backend.
+# Third-party backends can be specified by providing a Python path
+# to a module that defines an EmailBackend class.
+EMAIL_BACKEND = 'django.core.mail.backends.smtp'
+
# Host for sending e-mail.
EMAIL_HOST = 'localhost'
@@ -301,4 +307,5 @@
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'django.middleware.http.ConditionalGetMiddleware',
@@ -375,4 +382,16 @@
PASSWORD_RESET_TIMEOUT_DAYS = 3
+########
+# CSRF #
+########
+
+# Dotted path to callable to be used as view when a request is
+# rejected by the CSRF middleware.
+CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure'
+
+# Name and domain for CSRF cookie.
+CSRF_COOKIE_NAME = 'csrftoken'
+CSRF_COOKIE_DOMAIN = None
+
###########
# TESTING #
Index: /django/branches/soc2009/model-validation/django/forms/models.py
===================================================================
--- /django/branches/soc2009/model-validation/django/forms/models.py (revision 11233)
+++ /django/branches/soc2009/model-validation/django/forms/models.py (revision 11725)
@@ -259,4 +259,161 @@
return self.cleaned_data
+
+ def validate_unique(self):
+ unique_checks, date_checks = self._get_unique_checks()
+ form_errors = []
+ bad_fields = set()
+
+ field_errors, global_errors = self._perform_unique_checks(unique_checks)
+ bad_fields.union(field_errors)
+ form_errors.extend(global_errors)
+
+ field_errors, global_errors = self._perform_date_checks(date_checks)
+ bad_fields.union(field_errors)
+ form_errors.extend(global_errors)
+
+ for field_name in bad_fields:
+ del self.cleaned_data[field_name]
+ if form_errors:
+ # Raise the unique together errors since they are considered
+ # form-wide.
+ raise ValidationError(form_errors)
+
+ def _get_unique_checks(self):
+ from django.db.models.fields import FieldDoesNotExist, Field as ModelField
+
+ # Gather a list of checks to perform. We only perform unique checks
+ # for fields present and not None in cleaned_data. Since this is a
+ # ModelForm, some fields may have been excluded; we can't perform a unique
+ # check on a form that is missing fields involved in that check. It also does
+ # not make sense to check data that didn't validate, and since NULL does not
+ # equal NULL in SQL we should not do any unique checking for NULL values.
+ unique_checks = []
+ # these are checks for the unique_for_<date/year/month>
+ date_checks = []
+ for check in self.instance._meta.unique_together[:]:
+ fields_on_form = [field for field in check if self.cleaned_data.get(field) is not None]
+ if len(fields_on_form) == len(check):
+ unique_checks.append(check)
+
+ # Gather a list of checks for fields declared as unique and add them to
+ # the list of checks. Again, skip empty fields and any that did not validate.
+ for name in self.fields:
+ try:
+ f = self.instance._meta.get_field_by_name(name)[0]
+ except FieldDoesNotExist:
+ # This is an extra field that's not on the ModelForm, ignore it
+ continue
+ if not isinstance(f, ModelField):
+ # This is an extra field that happens to have a name that matches,
+ # for example, a related object accessor for this model. So
+ # get_field_by_name found it, but it is not a Field so do not proceed
+ # to use it as if it were.
+ continue
+ if self.cleaned_data.get(name) is None:
+ continue
+ if f.unique:
+ unique_checks.append((name,))
+ if f.unique_for_date and self.cleaned_data.get(f.unique_for_date) is not None:
+ date_checks.append(('date', name, f.unique_for_date))
+ if f.unique_for_year and self.cleaned_data.get(f.unique_for_year) is not None:
+ date_checks.append(('year', name, f.unique_for_year))
+ if f.unique_for_month and self.cleaned_data.get(f.unique_for_month) is not None:
+ date_checks.append(('month', name, f.unique_for_month))
+ return unique_checks, date_checks
+
+
+ def _perform_unique_checks(self, unique_checks):
+ bad_fields = set()
+ form_errors = []
+
+ for unique_check in unique_checks:
+ # Try to look up an existing object with the same values as this
+ # object's values for all the unique field.
+
+ lookup_kwargs = {}
+ for field_name in unique_check:
+ lookup_value = self.cleaned_data[field_name]
+ # ModelChoiceField will return an object instance rather than
+ # a raw primary key value, so convert it to a pk value before
+ # using it in a lookup.
+ if isinstance(self.fields[field_name], ModelChoiceField):
+ lookup_value = lookup_value.pk
+ lookup_kwargs[str(field_name)] = lookup_value
+
+ qs = self.instance.__class__._default_manager.filter(**lookup_kwargs)
+
+ # Exclude the current object from the query if we are editing an
+ # instance (as opposed to creating a new one)
+ if self.instance.pk is not None:
+ qs = qs.exclude(pk=self.instance.pk)
+
+ if qs.exists():
+ if len(unique_check) == 1:
+ self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)])
+ else:
+ form_errors.append(self.unique_error_message(unique_check))
+
+ # Mark these fields as needing to be removed from cleaned data
+ # later.
+ for field_name in unique_check:
+ bad_fields.add(field_name)
+ return bad_fields, form_errors
+
+ def _perform_date_checks(self, date_checks):
+ bad_fields = set()
+ for lookup_type, field, unique_for in date_checks:
+ lookup_kwargs = {}
+ # there's a ticket to add a date lookup, we can remove this special
+ # case if that makes it's way in
+ if lookup_type == 'date':
+ date = self.cleaned_data[unique_for]
+ lookup_kwargs['%s__day' % unique_for] = date.day
+ lookup_kwargs['%s__month' % unique_for] = date.month
+ lookup_kwargs['%s__year' % unique_for] = date.year
+ else:
+ lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(self.cleaned_data[unique_for], lookup_type)
+ lookup_kwargs[field] = self.cleaned_data[field]
+
+ qs = self.instance.__class__._default_manager.filter(**lookup_kwargs)
+ # Exclude the current object from the query if we are editing an
+ # instance (as opposed to creating a new one)
+ if self.instance.pk is not None:
+ qs = qs.exclude(pk=self.instance.pk)
+
+ if qs.exists():
+ self._errors[field] = ErrorList([
+ self.date_error_message(lookup_type, field, unique_for)
+ ])
+ bad_fields.add(field)
+ return bad_fields, []
+
+ def date_error_message(self, lookup_type, field, unique_for):
+ return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % {
+ 'field_name': unicode(self.fields[field].label),
+ 'date_field': unicode(self.fields[unique_for].label),
+ 'lookup': lookup_type,
+ }
+
+ def unique_error_message(self, unique_check):
+ model_name = capfirst(self.instance._meta.verbose_name)
+
+ # A unique field
+ if len(unique_check) == 1:
+ field_name = unique_check[0]
+ field_label = self.fields[field_name].label
+ # Insert the error into the error dict, very sneaky
+ return _(u"%(model_name)s with this %(field_label)s already exists.") % {
+ 'model_name': unicode(model_name),
+ 'field_label': unicode(field_label)
+ }
+ # unique_together
+ else:
+ field_labels = [self.fields[field_name].label for field_name in unique_check]
+ field_labels = get_text_list(field_labels, _('and'))
+ return _(u"%(model_name)s with this %(field_label)s already exists.") % {
+ 'model_name': unicode(model_name),
+ 'field_label': unicode(field_labels)
+ }
def save(self, commit=True):
@@ -578,5 +735,5 @@
from django.db.models.fields.related import RelatedObject
if instance is None:
- self.instance = self.model()
+ self.instance = self.fk.rel.to()
else:
self.instance = instance
Index: /django/branches/soc2009/model-validation/django/core/mail/__init__.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/__init__.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/__init__.py (revision 11725)
@@ -0,0 +1,110 @@
+"""
+Tools for sending email.
+"""
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
+
+# Imported for backwards compatibility, and for the sake
+# of a cleaner namespace. These symbols used to be in
+# django/core/mail.py before the introduction of email
+# backends and the subsequent reorganization (See #10355)
+from django.core.mail.utils import CachedDnsName, DNS_NAME
+from django.core.mail.message import \
+ EmailMessage, EmailMultiAlternatives, \
+ SafeMIMEText, SafeMIMEMultipart, \
+ DEFAULT_ATTACHMENT_MIME_TYPE, make_msgid, \
+ BadHeaderError, forbid_multi_line_headers
+from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection
+
+def get_connection(backend=None, fail_silently=False, **kwds):
+ """Load an e-mail backend and return an instance of it.
+
+ If backend is None (default) settings.EMAIL_BACKEND is used.
+
+ Both fail_silently and other keyword arguments are used in the
+ constructor of the backend.
+ """
+ path = backend or settings.EMAIL_BACKEND
+ try:
+ mod = import_module(path)
+ except ImportError, e:
+ raise ImproperlyConfigured(('Error importing email backend %s: "%s"'
+ % (path, e)))
+ try:
+ cls = getattr(mod, 'EmailBackend')
+ except AttributeError:
+ raise ImproperlyConfigured(('Module "%s" does not define a '
+ '"EmailBackend" class' % path))
+ return cls(fail_silently=fail_silently, **kwds)
+
+
+def send_mail(subject, message, from_email, recipient_list,
+ fail_silently=False, auth_user=None, auth_password=None,
+ connection=None):
+ """
+ Easy wrapper for sending a single message to a recipient list. All members
+ of the recipient list will see the other recipients in the 'To' field.
+
+ If auth_user is None, the EMAIL_HOST_USER setting is used.
+ If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+
+ Note: The API for this method is frozen. New code wanting to extend the
+ functionality should use the EmailMessage class directly.
+ """
+ connection = connection or get_connection(username=auth_user,
+ password=auth_password,
+ fail_silently=fail_silently)
+ return EmailMessage(subject, message, from_email, recipient_list,
+ connection=connection).send()
+
+
+def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
+ auth_password=None, connection=None):
+ """
+ Given a datatuple of (subject, message, from_email, recipient_list), sends
+ each message to each recipient list. Returns the number of e-mails sent.
+
+ If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
+ If auth_user and auth_password are set, they're used to log in.
+ If auth_user is None, the EMAIL_HOST_USER setting is used.
+ If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+
+ Note: The API for this method is frozen. New code wanting to extend the
+ functionality should use the EmailMessage class directly.
+ """
+ connection = connection or get_connection(username=auth_user,
+ password=auth_password,
+ fail_silently=fail_silently)
+ messages = [EmailMessage(subject, message, sender, recipient)
+ for subject, message, sender, recipient in datatuple]
+ return connection.send_messages(messages)
+
+
+def mail_admins(subject, message, fail_silently=False, connection=None):
+ """Sends a message to the admins, as defined by the ADMINS setting."""
+ if not settings.ADMINS:
+ return
+ EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+ settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS],
+ connection=connection).send(fail_silently=fail_silently)
+
+
+def mail_managers(subject, message, fail_silently=False, connection=None):
+ """Sends a message to the managers, as defined by the MANAGERS setting."""
+ if not settings.MANAGERS:
+ return
+ EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+ settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS],
+ connection=connection).send(fail_silently=fail_silently)
+
+
+class SMTPConnection(_SMTPConnection):
+ def __init__(self, *args, **kwds):
+ import warnings
+ warnings.warn(
+ 'mail.SMTPConnection is deprecated; use mail.get_connection() instead.',
+ DeprecationWarning
+ )
+ super(SMTPConnection, self).__init__(*args, **kwds)
Index: /django/branches/soc2009/model-validation/django/core/mail/utils.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/utils.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/utils.py (revision 11725)
@@ -0,0 +1,19 @@
+"""
+Email message and email sending related helper functions.
+"""
+
+import socket
+
+
+# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
+# seconds, which slows down the restart of the server.
+class CachedDnsName(object):
+ def __str__(self):
+ return self.get_fqdn()
+
+ def get_fqdn(self):
+ if not hasattr(self, '_fqdn'):
+ self._fqdn = socket.getfqdn()
+ return self._fqdn
+
+DNS_NAME = CachedDnsName()
Index: /django/branches/soc2009/model-validation/django/core/mail/backends/base.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/backends/base.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/backends/base.py (revision 11725)
@@ -0,0 +1,39 @@
+"""Base email backend class."""
+
+class BaseEmailBackend(object):
+ """
+ Base class for email backend implementations.
+
+ Subclasses must at least overwrite send_messages().
+ """
+ def __init__(self, fail_silently=False, **kwargs):
+ self.fail_silently = fail_silently
+
+ def open(self):
+ """Open a network connection.
+
+ This method can be overwritten by backend implementations to
+ open a network connection.
+
+ It's up to the backend implementation to track the status of
+ a network connection if it's needed by the backend.
+
+ This method can be called by applications to force a single
+ network connection to be used when sending mails. See the
+ send_messages() method of the SMTP backend for a reference
+ implementation.
+
+ The default implementation does nothing.
+ """
+ pass
+
+ def close(self):
+ """Close a network connection."""
+ pass
+
+ def send_messages(self, email_messages):
+ """
+ Sends one or more EmailMessage objects and returns the number of email
+ messages sent.
+ """
+ raise NotImplementedError
Index: /django/branches/soc2009/model-validation/django/core/mail/backends/dummy.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/backends/dummy.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/backends/dummy.py (revision 11725)
@@ -0,0 +1,9 @@
+"""
+Dummy email backend that does nothing.
+"""
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+class EmailBackend(BaseEmailBackend):
+ def send_messages(self, email_messages):
+ return len(email_messages)
Index: /django/branches/soc2009/model-validation/django/core/mail/backends/locmem.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/backends/locmem.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/backends/locmem.py (revision 11725)
@@ -0,0 +1,24 @@
+"""
+Backend for test environment.
+"""
+
+from django.core import mail
+from django.core.mail.backends.base import BaseEmailBackend
+
+class EmailBackend(BaseEmailBackend):
+ """A email backend for use during test sessions.
+
+ The test connection stores email messages in a dummy outbox,
+ rather than sending them out on the wire.
+
+ The dummy outbox is accessible through the outbox instance attribute.
+ """
+ def __init__(self, *args, **kwargs):
+ super(EmailBackend, self).__init__(*args, **kwargs)
+ if not hasattr(mail, 'outbox'):
+ mail.outbox = []
+
+ def send_messages(self, messages):
+ """Redirect messages to the dummy outbox"""
+ mail.outbox.extend(messages)
+ return len(messages)
Index: /django/branches/soc2009/model-validation/django/core/mail/backends/filebased.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/backends/filebased.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/backends/filebased.py (revision 11725)
@@ -0,0 +1,59 @@
+"""Email backend that writes messages to a file."""
+
+import datetime
+import os
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.mail.backends.console import EmailBackend as ConsoleEmailBackend
+
+class EmailBackend(ConsoleEmailBackend):
+ def __init__(self, *args, **kwargs):
+ self._fname = None
+ if 'file_path' in kwargs:
+ self.file_path = kwargs.pop('file_path')
+ else:
+ self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None)
+ # Make sure self.file_path is a string.
+ if not isinstance(self.file_path, basestring):
+ raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path)
+ self.file_path = os.path.abspath(self.file_path)
+ # Make sure that self.file_path is an directory if it exists.
+ if os.path.exists(self.file_path) and not os.path.isdir(self.file_path):
+ raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.file_path)
+ # Try to create it, if it not exists.
+ elif not os.path.exists(self.file_path):
+ try:
+ os.makedirs(self.file_path)
+ except OSError, err:
+ raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.file_path, err))
+ # Make sure that self.file_path is writable.
+ if not os.access(self.file_path, os.W_OK):
+ raise ImproperlyConfigured('Could not write to directory: %s' % self.file_path)
+ # Finally, call super().
+ # Since we're using the console-based backend as a base,
+ # force the stream to be None, so we don't default to stdout
+ kwargs['stream'] = None
+ super(EmailBackend, self).__init__(*args, **kwargs)
+
+ def _get_filename(self):
+ """Return a unique file name."""
+ if self._fname is None:
+ timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+ fname = "%s-%s.log" % (timestamp, abs(id(self)))
+ self._fname = os.path.join(self.file_path, fname)
+ return self._fname
+
+ def open(self):
+ if self.stream is None:
+ self.stream = open(self._get_filename(), 'a')
+ return True
+ return False
+
+ def close(self):
+ try:
+ if self.stream is not None:
+ self.stream.close()
+ finally:
+ self.stream = None
+
Index: /django/branches/soc2009/model-validation/django/core/mail/backends/__init__.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/backends/__init__.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/backends/__init__.py (revision 11725)
@@ -0,0 +1,1 @@
+# Mail backends shipped with Django.
Index: /django/branches/soc2009/model-validation/django/core/mail/backends/console.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/backends/console.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/backends/console.py (revision 11725)
@@ -0,0 +1,37 @@
+"""
+Email backend that writes messages to console instead of sending them.
+"""
+import sys
+import threading
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+class EmailBackend(BaseEmailBackend):
+ def __init__(self, *args, **kwargs):
+ self.stream = kwargs.pop('stream', sys.stdout)
+ self._lock = threading.RLock()
+ super(EmailBackend, self).__init__(*args, **kwargs)
+
+ def send_messages(self, email_messages):
+ """Write all messages to the stream in a thread-safe way."""
+ if not email_messages:
+ return
+ self._lock.acquire()
+ try:
+ # The try-except is nested to allow for
+ # Python 2.4 support (Refs #12147)
+ try:
+ stream_created = self.open()
+ for message in email_messages:
+ self.stream.write('%s\n' % message.message().as_string())
+ self.stream.write('-'*79)
+ self.stream.write('\n')
+ self.stream.flush() # flush after each message
+ if stream_created:
+ self.close()
+ except:
+ if not self.fail_silently:
+ raise
+ finally:
+ self._lock.release()
+ return len(email_messages)
Index: /django/branches/soc2009/model-validation/django/core/mail/backends/smtp.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/backends/smtp.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/backends/smtp.py (revision 11725)
@@ -0,0 +1,106 @@
+"""SMTP email backend class."""
+
+import smtplib
+import socket
+import threading
+
+from django.conf import settings
+from django.core.mail.backends.base import BaseEmailBackend
+from django.core.mail.utils import DNS_NAME
+
+class EmailBackend(BaseEmailBackend):
+ """
+ A wrapper that manages the SMTP network connection.
+ """
+ def __init__(self, host=None, port=None, username=None, password=None,
+ use_tls=None, fail_silently=False, **kwargs):
+ super(EmailBackend, self).__init__(fail_silently=fail_silently)
+ self.host = host or settings.EMAIL_HOST
+ self.port = port or settings.EMAIL_PORT
+ self.username = username or settings.EMAIL_HOST_USER
+ self.password = password or settings.EMAIL_HOST_PASSWORD
+ if use_tls is None:
+ self.use_tls = settings.EMAIL_USE_TLS
+ else:
+ self.use_tls = use_tls
+ self.connection = None
+ self._lock = threading.RLock()
+
+ def open(self):
+ """
+ Ensures we have a connection to the email server. Returns whether or
+ not a new connection was required (True or False).
+ """
+ if self.connection:
+ # Nothing to do if the connection is already open.
+ return False
+ try:
+ # If local_hostname is not specified, socket.getfqdn() gets used.
+ # For performance, we use the cached FQDN for local_hostname.
+ self.connection = smtplib.SMTP(self.host, self.port,
+ local_hostname=DNS_NAME.get_fqdn())
+ if self.use_tls:
+ self.connection.ehlo()
+ self.connection.starttls()
+ self.connection.ehlo()
+ if self.username and self.password:
+ self.connection.login(self.username, self.password)
+ return True
+ except:
+ if not self.fail_silently:
+ raise
+
+ def close(self):
+ """Closes the connection to the email server."""
+ try:
+ try:
+ self.connection.quit()
+ except socket.sslerror:
+ # This happens when calling quit() on a TLS connection
+ # sometimes.
+ self.connection.close()
+ except:
+ if self.fail_silently:
+ return
+ raise
+ finally:
+ self.connection = None
+
+ def send_messages(self, email_messages):
+ """
+ Sends one or more EmailMessage objects and returns the number of email
+ messages sent.
+ """
+ if not email_messages:
+ return
+ self._lock.acquire()
+ try:
+ new_conn_created = self.open()
+ if not self.connection:
+ # We failed silently on open().
+ # Trying to send would be pointless.
+ return
+ num_sent = 0
+ for message in email_messages:
+ sent = self._send(message)
+ if sent:
+ num_sent += 1
+ if new_conn_created:
+ self.close()
+ finally:
+ self._lock.release()
+ return num_sent
+
+ def _send(self, email_message):
+ """A helper method that does the actual sending."""
+ if not email_message.recipients():
+ return False
+ try:
+ self.connection.sendmail(email_message.from_email,
+ email_message.recipients(),
+ email_message.message().as_string())
+ except:
+ if not self.fail_silently:
+ raise
+ return False
+ return True
Index: /django/branches/soc2009/model-validation/django/core/mail/message.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail/message.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/core/mail/message.py (revision 11725)
@@ -0,0 +1,273 @@
+import mimetypes
+import os
+import random
+import time
+from email import Charset, Encoders
+from email.MIMEText import MIMEText
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from email.Header import Header
+from email.Utils import formatdate, getaddresses, formataddr
+
+from django.conf import settings
+from django.core.mail.utils import DNS_NAME
+from django.utils.encoding import smart_str, force_unicode
+
+# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
+# some spam filters.
+Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
+
+# Default MIME type to use on attachments (if it is not explicitly given
+# and cannot be guessed).
+DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
+
+
+class BadHeaderError(ValueError):
+ pass
+
+
+# Copied from Python standard library, with the following modifications:
+# * Used cached hostname for performance.
+# * Added try/except to support lack of getpid() in Jython (#5496).
+def make_msgid(idstring=None):
+ """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
+
+ <20020201195627.33539.96671@nightshade.la.mastaler.com>
+
+ Optional idstring if given is a string used to strengthen the
+ uniqueness of the message id.
+ """
+ timeval = time.time()
+ utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
+ try:
+ pid = os.getpid()
+ except AttributeError:
+ # No getpid() in Jython, for example.
+ pid = 1
+ randint = random.randrange(100000)
+ if idstring is None:
+ idstring = ''
+ else:
+ idstring = '.' + idstring
+ idhost = DNS_NAME
+ msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
+ return msgid
+
+
+def forbid_multi_line_headers(name, val):
+ """Forbids multi-line headers, to prevent header injection."""
+ val = force_unicode(val)
+ if '\n' in val or '\r' in val:
+ raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
+ try:
+ val = val.encode('ascii')
+ except UnicodeEncodeError:
+ if name.lower() in ('to', 'from', 'cc'):
+ result = []
+ for nm, addr in getaddresses((val,)):
+ nm = str(Header(nm, settings.DEFAULT_CHARSET))
+ result.append(formataddr((nm, str(addr))))
+ val = ', '.join(result)
+ else:
+ val = Header(val, settings.DEFAULT_CHARSET)
+ else:
+ if name.lower() == 'subject':
+ val = Header(val)
+ return name, val
+
+
+class SafeMIMEText(MIMEText):
+ def __setitem__(self, name, val):
+ name, val = forbid_multi_line_headers(name, val)
+ MIMEText.__setitem__(self, name, val)
+
+
+class SafeMIMEMultipart(MIMEMultipart):
+ def __setitem__(self, name, val):
+ name, val = forbid_multi_line_headers(name, val)
+ MIMEMultipart.__setitem__(self, name, val)
+
+
+class EmailMessage(object):
+ """
+ A container for email information.
+ """
+ content_subtype = 'plain'
+ mixed_subtype = 'mixed'
+ encoding = None # None => use settings default
+
+ def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+ connection=None, attachments=None, headers=None):
+ """
+ Initialize a single email message (which can be sent to multiple
+ recipients).
+
+ All strings used to create the message can be unicode strings
+ (or UTF-8 bytestrings). The SafeMIMEText class will handle any
+ necessary encoding conversions.
+ """
+ if to:
+ assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
+ self.to = list(to)
+ else:
+ self.to = []
+ if bcc:
+ assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
+ self.bcc = list(bcc)
+ else:
+ self.bcc = []
+ self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
+ self.subject = subject
+ self.body = body
+ self.attachments = attachments or []
+ self.extra_headers = headers or {}
+ self.connection = connection
+
+ def get_connection(self, fail_silently=False):
+ from django.core.mail import get_connection
+ if not self.connection:
+ self.connection = get_connection(fail_silently=fail_silently)
+ return self.connection
+
+ def message(self):
+ encoding = self.encoding or settings.DEFAULT_CHARSET
+ msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
+ self.content_subtype, encoding)
+ msg = self._create_message(msg)
+ msg['Subject'] = self.subject
+ msg['From'] = self.extra_headers.pop('From', self.from_email)
+ msg['To'] = ', '.join(self.to)
+
+ # Email header names are case-insensitive (RFC 2045), so we have to
+ # accommodate that when doing comparisons.
+ header_names = [key.lower() for key in self.extra_headers]
+ if 'date' not in header_names:
+ msg['Date'] = formatdate()
+ if 'message-id' not in header_names:
+ msg['Message-ID'] = make_msgid()
+ for name, value in self.extra_headers.items():
+ msg[name] = value
+ return msg
+
+ def recipients(self):
+ """
+ Returns a list of all recipients of the email (includes direct
+ addressees as well as Bcc entries).
+ """
+ return self.to + self.bcc
+
+ def send(self, fail_silently=False):
+ """Sends the email message."""
+ if not self.recipients():
+ # Don't bother creating the network connection if there's nobody to
+ # send to.
+ return 0
+ return self.get_connection(fail_silently).send_messages([self])
+
+ def attach(self, filename=None, content=None, mimetype=None):
+ """
+ Attaches a file with the given filename and content. The filename can
+ be omitted and the mimetype is guessed, if not provided.
+
+ If the first parameter is a MIMEBase subclass it is inserted directly
+ into the resulting message attachments.
+ """
+ if isinstance(filename, MIMEBase):
+ assert content == mimetype == None
+ self.attachments.append(filename)
+ else:
+ assert content is not None
+ self.attachments.append((filename, content, mimetype))
+
+ def attach_file(self, path, mimetype=None):
+ """Attaches a file from the filesystem."""
+ filename = os.path.basename(path)
+ content = open(path, 'rb').read()
+ self.attach(filename, content, mimetype)
+
+ def _create_message(self, msg):
+ return self._create_attachments(msg)
+
+ def _create_attachments(self, msg):
+ if self.attachments:
+ body_msg = msg
+ msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
+ if self.body:
+ msg.attach(body_msg)
+ for attachment in self.attachments:
+ if isinstance(attachment, MIMEBase):
+ msg.attach(attachment)
+ else:
+ msg.attach(self._create_attachment(*attachment))
+ return msg
+
+ def _create_mime_attachment(self, content, mimetype):
+ """
+ Converts the content, mimetype pair into a MIME attachment object.
+ """
+ basetype, subtype = mimetype.split('/', 1)
+ if basetype == 'text':
+ attachment = SafeMIMEText(smart_str(content,
+ settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
+ else:
+ # Encode non-text attachments with base64.
+ attachment = MIMEBase(basetype, subtype)
+ attachment.set_payload(content)
+ Encoders.encode_base64(attachment)
+ return attachment
+
+ def _create_attachment(self, filename, content, mimetype=None):
+ """
+ Converts the filename, content, mimetype triple into a MIME attachment
+ object.
+ """
+ if mimetype is None:
+ mimetype, _ = mimetypes.guess_type(filename)
+ if mimetype is None:
+ mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
+ attachment = self._create_mime_attachment(content, mimetype)
+ if filename:
+ attachment.add_header('Content-Disposition', 'attachment',
+ filename=filename)
+ return attachment
+
+
+class EmailMultiAlternatives(EmailMessage):
+ """
+ A version of EmailMessage that makes it easy to send multipart/alternative
+ messages. For example, including text and HTML versions of the text is
+ made easier.
+ """
+ alternative_subtype = 'alternative'
+
+ def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+ connection=None, attachments=None, headers=None, alternatives=None):
+ """
+ Initialize a single email message (which can be sent to multiple
+ recipients).
+
+ All strings used to create the message can be unicode strings (or UTF-8
+ bytestrings). The SafeMIMEText class will handle any necessary encoding
+ conversions.
+ """
+ super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
+ self.alternatives=alternatives or []
+
+ def attach_alternative(self, content, mimetype):
+ """Attach an alternative content representation."""
+ assert content is not None
+ assert mimetype is not None
+ self.alternatives.append((content, mimetype))
+
+ def _create_message(self, msg):
+ return self._create_attachments(self._create_alternatives(msg))
+
+ def _create_alternatives(self, msg):
+ if self.alternatives:
+ body_msg = msg
+ msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
+ if self.body:
+ msg.attach(body_msg)
+ for alternative in self.alternatives:
+ msg.attach(self._create_mime_attachment(*alternative))
+ return msg
Index: /django/branches/soc2009/model-validation/django/core/serializers/xml_serializer.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/serializers/xml_serializer.py (revision 10554)
+++ /django/branches/soc2009/model-validation/django/core/serializers/xml_serializer.py (revision 11725)
@@ -99,5 +99,5 @@
is not dumped, just the relation).
"""
- if field.creates_table:
+ if field.rel.through._meta.auto_created:
self._start_relational_field(field)
for relobj in getattr(obj, field.name).iterator():
@@ -234,3 +234,2 @@
pass
return u"".join(inner_text)
-
Index: /django/branches/soc2009/model-validation/django/core/serializers/python.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/serializers/python.py (revision 10554)
+++ /django/branches/soc2009/model-validation/django/core/serializers/python.py (revision 11725)
@@ -57,5 +57,5 @@
def handle_m2m_field(self, obj, field):
- if field.creates_table:
+ if field.rel.through._meta.auto_created:
self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
for related in getattr(obj, field.name).iterator()]
Index: /django/branches/soc2009/model-validation/django/core/context_processors.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/context_processors.py (revision 8263)
+++ /django/branches/soc2009/model-validation/django/core/context_processors.py (revision 11725)
@@ -9,4 +9,6 @@
from django.conf import settings
+from django.middleware.csrf import get_token
+from django.utils.functional import lazy, memoize, SimpleLazyObject
def auth(request):
@@ -18,14 +20,43 @@
django.contrib.auth).
"""
- if hasattr(request, 'user'):
- user = request.user
- else:
- from django.contrib.auth.models import AnonymousUser
- user = AnonymousUser()
+ # If we access request.user, request.session is accessed, which results in
+ # 'Vary: Cookie' being sent in every request that uses this context
+ # processor, which can easily be every request on a site if
+ # TEMPLATE_CONTEXT_PROCESSORS has this context processor added. This kills
+ # the ability to cache. So, we carefully ensure these attributes are lazy.
+ # We don't use django.utils.functional.lazy() for User, because that
+ # requires knowing the class of the object we want to proxy, which could
+ # break with custom auth backends. LazyObject is a less complete but more
+ # flexible solution that is a good enough wrapper for 'User'.
+ def get_user():
+ if hasattr(request, 'user'):
+ return request.user
+ else:
+ from django.contrib.auth.models import AnonymousUser
+ return AnonymousUser()
+
return {
- 'user': user,
- 'messages': user.get_and_delete_messages(),
- 'perms': PermWrapper(user),
+ 'user': SimpleLazyObject(get_user),
+ 'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
+ 'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
}
+
+def csrf(request):
+ """
+ Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if
+ it has not been provided by either a view decorator or the middleware
+ """
+ def _get_val():
+ token = get_token(request)
+ if token is None:
+ # In order to be able to provide debugging info in the
+ # case of misconfiguration, we use a sentinel value
+ # instead of returning an empty dict.
+ return 'NOTPROVIDED'
+ else:
+ return token
+ _get_val = lazy(_get_val, str)
+
+ return {'csrf_token': _get_val() }
def debug(request):
@@ -80,5 +111,5 @@
def __getitem__(self, module_name):
return PermLookupDict(self.user, module_name)
-
+
def __iter__(self):
# I am large, I contain multitudes.
Index: /django/branches/soc2009/model-validation/django/core/files/storage.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/files/storage.py (revision 10701)
+++ /django/branches/soc2009/model-validation/django/core/files/storage.py (revision 11725)
@@ -118,8 +118,4 @@
"""
raise NotImplementedError()
-
- # Needed by django.utils.functional.LazyObject (via DefaultStorage).
- def get_all_members(self):
- return self.__members__
class FileSystemStorage(Storage):
Index: /django/branches/soc2009/model-validation/django/core/management/commands/syncdb.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/management/commands/syncdb.py (revision 10088)
+++ /django/branches/soc2009/model-validation/django/core/management/commands/syncdb.py (revision 11725)
@@ -58,10 +58,13 @@
for app in models.get_apps():
app_name = app.__name__.split('.')[-2]
- model_list = models.get_models(app)
+ model_list = models.get_models(app, include_auto_created=True)
for model in model_list:
# Create the model's database table, if it doesn't already exist.
if verbosity >= 2:
print "Processing %s.%s model" % (app_name, model._meta.object_name)
- if connection.introspection.table_name_converter(model._meta.db_table) in tables:
+ opts = model._meta
+ if (connection.introspection.table_name_converter(opts.db_table) in tables or
+ (opts.auto_created and
+ connection.introspection.table_name_converter(opts.auto_created._meta.db_table in tables))):
continue
sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
@@ -79,17 +82,4 @@
tables.append(connection.introspection.table_name_converter(model._meta.db_table))
- # Create the m2m tables. This must be done after all tables have been created
- # to ensure that all referred tables will exist.
- for app in models.get_apps():
- app_name = app.__name__.split('.')[-2]
- model_list = models.get_models(app)
- for model in model_list:
- if model in created_models:
- sql = connection.creation.sql_for_many_to_many(model, self.style)
- if sql:
- if verbosity >= 2:
- print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name)
- for statement in sql:
- cursor.execute(statement)
transaction.commit_unless_managed()
Index: /django/branches/soc2009/model-validation/django/core/management/validation.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/management/validation.py (revision 10456)
+++ /django/branches/soc2009/model-validation/django/core/management/validation.py (revision 11725)
@@ -80,25 +80,26 @@
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
rel_query_name = f.related_query_name()
- for r in rel_opts.fields:
- if r.name == rel_name:
- e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
- if r.name == rel_query_name:
- e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
- for r in rel_opts.local_many_to_many:
- if r.name == rel_name:
- e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
- if r.name == rel_query_name:
- e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
- for r in rel_opts.get_all_related_many_to_many_objects():
- if r.get_accessor_name() == rel_name:
- e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
- if r.get_accessor_name() == rel_query_name:
- e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
- for r in rel_opts.get_all_related_objects():
- if r.field is not f:
+ if not f.rel.is_hidden():
+ for r in rel_opts.fields:
+ if r.name == rel_name:
+ e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ if r.name == rel_query_name:
+ e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ for r in rel_opts.local_many_to_many:
+ if r.name == rel_name:
+ e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ if r.name == rel_query_name:
+ e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+ for r in rel_opts.get_all_related_many_to_many_objects():
if r.get_accessor_name() == rel_name:
- e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
if r.get_accessor_name() == rel_query_name:
- e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ for r in rel_opts.get_all_related_objects():
+ if r.field is not f:
+ if r.get_accessor_name() == rel_name:
+ e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ if r.get_accessor_name() == rel_query_name:
+ e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
seen_intermediary_signatures = []
@@ -118,46 +119,78 @@
e.add(opts, "ManyToManyFields cannot be unique. Remove the unique argument on '%s'." % f.name)
- if getattr(f.rel, 'through', None) is not None:
- if hasattr(f.rel, 'through_model'):
- from_model, to_model = cls, f.rel.to
- if from_model == to_model and f.rel.symmetrical:
- e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
- seen_from, seen_to, seen_self = False, False, 0
- for inter_field in f.rel.through_model._meta.fields:
- rel_to = getattr(inter_field.rel, 'to', None)
- if from_model == to_model: # relation to self
- if rel_to == from_model:
- seen_self += 1
- if seen_self > 2:
- e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
- else:
- if rel_to == from_model:
- if seen_from:
- e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
- else:
- seen_from = True
- elif rel_to == to_model:
- if seen_to:
- e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name))
- else:
- seen_to = True
- if f.rel.through_model not in models.get_models():
- e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
- signature = (f.rel.to, cls, f.rel.through_model)
- if signature in seen_intermediary_signatures:
- e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name))
+ if f.rel.through is not None and not isinstance(f.rel.through, basestring):
+ from_model, to_model = cls, f.rel.to
+ if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
+ e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
+ seen_from, seen_to, seen_self = False, False, 0
+ for inter_field in f.rel.through._meta.fields:
+ rel_to = getattr(inter_field.rel, 'to', None)
+ if from_model == to_model: # relation to self
+ if rel_to == from_model:
+ seen_self += 1
+ if seen_self > 2:
+ e.add(opts, "Intermediary model %s has more than "
+ "two foreign keys to %s, which is ambiguous "
+ "and is not permitted." % (
+ f.rel.through._meta.object_name,
+ from_model._meta.object_name
+ )
+ )
else:
- seen_intermediary_signatures.append(signature)
- seen_related_fk, seen_this_fk = False, False
- for field in f.rel.through_model._meta.fields:
- if field.rel:
- if not seen_related_fk and field.rel.to == f.rel.to:
- seen_related_fk = True
- elif field.rel.to == cls:
- seen_this_fk = True
- if not seen_related_fk or not seen_this_fk:
- e.add(opts, "'%s' has a manually-defined m2m relation through model %s, which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name))
+ if rel_to == from_model:
+ if seen_from:
+ e.add(opts, "Intermediary model %s has more "
+ "than one foreign key to %s, which is "
+ "ambiguous and is not permitted." % (
+ f.rel.through._meta.object_name,
+ from_model._meta.object_name
+ )
+ )
+ else:
+ seen_from = True
+ elif rel_to == to_model:
+ if seen_to:
+ e.add(opts, "Intermediary model %s has more "
+ "than one foreign key to %s, which is "
+ "ambiguous and is not permitted." % (
+ f.rel.through._meta.object_name,
+ rel_to._meta.object_name
+ )
+ )
+ else:
+ seen_to = True
+ if f.rel.through not in models.get_models(include_auto_created=True):
+ e.add(opts, "'%s' specifies an m2m relation through model "
+ "%s, which has not been installed." % (f.name, f.rel.through)
+ )
+ signature = (f.rel.to, cls, f.rel.through)
+ if signature in seen_intermediary_signatures:
+ e.add(opts, "The model %s has two manually-defined m2m "
+ "relations through the model %s, which is not "
+ "permitted. Please consider using an extra field on "
+ "your intermediary model instead." % (
+ cls._meta.object_name,
+ f.rel.through._meta.object_name
+ )
+ )
else:
- e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
+ seen_intermediary_signatures.append(signature)
+ seen_related_fk, seen_this_fk = False, False
+ for field in f.rel.through._meta.fields:
+ if field.rel:
+ if not seen_related_fk and field.rel.to == f.rel.to:
+ seen_related_fk = True
+ elif field.rel.to == cls:
+ seen_this_fk = True
+ if not seen_related_fk or not seen_this_fk:
+ e.add(opts, "'%s' has a manually-defined m2m relation "
+ "through model %s, which does not have foreign keys "
+ "to %s and %s" % (f.name, f.rel.through._meta.object_name,
+ f.rel.to._meta.object_name, cls._meta.object_name)
+ )
+ elif isinstance(f.rel.through, basestring):
+ e.add(opts, "'%s' specifies an m2m relation through model %s, "
+ "which has not been installed" % (f.name, f.rel.through)
+ )
rel_opts = f.rel.to._meta
Index: /django/branches/soc2009/model-validation/django/core/management/sql.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/management/sql.py (revision 9535)
+++ /django/branches/soc2009/model-validation/django/core/management/sql.py (revision 11725)
@@ -24,5 +24,5 @@
# generate invalid SQL (leaving models out of known_models is harmless, so
# we can be conservative).
- app_models = models.get_models(app)
+ app_models = models.get_models(app, include_auto_created=True)
final_output = []
tables = connection.introspection.table_names()
@@ -40,8 +40,4 @@
# Keep track of the fact that we've created the table for this model.
known_models.add(model)
-
- # Create the many-to-many join tables.
- for model in app_models:
- final_output.extend(connection.creation.sql_for_many_to_many(model, style))
# Handle references to tables that are from other apps
@@ -83,5 +79,5 @@
references_to_delete = {}
- app_models = models.get_models(app)
+ app_models = models.get_models(app, include_auto_created=True)
for model in app_models:
if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
@@ -97,11 +93,4 @@
if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
-
- # Output DROP TABLE statements for many-to-many tables.
- for model in app_models:
- opts = model._meta
- for f in opts.local_many_to_many:
- if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names:
- output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))
# Close database connection explicitly, in case this output is being piped
Index: /ango/branches/soc2009/model-validation/django/core/mail.py
===================================================================
--- /django/branches/soc2009/model-validation/django/core/mail.py (revision 11038)
+++ (revision )
@@ -1,426 +1,0 @@
-"""
-Tools for sending email.
-"""
-
-import mimetypes
-import os
-import smtplib
-import socket
-import time
-import random
-from email import Charset, Encoders
-from email.MIMEText import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from email.Header import Header
-from email.Utils import formatdate, parseaddr, formataddr
-
-from django.conf import settings
-from django.utils.encoding import smart_str, force_unicode
-
-# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
-# some spam filters.
-Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
-
-# Default MIME type to use on attachments (if it is not explicitly given
-# and cannot be guessed).
-DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
-
-# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
-# seconds, which slows down the restart of the server.
-class CachedDnsName(object):
- def __str__(self):
- return self.get_fqdn()
-
- def get_fqdn(self):
- if not hasattr(self, '_fqdn'):
- self._fqdn = socket.getfqdn()
- return self._fqdn
-
-DNS_NAME = CachedDnsName()
-
-# Copied from Python standard library, with the following modifications:
-# * Used cached hostname for performance.
-# * Added try/except to support lack of getpid() in Jython (#5496).
-def make_msgid(idstring=None):
- """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
-
- <20020201195627.33539.96671@nightshade.la.mastaler.com>
-
- Optional idstring if given is a string used to strengthen the
- uniqueness of the message id.
- """
- timeval = time.time()
- utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
- try:
- pid = os.getpid()
- except AttributeError:
- # No getpid() in Jython, for example.
- pid = 1
- randint = random.randrange(100000)
- if idstring is None:
- idstring = ''
- else:
- idstring = '.' + idstring
- idhost = DNS_NAME
- msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
- return msgid
-
-class BadHeaderError(ValueError):
- pass
-
-def forbid_multi_line_headers(name, val):
- """Forbids multi-line headers, to prevent header injection."""
- val = force_unicode(val)
- if '\n' in val or '\r' in val:
- raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
- try:
- val = val.encode('ascii')
- except UnicodeEncodeError:
- if name.lower() in ('to', 'from', 'cc'):
- result = []
- for item in val.split(', '):
- nm, addr = parseaddr(item)
- nm = str(Header(nm, settings.DEFAULT_CHARSET))
- result.append(formataddr((nm, str(addr))))
- val = ', '.join(result)
- else:
- val = Header(val, settings.DEFAULT_CHARSET)
- else:
- if name.lower() == 'subject':
- val = Header(val)
- return name, val
-
-class SafeMIMEText(MIMEText):
- def __setitem__(self, name, val):
- name, val = forbid_multi_line_headers(name, val)
- MIMEText.__setitem__(self, name, val)
-
-class SafeMIMEMultipart(MIMEMultipart):
- def __setitem__(self, name, val):
- name, val = forbid_multi_line_headers(name, val)
- MIMEMultipart.__setitem__(self, name, val)
-
-class SMTPConnection(object):
- """
- A wrapper that manages the SMTP network connection.
- """
-
- def __init__(self, host=None, port=None, username=None, password=None,
- use_tls=None, fail_silently=False):
- self.host = host or settings.EMAIL_HOST
- self.port = port or settings.EMAIL_PORT
- self.username = username or settings.EMAIL_HOST_USER
- self.password = password or settings.EMAIL_HOST_PASSWORD
- self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
- self.fail_silently = fail_silently
- self.connection = None
-
- def open(self):
- """
- Ensures we have a connection to the email server. Returns whether or
- not a new connection was required (True or False).
- """
- if self.connection:
- # Nothing to do if the connection is already open.
- return False
- try:
- # If local_hostname is not specified, socket.getfqdn() gets used.
- # For performance, we use the cached FQDN for local_hostname.
- self.connection = smtplib.SMTP(self.host, self.port,
- local_hostname=DNS_NAME.get_fqdn())
- if self.use_tls:
- self.connection.ehlo()
- self.connection.starttls()
- self.connection.ehlo()
- if self.username and self.password:
- self.connection.login(self.username, self.password)
- return True
- except:
- if not self.fail_silently:
- raise
-
- def close(self):
- """Closes the connection to the email server."""
- try:
- try:
- self.connection.quit()
- except socket.sslerror:
- # This happens when calling quit() on a TLS connection
- # sometimes.
- self.connection.close()
- except:
- if self.fail_silently:
- return
- raise
- finally:
- self.connection = None
-
- def send_messages(self, email_messages):
- """
- Sends one or more EmailMessage objects and returns the number of email
- messages sent.
- """
- if not email_messages:
- return
- new_conn_created = self.open()
- if not self.connection:
- # We failed silently on open(). Trying to send would be pointless.
- return
- num_sent = 0
- for message in email_messages:
- sent = self._send(message)
- if sent:
- num_sent += 1
- if new_conn_created:
- self.close()
- return num_sent
-
- def _send(self, email_message):
- """A helper method that does the actual sending."""
- if not email_message.recipients():
- return False
- try:
- self.connection.sendmail(email_message.from_email,
- email_message.recipients(),
- email_message.message().as_string())
- except:
- if not self.fail_silently:
- raise
- return False
- return True
-
-class EmailMessage(object):
- """
- A container for email information.
- """
- content_subtype = 'plain'
- mixed_subtype = 'mixed'
- encoding = None # None => use settings default
-
- def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
- connection=None, attachments=None, headers=None):
- """
- Initialize a single email message (which can be sent to multiple
- recipients).
-
- All strings used to create the message can be unicode strings (or UTF-8
- bytestrings). The SafeMIMEText class will handle any necessary encoding
- conversions.
- """
- if to:
- assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
- self.to = list(to)
- else:
- self.to = []
- if bcc:
- assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
- self.bcc = list(bcc)
- else:
- self.bcc = []
- self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
- self.subject = subject
- self.body = body
- self.attachments = attachments or []
- self.extra_headers = headers or {}
- self.connection = connection
-
- def get_connection(self, fail_silently=False):
- if not self.connection:
- self.connection = SMTPConnection(fail_silently=fail_silently)
- return self.connection
-
- def message(self):
- encoding = self.encoding or settings.DEFAULT_CHARSET
- msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
- self.content_subtype, encoding)
- msg = self._create_message(msg)
- msg['Subject'] = self.subject
- msg['From'] = self.extra_headers.pop('From', self.from_email)
- msg['To'] = ', '.join(self.to)
-
- # Email header names are case-insensitive (RFC 2045), so we have to
- # accommodate that when doing comparisons.
- header_names = [key.lower() for key in self.extra_headers]
- if 'date' not in header_names:
- msg['Date'] = formatdate()
- if 'message-id' not in header_names:
- msg['Message-ID'] = make_msgid()
- for name, value in self.extra_headers.items():
- msg[name] = value
- return msg
-
- def recipients(self):
- """
- Returns a list of all recipients of the email (includes direct
- addressees as well as Bcc entries).
- """
- return self.to + self.bcc
-
- def send(self, fail_silently=False):
- """Sends the email message."""
- if not self.recipients():
- # Don't bother creating the network connection if there's nobody to
- # send to.
- return 0
- return self.get_connection(fail_silently).send_messages([self])
-
- def attach(self, filename=None, content=None, mimetype=None):
- """
- Attaches a file with the given filename and content. The filename can
- be omitted and the mimetype is guessed, if not provided.
-
- If the first parameter is a MIMEBase subclass it is inserted directly
- into the resulting message attachments.
- """
- if isinstance(filename, MIMEBase):
- assert content == mimetype == None
- self.attachments.append(filename)
- else:
- assert content is not None
- self.attachments.append((filename, content, mimetype))
-
- def attach_file(self, path, mimetype=None):
- """Attaches a file from the filesystem."""
- filename = os.path.basename(path)
- content = open(path, 'rb').read()
- self.attach(filename, content, mimetype)
-
- def _create_message(self, msg):
- return self._create_attachments(msg)
-
- def _create_attachments(self, msg):
- if self.attachments:
- body_msg = msg
- msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
- if self.body:
- msg.attach(body_msg)
- for attachment in self.attachments:
- if isinstance(attachment, MIMEBase):
- msg.attach(attachment)
- else:
- msg.attach(self._create_attachment(*attachment))
- return msg
-
- def _create_mime_attachment(self, content, mimetype):
- """
- Converts the content, mimetype pair into a MIME attachment object.
- """
- basetype, subtype = mimetype.split('/', 1)
- if basetype == 'text':
- attachment = SafeMIMEText(smart_str(content,
- settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
- else:
- # Encode non-text attachments with base64.
- attachment = MIMEBase(basetype, subtype)
- attachment.set_payload(content)
- Encoders.encode_base64(attachment)
- return attachment
-
- def _create_attachment(self, filename, content, mimetype=None):
- """
- Converts the filename, content, mimetype triple into a MIME attachment
- object.
- """
- if mimetype is None:
- mimetype, _ = mimetypes.guess_type(filename)
- if mimetype is None:
- mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
- attachment = self._create_mime_attachment(content, mimetype)
- if filename:
- attachment.add_header('Content-Disposition', 'attachment',
- filename=filename)
- return attachment
-
-class EmailMultiAlternatives(EmailMessage):
- """
- A version of EmailMessage that makes it easy to send multipart/alternative
- messages. For example, including text and HTML versions of the text is
- made easier.
- """
- alternative_subtype = 'alternative'
-
- def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
- connection=None, attachments=None, headers=None, alternatives=None):
- """
- Initialize a single email message (which can be sent to multiple
- recipients).
-
- All strings used to create the message can be unicode strings (or UTF-8
- bytestrings). The SafeMIMEText class will handle any necessary encoding
- conversions.
- """
- super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
- self.alternatives=alternatives or []
-
- def attach_alternative(self, content, mimetype):
- """Attach an alternative content representation."""
- assert content is not None
- assert mimetype is not None
- self.alternatives.append((content, mimetype))
-
- def _create_message(self, msg):
- return self._create_attachments(self._create_alternatives(msg))
-
- def _create_alternatives(self, msg):
- if self.alternatives:
- body_msg = msg
- msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
- if self.body:
- msg.attach(body_msg)
- for alternative in self.alternatives:
- msg.attach(self._create_mime_attachment(*alternative))
- return msg
-
-def send_mail(subject, message, from_email, recipient_list,
- fail_silently=False, auth_user=None, auth_password=None):
- """
- Easy wrapper for sending a single message to a recipient list. All members
- of the recipient list will see the other recipients in the 'To' field.
-
- If auth_user is None, the EMAIL_HOST_USER setting is used.
- If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
-
- Note: The API for this method is frozen. New code wanting to extend the
- functionality should use the EmailMessage class directly.
- """
- connection = SMTPConnection(username=auth_user, password=auth_password,
- fail_silently=fail_silently)
- return EmailMessage(subject, message, from_email, recipient_list,
- connection=connection).send()
-
-def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
- auth_password=None):
- """
- Given a datatuple of (subject, message, from_email, recipient_list), sends
- each message to each recipient list. Returns the number of e-mails sent.
-
- If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
- If auth_user and auth_password are set, they're used to log in.
- If auth_user is None, the EMAIL_HOST_USER setting is used.
- If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
-
- Note: The API for this method is frozen. New code wanting to extend the
- functionality should use the EmailMessage class directly.
- """
- connection = SMTPConnection(username=auth_user, password=auth_password,
- fail_silently=fail_silently)
- messages = [EmailMessage(subject, message, sender, recipient)
- for subject, message, sender, recipient in datatuple]
- return connection.send_messages(messages)
-
-def mail_admins(subject, message, fail_silently=False):
- """Sends a message to the admins, as defined by the ADMINS setting."""
- if not settings.ADMINS:
- return
- EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
- settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
- ).send(fail_silently=fail_silently)
-
-def mail_managers(subject, message, fail_silently=False):
- """Sends a message to the managers, as defined by the MANAGERS setting."""
- if not settings.MANAGERS:
- return
- EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
- settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
- ).send(fail_silently=fail_silently)
Index: /django/branches/soc2009/model-validation/django/views/csrf.py
===================================================================
--- /django/branches/soc2009/model-validation/django/views/csrf.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/views/csrf.py (revision 11725)
@@ -0,0 +1,69 @@
+from django.http import HttpResponseForbidden
+from django.template import Context, Template
+from django.conf import settings
+
+# We include the template inline since we need to be able to reliably display
+# this error message, especially for the sake of developers, and there isn't any
+# other way of making it available independent of what is in the settings file.
+
+CSRF_FAILRE_TEMPLATE = """
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>403 Forbidden</title>
+</head>
+<body>
+ <h1>403 Forbidden</h1>
+ <p>CSRF verification failed. Request aborted.</p>
+ {% if DEBUG %}
+ <h2>Help</h2>
+ {% if reason %}
+ <p>Reason given for failure:</p>
+ <pre>
+ {{ reason }}
+ </pre>
+ {% endif %}
+
+ <p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
+ <a
+ href='http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ref-contrib-csrf'>Django's
+ CSRF mechanism</a> has not been used correctly. For POST forms, you need to
+ ensure:</p>
+
+ <ul>
+ <li>The view function uses <a
+ href='http://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext'><code>RequestContext</code></a>
+ for the template, instead of <code>Context</code>.</li>
+
+ <li>In the template, there is a <code>{% templatetag openblock %} csrf_token
+ {% templatetag closeblock %}</code> template tag inside each POST form that
+ targets an internal URL.</li>
+
+ <li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
+ <code>csrf_protect</code> on any views that use the <code>csrf_token</code>
+ template tag, as well as those that accept the POST data.</li>
+
+ </ul>
+
+ <p>You're seeing the help section of this page because you have <code>DEBUG =
+ True</code> in your Django settings file. Change that to <code>False</code>,
+ and only the initial error message will be displayed. </p>
+
+ <p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
+ {% else %}
+ <p><small>More information is available with DEBUG=True.</small></p>
+
+ {% endif %}
+</body>
+</html>
+"""
+
+def csrf_failure(request, reason=""):
+ """
+ Default view used when request fails CSRF protection
+ """
+ t = Template(CSRF_FAILRE_TEMPLATE)
+ c = Context({'DEBUG': settings.DEBUG,
+ 'reason': reason})
+ return HttpResponseForbidden(t.render(c), mimetype='text/html')
Index: /django/branches/soc2009/model-validation/django/views/decorators/csrf.py
===================================================================
--- /django/branches/soc2009/model-validation/django/views/decorators/csrf.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/views/decorators/csrf.py (revision 11725)
@@ -0,0 +1,47 @@
+from django.middleware.csrf import CsrfViewMiddleware
+from django.utils.decorators import decorator_from_middleware
+try:
+ from functools import wraps
+except ImportError:
+ from django.utils.functional import wraps # Python 2.3, 2.4 fallback.
+
+csrf_protect = decorator_from_middleware(CsrfViewMiddleware)
+csrf_protect.__name__ = "csrf_protect"
+csrf_protect.__doc__ = """
+This decorator adds CSRF protection in exactly the same way as
+CsrfViewMiddleware, but it can be used on a per view basis. Using both, or
+using the decorator multiple times, is harmless and efficient.
+"""
+
+def csrf_response_exempt(view_func):
+ """
+ Modifies a view function so that its response is exempt
+ from the post-processing of the CSRF middleware.
+ """
+ def wrapped_view(*args, **kwargs):
+ resp = view_func(*args, **kwargs)
+ resp.csrf_exempt = True
+ return resp
+ return wraps(view_func)(wrapped_view)
+
+def csrf_view_exempt(view_func):
+ """
+ Marks a view function as being exempt from CSRF view protection.
+ """
+ # We could just do view_func.csrf_exempt = True, but decorators
+ # are nicer if they don't have side-effects, so we return a new
+ # function.
+ def wrapped_view(*args, **kwargs):
+ return view_func(*args, **kwargs)
+ wrapped_view.csrf_exempt = True
+ return wraps(view_func)(wrapped_view)
+
+def csrf_exempt(view_func):
+ """
+ Marks a view function as being exempt from the CSRF checks
+ and post processing.
+
+ This is the same as using both the csrf_view_exempt and
+ csrf_response_exempt decorators.
+ """
+ return csrf_response_exempt(csrf_view_exempt(view_func))
Index: /django/branches/soc2009/model-validation/django/utils/functional.py
===================================================================
--- /django/branches/soc2009/model-validation/django/utils/functional.py (revision 9945)
+++ /django/branches/soc2009/model-validation/django/utils/functional.py (revision 11725)
@@ -258,7 +258,6 @@
wrapped class.
- This is useful, for example, if the wrapped class needs to use Django
- settings at creation time: we want to permit it to be imported without
- accessing settings.
+ By subclassing, you have the opportunity to intercept and alter the
+ instantiation. If you don't need to do that, use SimpleLazyObject.
"""
def __init__(self):
@@ -268,7 +267,4 @@
if self._wrapped is None:
self._setup()
- if name == "__members__":
- # Used to implement dir(obj)
- return self._wrapped.get_all_members()
return getattr(self._wrapped, name)
@@ -288,2 +284,67 @@
raise NotImplementedError
+ # introspection support:
+ __members__ = property(lambda self: self.__dir__())
+
+ def __dir__(self):
+ if self._wrapped is None:
+ self._setup()
+ return dir(self._wrapped)
+
+class SimpleLazyObject(LazyObject):
+ """
+ A lazy object initialised from any function.
+
+ Designed for compound objects of unknown type. For builtins or objects of
+ known type, use django.utils.functional.lazy.
+ """
+ def __init__(self, func):
+ """
+ Pass in a callable that returns the object to be wrapped.
+
+ If copies are made of the resulting SimpleLazyObject, which can happen
+ in various circumstances within Django, then you must ensure that the
+ callable can be safely run more than once and will return the same
+ value.
+ """
+ self.__dict__['_setupfunc'] = func
+ # For some reason, we have to inline LazyObject.__init__ here to avoid
+ # recursion
+ self._wrapped = None
+
+ def __str__(self):
+ if self._wrapped is None: self._setup()
+ return str(self._wrapped)
+
+ def __unicode__(self):
+ if self._wrapped is None: self._setup()
+ return unicode(self._wrapped)
+
+ def __deepcopy__(self, memo):
+ if self._wrapped is None:
+ # We have to use SimpleLazyObject, not self.__class__, because the
+ # latter is proxied.
+ result = SimpleLazyObject(self._setupfunc)
+ memo[id(self)] = result
+ return result
+ else:
+ import copy
+ return copy.deepcopy(self._wrapped, memo)
+
+ # Need to pretend to be the wrapped class, for the sake of objects that care
+ # about this (especially in equality tests)
+ def __get_class(self):
+ if self._wrapped is None: self._setup()
+ return self._wrapped.__class__
+ __class__ = property(__get_class)
+
+ def __eq__(self, other):
+ if self._wrapped is None: self._setup()
+ return self._wrapped == other
+
+ def __hash__(self):
+ if self._wrapped is None: self._setup()
+ return hash(self._wrapped)
+
+ def _setup(self):
+ self._wrapped = self._setupfunc()
Index: /django/branches/soc2009/model-validation/django/utils/decorators.py
===================================================================
--- /django/branches/soc2009/model-validation/django/utils/decorators.py (revision 11617)
+++ /django/branches/soc2009/model-validation/django/utils/decorators.py (revision 11725)
@@ -6,4 +6,17 @@
except ImportError:
from django.utils.functional import wraps, update_wrapper # Python 2.3, 2.4 fallback.
+
+
+# Licence for MethodDecoratorAdaptor and auto_adapt_to_methods
+#
+# This code is taken from stackoverflow.com [1], the code being supplied by
+# users 'Ants Aasma' [2] and 'Silent Ghost' [3] with modifications. It is
+# legally included here under the terms of the Creative Commons
+# Attribution-Share Alike 2.5 Generic Licence [4]
+#
+# [1] http://stackoverflow.com/questions/1288498/using-the-same-decorator-with-arguments-with-functions-and-methods
+# [2] http://stackoverflow.com/users/107366/ants-aasma
+# [3] http://stackoverflow.com/users/12855/silentghost
+# [4] http://creativecommons.org/licenses/by-sa/2.5/
class MethodDecoratorAdaptor(object):
Index: /django/branches/soc2009/model-validation/django/contrib/gis/utils/layermapping.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/gis/utils/layermapping.py (revision 9688)
+++ /django/branches/soc2009/model-validation/django/contrib/gis/utils/layermapping.py (revision 11725)
@@ -515,14 +515,24 @@
"Returns the GeometryColumn model associated with the geographic column."
from django.contrib.gis.models import GeometryColumns
- # Getting the GeometryColumn object.
+ # Use the `get_field_by_name` on the model's options so that we
+ # get the correct model if there's model inheritance -- otherwise
+ # the returned model is None.
+ opts = self.model._meta
+ fld, model, direct, m2m = opts.get_field_by_name(self.geom_field)
+ if model is None: model = self.model
+
+ # Trying to get the `GeometryColumns` object that corresponds to the
+ # the geometry field.
try:
- db_table = self.model._meta.db_table
- geo_col = self.geom_field
+ db_table = model._meta.db_table
+ geo_col = fld.column
+
if SpatialBackend.oracle:
# Making upper case for Oracle.
db_table = db_table.upper()
geo_col = geo_col.upper()
- gc_kwargs = {GeometryColumns.table_name_col() : db_table,
- GeometryColumns.geom_col_name() : geo_col,
+
+ gc_kwargs = { GeometryColumns.table_name_col() : db_table,
+ GeometryColumns.geom_col_name() : geo_col,
}
return GeometryColumns.objects.get(**gc_kwargs)
Index: /django/branches/soc2009/model-validation/django/contrib/gis/tests/layermap/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/gis/tests/layermap/tests.py (revision 10197)
+++ /django/branches/soc2009/model-validation/django/contrib/gis/tests/layermap/tests.py (revision 11725)
@@ -2,5 +2,5 @@
from copy import copy
from decimal import Decimal
-from models import City, County, CountyFeat, Interstate, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
+from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
from django.contrib.gis.db.backend import SpatialBackend
from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
@@ -243,4 +243,24 @@
self.county_helper(county_feat=False)
+ def test06_model_inheritance(self):
+ "Tests LayerMapping on inherited models. See #12093."
+ icity_mapping = {'name' : 'Name',
+ 'population' : 'Population',
+ 'density' : 'Density',
+ 'point' : 'POINT',
+ 'dt' : 'Created',
+ }
+
+ # Parent model has geometry field.
+ lm1 = LayerMapping(ICity1, city_shp, icity_mapping)
+ lm1.save()
+
+ # Grandparent has geometry field.
+ lm2 = LayerMapping(ICity2, city_shp, icity_mapping)
+ lm2.save()
+
+ self.assertEqual(6, ICity1.objects.count())
+ self.assertEqual(3, ICity2.objects.count())
+
def suite():
s = unittest.TestSuite()
Index: /django/branches/soc2009/model-validation/django/contrib/gis/tests/layermap/models.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/gis/tests/layermap/models.py (revision 8219)
+++ /django/branches/soc2009/model-validation/django/contrib/gis/tests/layermap/models.py (revision 11725)
@@ -30,4 +30,18 @@
objects = models.GeoManager()
+# Same as `City` above, but for testing model inheritance.
+class CityBase(models.Model):
+ name = models.CharField(max_length=25)
+ population = models.IntegerField()
+ density = models.DecimalField(max_digits=7, decimal_places=1)
+ point = models.PointField()
+ objects = models.GeoManager()
+
+class ICity1(CityBase):
+ dt = models.DateField()
+
+class ICity2(ICity1):
+ dt_time = models.DateTimeField(auto_now=True)
+
# Mapping dictionaries for the models above.
co_mapping = {'name' : 'Name',
Index: /django/branches/soc2009/model-validation/django/contrib/gis/tests/geoapp/test_regress.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/gis/tests/geoapp/test_regress.py (revision 11617)
+++ /django/branches/soc2009/model-validation/django/contrib/gis/tests/geoapp/test_regress.py (revision 11725)
@@ -29,4 +29,5 @@
@no_spatialite
+ @no_mysql
def test03_extent(self):
"Testing `extent` on a table with a single point, see #11827."
Index: /django/branches/soc2009/model-validation/django/contrib/gis/gdal/prototypes/geom.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/gis/gdal/prototypes/geom.py (revision 9985)
+++ /django/branches/soc2009/model-validation/django/contrib/gis/gdal/prototypes/geom.py (revision 11725)
@@ -84,5 +84,6 @@
get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p])
get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p])
-get_coord_dims = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p])
+get_coord_dim = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p])
+set_coord_dim = void_output(lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False)
get_geom_count = int_output(lgdal.OGR_G_GetGeometryCount, [c_void_p])
Index: /django/branches/soc2009/model-validation/django/contrib/gis/gdal/geometries.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/gis/gdal/geometries.py (revision 9985)
+++ /django/branches/soc2009/model-validation/django/contrib/gis/gdal/geometries.py (revision 11725)
@@ -30,5 +30,5 @@
>>> print mpnt
MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641)
-
+
The OGRGeomType class is to make it easy to specify an OGR geometry type:
>>> from django.contrib.gis.gdal import OGRGeomType
@@ -79,5 +79,5 @@
str_instance = False
- # Constructing the geometry,
+ # Constructing the geometry,
if str_instance:
# Checking if unicode
@@ -131,10 +131,10 @@
@classmethod
- def from_bbox(cls, bbox):
+ def from_bbox(cls, bbox):
"Constructs a Polygon from a bounding box (4-tuple)."
x0, y0, x1, y1 = bbox
return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % (
x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
-
+
def __del__(self):
"Deletes this Geometry."
@@ -180,8 +180,15 @@
return capi.get_dims(self.ptr)
- @property
- def coord_dim(self):
+ def _get_coord_dim(self):
"Returns the coordinate dimension of the Geometry."
- return capi.get_coord_dims(self.ptr)
+ return capi.get_coord_dim(self.ptr)
+
+ def _set_coord_dim(self, dim):
+ "Sets the coordinate dimension of this Geometry."
+ if not dim in (2, 3):
+ raise ValueError('Geometry dimension must be either 2 or 3')
+ capi.set_coord_dim(self.ptr, dim)
+
+ coord_dim = property(_get_coord_dim, _set_coord_dim)
@property
@@ -238,5 +245,5 @@
#### SpatialReference-related Properties ####
-
+
# The SRS property
def _get_srs(self):
@@ -250,9 +257,13 @@
def _set_srs(self, srs):
"Sets the SpatialReference for this geometry."
+ # Do not have to clone the `SpatialReference` object pointer because
+ # when it is assigned to this `OGRGeometry` it's internal OGR
+ # reference count is incremented, and will likewise be released
+ # (decremented) when this geometry's destructor is called.
if isinstance(srs, SpatialReference):
- srs_ptr = srs_api.clone_srs(srs.ptr)
+ srs_ptr = srs.ptr
elif isinstance(srs, (int, long, basestring)):
sr = SpatialReference(srs)
- srs_ptr = srs_api.clone_srs(sr.ptr)
+ srs_ptr = sr.ptr
else:
raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
@@ -299,5 +310,5 @@
GDAL 1.5+).
"""
- if GEOJSON:
+ if GEOJSON:
return capi.to_json(self.ptr)
else:
@@ -336,5 +347,5 @@
"Returns the WKT representation of the Geometry."
return capi.to_wkt(self.ptr, byref(c_char_p()))
-
+
#### Geometry Methods ####
def clone(self):
@@ -364,4 +375,14 @@
klone.transform(coord_trans)
return klone
+
+ # Have to get the coordinate dimension of the original geometry
+ # so it can be used to reset the transformed geometry's dimension
+ # afterwards. This is done because of GDAL bug (in versions prior
+ # to 1.7) that turns geometries 3D after transformation, see:
+ # http://trac.osgeo.org/gdal/changeset/17792
+ orig_dim = self.coord_dim
+
+ # Depending on the input type, use the appropriate OGR routine
+ # to perform the transformation.
if isinstance(coord_trans, CoordTransform):
capi.geom_transform(self.ptr, coord_trans.ptr)
@@ -374,4 +395,8 @@
raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.')
+ # Setting with original dimension, see comment above.
+ if self.coord_dim != orig_dim:
+ self.coord_dim = orig_dim
+
def transform_to(self, srs):
"For backwards-compatibility."
@@ -392,5 +417,5 @@
"Returns True if this geometry intersects with the other."
return self._topology(capi.ogr_intersects, other)
-
+
def equals(self, other):
"Returns True if this geometry is equivalent to the other."
@@ -437,5 +462,5 @@
def convex_hull(self):
"""
- Returns the smallest convex Polygon that contains all the points in
+ Returns the smallest convex Polygon that contains all the points in
this Geometry.
"""
@@ -457,5 +482,5 @@
def sym_difference(self, other):
- """
+ """
Returns a new geometry which is the symmetric difference of this
geometry and the other.
@@ -546,5 +571,5 @@
"Returns the Y coordinates in a list."
return self._listarr(capi.gety)
-
+
@property
def z(self):
@@ -611,5 +636,5 @@
else:
return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
-
+
def __iter__(self):
"Iterates over each Geometry."
@@ -659,4 +684,4 @@
6 : MultiPolygon,
7 : GeometryCollection,
- 101: LinearRing,
+ 101: LinearRing,
}
Index: /django/branches/soc2009/model-validation/django/contrib/gis/gdal/tests/test_geom.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/gis/gdal/tests/test_geom.py (revision 9985)
+++ /django/branches/soc2009/model-validation/django/contrib/gis/gdal/tests/test_geom.py (revision 11725)
@@ -319,4 +319,16 @@
self.assertAlmostEqual(trans.x, p.x, prec)
self.assertAlmostEqual(trans.y, p.y, prec)
+
+ def test09c_transform_dim(self):
+ "Testing coordinate dimension is the same on transformed geometries."
+ ls_orig = OGRGeometry('LINESTRING(-104.609 38.255)', 4326)
+ ls_trans = OGRGeometry('LINESTRING(992385.4472045 481455.4944650)', 2774)
+
+ prec = 3
+ ls_orig.transform(ls_trans.srs)
+ # Making sure the coordinate dimension is still 2D.
+ self.assertEqual(2, ls_orig.coord_dim)
+ self.assertAlmostEqual(ls_trans.x[0], ls_orig.x[0], prec)
+ self.assertAlmostEqual(ls_trans.y[0], ls_orig.y[0], prec)
def test10_difference(self):
Index: /django/branches/soc2009/model-validation/django/contrib/formtools/wizard.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/formtools/wizard.py (revision 8679)
+++ /django/branches/soc2009/model-validation/django/contrib/formtools/wizard.py (revision 11725)
@@ -15,4 +15,5 @@
from django.utils.translation import ugettext_lazy as _
from django.contrib.formtools.utils import security_hash
+from django.views.decorators.csrf import csrf_protect
class FormWizard(object):
@@ -45,4 +46,5 @@
return len(self.form_list)
+ @csrf_protect
def __call__(self, request, *args, **kwargs):
"""
Index: /django/branches/soc2009/model-validation/django/contrib/formtools/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/formtools/tests.py (revision 10753)
+++ /django/branches/soc2009/model-validation/django/contrib/formtools/tests.py (revision 11725)
@@ -148,13 +148,16 @@
class WizardClass(wizard.FormWizard):
def render_template(self, *args, **kw):
- return ""
+ return http.HttpResponse("")
def done(self, request, cleaned_data):
return http.HttpResponse(success_string)
-class DummyRequest(object):
+class DummyRequest(http.HttpRequest):
def __init__(self, POST=None):
+ super(DummyRequest, self).__init__()
self.method = POST and "POST" or "GET"
- self.POST = POST
+ if POST is not None:
+ self.POST.update(POST)
+ self._dont_enforce_csrf_checks = True
class WizardTests(TestCase):
Index: /django/branches/soc2009/model-validation/django/contrib/formtools/templates/formtools/preview.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/formtools/templates/formtools/preview.html (revision 8984)
+++ /django/branches/soc2009/model-validation/django/contrib/formtools/templates/formtools/preview.html (revision 11725)
@@ -16,5 +16,5 @@
<p>Security hash: {{ hash_value }}</p>
-<form action="" method="post">
+<form action="" method="post">{% csrf_token %}
{% for field in form %}{{ field.as_hidden }}
{% endfor %}
@@ -26,5 +26,5 @@
<h1>Or edit it again</h1>
-<form action="" method="post">
+<form action="" method="post">{% csrf_token %}
<table>
{{ form }}
Index: /django/branches/soc2009/model-validation/django/contrib/formtools/templates/formtools/form.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/formtools/templates/formtools/form.html (revision 7294)
+++ /django/branches/soc2009/model-validation/django/contrib/formtools/templates/formtools/form.html (revision 11725)
@@ -5,5 +5,5 @@
{% if form.errors %}<h1>Please correct the following errors</h1>{% else %}<h1>Submit</h1>{% endif %}
-<form action="" method="post">
+<form action="" method="post">{% csrf_token %}
<table>
{{ form }}
Index: /django/branches/soc2009/model-validation/django/contrib/comments/admin.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/admin.py (revision 11617)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/admin.py (revision 11725)
@@ -1,6 +1,7 @@
from django.contrib import admin
from django.contrib.comments.models import Comment
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext_lazy as _, ungettext
from django.contrib.comments import get_model
+from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete
class CommentsAdmin(admin.ModelAdmin):
@@ -23,4 +24,42 @@
raw_id_fields = ('user',)
search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address')
+ actions = ["flag_comments", "approve_comments", "remove_comments"]
+
+ def get_actions(self, request):
+ actions = super(CommentsAdmin, self).get_actions(request)
+ # Only superusers should be able to delete the comments from the DB.
+ if not request.user.is_superuser:
+ actions.pop('delete_selected')
+ if not request.user.has_perm('comments.can_moderate'):
+ actions.pop('approve_comments')
+ actions.pop('remove_comments')
+ return actions
+
+ def flag_comments(self, request, queryset):
+ self._bulk_flag(request, queryset, perform_flag, _("flagged"))
+ flag_comments.short_description = _("Flag selected comments")
+
+ def approve_comments(self, request, queryset):
+ self._bulk_flag(request, queryset, perform_approve, _('approved'))
+ approve_comments.short_description = _("Approve selected comments")
+
+ def remove_comments(self, request, queryset):
+ self._bulk_flag(request, queryset, perform_delete, _('removed'))
+ remove_comments.short_description = _("Remove selected comments")
+
+ def _bulk_flag(self, request, queryset, action, description):
+ """
+ Flag, approve, or remove some comments from an admin action. Actually
+ calls the `action` argument to perform the heavy lifting.
+ """
+ n_comments = 0
+ for comment in queryset:
+ action(request, comment)
+ n_comments += 1
+
+ msg = ungettext(u'1 comment was successfully %(action)s.',
+ u'%(count)s comments were successfully %(action)s.',
+ n_comments)
+ self.message_user(request, msg % {'count': n_comments, 'action': description})
# Only register the default admin if the model is the built-in comment model
Index: /django/branches/soc2009/model-validation/django/contrib/comments/urls.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/urls.py (revision 10422)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/urls.py (revision 11725)
@@ -8,5 +8,4 @@
url(r'^delete/(\d+)/$', 'moderation.delete', name='comments-delete'),
url(r'^deleted/$', 'moderation.delete_done', name='comments-delete-done'),
- url(r'^moderate/$', 'moderation.moderation_queue', name='comments-moderation-queue'),
url(r'^approve/(\d+)/$', 'moderation.approve', name='comments-approve'),
url(r'^approved/$', 'moderation.approve_done', name='comments-approve-done'),
Index: /django/branches/soc2009/model-validation/django/contrib/comments/views/moderation.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/views/moderation.py (revision 8589)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/views/moderation.py (revision 11725)
@@ -4,10 +4,10 @@
from django.contrib.auth.decorators import login_required, permission_required
from utils import next_redirect, confirmation_view
-from django.core.paginator import Paginator, InvalidPage
-from django.http import Http404
from django.contrib import comments
from django.contrib.comments import signals
+from django.views.decorators.csrf import csrf_protect
-#@login_required
+@csrf_protect
+@login_required
def flag(request, comment_id, next=None):
"""
@@ -23,16 +23,5 @@
# Flag on POST
if request.method == 'POST':
- flag, created = comments.models.CommentFlag.objects.get_or_create(
- comment = comment,
- user = request.user,
- flag = comments.models.CommentFlag.SUGGEST_REMOVAL
- )
- signals.comment_was_flagged.send(
- sender = comment.__class__,
- comment = comment,
- flag = flag,
- created = created,
- request = request,
- )
+ perform_flag(request, comment)
return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk)
@@ -43,7 +32,7 @@
template.RequestContext(request)
)
-flag = login_required(flag)
-#@permission_required("comments.delete_comment")
+@csrf_protect
+@permission_required("comments.can_moderate")
def delete(request, comment_id, next=None):
"""
@@ -61,18 +50,5 @@
if request.method == 'POST':
# Flag the comment as deleted instead of actually deleting it.
- flag, created = comments.models.CommentFlag.objects.get_or_create(
- comment = comment,
- user = request.user,
- flag = comments.models.CommentFlag.MODERATOR_DELETION
- )
- comment.is_removed = True
- comment.save()
- signals.comment_was_flagged.send(
- sender = comment.__class__,
- comment = comment,
- flag = flag,
- created = created,
- request = request,
- )
+ perform_delete(request, comment)
return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk)
@@ -83,7 +59,7 @@
template.RequestContext(request)
)
-delete = permission_required("comments.can_moderate")(delete)
-#@permission_required("comments.can_moderate")
+@csrf_protect
+@permission_required("comments.can_moderate")
def approve(request, comment_id, next=None):
"""
@@ -101,21 +77,5 @@
if request.method == 'POST':
# Flag the comment as approved.
- flag, created = comments.models.CommentFlag.objects.get_or_create(
- comment = comment,
- user = request.user,
- flag = comments.models.CommentFlag.MODERATOR_APPROVAL,
- )
-
- comment.is_removed = False
- comment.is_public = True
- comment.save()
-
- signals.comment_was_flagged.send(
- sender = comment.__class__,
- comment = comment,
- flag = flag,
- created = created,
- request = request,
- )
+ perform_approve(request, comment)
return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk)
@@ -127,67 +87,62 @@
)
-approve = permission_required("comments.can_moderate")(approve)
+# The following functions actually perform the various flag/aprove/delete
+# actions. They've been broken out into seperate functions to that they
+# may be called from admin actions.
+
+def perform_flag(request, comment):
+ """
+ Actually perform the flagging of a comment from a request.
+ """
+ flag, created = comments.models.CommentFlag.objects.get_or_create(
+ comment = comment,
+ user = request.user,
+ flag = comments.models.CommentFlag.SUGGEST_REMOVAL
+ )
+ signals.comment_was_flagged.send(
+ sender = comment.__class__,
+ comment = comment,
+ flag = flag,
+ created = created,
+ request = request,
+ )
+
+def perform_delete(request, comment):
+ flag, created = comments.models.CommentFlag.objects.get_or_create(
+ comment = comment,
+ user = request.user,
+ flag = comments.models.CommentFlag.MODERATOR_DELETION
+ )
+ comment.is_removed = True
+ comment.save()
+ signals.comment_was_flagged.send(
+ sender = comment.__class__,
+ comment = comment,
+ flag = flag,
+ created = created,
+ request = request,
+ )
-#@permission_required("comments.can_moderate")
-def moderation_queue(request):
- """
- Displays a list of unapproved comments to be approved.
+def perform_approve(request, comment):
+ flag, created = comments.models.CommentFlag.objects.get_or_create(
+ comment = comment,
+ user = request.user,
+ flag = comments.models.CommentFlag.MODERATOR_APPROVAL,
+ )
- Templates: `comments/moderation_queue.html`
- Context:
- comments
- Comments to be approved (paginated).
- empty
- Is the comment list empty?
- is_paginated
- Is there more than one page?
- results_per_page
- Number of comments per page
- has_next
- Is there a next page?
- has_previous
- Is there a previous page?
- page
- The current page number
- next
- The next page number
- pages
- Number of pages
- hits
- Total number of comments
- page_range
- Range of page numbers
+ comment.is_removed = False
+ comment.is_public = True
+ comment.save()
- """
- qs = comments.get_model().objects.filter(is_public=False, is_removed=False)
- paginator = Paginator(qs, 100)
+ signals.comment_was_flagged.send(
+ sender = comment.__class__,
+ comment = comment,
+ flag = flag,
+ created = created,
+ request = request,
+ )
- try:
- page = int(request.GET.get("page", 1))
- except ValueError:
- raise Http404
-
- try:
- comments_per_page = paginator.page(page)
- except InvalidPage:
- raise Http404
-
- return render_to_response("comments/moderation_queue.html", {
- 'comments' : comments_per_page.object_list,
- 'empty' : page == 1 and paginator.count == 0,
- 'is_paginated': paginator.num_pages > 1,
- 'results_per_page': 100,
- 'has_next': comments_per_page.has_next(),
- 'has_previous': comments_per_page.has_previous(),
- 'page': page,
- 'next': page + 1,
- 'previous': page - 1,
- 'pages': paginator.num_pages,
- 'hits' : paginator.count,
- 'page_range' : paginator.page_range
- }, context_instance=template.RequestContext(request))
-
-moderation_queue = permission_required("comments.can_moderate")(moderation_queue)
+# Confirmation views.
flag_done = confirmation_view(
Index: /django/branches/soc2009/model-validation/django/contrib/comments/views/comments.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/views/comments.py (revision 11038)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/views/comments.py (revision 11725)
@@ -11,4 +11,5 @@
from django.contrib import comments
from django.contrib.comments import signals
+from django.views.decorators.csrf import csrf_protect
class CommentPostBadRequest(http.HttpResponseBadRequest):
@@ -23,4 +24,6 @@
self.content = render_to_string("comments/400-debug.html", {"why": why})
+@csrf_protect
+@require_POST
def post_comment(request, next=None):
"""
@@ -117,6 +120,4 @@
return next_redirect(data, next, comment_done, c=comment._get_pk_val())
-post_comment = require_POST(post_comment)
-
comment_done = confirmation_view(
template = "comments/posted.html",
Index: /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/approve.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/approve.html (revision 10429)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/approve.html (revision 11725)
@@ -7,5 +7,5 @@
<h1>{% trans "Really make this comment public?" %}</h1>
<blockquote>{{ comment|linebreaks }}</blockquote>
- <form action="." method="post">
+ <form action="." method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
<p class="submit">
Index: /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/preview.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/preview.html (revision 10420)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/preview.html (revision 11725)
@@ -6,5 +6,5 @@
{% block content %}
{% load comments %}
- <form action="{% comment_form_target %}" method="post">
+ <form action="{% comment_form_target %}" method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
{% if form.errors %}
Index: /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/delete.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/delete.html (revision 10429)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/delete.html (revision 11725)
@@ -7,5 +7,5 @@
<h1>{% trans "Really remove this comment?" %}</h1>
<blockquote>{{ comment|linebreaks }}</blockquote>
- <form action="." method="post">
+ <form action="." method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
<p class="submit">
Index: /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/form.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/form.html (revision 10420)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/form.html (revision 11725)
@@ -1,4 +1,4 @@
{% load comments i18n %}
-<form action="{% comment_form_target %}" method="post">
+<form action="{% comment_form_target %}" method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
{% for field in form %}
Index: /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/flag.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/flag.html (revision 10429)
+++ /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/flag.html (revision 11725)
@@ -7,5 +7,5 @@
<h1>{% trans "Really flag this comment?" %}</h1>
<blockquote>{{ comment|linebreaks }}</blockquote>
- <form action="." method="post">
+ <form action="." method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
<p class="submit">
Index: /ango/branches/soc2009/model-validation/django/contrib/comments/templates/comments/moderation_queue.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/comments/templates/comments/moderation_queue.html (revision 9119)
+++ (revision )
@@ -1,75 +1,0 @@
-{% extends "admin/change_list.html" %}
-{% load adminmedia i18n %}
-
-{% block title %}{% trans "Comment moderation queue" %}{% endblock %}
-
-{% block extrahead %}
- {{ block.super }}
- <style type="text/css" media="screen">
- p#nocomments { font-size: 200%; text-align: center; border: 1px #ccc dashed; padding: 4em; }
- td.actions { width: 11em; }
- td.actions form { display: inline; }
- td.actions form input.submit { width: 5em; padding: 2px 4px; margin-right: 4px;}
- td.actions form input.approve { background: green; color: white; }
- td.actions form input.remove { background: red; color: white; }
- </style>
-{% endblock %}
-
-{% block branding %}
-<h1 id="site-name">{% trans "Comment moderation queue" %}</h1>
-{% endblock %}
-
-{% block breadcrumbs %}{% endblock %}
-
-{% block content %}
-{% if empty %}
-<p id="nocomments">{% trans "No comments to moderate" %}.</p>
-{% else %}
-<div id="content-main">
- <div class="module" id="changelist">
- <table cellspacing="0">
- <thead>
- <tr>
- <th>{% trans "Action" %}</th>
- <th>{% trans "Name" %}</th>
- <th>{% trans "Comment" %}</th>
- <th>{% trans "Email" %}</th>
- <th>{% trans "URL" %}</th>
- <th>{% trans "Authenticated?" %}</th>
- <th>{% trans "IP Address" %}</th>
- <th class="sorted desc">{% trans "Date posted" %}</th>
- </tr>
- </thead>
- <tbody>
- {% for comment in comments %}
- <tr class="{% cycle 'row1' 'row2' %}">
- <td class="actions">
- <form action="{% url comments-approve comment.pk %}" method="post">
- <input type="hidden" name="next" value="{% url comments-moderation-queue %}" />
- <input class="approve submit" type="submit" name="submit" value="{% trans "Approve" %}" />
- </form>
- <form action="{% url comments-delete comment.pk %}" method="post">
- <input type="hidden" name="next" value="{% url comments-moderation-queue %}" />
- <input class="remove submit" type="submit" name="submit" value="{% trans "Remove" %}" />
- </form>
- </td>
- <td>{{ comment.name }}</td>
- <td>{{ comment.comment|truncatewords:"50" }}</td>
- <td>{{ comment.email }}</td>
- <td>{{ comment.url }}</td>
- <td>
- <img
- src="{% admin_media_prefix %}img/admin/icon-{% if comment.user %}yes{% else %}no{% endif %}.gif"
- alt="{% if comment.user %}{% trans "yes" %}{% else %}{% trans "no" %}{% endif %}"
- />
- </td>
- <td>{{ comment.ip_address }}</td>
- <td>{{ comment.submit_date|date:"F j, P" }}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- </div>
-</div>
-{% endif %}
-{% endblock %}
Index: /django/branches/soc2009/model-validation/django/contrib/admin/media/css/changelists.css
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/media/css/changelists.css (revision 10258)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/media/css/changelists.css (revision 11725)
@@ -54,5 +54,5 @@
}
-#changelist table thead th:first-child {
+#changelist table thead th.action-checkbox-column {
width: 1.5em;
text-align: center;
Index: /django/branches/soc2009/model-validation/django/contrib/admin/validation.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/validation.py (revision 10732)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/validation.py (revision 11725)
@@ -150,4 +150,5 @@
def validate_inline(cls, parent, parent_model):
+
# model is already verified to exist and be a Model
if cls.fk_name: # default value is None
@@ -156,4 +157,7 @@
raise ImproperlyConfigured("'%s.fk_name is not an instance of "
"models.ForeignKey." % cls.__name__)
+
+ fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
+
# extra = 3
# max_num = 0
@@ -170,5 +174,4 @@
# exclude
if hasattr(cls, 'exclude') and cls.exclude:
- fk = _get_foreign_key(parent_model, cls.model, can_fail=True)
if fk and fk.name in cls.exclude:
raise ImproperlyConfigured("%s cannot exclude the field "
Index: /django/branches/soc2009/model-validation/django/contrib/admin/options.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/options.py (revision 11504)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/options.py (revision 11725)
@@ -7,4 +7,5 @@
from django.contrib.admin import helpers
from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
+from django.views.decorators.csrf import csrf_protect
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
@@ -153,6 +154,7 @@
Get a form Field for a ManyToManyField.
"""
- # If it uses an intermediary model, don't show field in admin.
- if db_field.rel.through is not None:
+ # If it uses an intermediary model that isn't auto created, don't show
+ # a field in admin.
+ if not db_field.rel.through._meta.auto_created:
return None
@@ -702,4 +704,6 @@
return HttpResponseRedirect(".")
+ @csrf_protect
+ @transaction.commit_on_success
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
@@ -787,6 +791,7 @@
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
- add_view = transaction.commit_on_success(add_view)
-
+
+ @csrf_protect
+ @transaction.commit_on_success
def change_view(self, request, object_id, extra_context=None):
"The 'change' admin view for this model."
@@ -876,6 +881,6 @@
context.update(extra_context or {})
return self.render_change_form(request, context, change=True, obj=obj)
- change_view = transaction.commit_on_success(change_view)
-
+
+ @csrf_protect
def changelist_view(self, request, extra_context=None):
"The 'change list' admin view for this model."
@@ -990,4 +995,5 @@
], context, context_instance=context_instance)
+ @csrf_protect
def delete_view(self, request, object_id, extra_context=None):
"The 'delete' admin view for this model."
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templatetags/admin_list.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templatetags/admin_list.py (revision 11513)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templatetags/admin_list.py (revision 11725)
@@ -107,4 +107,9 @@
header = field_name
header = header.replace('_', ' ')
+ # if the field is the action checkbox: no sorting and special class
+ if field_name == 'action_checkbox':
+ yield {"text": header,
+ "class_attrib": mark_safe(' class="action-checkbox-column"')}
+ continue
# It is a non-field, but perhaps one that is sortable
Index: /django/branches/soc2009/model-validation/django/contrib/admin/sites.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/sites.py (revision 11513)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/sites.py (revision 11725)
@@ -4,4 +4,5 @@
from django.contrib.admin import actions
from django.contrib.auth import authenticate, login
+from django.views.decorators.csrf import csrf_protect
from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured
@@ -187,8 +188,14 @@
if not cacheable:
inner = never_cache(inner)
+ # We add csrf_protect here so this function can be used as a utility
+ # function for any view, without having to repeat 'csrf_protect'.
+ inner = csrf_protect(inner)
return update_wrapper(inner, view)
def get_urls(self):
from django.conf.urls.defaults import patterns, url, include
+
+ if settings.DEBUG:
+ self.check_dependencies()
def wrap(view, cacheable=False):
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/change_list.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/change_list.html (revision 10408)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/change_list.html (revision 11725)
@@ -69,5 +69,5 @@
{% endblock %}
- <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
+ <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
{% if cl.formset %}
{{ cl.formset.management_form }}
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/template_validator.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/template_validator.html (revision 8984)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/template_validator.html (revision 11725)
@@ -5,5 +5,5 @@
<div id="content-main">
-<form action="" method="post">
+<form action="" method="post">{% csrf_token %}
{% if form.errors %}
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/delete_selected_confirmation.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/delete_selected_confirmation.html (revision 11504)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/delete_selected_confirmation.html (revision 11725)
@@ -24,5 +24,5 @@
<ul>{{ deleteable_object|unordered_list }}</ul>
{% endfor %}
- <form action="" method="post">
+ <form action="" method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/auth/user/change_password.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/auth/user/change_password.html (revision 10234)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/auth/user/change_password.html (revision 11725)
@@ -16,5 +16,5 @@
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
-<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
+<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/change_form.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/change_form.html (revision 10714)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/change_form.html (revision 11725)
@@ -30,5 +30,5 @@
{% endif %}{% endif %}
{% endblock %}
-<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/login.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/login.html (revision 9690)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/login.html (revision 11725)
@@ -15,5 +15,5 @@
{% endif %}
<div id="content-main">
-<form action="{{ app_path }}" method="post" id="login-form">
+<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
<div class="form-row">
<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/delete_confirmation.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/delete_confirmation.html (revision 8984)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/admin/delete_confirmation.html (revision 11725)
@@ -23,5 +23,5 @@
<p>{% blocktrans with object as escaped_object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}</p>
<ul>{{ deleted_objects|unordered_list }}</ul>
- <form action="" method="post">
+ <form action="" method="post">{% csrf_token %}
<div>
<input type="hidden" name="post" value="yes" />
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_reset_confirm.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_reset_confirm.html (revision 9599)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_reset_confirm.html (revision 11725)
@@ -14,5 +14,5 @@
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
-<form action="" method="post">
+<form action="" method="post">{% csrf_token %}
{{ form.new_password1.errors }}
<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_reset_form.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_reset_form.html (revision 8984)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_reset_form.html (revision 11725)
@@ -12,5 +12,5 @@
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
-<form action="" method="post">
+<form action="" method="post">{% csrf_token %}
{{ form.email.errors }}
<p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
Index: /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_change_form.html
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_change_form.html (revision 9079)
+++ /django/branches/soc2009/model-validation/django/contrib/admin/templates/registration/password_change_form.html (revision 11725)
@@ -12,5 +12,5 @@
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
-<form action="" method="post">
+<form action="" method="post">{% csrf_token %}
{{ form.old_password.errors }}
Index: /django/branches/soc2009/model-validation/django/contrib/csrf/middleware.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/csrf/middleware.py (revision 10617)
+++ /django/branches/soc2009/model-validation/django/contrib/csrf/middleware.py (revision 11725)
@@ -1,160 +1,7 @@
-"""
-Cross Site Request Forgery Middleware.
+from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware, CsrfResponseMiddleware
+from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, csrf_response_exempt
-This module provides a middleware that implements protection
-against request forgeries from other sites.
-"""
-
-import re
-import itertools
-try:
- from functools import wraps
-except ImportError:
- from django.utils.functional import wraps # Python 2.3, 2.4 fallback.
-
-from django.conf import settings
-from django.http import HttpResponseForbidden
-from django.utils.hashcompat import md5_constructor
-from django.utils.safestring import mark_safe
-
-_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>')
-
-_POST_FORM_RE = \
- re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
-
-_HTML_TYPES = ('text/html', 'application/xhtml+xml')
-
-def _make_token(session_id):
- return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
-
-class CsrfViewMiddleware(object):
- """
- Middleware that requires a present and correct csrfmiddlewaretoken
- for POST requests that have an active session.
- """
- def process_view(self, request, callback, callback_args, callback_kwargs):
- if request.method == 'POST':
- if getattr(callback, 'csrf_exempt', False):
- return None
-
- if request.is_ajax():
- return None
-
- try:
- session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
- except KeyError:
- # No session, no check required
- return None
-
- csrf_token = _make_token(session_id)
- # check incoming token
- try:
- request_csrf_token = request.POST['csrfmiddlewaretoken']
- except KeyError:
- return HttpResponseForbidden(_ERROR_MSG)
-
- if request_csrf_token != csrf_token:
- return HttpResponseForbidden(_ERROR_MSG)
-
- return None
-
-class CsrfResponseMiddleware(object):
- """
- Middleware that post-processes a response to add a
- csrfmiddlewaretoken if the response/request have an active
- session.
- """
- def process_response(self, request, response):
- if getattr(response, 'csrf_exempt', False):
- return response
-
- csrf_token = None
- try:
- # This covers a corner case in which the outgoing response
- # both contains a form and sets a session cookie. This
- # really should not be needed, since it is best if views
- # that create a new session (login pages) also do a
- # redirect, as is done by all such view functions in
- # Django.
- cookie = response.cookies[settings.SESSION_COOKIE_NAME]
- csrf_token = _make_token(cookie.value)
- except KeyError:
- # Normal case - look for existing session cookie
- try:
- session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
- csrf_token = _make_token(session_id)
- except KeyError:
- # no incoming or outgoing cookie
- pass
-
- if csrf_token is not None and \
- response['Content-Type'].split(';')[0] in _HTML_TYPES:
-
- # ensure we don't add the 'id' attribute twice (HTML validity)
- idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
- itertools.repeat(''))
- def add_csrf_field(match):
- """Returns the matched <form> tag plus the added <input> element"""
- return mark_safe(match.group() + "<div style='display:none;'>" + \
- "<input type='hidden' " + idattributes.next() + \
- " name='csrfmiddlewaretoken' value='" + csrf_token + \
- "' /></div>")
-
- # Modify any POST forms
- response.content = _POST_FORM_RE.sub(add_csrf_field, response.content)
- return response
-
-class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
- """Django middleware that adds protection against Cross Site
- Request Forgeries by adding hidden form fields to POST forms and
- checking requests for the correct value.
-
- In the list of middlewares, SessionMiddleware is required, and
- must come after this middleware. CsrfMiddleWare must come after
- compression middleware.
-
- If a session ID cookie is present, it is hashed with the
- SECRET_KEY setting to create an authentication token. This token
- is added to all outgoing POST forms and is expected on all
- incoming POST requests that have a session ID cookie.
-
- If you are setting cookies directly, instead of using Django's
- session framework, this middleware will not work.
-
- CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware
- and CsrfResponseMiddleware which can be used independently.
- """
- pass
-
-def csrf_response_exempt(view_func):
- """
- Modifies a view function so that its response is exempt
- from the post-processing of the CSRF middleware.
- """
- def wrapped_view(*args, **kwargs):
- resp = view_func(*args, **kwargs)
- resp.csrf_exempt = True
- return resp
- return wraps(view_func)(wrapped_view)
-
-def csrf_view_exempt(view_func):
- """
- Marks a view function as being exempt from CSRF view protection.
- """
- # We could just do view_func.csrf_exempt = True, but decorators
- # are nicer if they don't have side-effects, so we return a new
- # function.
- def wrapped_view(*args, **kwargs):
- return view_func(*args, **kwargs)
- wrapped_view.csrf_exempt = True
- return wraps(view_func)(wrapped_view)
-
-def csrf_exempt(view_func):
- """
- Marks a view function as being exempt from the CSRF checks
- and post processing.
-
- This is the same as using both the csrf_view_exempt and
- csrf_response_exempt decorators.
- """
- return csrf_response_exempt(csrf_view_exempt(view_func))
+import warnings
+warnings.warn("This import for CSRF functionality is deprecated. Please use django.middleware.csrf for the middleware and django.views.decorators.csrf for decorators.",
+ PendingDeprecationWarning
+ )
Index: /ango/branches/soc2009/model-validation/django/contrib/csrf/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/csrf/tests.py (revision 10801)
+++ (revision )
@@ -1,144 +1,0 @@
-# -*- coding: utf-8 -*-
-
-from django.test import TestCase
-from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
-from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, csrf_exempt
-from django.conf import settings
-
-
-def post_form_response():
- resp = HttpResponse(content="""
-<html><body><form method="POST"><input type="text" /></form></body></html>
-""", mimetype="text/html")
- return resp
-
-def test_view(request):
- return post_form_response()
-
-class CsrfMiddlewareTest(TestCase):
-
- _session_id = "1"
-
- def _get_GET_no_session_request(self):
- return HttpRequest()
-
- def _get_GET_session_request(self):
- req = self._get_GET_no_session_request()
- req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
- return req
-
- def _get_POST_session_request(self):
- req = self._get_GET_session_request()
- req.method = "POST"
- return req
-
- def _get_POST_no_session_request(self):
- req = self._get_GET_no_session_request()
- req.method = "POST"
- return req
-
- def _get_POST_session_request_with_token(self):
- req = self._get_POST_session_request()
- req.POST['csrfmiddlewaretoken'] = _make_token(self._session_id)
- return req
-
- def _get_post_form_response(self):
- return post_form_response()
-
- def _get_new_session_response(self):
- resp = self._get_post_form_response()
- resp.cookies[settings.SESSION_COOKIE_NAME] = self._session_id
- return resp
-
- def _check_token_present(self, response):
- self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
-
- def get_view(self):
- return test_view
-
- # Check the post processing
- def test_process_response_no_session(self):
- """
- Check the post-processor does nothing if no session active
- """
- req = self._get_GET_no_session_request()
- resp = self._get_post_form_response()
- resp_content = resp.content # needed because process_response modifies resp
- resp2 = CsrfMiddleware().process_response(req, resp)
- self.assertEquals(resp_content, resp2.content)
-
- def test_process_response_existing_session(self):
- """
- Check that the token is inserted if there is an existing session
- """
- req = self._get_GET_session_request()
- resp = self._get_post_form_response()
- resp_content = resp.content # needed because process_response modifies resp
- resp2 = CsrfMiddleware().process_response(req, resp)
- self.assertNotEqual(resp_content, resp2.content)
- self._check_token_present(resp2)
-
- def test_process_response_new_session(self):
- """
- Check that the token is inserted if there is a new session being started
- """
- req = self._get_GET_no_session_request() # no session in request
- resp = self._get_new_session_response() # but new session started
- resp_content = resp.content # needed because process_response modifies resp
- resp2 = CsrfMiddleware().process_response(req, resp)
- self.assertNotEqual(resp_content, resp2.content)
- self._check_token_present(resp2)
-
- def test_process_response_exempt_view(self):
- """
- Check that no post processing is done for an exempt view
- """
- req = self._get_POST_session_request()
- resp = csrf_exempt(self.get_view())(req)
- resp_content = resp.content
- resp2 = CsrfMiddleware().process_response(req, resp)
- self.assertEquals(resp_content, resp2.content)
-
- # Check the request processing
- def test_process_request_no_session(self):
- """
- Check that if no session is present, the middleware does nothing.
- to the incoming request.
- """
- req = self._get_POST_no_session_request()
- req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
- self.assertEquals(None, req2)
-
- def test_process_request_session_no_token(self):
- """
- Check that if a session is present but no token, we get a 'forbidden'
- """
- req = self._get_POST_session_request()
- req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
- self.assertEquals(HttpResponseForbidden, req2.__class__)
-
- def test_process_request_session_and_token(self):
- """
- Check that if a session is present and a token, the middleware lets it through
- """
- req = self._get_POST_session_request_with_token()
- req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
- self.assertEquals(None, req2)
-
- def test_process_request_session_no_token_exempt_view(self):
- """
- Check that if a session is present and no token, but the csrf_exempt
- decorator has been applied to the view, the middleware lets it through
- """
- req = self._get_POST_session_request()
- req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {})
- self.assertEquals(None, req2)
-
- def test_ajax_exemption(self):
- """
- Check that AJAX requests are automatically exempted.
- """
- req = self._get_POST_session_request()
- req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
- self.assertEquals(None, req2)
Index: /ango/branches/soc2009/model-validation/django/contrib/csrf/models.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/csrf/models.py (revision 9551)
+++ (revision )
@@ -1,1 +1,0 @@
-# models.py file for tests to run.
Index: /django/branches/soc2009/model-validation/django/contrib/contenttypes/generic.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/contenttypes/generic.py (revision 11038)
+++ /django/branches/soc2009/model-validation/django/contrib/contenttypes/generic.py (revision 11725)
@@ -106,6 +106,4 @@
symmetrical=kwargs.pop('symmetrical', True))
- # By its very nature, a GenericRelation doesn't create a table.
- self.creates_table = False
# Override content-type/object-id field names on the related class
Index: /django/branches/soc2009/model-validation/django/contrib/auth/tests/remote_user.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/auth/tests/remote_user.py (revision 10674)
+++ /django/branches/soc2009/model-validation/django/contrib/auth/tests/remote_user.py (revision 11725)
@@ -3,5 +3,5 @@
from django.conf import settings
from django.contrib.auth.backends import RemoteUserBackend
-from django.contrib.auth.models import AnonymousUser, User
+from django.contrib.auth.models import User
from django.test import TestCase
@@ -31,13 +31,13 @@
response = self.client.get('/remote_user/')
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
response = self.client.get('/remote_user/', REMOTE_USER=None)
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
response = self.client.get('/remote_user/', REMOTE_USER='')
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
@@ -116,5 +116,5 @@
num_users = User.objects.count()
response = self.client.get('/remote_user/', REMOTE_USER='newuser')
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
Index: /django/branches/soc2009/model-validation/django/contrib/auth/views.py
===================================================================
--- /django/branches/soc2009/model-validation/django/contrib/auth/views.py (revision 10332)
+++ /django/branches/soc2009/model-validation/django/contrib/auth/views.py (revision 11725)
@@ -5,4 +5,5 @@
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm
from django.contrib.auth.tokens import default_token_generator
+from django.views.decorators.csrf import csrf_protect
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response, get_object_or_404
@@ -15,9 +16,13 @@
from django.views.decorators.cache import never_cache
-def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
+@csrf_protect
+@never_cache
+def login(request, template_name='registration/login.html',
+ redirect_field_name=REDIRECT_FIELD_NAME,
+ authentication_form=AuthenticationForm):
"Displays the login form and handles the login action."
redirect_to = request.REQUEST.get(redirect_field_name, '')
if request.method == "POST":
- form = AuthenticationForm(data=request.POST)
+ form = authentication_form(data=request.POST)
if form.is_valid():
# Light security check -- make sure redirect_to isn't garbage.
@@ -30,5 +35,5 @@
return HttpResponseRedirect(redirect_to)
else:
- form = AuthenticationForm(request)
+ form = authentication_form(request)
request.session.set_test_cookie()
if Site._meta.installed:
@@ -42,5 +47,4 @@
'site_name': current_site.name,
}, context_instance=RequestContext(request))
-login = never_cache(login)
def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME):
@@ -79,4 +83,5 @@
# - password_reset_complete shows a success message for the above
+@csrf_protect
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
@@ -108,4 +113,5 @@
return render_to_response(template_name, context_instance=RequestContext(request))
+# Doesn't need csrf_protect since no-one can guess the URL
def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html',
token_generator=default_token_generator, set_password_form=SetPasswordForm,
@@ -138,5 +144,5 @@
context_instance['validlink'] = False
form = None
- context_instance['form'] = form
+ context_instance['form'] = form
return render_to_response(template_name, context_instance=context_instance)
@@ -145,19 +151,20 @@
{'login_url': settings.LOGIN_URL}))
+@csrf_protect
+@login_required
def password_change(request, template_name='registration/password_change_form.html',
- post_change_redirect=None):
+ post_change_redirect=None, password_change_form=PasswordChangeForm):
if post_change_redirect is None:
post_change_redirect = reverse('django.contrib.auth.views.password_change_done')
if request.method == "POST":
- form = PasswordChangeForm(request.user, request.POST)
+ form = password_change_form(user=request.user, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(post_change_redirect)
else:
- form = PasswordChangeForm(request.user)
+ form = password_change_form(user=request.user)
return render_to_response(template_name, {
'form': form,
}, context_instance=RequestContext(request))
-password_change = login_required(password_change)
def password_change_done(request, template_name='registration/password_change_done.html'):
Index: /django/branches/soc2009/model-validation/django/template/defaultfilters.py
===================================================================
--- /django/branches/soc2009/model-validation/django/template/defaultfilters.py (revision 10543)
+++ /django/branches/soc2009/model-validation/django/template/defaultfilters.py (revision 11725)
@@ -163,5 +163,5 @@
try:
m = int(d) - d
- except (OverflowError, InvalidOperation):
+ except (ValueError, OverflowError, InvalidOperation):
return input_val
Index: /django/branches/soc2009/model-validation/django/template/__init__.py
===================================================================
--- /django/branches/soc2009/model-validation/django/template/__init__.py (revision 10519)
+++ /django/branches/soc2009/model-validation/django/template/__init__.py (revision 11725)
@@ -943,6 +943,12 @@
t = get_template(file_name)
self.nodelist = t.nodelist
- return self.nodelist.render(context_class(dict,
- autoescape=context.autoescape))
+ new_context = context_class(dict, autoescape=context.autoescape)
+ # Copy across the CSRF token, if present, because inclusion
+ # tags are often used for forms, and we need instructions
+ # for using CSRF protection to be as simple as possible.
+ csrf_token = context.get('csrf_token', None)
+ if csrf_token is not None:
+ new_context['csrf_token'] = csrf_token
+ return self.nodelist.render(new_context)
compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
Index: /django/branches/soc2009/model-validation/django/template/defaulttags.py
===================================================================
--- /django/branches/soc2009/model-validation/django/template/defaulttags.py (revision 11395)
+++ /django/branches/soc2009/model-validation/django/template/defaulttags.py (revision 11725)
@@ -37,4 +37,21 @@
def render(self, context):
return ''
+
+class CsrfTokenNode(Node):
+ def render(self, context):
+ csrf_token = context.get('csrf_token', None)
+ if csrf_token:
+ if csrf_token == 'NOTPROVIDED':
+ return mark_safe(u"")
+ else:
+ return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % (csrf_token))
+ else:
+ # It's very probable that the token is missing because of
+ # misconfiguration, so we raise a warning
+ from django.conf import settings
+ if settings.DEBUG:
+ import warnings
+ warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.")
+ return u''
class CycleNode(Node):
@@ -523,4 +540,8 @@
return node
cycle = register.tag(cycle)
+
+def csrf_token(parser, token):
+ return CsrfTokenNode()
+register.tag(csrf_token)
def debug(parser, token):
Index: /django/branches/soc2009/model-validation/django/template/context.py
===================================================================
--- /django/branches/soc2009/model-validation/django/template/context.py (revision 11395)
+++ /django/branches/soc2009/model-validation/django/template/context.py (revision 11725)
@@ -2,5 +2,10 @@
from django.utils.importlib import import_module
+# Cache of actual callables.
_standard_context_processors = None
+# We need the CSRF processor no matter what the user has in their settings,
+# because otherwise it is a security vulnerability, and we can't afford to leave
+# this to human error or failure to read migration instructions.
+_builtin_context_processors = ('django.core.context_processors.csrf',)
class ContextPopException(Exception):
@@ -76,5 +81,8 @@
if _standard_context_processors is None:
processors = []
- for path in settings.TEMPLATE_CONTEXT_PROCESSORS:
+ collect = []
+ collect.extend(_builtin_context_processors)
+ collect.extend(settings.TEMPLATE_CONTEXT_PROCESSORS)
+ for path in collect:
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
Index: /django/branches/soc2009/model-validation/django/middleware/csrf.py
===================================================================
--- /django/branches/soc2009/model-validation/django/middleware/csrf.py (revision 11725)
+++ /django/branches/soc2009/model-validation/django/middleware/csrf.py (revision 11725)
@@ -0,0 +1,265 @@
+"""
+Cross Site Request Forgery Middleware.
+
+This module provides a middleware that implements protection
+against request forgeries from other sites.
+"""
+
+import itertools
+import re
+import random
+
+from django.conf import settings
+from django.core.urlresolvers import get_callable
+from django.utils.cache import patch_vary_headers
+from django.utils.hashcompat import md5_constructor
+from django.utils.safestring import mark_safe
+
+_POST_FORM_RE = \
+ re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
+
+_HTML_TYPES = ('text/html', 'application/xhtml+xml')
+
+# Use the system (hardware-based) random number generator if it exists.
+if hasattr(random, 'SystemRandom'):
+ randrange = random.SystemRandom().randrange
+else:
+ randrange = random.randrange
+_MAX_CSRF_KEY = 18446744073709551616L # 2 << 63
+
+def _get_failure_view():
+ """
+ Returns the view to be used for CSRF rejections
+ """
+ return get_callable(settings.CSRF_FAILURE_VIEW)
+
+def _get_new_csrf_key():
+ return md5_constructor("%s%s"
+ % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
+
+def _make_legacy_session_token(session_id):
+ return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
+
+def get_token(request):
+ """
+ Returns the the CSRF token required for a POST form.
+
+ A side effect of calling this function is to make the the csrf_protect
+ decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
+ header to the outgoing response. For this reason, you may need to use this
+ function lazily, as is done by the csrf context processor.
+ """
+ request.META["CSRF_COOKIE_USED"] = True
+ return request.META.get("CSRF_COOKIE", None)
+
+class CsrfViewMiddleware(object):
+ """
+ Middleware that requires a present and correct csrfmiddlewaretoken
+ for POST requests that have a CSRF cookie, and sets an outgoing
+ CSRF cookie.
+
+ This middleware should be used in conjunction with the csrf_token template
+ tag.
+ """
+ def process_view(self, request, callback, callback_args, callback_kwargs):
+ if getattr(callback, 'csrf_exempt', False):
+ return None
+
+ if getattr(request, 'csrf_processing_done', False):
+ return None
+
+ reject = lambda s: _get_failure_view()(request, reason=s)
+ def accept():
+ # Avoid checking the request twice by adding a custom attribute to
+ # request. This will be relevant when both decorator and middleware
+ # are used.
+ request.csrf_processing_done = True
+ return None
+
+ # If the user doesn't have a CSRF cookie, generate one and store it in the
+ # request, so it's available to the view. We'll store it in a cookie when
+ # we reach the response.
+ try:
+ request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME]
+ cookie_is_new = False
+ except KeyError:
+ # No cookie, so create one. This will be sent with the next
+ # response.
+ request.META["CSRF_COOKIE"] = _get_new_csrf_key()
+ # Set a flag to allow us to fall back and allow the session id in
+ # place of a CSRF cookie for this request only.
+ cookie_is_new = True
+
+ if request.method == 'POST':
+ if getattr(request, '_dont_enforce_csrf_checks', False):
+ # Mechanism to turn off CSRF checks for test suite. It comes after
+ # the creation of CSRF cookies, so that everything else continues to
+ # work exactly the same (e.g. cookies are sent etc), but before the
+ # any branches that call reject()
+ return accept()
+
+ if request.is_ajax():
+ # .is_ajax() is based on the presence of X-Requested-With. In
+ # the context of a browser, this can only be sent if using
+ # XmlHttpRequest. Browsers implement careful policies for
+ # XmlHttpRequest:
+ #
+ # * Normally, only same-domain requests are allowed.
+ #
+ # * Some browsers (e.g. Firefox 3.5 and later) relax this
+ # carefully:
+ #
+ # * if it is a 'simple' GET or POST request (which can
+ # include no custom headers), it is allowed to be cross
+ # domain. These requests will not be recognized as AJAX.
+ #
+ # * if a 'preflight' check with the server confirms that the
+ # server is expecting and allows the request, cross domain
+ # requests even with custom headers are allowed. These
+ # requests will be recognized as AJAX, but can only get
+ # through when the developer has specifically opted in to
+ # allowing the cross-domain POST request.
+ #
+ # So in all cases, it is safe to allow these requests through.
+ return accept()
+
+ if request.is_secure():
+ # Strict referer checking for HTTPS
+ referer = request.META.get('HTTP_REFERER')
+ if referer is None:
+ return reject("Referer checking failed - no Referer.")
+
+ # The following check ensures that the referer is HTTPS,
+ # the domains match and the ports match. This might be too strict.
+ good_referer = 'https://%s/' % request.get_host()
+ if not referer.startswith(good_referer):
+ return reject("Referer checking failed - %s does not match %s." %
+ (referer, good_referer))
+
+ # If the user didn't already have a CSRF cookie, then fall back to
+ # the Django 1.1 method (hash of session ID), so a request is not
+ # rejected if the form was sent to the user before upgrading to the
+ # Django 1.2 method (session independent nonce)
+ if cookie_is_new:
+ try:
+ session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
+ csrf_token = _make_legacy_session_token(session_id)
+ except KeyError:
+ # No CSRF cookie and no session cookie. For POST requests,
+ # we insist on a CSRF cookie, and in this way we can avoid
+ # all CSRF attacks, including login CSRF.
+ return reject("No CSRF or session cookie.")
+ else:
+ csrf_token = request.META["CSRF_COOKIE"]
+
+ # check incoming token
+ request_csrf_token = request.POST.get('csrfmiddlewaretoken', None)
+ if request_csrf_token != csrf_token:
+ if cookie_is_new:
+ # probably a problem setting the CSRF cookie
+ return reject("CSRF cookie not set.")
+ else:
+ return reject("CSRF token missing or incorrect.")
+
+ return accept()
+
+ def process_response(self, request, response):
+ if getattr(response, 'csrf_processing_done', False):
+ return response
+
+ # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
+ # never called, probaby because a request middleware returned a response
+ # (for example, contrib.auth redirecting to a login page).
+ if request.META.get("CSRF_COOKIE") is None:
+ return response
+
+ if not request.META.get("CSRF_COOKIE_USED", False):
+ return response
+
+ # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
+ response.set_cookie(settings.CSRF_COOKIE_NAME,
+ request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
+ domain=settings.CSRF_COOKIE_DOMAIN)
+ # Content varies with the CSRF cookie, so set the Vary header.
+ patch_vary_headers(response, ('Cookie',))
+ response.csrf_processing_done = True
+ return response
+
+class CsrfResponseMiddleware(object):
+ """
+ DEPRECATED
+ Middleware that post-processes a response to add a csrfmiddlewaretoken.
+
+ This exists for backwards compatibility and as an interim measure until
+ applications are converted to using use the csrf_token template tag
+ instead. It will be removed in Django 1.4.
+ """
+ def __init__(self):
+ import warnings
+ warnings.warn(
+ "CsrfResponseMiddleware and CsrfMiddleware are deprecated; use CsrfViewMiddleware and the template tag instead (see CSRF documentation).",
+ PendingDeprecationWarning
+ )
+
+ def process_response(self, request, response):
+ if getattr(response, 'csrf_exempt', False):
+ return response
+
+ if response['Content-Type'].split(';')[0] in _HTML_TYPES:
+ csrf_token = get_token(request)
+ # If csrf_token is None, we have no token for this request, which probably
+ # means that this is a response from a request middleware.
+ if csrf_token is None:
+ return response
+
+ # ensure we don't add the 'id' attribute twice (HTML validity)
+ idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
+ itertools.repeat(''))
+ def add_csrf_field(match):
+ """Returns the matched <form> tag plus the added <input> element"""
+ return mark_safe(match.group() + "<div style='display:none;'>" + \
+ "<input type='hidden' " + idattributes.next() + \
+ " name='csrfmiddlewaretoken' value='" + csrf_token + \
+ "' /></div>")
+
+ # Modify any POST forms
+ response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content)
+ if n > 0:
+ # Content varies with the CSRF cookie, so set the Vary header.
+ patch_vary_headers(response, ('Cookie',))
+
+ # Since the content has been modified, any Etag will now be
+ # incorrect. We could recalculate, but only if we assume that
+ # the Etag was set by CommonMiddleware. The safest thing is just
+ # to delete. See bug #9163
+ del response['ETag']
+ return response
+
+class CsrfMiddleware(object):
+ """
+ Django middleware that adds protection against Cross Site
+ Request Forgeries by adding hidden form fields to POST forms and
+ checking requests for the correct value.
+
+ CsrfMiddleware uses two middleware, CsrfViewMiddleware and
+ CsrfResponseMiddleware, which can be used independently. It is recommended
+ to use only CsrfViewMiddleware and use the csrf_token template tag in
+ templates for inserting the token.
+ """
+ # We can't just inherit from CsrfViewMiddleware and CsrfResponseMiddleware
+ # because both have process_response methods.
+ def __init__(self):
+ self.response_middleware = CsrfResponseMiddleware()
+ self.view_middleware = CsrfViewMiddleware()
+
+ def process_response(self, request, resp):
+ # We must do the response post-processing first, because that calls
+ # get_token(), which triggers a flag saying that the CSRF cookie needs
+ # to be sent (done in CsrfViewMiddleware.process_response)
+ resp2 = self.response_middleware.process_response(request, resp)
+ return self.view_middleware.process_response(request, resp2)
+
+ def process_view(self, request, callback, callback_args, callback_kwargs):
+ return self.view_middleware.process_view(request, callback, callback_args,
+ callback_kwargs)
+
Index: /django/branches/soc2009/model-validation/tests/modeltests/invalid_models/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/invalid_models/models.py (revision 10456)
+++ /django/branches/soc2009/model-validation/tests/modeltests/invalid_models/models.py (revision 11725)
@@ -182,4 +182,5 @@
""" Model to test for unique ManyToManyFields, which are invalid. """
unique_people = models.ManyToManyField( Person, unique=True )
+
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
Index: /django/branches/soc2009/model-validation/tests/modeltests/m2m_through/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/m2m_through/models.py (revision 8571)
+++ /django/branches/soc2009/model-validation/tests/modeltests/m2m_through/models.py (revision 11725)
@@ -134,5 +134,5 @@
Traceback (most recent call last):
...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
# Remove has similar complications, and is not provided either.
@@ -161,5 +161,5 @@
Traceback (most recent call last):
...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
# Let's re-save those instances that we've cleared.
@@ -185,5 +185,5 @@
Traceback (most recent call last):
...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
# Remove has similar complications, and is not provided either.
@@ -210,5 +210,5 @@
Traceback (most recent call last):
...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
# Let's re-save those instances that we've cleared.
Index: /django/branches/soc2009/model-validation/tests/modeltests/lookup/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/lookup/models.py (revision 9649)
+++ /django/branches/soc2009/model-validation/tests/modeltests/lookup/models.py (revision 11725)
@@ -18,4 +18,8 @@
__test__ = {'API_TESTS': r"""
+# We can use .exists() to check that there are none yet
+>>> Article.objects.exists()
+False
+
# Create a couple of Articles.
>>> from datetime import datetime
@@ -34,4 +38,8 @@
>>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
>>> a7.save()
+
+# There should be some now!
+>>> Article.objects.exists()
+True
"""}
Index: /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/publication.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/publication.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/publication.py (revision 11725)
@@ -0,0 +1,7 @@
+from django.db import models
+
+class Publication(models.Model):
+ title = models.CharField(max_length=30)
+
+ class Meta:
+ app_label = 'model_package'
Index: /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/__init__.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/__init__.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/__init__.py (revision 11725)
@@ -0,0 +1,3 @@
+# Import all the models from subpackages
+from article import Article
+from publication import Publication
Index: /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/article.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/article.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/modeltests/model_package/models/article.py (revision 11725)
@@ -0,0 +1,10 @@
+from django.db import models
+from django.contrib.sites.models import Site
+
+class Article(models.Model):
+ sites = models.ManyToManyField(Site)
+ headline = models.CharField(max_length=100)
+ publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,)
+
+ class Meta:
+ app_label = 'model_package'
Index: /django/branches/soc2009/model-validation/tests/modeltests/model_package/__init__.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/model_package/__init__.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/modeltests/model_package/__init__.py (revision 11725)
@@ -0,0 +1,1 @@
+
Index: /django/branches/soc2009/model-validation/tests/modeltests/model_package/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/modeltests/model_package/tests.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/modeltests/model_package/tests.py (revision 11725)
@@ -0,0 +1,34 @@
+"""
+>>> from models.publication import Publication
+>>> from models.article import Article
+>>> from django.contrib.auth.views import Site
+
+>>> p = Publication(title="FooBar")
+>>> p.save()
+>>> p
+<Publication: Publication object>
+
+>>> from django.contrib.sites.models import Site
+>>> current_site = Site.objects.get_current()
+>>> current_site
+<Site: example.com>
+
+# Regression for #12168: models split into subpackages still get M2M tables
+
+>>> a = Article(headline="a foo headline")
+>>> a.save()
+>>> a.publications.add(p)
+>>> a.sites.add(current_site)
+>>> a.save()
+
+>>> a = Article.objects.get(id=1)
+>>> a
+<Article: Article object>
+>>> a.id
+1
+>>> a.sites.count()
+1
+
+"""
+
+
Index: /django/branches/soc2009/model-validation/tests/regressiontests/model_inheritance_regress/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/model_inheritance_regress/models.py (revision 9971)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/model_inheritance_regress/models.py (revision 11725)
@@ -110,4 +110,34 @@
return "PK = %d, base_name = %s, derived_name = %s" \
% (self.customPK, self.base_name, self.derived_name)
+
+# Check that abstract classes don't get m2m tables autocreated.
+class Person(models.Model):
+ name = models.CharField(max_length=100)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+class AbstractEvent(models.Model):
+ name = models.CharField(max_length=100)
+ attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
+
+ class Meta:
+ abstract = True
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+class BirthdayParty(AbstractEvent):
+ pass
+
+class BachelorParty(AbstractEvent):
+ pass
+
+class MessyBachelorParty(BachelorParty):
+ pass
__test__ = {'API_TESTS':"""
@@ -319,4 +349,40 @@
"parent"
+# Check that many-to-many relations defined on an abstract base class
+# are correctly inherited (and created) on the child class.
+>>> p1 = Person.objects.create(name='Alice')
+>>> p2 = Person.objects.create(name='Bob')
+>>> p3 = Person.objects.create(name='Carol')
+>>> p4 = Person.objects.create(name='Dave')
+
+>>> birthday = BirthdayParty.objects.create(name='Birthday party for Alice')
+>>> birthday.attendees = [p1, p3]
+
+>>> bachelor = BachelorParty.objects.create(name='Bachelor party for Bob')
+>>> bachelor.attendees = [p2, p4]
+
+>>> print p1.birthdayparty_set.all()
+[<BirthdayParty: Birthday party for Alice>]
+
+>>> print p1.bachelorparty_set.all()
+[]
+
+>>> print p2.bachelorparty_set.all()
+[<BachelorParty: Bachelor party for Bob>]
+
+# Check that a subclass of a subclass of an abstract model
+# doesn't get it's own accessor.
+>>> p2.messybachelorparty_set.all()
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'messybachelorparty_set'
+
+# ... but it does inherit the m2m from it's parent
+>>> messy = MessyBachelorParty.objects.create(name='Bachelor party for Dave')
+>>> messy.attendees = [p4]
+
+>>> p4.bachelorparty_set.all()
+[<BachelorParty: Bachelor party for Bob>, <BachelorParty: Bachelor party for Dave>]
+
"""}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/cache/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/cache/tests.py (revision 10335)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/cache/tests.py (revision 11725)
@@ -17,4 +17,5 @@
from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key
from django.utils.hashcompat import md5_constructor
+from regressiontests.cache.models import Poll, expensive_calculation
# functions/classes for complex data type tests
@@ -211,4 +212,45 @@
self.cache.set("stuff", stuff)
self.assertEqual(self.cache.get("stuff"), stuff)
+
+ def test_cache_read_for_model_instance(self):
+ # Don't want fields with callable as default to be called on cache read
+ expensive_calculation.num_runs = 0
+ Poll.objects.all().delete()
+ my_poll = Poll.objects.create(question="Well?")
+ self.assertEqual(Poll.objects.count(), 1)
+ pub_date = my_poll.pub_date
+ self.cache.set('question', my_poll)
+ cached_poll = self.cache.get('question')
+ self.assertEqual(cached_poll.pub_date, pub_date)
+ # We only want the default expensive calculation run once
+ self.assertEqual(expensive_calculation.num_runs, 1)
+
+ def test_cache_write_for_model_instance_with_deferred(self):
+ # Don't want fields with callable as default to be called on cache write
+ expensive_calculation.num_runs = 0
+ Poll.objects.all().delete()
+ my_poll = Poll.objects.create(question="What?")
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ defer_qs = Poll.objects.all().defer('question')
+ self.assertEqual(defer_qs.count(), 1)
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ self.cache.set('deferred_queryset', defer_qs)
+ # cache set should not re-evaluate default functions
+ self.assertEqual(expensive_calculation.num_runs, 1)
+
+ def test_cache_read_for_model_instance_with_deferred(self):
+ # Don't want fields with callable as default to be called on cache read
+ expensive_calculation.num_runs = 0
+ Poll.objects.all().delete()
+ my_poll = Poll.objects.create(question="What?")
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ defer_qs = Poll.objects.all().defer('question')
+ self.assertEqual(defer_qs.count(), 1)
+ self.cache.set('deferred_queryset', defer_qs)
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ runs_before_cache_read = expensive_calculation.num_runs
+ cached_polls = self.cache.get('deferred_queryset')
+ # We only want the default expensive calculation run on creation and set
+ self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read)
def test_expiration(self):
Index: /django/branches/soc2009/model-validation/tests/regressiontests/cache/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/cache/models.py (revision 5876)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/cache/models.py (revision 11725)
@@ -0,0 +1,11 @@
+from django.db import models
+from datetime import datetime
+
+def expensive_calculation():
+ expensive_calculation.num_runs += 1
+ return datetime.now()
+
+class Poll(models.Model):
+ question = models.CharField(max_length=200)
+ answer = models.CharField(max_length=200)
+ pub_date = models.DateTimeField('date published', default=expensive_calculation)
Index: /django/branches/soc2009/model-validation/tests/regressiontests/admin_views/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/admin_views/tests.py (revision 11617)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/admin_views/tests.py (revision 11725)
@@ -886,6 +886,7 @@
# main form submit button = 1
# search field and search submit button = 2
- # 6 + 2 + 1 + 2 = 11 inputs
- self.failUnlessEqual(response.content.count("<input"), 15)
+ # CSRF field = 1
+ # 6 + 2 + 4 + 1 + 2 + 1 = 16 inputs
+ self.failUnlessEqual(response.content.count("<input"), 16)
# 1 select per object = 3 selects
self.failUnlessEqual(response.content.count("<select"), 4)
@@ -1141,4 +1142,14 @@
"Found an unexpected action toggle checkboxbox in response"
)
+ self.assert_('action-checkbox-column' not in response.content,
+ "Found unexpected action-checkbox-column class in response")
+
+ def test_action_column_class(self):
+ "Tests that the checkbox column class is present in the response"
+ response = self.client.get('/test_admin/admin/admin_views/subscriber/')
+ self.assertNotEquals(response.context["action_form"], None)
+ self.assert_('action-checkbox-column' in response.content,
+ "Expected an action-checkbox-column in response")
+
def test_multiple_actions_form(self):
Index: /django/branches/soc2009/model-validation/tests/regressiontests/views/tests/generic/date_based.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/views/tests/generic/date_based.py (revision 10556)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/views/tests/generic/date_based.py (revision 11725)
@@ -101,5 +101,5 @@
now = datetime.now()
prev_month = now.date().replace(day=1)
- if prev_month.month == 11:
+ if prev_month.month == 1:
prev_month = prev_month.replace(year=prev_month.year-1, month=12)
else:
Index: /django/branches/soc2009/model-validation/tests/regressiontests/admin_validation/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/admin_validation/models.py (revision 10668)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/admin_validation/models.py (revision 11725)
@@ -5,6 +5,8 @@
from django.db import models
+
class Album(models.Model):
title = models.CharField(max_length=150)
+
class Song(models.Model):
@@ -18,9 +20,17 @@
return self.title
+
+class TwoAlbumFKAndAnE(models.Model):
+ album1 = models.ForeignKey(Album, related_name="album1_set")
+ album2 = models.ForeignKey(Album, related_name="album2_set")
+ e = models.CharField(max_length=1)
+
+
+
__test__ = {'API_TESTS':"""
>>> from django import forms
>>> from django.contrib import admin
->>> from django.contrib.admin.validation import validate
+>>> from django.contrib.admin.validation import validate, validate_inline
# Regression test for #8027: custom ModelForms with fields/fieldsets
@@ -59,3 +69,30 @@
ImproperlyConfigured: SongInline cannot exclude the field 'album' - this is the foreign key to the parent model Album.
+# Regression test for #11709 - when testing for fk excluding (when exclude is
+# given) make sure fk_name is honored or things blow up when there is more
+# than one fk to the parent model.
+
+>>> class TwoAlbumFKAndAnEInline(admin.TabularInline):
+... model = TwoAlbumFKAndAnE
+... exclude = ("e",)
+... fk_name = "album1"
+
+>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+
+# Ensure inlines validate that they can be used correctly.
+
+>>> class TwoAlbumFKAndAnEInline(admin.TabularInline):
+... model = TwoAlbumFKAndAnE
+
+>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+Traceback (most recent call last):
+ ...
+Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'regressiontests.admin_validation.models.Album'>
+
+>>> class TwoAlbumFKAndAnEInline(admin.TabularInline):
+... model = TwoAlbumFKAndAnE
+... fk_name = "album1"
+
+>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+
"""}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/test_client_regress/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/test_client_regress/models.py (revision 10513)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/test_client_regress/models.py (revision 11725)
@@ -575,4 +575,21 @@
self.assertEqual(response.content, 'request method: DELETE')
+class RequestMethodStringDataTests(TestCase):
+ def test_post(self):
+ "Request a view with string data via request method POST"
+ # Regression test for #11371
+ data = u'{"test": "json"}'
+ response = self.client.post('/test_client_regress/request_methods/', data=data, content_type='application/json')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: POST')
+
+ def test_put(self):
+ "Request a view with string data via request method PUT"
+ # Regression test for #11371
+ data = u'{"test": "json"}'
+ response = self.client.put('/test_client_regress/request_methods/', data=data, content_type='application/json')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: PUT')
+
class QueryStringTests(TestCase):
def test_get_like_requests(self):
Index: /django/branches/soc2009/model-validation/tests/regressiontests/comment_tests/tests/moderation_view_tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/comment_tests/tests/moderation_view_tests.py (revision 9756)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/comment_tests/tests/moderation_view_tests.py (revision 11725)
@@ -160,30 +160,31 @@
self.assertTemplateUsed(response, "comments/approved.html")
+class AdminActionsTests(CommentTestCase):
+ urls = "regressiontests.comment_tests.urls_admin"
+
+ def setUp(self):
+ super(AdminActionsTests, self).setUp()
+
+ # Make "normaluser" a moderator
+ u = User.objects.get(username="normaluser")
+ u.is_staff = True
+ perms = Permission.objects.filter(
+ content_type__app_label = 'comments',
+ codename__endswith = 'comment'
+ )
+ for perm in perms:
+ u.user_permissions.add(perm)
+ u.save()
-class ModerationQueueTests(CommentTestCase):
+ def testActionsNonModerator(self):
+ comments = self.createSomeComments()
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/admin/comments/comment/")
+ self.assertEquals("approve_comments" in response.content, False)
- def testModerationQueuePermissions(self):
- """Only moderators can view the moderation queue"""
- self.client.login(username="normaluser", password="normaluser")
- response = self.client.get("/moderate/")
- self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/moderate/")
-
- makeModerator("normaluser")
- response = self.client.get("/moderate/")
- self.assertEqual(response.status_code, 200)
-
- def testModerationQueueContents(self):
- """Moderation queue should display non-public, non-removed comments."""
- c1, c2, c3, c4 = self.createSomeComments()
+ def testActionsModerator(self):
+ comments = self.createSomeComments()
makeModerator("normaluser")
self.client.login(username="normaluser", password="normaluser")
-
- c1.is_public = c2.is_public = False
- c1.save(); c2.save()
- response = self.client.get("/moderate/")
- self.assertEqual(list(response.context[0]["comments"]), [c1, c2])
-
- c2.is_removed = True
- c2.save()
- response = self.client.get("/moderate/")
- self.assertEqual(list(response.context[0]["comments"]), [c1])
+ response = self.client.get("/admin/comments/comment/")
+ self.assertEquals("approve_comments" in response.content, True)
Index: /django/branches/soc2009/model-validation/tests/regressiontests/comment_tests/urls_admin.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/comment_tests/urls_admin.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/comment_tests/urls_admin.py (revision 11725)
@@ -0,0 +1,13 @@
+from django.conf.urls.defaults import *
+from django.contrib import admin
+from django.contrib.comments.admin import CommentsAdmin
+from django.contrib.comments.models import Comment
+
+# Make a new AdminSite to avoid picking up the deliberately broken admin
+# modules in other tests.
+admin_site = admin.AdminSite()
+admin_site.register(Comment, CommentsAdmin)
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin_site.urls)),
+)
Index: /django/branches/soc2009/model-validation/tests/regressiontests/m2m_through_regress/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/m2m_through_regress/models.py (revision 11233)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/m2m_through_regress/models.py (revision 11725)
@@ -85,20 +85,20 @@
Traceback (most recent call last):
...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead.
>>> roll.members = []
Traceback (most recent call last):
...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead.
>>> rock.members.create(name='Anne')
Traceback (most recent call last):
...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead.
>>> bob.group_set.create(name='Funk')
Traceback (most recent call last):
...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead.
# Now test that the intermediate with a relationship outside
Index: /django/branches/soc2009/model-validation/tests/regressiontests/dateformat/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/dateformat/tests.py (revision 10716)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/dateformat/tests.py (revision 11725)
@@ -1,90 +1,92 @@
-r"""
->>> format(my_birthday, '')
-u''
->>> format(my_birthday, 'a')
-u'p.m.'
->>> format(my_birthday, 'A')
-u'PM'
->>> format(my_birthday, 'd')
-u'08'
->>> format(my_birthday, 'j')
-u'8'
->>> format(my_birthday, 'l')
-u'Sunday'
->>> format(my_birthday, 'L')
-u'False'
->>> format(my_birthday, 'm')
-u'07'
->>> format(my_birthday, 'M')
-u'Jul'
->>> format(my_birthday, 'b')
-u'jul'
->>> format(my_birthday, 'n')
-u'7'
->>> format(my_birthday, 'N')
-u'July'
->>> no_tz or format(my_birthday, 'O') == '+0100'
-True
->>> format(my_birthday, 'P')
-u'10 p.m.'
->>> no_tz or format(my_birthday, 'r') == 'Sun, 8 Jul 1979 22:00:00 +0100'
-True
->>> format(my_birthday, 's')
-u'00'
->>> format(my_birthday, 'S')
-u'th'
->>> format(my_birthday, 't')
-u'31'
->>> no_tz or format(my_birthday, 'T') == 'CET'
-True
->>> no_tz or format(my_birthday, 'U') == '300315600'
-True
->>> format(my_birthday, 'w')
-u'0'
->>> format(my_birthday, 'W')
-u'27'
->>> format(my_birthday, 'y')
-u'79'
->>> format(my_birthday, 'Y')
-u'1979'
->>> format(my_birthday, 'z')
-u'189'
->>> no_tz or format(my_birthday, 'Z') == '3600'
-True
-
->>> no_tz or format(summertime, 'I') == '1'
-True
->>> no_tz or format(summertime, 'O') == '+0200'
-True
->>> no_tz or format(wintertime, 'I') == '0'
-True
->>> no_tz or format(wintertime, 'O') == '+0100'
-True
-
->>> format(my_birthday, r'Y z \C\E\T')
-u'1979 189 CET'
-
->>> format(my_birthday, r'jS o\f F')
-u'8th of July'
-
->>> format(the_future, r'Y')
-u'2100'
-"""
from django.utils import dateformat, translation
+from unittest import TestCase
import datetime, os, time
-format = dateformat.format
-os.environ['TZ'] = 'Europe/Copenhagen'
-translation.activate('en-us')
+class DateFormatTests(TestCase):
+ def setUp(self):
+ self.old_TZ = os.environ.get('TZ')
+ os.environ['TZ'] = 'Europe/Copenhagen'
+ translation.activate('en-us')
-try:
- time.tzset()
- no_tz = False
-except AttributeError:
- no_tz = True
+ try:
+ # Check if a timezone has been set
+ time.tzset()
+ self.tz_tests = True
+ except AttributeError:
+ # No timezone available. Don't run the tests that require a TZ
+ self.tz_tests = False
-my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
-summertime = datetime.datetime(2005, 10, 30, 1, 00)
-wintertime = datetime.datetime(2005, 10, 30, 4, 00)
-the_future = datetime.datetime(2100, 10, 25, 0, 00)
+ def tearDown(self):
+ if self.old_TZ is None:
+ del os.environ['TZ']
+ else:
+ os.environ['TZ'] = self.old_TZ
+
+ # Cleanup - force re-evaluation of TZ environment variable.
+ if self.tz_tests:
+ time.tzset()
+
+ def test_empty_format(self):
+ my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, ''), u'')
+
+ def test_am_pm(self):
+ my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, 'a'), u'p.m.')
+
+ def test_date_formats(self):
+ my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, 'A'), u'PM')
+ self.assertEquals(dateformat.format(my_birthday, 'd'), u'08')
+ self.assertEquals(dateformat.format(my_birthday, 'j'), u'8')
+ self.assertEquals(dateformat.format(my_birthday, 'l'), u'Sunday')
+ self.assertEquals(dateformat.format(my_birthday, 'L'), u'False')
+ self.assertEquals(dateformat.format(my_birthday, 'm'), u'07')
+ self.assertEquals(dateformat.format(my_birthday, 'M'), u'Jul')
+ self.assertEquals(dateformat.format(my_birthday, 'b'), u'jul')
+ self.assertEquals(dateformat.format(my_birthday, 'n'), u'7')
+ self.assertEquals(dateformat.format(my_birthday, 'N'), u'July')
+
+ def test_time_formats(self):
+ my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, 'P'), u'10 p.m.')
+ self.assertEquals(dateformat.format(my_birthday, 's'), u'00')
+ self.assertEquals(dateformat.format(my_birthday, 'S'), u'th')
+ self.assertEquals(dateformat.format(my_birthday, 't'), u'31')
+ self.assertEquals(dateformat.format(my_birthday, 'w'), u'0')
+ self.assertEquals(dateformat.format(my_birthday, 'W'), u'27')
+ self.assertEquals(dateformat.format(my_birthday, 'y'), u'79')
+ self.assertEquals(dateformat.format(my_birthday, 'Y'), u'1979')
+ self.assertEquals(dateformat.format(my_birthday, 'z'), u'189')
+
+ def test_dateformat(self):
+ my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, r'Y z \C\E\T'), u'1979 189 CET')
+
+ self.assertEquals(dateformat.format(my_birthday, r'jS o\f F'), u'8th of July')
+
+ def test_futuredates(self):
+ the_future = datetime.datetime(2100, 10, 25, 0, 00)
+ self.assertEquals(dateformat.format(the_future, r'Y'), u'2100')
+
+ def test_timezones(self):
+ my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
+ summertime = datetime.datetime(2005, 10, 30, 1, 00)
+ wintertime = datetime.datetime(2005, 10, 30, 4, 00)
+
+ if self.tz_tests:
+ self.assertEquals(dateformat.format(my_birthday, 'O'), u'+0100')
+ self.assertEquals(dateformat.format(my_birthday, 'r'), u'Sun, 8 Jul 1979 22:00:00 +0100')
+ self.assertEquals(dateformat.format(my_birthday, 'T'), u'CET')
+ self.assertEquals(dateformat.format(my_birthday, 'U'), u'300315600')
+ self.assertEquals(dateformat.format(my_birthday, 'Z'), u'3600')
+ self.assertEquals(dateformat.format(summertime, 'I'), u'1')
+ self.assertEquals(dateformat.format(summertime, 'O'), u'+0200')
+ self.assertEquals(dateformat.format(wintertime, 'I'), u'0')
+ self.assertEquals(dateformat.format(wintertime, 'O'), u'+0100')
Index: /django/branches/soc2009/model-validation/tests/regressiontests/signals_regress/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/signals_regress/models.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/signals_regress/models.py (revision 11725)
@@ -0,0 +1,89 @@
+"""
+Testing signals before/after saving and deleting.
+"""
+
+from django.db import models
+
+class Author(models.Model):
+ name = models.CharField(max_length=20)
+
+ def __unicode__(self):
+ return self.name
+
+class Book(models.Model):
+ name = models.CharField(max_length=20)
+ authors = models.ManyToManyField(Author)
+
+ def __unicode__(self):
+ return self.name
+
+def pre_save_test(signal, sender, instance, **kwargs):
+ print 'pre_save signal,', instance
+ if kwargs.get('raw'):
+ print 'Is raw'
+
+def post_save_test(signal, sender, instance, **kwargs):
+ print 'post_save signal,', instance
+ if 'created' in kwargs:
+ if kwargs['created']:
+ print 'Is created'
+ else:
+ print 'Is updated'
+ if kwargs.get('raw'):
+ print 'Is raw'
+
+def pre_delete_test(signal, sender, instance, **kwargs):
+ print 'pre_delete signal,', instance
+ print 'instance.id is not None: %s' % (instance.id != None)
+
+def post_delete_test(signal, sender, instance, **kwargs):
+ print 'post_delete signal,', instance
+ print 'instance.id is not None: %s' % (instance.id != None)
+
+__test__ = {'API_TESTS':"""
+
+# Save up the number of connected signals so that we can check at the end
+# that all the signals we register get properly unregistered (#9989)
+>>> pre_signals = (len(models.signals.pre_save.receivers),
+... len(models.signals.post_save.receivers),
+... len(models.signals.pre_delete.receivers),
+... len(models.signals.post_delete.receivers))
+
+>>> models.signals.pre_save.connect(pre_save_test)
+>>> models.signals.post_save.connect(post_save_test)
+>>> models.signals.pre_delete.connect(pre_delete_test)
+>>> models.signals.post_delete.connect(post_delete_test)
+
+>>> a1 = Author(name='Neal Stephenson')
+>>> a1.save()
+pre_save signal, Neal Stephenson
+post_save signal, Neal Stephenson
+Is created
+
+>>> b1 = Book(name='Snow Crash')
+>>> b1.save()
+pre_save signal, Snow Crash
+post_save signal, Snow Crash
+Is created
+
+# Assigning to m2m shouldn't generate an m2m signal
+>>> b1.authors = [a1]
+
+# Removing an author from an m2m shouldn't generate an m2m signal
+>>> b1.authors = []
+
+>>> models.signals.post_delete.disconnect(post_delete_test)
+>>> models.signals.pre_delete.disconnect(pre_delete_test)
+>>> models.signals.post_save.disconnect(post_save_test)
+>>> models.signals.pre_save.disconnect(pre_save_test)
+
+# Check that all our signals got disconnected properly.
+>>> post_signals = (len(models.signals.pre_save.receivers),
+... len(models.signals.post_save.receivers),
+... len(models.signals.pre_delete.receivers),
+... len(models.signals.post_delete.receivers))
+
+>>> pre_signals == post_signals
+True
+
+"""}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/views.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/views.py (revision 8202)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/views.py (revision 11725)
@@ -7,2 +7,31 @@
return render_to_response('context_processors/request_attrs.html',
RequestContext(request, {}, processors=[context_processors.request]))
+
+def auth_processor_no_attr_access(request):
+ r1 = render_to_response('context_processors/auth_attrs_no_access.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+ # *After* rendering, we check whether the session was accessed
+ return render_to_response('context_processors/auth_attrs_test_access.html',
+ {'session_accessed':request.session.accessed})
+
+def auth_processor_attr_access(request):
+ r1 = render_to_response('context_processors/auth_attrs_access.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+ return render_to_response('context_processors/auth_attrs_test_access.html',
+ {'session_accessed':request.session.accessed})
+
+def auth_processor_user(request):
+ return render_to_response('context_processors/auth_attrs_user.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def auth_processor_perms(request):
+ return render_to_response('context_processors/auth_attrs_perms.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def auth_processor_messages(request):
+ request.user.message_set.create(message="Message 1")
+ return render_to_response('context_processors/auth_attrs_messages.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def userpage(request):
+ pass
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/fixtures/context-processors-users.xml
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/fixtures/context-processors-users.xml (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/fixtures/context-processors-users.xml (revision 11725)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+</django-objects>
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/tests.py (revision 8202)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/tests.py (revision 11725)
@@ -4,6 +4,8 @@
from django.conf import settings
+from django.contrib.auth import authenticate
+from django.db.models import Q
from django.test import TestCase
-
+from django.template import Template
class RequestContextProcessorTests(TestCase):
@@ -37,2 +39,74 @@
response = self.client.post(url, {'path': '/blah/'})
self.assertContains(response, url)
+
+class AuthContextProcessorTests(TestCase):
+ """
+ Tests for the ``django.core.context_processors.auth`` processor
+ """
+ urls = 'regressiontests.context_processors.urls'
+ fixtures = ['context-processors-users.xml']
+
+ def test_session_not_accessed(self):
+ """
+ Tests that the session is not accessed simply by including
+ the auth context processor
+ """
+ response = self.client.get('/auth_processor_no_attr_access/')
+ self.assertContains(response, "Session not accessed")
+
+ def test_session_is_accessed(self):
+ """
+ Tests that the session is accessed if the auth context processor
+ is used and relevant attributes accessed.
+ """
+ response = self.client.get('/auth_processor_attr_access/')
+ self.assertContains(response, "Session accessed")
+
+ def test_perms_attrs(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/auth_processor_perms/')
+ self.assertContains(response, "Has auth permissions")
+
+ def test_message_attrs(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/auth_processor_messages/')
+ self.assertContains(response, "Message 1")
+
+ def test_user_attrs(self):
+ """
+ Test that the lazy objects returned behave just like the wrapped objects.
+ """
+ # These are 'functional' level tests for common use cases. Direct
+ # testing of the implementation (SimpleLazyObject) is in the 'utils'
+ # tests.
+ self.client.login(username='super', password='secret')
+ user = authenticate(username='super', password='secret')
+ response = self.client.get('/auth_processor_user/')
+ self.assertContains(response, "unicode: super")
+ self.assertContains(response, "id: 100")
+ self.assertContains(response, "username: super")
+ # bug #12037 is tested by the {% url %} in the template:
+ self.assertContains(response, "url: /userpage/super/")
+
+ # See if this object can be used for queries where a Q() comparing
+ # a user can be used with another Q() (in an AND or OR fashion).
+ # This simulates what a template tag might do with the user from the
+ # context. Note that we don't need to execute a query, just build it.
+ #
+ # The failure case (bug #12049) on Python 2.4 with a LazyObject-wrapped
+ # User is a fatal TypeError: "function() takes at least 2 arguments
+ # (0 given)" deep inside deepcopy().
+ #
+ # Python 2.5 and 2.6 succeeded, but logged internally caught exception
+ # spew:
+ #
+ # Exception RuntimeError: 'maximum recursion depth exceeded while
+ # calling a Python object' in <type 'exceptions.AttributeError'>
+ # ignored"
+ query = Q(user=response.context['user']) & Q(someflag=True)
+
+ # Tests for user equality. This is hard because User defines
+ # equality in a non-duck-typing way
+ # See bug #12060
+ self.assertEqual(response.context['user'], user)
+ self.assertEqual(user, response.context['user'])
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/urls.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/urls.py (revision 8202)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/urls.py (revision 11725)
@@ -6,3 +6,9 @@
urlpatterns = patterns('',
(r'^request_attrs/$', views.request_processor),
+ (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access),
+ (r'^auth_processor_attr_access/$', views.auth_processor_attr_access),
+ (r'^auth_processor_user/$', views.auth_processor_user),
+ (r'^auth_processor_perms/$', views.auth_processor_perms),
+ (r'^auth_processor_messages/$', views.auth_processor_messages),
+ url(r'^userpage/(.+)/$', views.userpage, name="userpage"),
)
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html (revision 11725)
@@ -0,0 +1,4 @@
+unicode: {{ user }}
+id: {{ user.id }}
+username: {{ user.username }}
+url: {% url userpage user %}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html (revision 11725)
@@ -0,0 +1,1 @@
+{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html (revision 11725)
@@ -0,0 +1,1 @@
+{{ user }}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html (revision 11725)
@@ -0,0 +1,1 @@
+{% if perms.auth %}Has auth permissions{% endif %}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html (revision 11725)
@@ -0,0 +1,1 @@
+{% for m in messages %}{{ m }}{% endfor %}
Index: /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html (revision 11725)
@@ -0,0 +1,1 @@
+
Index: /django/branches/soc2009/model-validation/tests/regressiontests/model_formsets_regress/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/model_formsets_regress/tests.py (revision 10828)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/model_formsets_regress/tests.py (revision 11725)
@@ -141,2 +141,12 @@
else:
self.fail('Errors found on formset:%s' % form_set.errors)
+
+ def test_formset_with_none_instance(self):
+ "A formset with instance=None can be created. Regression for #11872"
+ Form = modelform_factory(User)
+ FormSet = inlineformset_factory(User, UserSite)
+
+ # Instantiate the Form and FormSet to prove
+ # you can create a formset with an instance of None
+ form = Form(instance=None)
+ formset = FormSet(instance=None)
Index: /django/branches/soc2009/model-validation/tests/regressiontests/csrf_tests/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/csrf_tests/tests.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/csrf_tests/tests.py (revision 11725)
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+
+from django.test import TestCase
+from django.http import HttpRequest, HttpResponse
+from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware
+from django.views.decorators.csrf import csrf_exempt
+from django.core.context_processors import csrf
+from django.contrib.sessions.middleware import SessionMiddleware
+from django.utils.importlib import import_module
+from django.conf import settings
+from django.template import RequestContext, Template
+
+# Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests
+def post_form_response():
+ resp = HttpResponse(content="""
+<html><body><form method="POST"><input type="text" /></form></body></html>
+""", mimetype="text/html")
+ return resp
+
+def post_form_response_non_html():
+ resp = post_form_response()
+ resp["Content-Type"] = "application/xml"
+ return resp
+
+def post_form_view(request):
+ """A view that returns a POST form (without a token)"""
+ return post_form_response()
+
+# Response/views used for template tag tests
+def _token_template():
+ return Template("{% csrf_token %}")
+
+def _render_csrf_token_template(req):
+ context = RequestContext(req, processors=[csrf])
+ template = _token_template()
+ return template.render(context)
+
+def token_view(request):
+ """A view that uses {% csrf_token %}"""
+ return HttpResponse(_render_csrf_token_template(request))
+
+def non_token_view_using_request_processor(request):
+ """
+ A view that doesn't use the token, but does use the csrf view processor.
+ """
+ context = RequestContext(request, processors=[csrf])
+ template = Template("")
+ return HttpResponse(template.render(context))
+
+class TestingHttpRequest(HttpRequest):
+ """
+ A version of HttpRequest that allows us to change some things
+ more easily
+ """
+ def is_secure(self):
+ return getattr(self, '_is_secure', False)
+
+class CsrfMiddlewareTest(TestCase):
+ _csrf_id = "1"
+
+ # This is a valid session token for this ID and secret key. This was generated using
+ # the old code that we're to be backwards-compatible with. Don't use the CSRF code
+ # to generate this hash, or we're merely testing the code against itself and not
+ # checking backwards-compatibility. This is also the output of (echo -n test1 | md5sum).
+ _session_token = "5a105e8b9d40e1329780d62ea2265d8a"
+ _session_id = "1"
+ _secret_key_for_session_test= "test"
+
+ def _get_GET_no_csrf_cookie_request(self):
+ return TestingHttpRequest()
+
+ def _get_GET_csrf_cookie_request(self):
+ req = TestingHttpRequest()
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id
+ return req
+
+ def _get_POST_csrf_cookie_request(self):
+ req = self._get_GET_csrf_cookie_request()
+ req.method = "POST"
+ return req
+
+ def _get_POST_no_csrf_cookie_request(self):
+ req = self._get_GET_no_csrf_cookie_request()
+ req.method = "POST"
+ return req
+
+ def _get_POST_request_with_token(self):
+ req = self._get_POST_csrf_cookie_request()
+ req.POST['csrfmiddlewaretoken'] = self._csrf_id
+ return req
+
+ def _get_POST_session_request_with_token(self):
+ req = self._get_POST_no_csrf_cookie_request()
+ req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
+ req.POST['csrfmiddlewaretoken'] = self._session_token
+ return req
+
+ def _get_POST_session_request_no_token(self):
+ req = self._get_POST_no_csrf_cookie_request()
+ req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
+ return req
+
+ def _check_token_present(self, response, csrf_id=None):
+ self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id))
+
+ # Check the post processing and outgoing cookie
+ def test_process_response_no_csrf_cookie(self):
+ """
+ When no prior CSRF cookie exists, check that the cookie is created and a
+ token is inserted.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ CsrfMiddleware().process_view(req, post_form_view, (), {})
+
+ resp = post_form_response()
+ resp_content = resp.content # needed because process_response modifies resp
+ resp2 = CsrfMiddleware().process_response(req, resp)
+
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertNotEqual(csrf_cookie, False)
+ self.assertNotEqual(resp_content, resp2.content)
+ self._check_token_present(resp2, csrf_cookie.value)
+ # Check the Vary header got patched correctly
+ self.assert_('Cookie' in resp2.get('Vary',''))
+
+ def test_process_response_no_csrf_cookie_view_only_get_token_used(self):
+ """
+ When no prior CSRF cookie exists, check that the cookie is created, even
+ if only CsrfViewMiddleware is used.
+ """
+ # This is checking that CsrfViewMiddleware has the cookie setting
+ # code. Most of the other tests use CsrfMiddleware.
+ req = self._get_GET_no_csrf_cookie_request()
+ # token_view calls get_token() indirectly
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertNotEqual(csrf_cookie, False)
+
+ def test_process_response_get_token_not_used(self):
+ """
+ Check that if get_token() is not called, the view middleware does not
+ add a cookie.
+ """
+ # This is important to make pages cacheable. Pages which do call
+ # get_token(), assuming they use the token, are not cacheable because
+ # the token is specific to the user
+ req = self._get_GET_no_csrf_cookie_request()
+ # non_token_view_using_request_processor does not call get_token(), but
+ # does use the csrf request processor. By using this, we are testing
+ # that the view processor is properly lazy and doesn't call get_token()
+ # until needed.
+ CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {})
+ resp = non_token_view_using_request_processor(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertEqual(csrf_cookie, False)
+
+ def test_process_response_existing_csrf_cookie(self):
+ """
+ Check that the token is inserted when a prior CSRF cookie exists
+ """
+ req = self._get_GET_csrf_cookie_request()
+ CsrfMiddleware().process_view(req, post_form_view, (), {})
+
+ resp = post_form_response()
+ resp_content = resp.content # needed because process_response modifies resp
+ resp2 = CsrfMiddleware().process_response(req, resp)
+ self.assertNotEqual(resp_content, resp2.content)
+ self._check_token_present(resp2)
+
+ def test_process_response_non_html(self):
+ """
+ Check the the post-processor does nothing for content-types not in _HTML_TYPES.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ CsrfMiddleware().process_view(req, post_form_view, (), {})
+ resp = post_form_response_non_html()
+ resp_content = resp.content # needed because process_response modifies resp
+ resp2 = CsrfMiddleware().process_response(req, resp)
+ self.assertEquals(resp_content, resp2.content)
+
+ def test_process_response_exempt_view(self):
+ """
+ Check that no post processing is done for an exempt view
+ """
+ req = self._get_POST_csrf_cookie_request()
+ resp = csrf_exempt(post_form_view)(req)
+ resp_content = resp.content
+ resp2 = CsrfMiddleware().process_response(req, resp)
+ self.assertEquals(resp_content, resp2.content)
+
+ # Check the request processing
+ def test_process_request_no_session_no_csrf_cookie(self):
+ """
+ Check that if neither a CSRF cookie nor a session cookie are present,
+ the middleware rejects the incoming request. This will stop login CSRF.
+ """
+ req = self._get_POST_no_csrf_cookie_request()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(403, req2.status_code)
+
+ def test_process_request_csrf_cookie_no_token(self):
+ """
+ Check that if a CSRF cookie is present but no token, the middleware
+ rejects the incoming request.
+ """
+ req = self._get_POST_csrf_cookie_request()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(403, req2.status_code)
+
+ def test_process_request_csrf_cookie_and_token(self):
+ """
+ Check that if both a cookie and a token is present, the middleware lets it through.
+ """
+ req = self._get_POST_request_with_token()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
+
+ def test_process_request_session_cookie_no_csrf_cookie_token(self):
+ """
+ When no CSRF cookie exists, but the user has a session, check that a token
+ using the session cookie as a legacy CSRF cookie is accepted.
+ """
+ orig_secret_key = settings.SECRET_KEY
+ settings.SECRET_KEY = self._secret_key_for_session_test
+ try:
+ req = self._get_POST_session_request_with_token()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
+ finally:
+ settings.SECRET_KEY = orig_secret_key
+
+ def test_process_request_session_cookie_no_csrf_cookie_no_token(self):
+ """
+ Check that if a session cookie is present but no token and no CSRF cookie,
+ the request is rejected.
+ """
+ req = self._get_POST_session_request_no_token()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(403, req2.status_code)
+
+ def test_process_request_csrf_cookie_no_token_exempt_view(self):
+ """
+ Check that if a CSRF cookie is present and no token, but the csrf_exempt
+ decorator has been applied to the view, the middleware lets it through
+ """
+ req = self._get_POST_csrf_cookie_request()
+ req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {})
+ self.assertEquals(None, req2)
+
+ def test_ajax_exemption(self):
+ """
+ Check that AJAX requests are automatically exempted.
+ """
+ req = self._get_POST_csrf_cookie_request()
+ req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
+
+ # Tests for the template tag method
+ def test_token_node_no_csrf_cookie(self):
+ """
+ Check that CsrfTokenNode works when no CSRF cookie is set
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ resp = token_view(req)
+ self.assertEquals(u"", resp.content)
+
+ def test_token_node_with_csrf_cookie(self):
+ """
+ Check that CsrfTokenNode works when a CSRF cookie is set
+ """
+ req = self._get_GET_csrf_cookie_request()
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ self._check_token_present(resp)
+
+ def test_token_node_with_new_csrf_cookie(self):
+ """
+ Check that CsrfTokenNode works when a CSRF cookie is created by
+ the middleware (when one was not already present)
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+ csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME]
+ self._check_token_present(resp, csrf_id=csrf_cookie.value)
+
+ def test_response_middleware_without_view_middleware(self):
+ """
+ Check that CsrfResponseMiddleware finishes without error if the view middleware
+ has not been called, as is the case if a request middleware returns a response.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ resp = post_form_view(req)
+ CsrfMiddleware().process_response(req, resp)
+
+ def test_https_bad_referer(self):
+ """
+ Test that a POST HTTPS request with a bad referer is rejected
+ """
+ req = self._get_POST_request_with_token()
+ req._is_secure = True
+ req.META['HTTP_HOST'] = 'www.example.com'
+ req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage'
+ req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
+ self.assertNotEqual(None, req2)
+ self.assertEquals(403, req2.status_code)
+
+ def test_https_good_referer(self):
+ """
+ Test that a POST HTTPS request with a good referer is accepted
+ """
+ req = self._get_POST_request_with_token()
+ req._is_secure = True
+ req.META['HTTP_HOST'] = 'www.example.com'
+ req.META['HTTP_REFERER'] = 'https://www.example.com/somepage'
+ req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
Index: /django/branches/soc2009/model-validation/tests/regressiontests/csrf_tests/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/csrf_tests/models.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/csrf_tests/models.py (revision 11725)
@@ -0,0 +1,1 @@
+# models.py file for tests to run.
Index: /django/branches/soc2009/model-validation/tests/regressiontests/mail/custombackend.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/mail/custombackend.py (revision 11725)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/mail/custombackend.py (revision 11725)
@@ -0,0 +1,15 @@
+"""A custom backend for testing."""
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+ def __init__(self, *args, **kwargs):
+ super(EmailBackend, self).__init__(*args, **kwargs)
+ self.test_outbox = []
+
+ def send_messages(self, email_messages):
+ # Messages are stored in a instance variable for testing.
+ self.test_outbox.extend(email_messages)
+ return len(email_messages)
Index: /django/branches/soc2009/model-validation/tests/regressiontests/mail/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/mail/tests.py (revision 11395)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/mail/tests.py (revision 11725)
@@ -1,9 +1,17 @@
# coding: utf-8
+
r"""
# Tests for the django.core.mail.
+>>> import os
+>>> import shutil
+>>> import tempfile
+>>> from StringIO import StringIO
>>> from django.conf import settings
>>> from django.core import mail
>>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
+>>> from django.core.mail import send_mail, send_mass_mail
+>>> from django.core.mail.backends.base import BaseEmailBackend
+>>> from django.core.mail.backends import console, dummy, locmem, filebased, smtp
>>> from django.utils.translation import ugettext_lazy
@@ -86,6 +94,4 @@
>>> len(mail.outbox)
1
->>> settings.ADMINS = old_admins
->>> settings.MANAGERS = old_managers
# Make sure we can manually set the From header (#9214)
@@ -95,4 +101,15 @@
>>> message['From']
'from@example.com'
+
+# Regression for #11144 - When a to/from/cc header contains unicode,
+# make sure the email addresses are parsed correctly (especially
+# with regards to commas)
[[BINARY DATA]]
+>>> email.message()['To']
+'=?utf-8?q?Firstname_S=C3=BCrname?= <to@example.com>, other@example.com'
+
[[BINARY DATA]]
+>>> email.message()['To']
+'=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com'
# Handle attachments within an multipart/alternative mail correctly (#9367)
@@ -139,3 +156,216 @@
...
+# Make sure that the console backend writes to stdout by default
+>>> connection = console.EmailBackend()
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email])
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject
+From: from@example.com
+To: to@example.com
+Date: ...
+Message-ID: ...
+
+Content
+-------------------------------------------------------------------------------
+1
+
+# Test that the console backend can be pointed at an arbitrary stream
+>>> s = StringIO()
+>>> connection = mail.get_connection('django.core.mail.backends.console', stream=s)
+>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
+1
+>>> print s.getvalue()
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject
+From: from@example.com
+To: to@example.com
+Date: ...
+Message-ID: ...
+
+Content
+-------------------------------------------------------------------------------
+
+# Make sure that dummy backends returns correct number of sent messages
+>>> connection = dummy.EmailBackend()
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email, email, email])
+3
+
+# Make sure that locmen backend populates the outbox
+>>> mail.outbox = []
+>>> connection = locmem.EmailBackend()
+>>> email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email1, email2])
+2
+>>> len(mail.outbox)
+2
+>>> mail.outbox[0].subject
+'Subject'
+>>> mail.outbox[1].subject
+'Subject 2'
+
+# Make sure that multiple locmem connections share mail.outbox
+>>> mail.outbox = []
+>>> connection1 = locmem.EmailBackend()
+>>> connection2 = locmem.EmailBackend()
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection1.send_messages([email])
+1
+>>> connection2.send_messages([email])
+1
+>>> len(mail.outbox)
+2
+
+# Make sure that the file backend write to the right location
+>>> tmp_dir = tempfile.mkdtemp()
+>>> connection = filebased.EmailBackend(file_path=tmp_dir)
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email])
+1
+>>> len(os.listdir(tmp_dir))
+1
+>>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read()
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject
+From: from@example.com
+To: to@example.com
+Date: ...
+Message-ID: ...
+
+Content
+-------------------------------------------------------------------------------
+
+>>> connection2 = filebased.EmailBackend(file_path=tmp_dir)
+>>> connection2.send_messages([email])
+1
+>>> len(os.listdir(tmp_dir))
+2
+>>> connection.send_messages([email])
+1
+>>> len(os.listdir(tmp_dir))
+2
+>>> email.connection = filebased.EmailBackend(file_path=tmp_dir)
+>>> connection_created = connection.open()
+>>> num_sent = email.send()
+>>> len(os.listdir(tmp_dir))
+3
+>>> num_sent = email.send()
+>>> len(os.listdir(tmp_dir))
+3
+>>> connection.close()
+>>> shutil.rmtree(tmp_dir)
+
+# Make sure that get_connection() accepts arbitrary keyword that might be
+# used with custom backends.
+>>> c = mail.get_connection(fail_silently=True, foo='bar')
+>>> c.fail_silently
+True
+
+# Test custom backend defined in this suite.
+>>> conn = mail.get_connection('regressiontests.mail.custombackend')
+>>> hasattr(conn, 'test_outbox')
+True
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> conn.send_messages([email])
+1
+>>> len(conn.test_outbox)
+1
+
+# Test backend argument of mail.get_connection()
+>>> isinstance(mail.get_connection('django.core.mail.backends.smtp'), smtp.EmailBackend)
+True
+>>> isinstance(mail.get_connection('django.core.mail.backends.locmem'), locmem.EmailBackend)
+True
+>>> isinstance(mail.get_connection('django.core.mail.backends.dummy'), dummy.EmailBackend)
+True
+>>> isinstance(mail.get_connection('django.core.mail.backends.console'), console.EmailBackend)
+True
+>>> tmp_dir = tempfile.mkdtemp()
+>>> isinstance(mail.get_connection('django.core.mail.backends.filebased', file_path=tmp_dir), filebased.EmailBackend)
+True
+>>> shutil.rmtree(tmp_dir)
+>>> isinstance(mail.get_connection(), locmem.EmailBackend)
+True
+
+# Test connection argument of send_mail() et al
+>>> connection = mail.get_connection('django.core.mail.backends.console')
+>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject
+From: from@example.com
+To: to@example.com
+Date: ...
+Message-ID: ...
+
+Content
+-------------------------------------------------------------------------------
+1
+
+>>> send_mass_mail([
+... ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']),
+... ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com'])
+... ], connection=connection)
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject1
+From: from1@example.com
+To: to1@example.com
+Date: ...
+Message-ID: ...
+
+Content1
+-------------------------------------------------------------------------------
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject2
+From: from2@example.com
+To: to2@example.com
+Date: ...
+Message-ID: ...
+
+Content2
+-------------------------------------------------------------------------------
+2
+
+>>> mail_admins('Subject', 'Content', connection=connection)
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: [Django] Subject
+From: root@localhost
+To: nobody@example.com
+Date: ...
+Message-ID: ...
+
+Content
+-------------------------------------------------------------------------------
+
+>>> mail_managers('Subject', 'Content', connection=connection)
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: [Django] Subject
+From: root@localhost
+To: nobody@example.com
+Date: ...
+Message-ID: ...
+
+Content
+-------------------------------------------------------------------------------
+
+>>> settings.ADMINS = old_admins
+>>> settings.MANAGERS = old_managers
+
"""
Index: /django/branches/soc2009/model-validation/tests/regressiontests/serializers_regress/models.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/serializers_regress/models.py (revision 10456)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/serializers_regress/models.py (revision 11725)
@@ -106,4 +106,7 @@
data = models.CharField(max_length=30)
+ class Meta:
+ ordering = ('id',)
+
class UniqueAnchor(models.Model):
"""This is a model that can be used as
@@ -136,5 +139,5 @@
class M2MIntermediateData(models.Model):
data = models.ManyToManyField(Anchor, null=True, through='Intermediate')
-
+
class Intermediate(models.Model):
left = models.ForeignKey(M2MIntermediateData)
@@ -243,5 +246,5 @@
class InheritAbstractModel(AbstractBaseModel):
child_data = models.IntegerField()
-
+
class BaseModel(models.Model):
parent_data = models.IntegerField()
@@ -253,3 +256,2 @@
parent = models.OneToOneField(BaseModel)
child_data = models.IntegerField()
-
Index: /django/branches/soc2009/model-validation/tests/regressiontests/utils/tests.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/utils/tests.py (revision 10716)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/utils/tests.py (revision 11725)
@@ -6,4 +6,5 @@
from django.utils import html, checksums
+from django.utils.functional import SimpleLazyObject
import timesince
@@ -24,7 +25,8 @@
'timesince': timesince,
'datastructures': datastructures,
- 'dateformat': dateformat,
'itercompat': itercompat,
}
+
+from dateformat import *
class TestUtilsHtml(TestCase):
@@ -162,4 +164,78 @@
self.check_output(f, value, output)
+class _ComplexObject(object):
+ def __init__(self, name):
+ self.name = name
+
+ def __eq__(self, other):
+ return self.name == other.name
+
+ def __hash__(self):
+ return hash(self.name)
+
+ def __str__(self):
+ return "I am _ComplexObject(%r)" % self.name
+
+ def __unicode__(self):
+ return unicode(self.name)
+
+ def __repr__(self):
+ return "_ComplexObject(%r)" % self.name
+
+complex_object = lambda: _ComplexObject("joe")
+
+class TestUtilsSimpleLazyObject(TestCase):
+ """
+ Tests for SimpleLazyObject
+ """
+ # Note that concrete use cases for SimpleLazyObject are also found in the
+ # auth context processor tests (unless the implementation of that function
+ # is changed).
+
+ def test_equality(self):
+ self.assertEqual(complex_object(), SimpleLazyObject(complex_object))
+ self.assertEqual(SimpleLazyObject(complex_object), complex_object())
+
+ def test_hash(self):
+ # hash() equality would not be true for many objects, but it should be
+ # for _ComplexObject
+ self.assertEqual(hash(complex_object()),
+ hash(SimpleLazyObject(complex_object)))
+
+ def test_repr(self):
+ # For debugging, it will really confuse things if there is no clue that
+ # SimpleLazyObject is actually a proxy object. So we don't
+ # proxy __repr__
+ self.assert_("SimpleLazyObject" in repr(SimpleLazyObject(complex_object)))
+
+ def test_str(self):
+ self.assertEqual("I am _ComplexObject('joe')", str(SimpleLazyObject(complex_object)))
+
+ def test_unicode(self):
+ self.assertEqual(u"joe", unicode(SimpleLazyObject(complex_object)))
+
+ def test_class(self):
+ # This is important for classes that use __class__ in things like
+ # equality tests.
+ self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__)
+
+ def test_deepcopy(self):
+ import copy
+ # Check that we *can* do deep copy, and that it returns the right
+ # objects.
+
+ # First, for an unevaluated SimpleLazyObject
+ s = SimpleLazyObject(complex_object)
+ assert s._wrapped is None
+ s2 = copy.deepcopy(s)
+ assert s._wrapped is None # something has gone wrong is s is evaluated
+ self.assertEqual(s2, complex_object())
+
+ # Second, for an evaluated SimpleLazyObject
+ name = s.name # evaluate
+ assert s._wrapped is not None
+ s3 = copy.deepcopy(s)
+ self.assertEqual(s3, complex_object())
+
if __name__ == "__main__":
import doctest
Index: /django/branches/soc2009/model-validation/tests/regressiontests/utils/dateformat.py
===================================================================
--- /django/branches/soc2009/model-validation/tests/regressiontests/utils/dateformat.py (revision 10716)
+++ /django/branches/soc2009/model-validation/tests/regressiontests/utils/dateformat.py (revision 11725)
@@ -1,48 +1,35 @@
-"""
->>> from datetime import datetime, date
->>> from django.utils.dateformat import format
->>> from django.utils.tzinfo import FixedOffset, LocalTimezone
+import os
+from unittest import TestCase
+from datetime import datetime, date
+from django.utils.dateformat import format
+from django.utils.tzinfo import FixedOffset, LocalTimezone
-# date
->>> d = date(2009, 5, 16)
->>> date.fromtimestamp(int(format(d, 'U'))) == d
-True
+class DateFormatTests(TestCase):
+ def test_date(self):
+ d = date(2009, 5, 16)
+ self.assertEquals(date.fromtimestamp(int(format(d, 'U'))), d)
-# Naive datetime
->>> dt = datetime(2009, 5, 16, 5, 30, 30)
->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt
-True
+ def test_naive_datetime(self):
+ dt = datetime(2009, 5, 16, 5, 30, 30)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt)
-# datetime with local tzinfo
->>> ltz = LocalTimezone(datetime.now())
->>> dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz)
->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz) == dt
-True
->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt.replace(tzinfo=None)
-True
+ def test_datetime_with_local_tzinfo(self):
+ ltz = LocalTimezone(datetime.now())
+ dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.replace(tzinfo=None))
-# datetime with arbitrary tzinfo
->>> tz = FixedOffset(-510)
->>> ltz = LocalTimezone(datetime.now())
->>> dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz)
->>> datetime.fromtimestamp(int(format(dt, 'U')), tz) == dt
-True
->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz) == dt
-True
->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt.astimezone(ltz).replace(tzinfo=None)
-True
->>> datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple() == dt.utctimetuple()
-True
->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple() == dt.utctimetuple()
-True
+ def test_datetime_with_tzinfo(self):
+ tz = FixedOffset(-510)
+ ltz = LocalTimezone(datetime.now())
+ dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz), dt)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None))
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple(), dt.utctimetuple())
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple())
-# Epoch
->>> utc = FixedOffset(0)
->>> udt = datetime(1970, 1, 1, tzinfo=utc)
->>> format(udt, 'U')
-u'0'
-"""
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
+ def test_epoch(self):
+ utc = FixedOffset(0)
+ udt = datetime(1970, 1, 1, tzinfo=utc)
+ self.assertEquals(format(udt, 'U'), u'0')
Index: /django/branches/soc2009/model-validation/AUTHORS
===================================================================
--- /django/branches/soc2009/model-validation/AUTHORS (revision 11617)
+++ /django/branches/soc2009/model-validation/AUTHORS (revision 11725)
@@ -17,4 +17,5 @@
* Justin Bronn
* Karen Tracey
+ * Jannis Leidel
More information on the main contributors to Django can be found in
@@ -27,4 +28,5 @@
ajs <adi@sieker.info>
alang@bright-green.com
+ Andi Albrecht <albrecht.andi@gmail.com>
Marty Alchin <gulopine@gamemusic.org>
Ahmad Alhashemi <trans@ahmadh.com>
@@ -269,5 +271,4 @@
Jeong-Min Lee <falsetru@gmail.com>
Tai Lee <real.human@mrmachine.net>
- Jannis Leidel <jl@websushi.org>
Christopher Lenz <http://www.cmlenz.net/>
lerouxb@gmail.com
@@ -424,5 +425,5 @@
thebjorn <bp@datakortet.no>
Zach Thompson <zthompson47@gmail.com>
- Michael Thornhill
+ Michael Thornhill <michael.thornhill@gmail.com>
Deepak Thukral <deep.thukral@gmail.com>
tibimicu@gmx.net
@@ -472,4 +473,6 @@
Jarek Zgoda <jarek.zgoda@gmail.com>
Cheng Zhang
+ Glenn Maynard <glenn@zewt.org>
+ bthomas
A big THANK YOU goes to:
Index: /django/branches/soc2009/model-validation/INSTALL
===================================================================
--- /django/branches/soc2009/model-validation/INSTALL (revision 8971)
+++ /django/branches/soc2009/model-validation/INSTALL (revision 11725)
@@ -1,11 +1,8 @@
Thanks for downloading Django.
-To install it, make sure you have Python 2.3 or greater installed. Then run
+To install it, make sure you have Python 2.4 or greater installed. Then run
this command from the command prompt:
python setup.py install
-
-Note this requires a working Internet connection if you don't already have the
-Python utility "setuptools" installed.
AS AN ALTERNATIVE, you can just copy the entire "django" directory to Python's
@@ -13,10 +10,7 @@
lives. Some places you might check are:
+ /usr/lib/python2.5/site-packages (Unix, Python 2.5)
/usr/lib/python2.4/site-packages (Unix, Python 2.4)
- /usr/lib/python2.3/site-packages (Unix, Python 2.3)
C:\\PYTHON\site-packages (Windows)
-This second solution does not require a working Internet connection; it
-bypasses "setuptools" entirely.
-
For more detailed instructions, see docs/intro/install.txt.
Index: /django/branches/soc2009/model-validation/extras/csrf_migration_helper.py
===================================================================
--- /django/branches/soc2009/model-validation/extras/csrf_migration_helper.py (revision 11725)
+++ /django/branches/soc2009/model-validation/extras/csrf_migration_helper.py (revision 11725)
@@ -0,0 +1,369 @@
+#!/usr/bin/env python
+
+# This script aims to help developers locate forms and view code that needs to
+# use the new CSRF protection in Django 1.2. It tries to find all the code that
+# may need the steps described in the CSRF documentation. It does not modify
+# any code directly, it merely attempts to locate it. Developers should be
+# aware of its limitations, described below.
+#
+# For each template that contains at least one POST form, the following info is printed:
+#
+# <Absolute path to template>
+# AKA: <Aliases (relative to template directory/directories that contain it)>
+# POST forms: <Number of POST forms>
+# With token: <Number of POST forms with the CSRF token already added>
+# Without token:
+# <File name and line number of form without token>
+#
+# Searching for:
+# <Template names that need to be searched for in view code
+# (includes templates that 'include' current template)>
+#
+# Found:
+# <File name and line number of any view code found>
+#
+# The format used allows this script to be used in Emacs grep mode:
+# M-x grep
+# Run grep (like this): /path/to/my/virtualenv/python /path/to/django/src/extras/csrf_migration_helper.py --settings=mysettings /path/to/my/srcs
+
+
+# Limitations
+# ===========
+#
+# - All templates must be stored on disk in '.html' or '.htm' files.
+# (extensions configurable below)
+#
+# - All Python code must be stored on disk in '.py' files. (extensions
+# configurable below)
+#
+# - All templates must be accessible from TEMPLATE_DIRS or from the 'templates/'
+# directory in apps specified in INSTALLED_APPS. Non-file based template
+# loaders are out of the picture, because there is no way to ask them to
+# return all templates.
+#
+# - If you put the {% csrf_token %} tag on the same line as the <form> tag it
+# will be detected, otherwise it will be assumed that the form does not have
+# the token.
+#
+# - It's impossible to programmatically determine which forms should and should
+# not have the token added. The developer must decide when to do this,
+# ensuring that the token is only added to internally targetted forms.
+#
+# - It's impossible to programmatically work out when a template is used. The
+# attempts to trace back to view functions are guesses, and could easily fail
+# in the following ways:
+#
+# * If the 'include' template tag is used with a variable
+# i.e. {% include tname %} where tname is a variable containing the actual
+# template name, rather than {% include "my_template.html" %}.
+#
+# * If the template name has been built up by view code instead of as a simple
+# string. For example, generic views and the admin both do this. (These
+# apps are both contrib and both use RequestContext already, as it happens).
+#
+# * If the 'ssl' tag (or any template tag other than 'include') is used to
+# include the template in another template.
+#
+# - All templates belonging to apps referenced in INSTALLED_APPS will be
+# searched, which may include third party apps or Django contrib. In some
+# cases, this will be a good thing, because even if the templates of these
+# apps have been fixed by someone else, your own view code may reference the
+# same template and may need to be updated.
+#
+# You may, however, wish to comment out some entries in INSTALLED_APPS or
+# TEMPLATE_DIRS before running this script.
+
+# Improvements to this script are welcome!
+
+# Configuration
+# =============
+
+TEMPLATE_EXTENSIONS = [
+ ".html",
+ ".htm",
+ ]
+
+PYTHON_SOURCE_EXTENSIONS = [
+ ".py",
+ ]
+
+TEMPLATE_ENCODING = "UTF-8"
+
+PYTHON_ENCODING = "UTF-8"
+
+# Method
+# ======
+
+# Find templates:
+# - template dirs
+# - installed apps
+#
+# Search for POST forms
+# - Work out what the name of the template is, as it would appear in an
+# 'include' or get_template() call. This can be done by comparing template
+# filename to all template dirs. Some templates can have more than one
+# 'name' e.g. if a directory and one of its child directories are both in
+# TEMPLATE_DIRS. This is actually a common hack used for
+# overriding-and-extending admin templates.
+#
+# For each POST form,
+# - see if it already contains '{% csrf_token %}' immediately after <form>
+# - work back to the view function(s):
+# - First, see if the form is included in any other templates, then
+# recursively compile a list of affected templates.
+# - Find any code function that references that template. This is just a
+# brute force text search that can easily return false positives
+# and fail to find real instances.
+
+
+import os
+import sys
+import re
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+
+USAGE = """
+This tool helps to locate forms that need CSRF tokens added and the
+corresponding view code. This processing is NOT fool proof, and you should read
+the help contained in the script itself. Also, this script may need configuring
+(by editing the script) before use.
+
+Usage:
+
+python csrf_migration_helper.py [--settings=path.to.your.settings] /path/to/python/code [more paths...]
+
+ Paths can be specified as relative paths.
+
+ With no arguments, this help is printed.
+"""
+
+_POST_FORM_RE = \
+ re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
+_TOKEN_RE = re.compile('\{% csrf_token')
+
+def get_template_dirs():
+ """
+ Returns a set of all directories that contain project templates.
+ """
+ from django.conf import settings
+ dirs = set()
+ if 'django.template.loaders.filesystem.load_template_source' in settings.TEMPLATE_LOADERS:
+ dirs.update(map(unicode, settings.TEMPLATE_DIRS))
+
+ if 'django.template.loaders.app_directories.load_template_source' in settings.TEMPLATE_LOADERS:
+ from django.template.loaders.app_directories import app_template_dirs
+ dirs.update(app_template_dirs)
+ return dirs
+
+def make_template_info(filename, root_dirs):
+ """
+ Creates a Template object for a filename, calculating the possible
+ relative_filenames from the supplied filename and root template directories
+ """
+ return Template(filename,
+ [filename[len(d)+1:] for d in root_dirs if filename.startswith(d)])
+
+
+class Template(object):
+ def __init__(self, absolute_filename, relative_filenames):
+ self.absolute_filename, self.relative_filenames = absolute_filename, relative_filenames
+
+ def content(self):
+ try:
+ return self._content
+ except AttributeError:
+ fd = open(self.absolute_filename)
+ content = fd.read().decode(TEMPLATE_ENCODING)
+ fd.close()
+ self._content = content
+ return content
+ content = property(content)
+
+ def post_form_info(self):
+ """
+ Get information about any POST forms in the template.
+ Returns [(linenumber, csrf_token added)]
+ """
+ matches = []
+ for ln, line in enumerate(self.content.split("\n")):
+ m = _POST_FORM_RE.search(line)
+ if m is not None:
+ matches.append((ln + 1, _TOKEN_RE.search(line) is not None))
+ return matches
+
+ def includes_template(self, t):
+ """
+ Returns true if this template includes template 't' (via {% include %})
+ """
+ for r in t.relative_filenames:
+ if re.search(r'\{%\s*include\s+"' + re.escape(r) + r'"\s*%\}', self.content):
+ return True
+ return False
+
+ def related_templates(self):
+ """
+ Returns all templates that include this one, recursively. (starting
+ with this one)
+ """
+ try:
+ return self._related_templates
+ except AttributeError:
+ pass
+
+ retval = set([self])
+ for r in self.relative_filenames:
+ for t in self.all_templates:
+ if t.includes_template(self):
+ # If two templates mutually include each other, directly or
+ # indirectly, we have a problem here...
+ retval = retval.union(t.related_templates())
+
+ self._related_templates = retval
+ return retval
+
+ def __repr__(self):
+ return repr(self.absolute_filename)
+
+ def __eq__(self, other):
+ return self.absolute_filename == other.absolute_filename
+
+ def __hash__(self):
+ return hash(self.absolute_filename)
+
+def get_templates(dirs):
+ """
+ Returns all files in dirs that have template extensions, as Template
+ objects.
+ """
+ templates = set()
+ for root in dirs:
+ for (dirpath, dirnames, filenames) in os.walk(root):
+ for f in filenames:
+ if len([True for e in TEMPLATE_EXTENSIONS if f.endswith(e)]) > 0:
+ t = make_template_info(os.path.join(dirpath, f), dirs)
+ # templates need to be able to search others:
+ t.all_templates = templates
+ templates.add(t)
+ return templates
+
+def get_python_code(paths):
+ """
+ Returns all Python code, as a list of tuples, each one being:
+ (filename, list of lines)
+ """
+ retval = []
+ for p in paths:
+ for (dirpath, dirnames, filenames) in os.walk(p):
+ for f in filenames:
+ if len([True for e in PYTHON_SOURCE_EXTENSIONS if f.endswith(e)]) > 0:
+ fn = os.path.join(dirpath, f)
+ fd = open(fn)
+ content = [l.decode(PYTHON_ENCODING) for l in fd.readlines()]
+ fd.close()
+ retval.append((fn, content))
+ return retval
+
+def search_python_list(python_code, template_names):
+ """
+ Searches python code for a list of template names.
+ Returns a list of tuples, each one being:
+ (filename, line number)
+ """
+ retval = []
+ for tn in template_names:
+ retval.extend(search_python(python_code, tn))
+ retval = list(set(retval))
+ retval.sort()
+ return retval
+
+def search_python(python_code, template_name):
+ """
+ Searches Python code for a template name.
+ Returns a list of tuples, each one being:
+ (filename, line number)
+ """
+ retval = []
+ for fn, content in python_code:
+ for ln, line in enumerate(content):
+ if ((u'"%s"' % template_name) in line) or \
+ ((u"'%s'" % template_name) in line):
+ retval.append((fn, ln + 1))
+ return retval
+
+def main(pythonpaths):
+ template_dirs = get_template_dirs()
+ templates = get_templates(template_dirs)
+ python_code = get_python_code(pythonpaths)
+ for t in templates:
+ # Logic
+ form_matches = t.post_form_info()
+ num_post_forms = len(form_matches)
+ form_lines_without_token = [ln for (ln, has_token) in form_matches if not has_token]
+ if num_post_forms == 0:
+ continue
+ to_search = [rf for rt in t.related_templates() for rf in rt.relative_filenames]
+ found = search_python_list(python_code, to_search)
+
+ # Display:
+ print t.absolute_filename
+ for r in t.relative_filenames:
+ print u" AKA %s" % r
+ print u" POST forms: %s" % num_post_forms
+ print u" With token: %s" % (num_post_forms - len(form_lines_without_token))
+ if form_lines_without_token:
+ print u" Without token:"
+ for ln in form_lines_without_token:
+ print "%s:%d:" % (t.absolute_filename, ln)
+ print
+ print u" Searching for:"
+ for r in to_search:
+ print u" " + r
+ print
+ print u" Found:"
+ if len(found) == 0:
+ print " Nothing"
+ else:
+ for fn, ln in found:
+ print "%s:%d:" % (fn, ln)
+
+ print
+ print "----"
+
+
+if __name__ == '__main__':
+ # Hacky argument parsing, one day I'll learn OptParse...
+ args = list(sys.argv[1:])
+ if len(args) > 0:
+ if args[0] in ['--help', '-h', '-?', '--usage']:
+ print USAGE
+ sys.exit(0)
+ else:
+ if args[0].startswith('--settings='):
+ module = args[0][len('--settings='):]
+ os.environ["DJANGO_SETTINGS_MODULE"] = module
+ args = args[1:]
+
+ if args[0].startswith('-'):
+ print "Unknown option: %s" % args[0]
+ print USAGE
+ sys.exit(1)
+
+ pythonpaths = args
+
+ if os.environ.get("DJANGO_SETTINGS_MODULE", None) is None:
+ print "You need to set DJANGO_SETTINGS_MODULE or use the '--settings' parameter"
+ sys.exit(1)
+ if len(pythonpaths) == 0:
+ print "Unrecognised command: %s" % command
+ print USAGE
+ sys.exit(1)
+
+ main(pythonpaths)
+
+ else:
+ # no args
+ print USAGE
+ sys.exit(0)
Index: /django/branches/soc2009/model-validation/docs/intro/tutorial01.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/intro/tutorial01.txt (revision 11617)
+++ /django/branches/soc2009/model-validation/docs/intro/tutorial01.txt (revision 11725)
@@ -282,4 +282,5 @@
__init__.py
models.py
+ tests.py
views.py
Index: /django/branches/soc2009/model-validation/docs/intro/tutorial02.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/intro/tutorial02.txt (revision 11083)
+++ /django/branches/soc2009/model-validation/docs/intro/tutorial02.txt (revision 11725)
@@ -35,9 +35,9 @@
to :setting:`INSTALLED_APPS`, the database tables need to be updated.
- * Edit your ``mysite/urls.py`` file and uncomment the lines below the
- "Uncomment the next two lines..." comment. This file is a URLconf;
- we'll dig into URLconfs in the next tutorial. For now, all you need to
- know is that it maps URL roots to applications. In the end, you should
- have a ``urls.py`` file that looks like this:
+ * Edit your ``mysite/urls.py`` file and uncomment the lines that reference
+ the admin -- there are three lines in total to uncomment. This file is a
+ URLconf; we'll dig into URLconfs in the next tutorial. For now, all you
+ need to know is that it maps URL roots to applications. In the end, you
+ should have a ``urls.py`` file that looks like this:
.. versionchanged:: 1.1
Index: /django/branches/soc2009/model-validation/docs/intro/install.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/intro/install.txt (revision 9632)
+++ /django/branches/soc2009/model-validation/docs/intro/install.txt (revision 11725)
@@ -13,5 +13,5 @@
Being a Python Web framework, Django requires Python. It works with any Python
-version from 2.3 to 2.6 (due to backwards
+version from 2.4 to 2.6 (due to backwards
incompatibilities in Python 3.0, Django does not currently work with
Python 3.0; see :ref:`the Django FAQ <faq-install>` for more
Index: /django/branches/soc2009/model-validation/docs/intro/tutorial03.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/intro/tutorial03.txt (revision 11395)
+++ /django/branches/soc2009/model-validation/docs/intro/tutorial03.txt (revision 11725)
@@ -172,13 +172,21 @@
should see your text.
-Now add the following view. It's slightly different, because it takes an
-argument (which, remember, is passed in from whatever was captured by the
-regular expression in the URLconf)::
+Now lets add a few more views. These views are slightly different, because
+they take an argument (which, remember, is passed in from whatever was
+captured by the regular expression in the URLconf)::
def detail(request, poll_id):
return HttpResponse("You're looking at poll %s." % poll_id)
-Take a look in your browser, at "/polls/34/". It'll display whatever ID you
-provide in the URL.
+ def results(request, poll_id):
+ return HttpResponse("You're looking at the results of poll %s." % poll_id)
+
+ def vote(request, poll_id):
+ return HttpResponse("You're voting on poll %s." % poll_id)
+
+Take a look in your browser, at "/polls/34/". It'll run the `detail()` method
+and display whatever ID you provide in the URL. Try "/polls/34/results/" and
+"/polls/34/vote/" too -- these will display the placeholder results and voting
+pages.
Write views that actually do something
@@ -468,8 +476,8 @@
:func:`~django.conf.urls.defaults.include`::
- ...
+ # ...
urlpatterns = patterns('',
(r'^polls/', include('mysite.polls.urls')),
- ...
+ # ...
:func:`~django.conf.urls.defaults.include`, simply, references another URLconf.
Index: /django/branches/soc2009/model-validation/docs/intro/tutorial04.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/intro/tutorial04.txt (revision 11038)
+++ /django/branches/soc2009/model-validation/docs/intro/tutorial04.txt (revision 11725)
@@ -22,4 +22,5 @@
<form action="/polls/{{ poll.id }}/vote/" method="post">
+ {% csrf_token %}
{% for choice in poll.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
@@ -47,4 +48,26 @@
through its loop
+ * Since we are creating a POST form (which can have the effect of modifying
+ data), we unfortunately need to worry about Cross Site Request Forgeries.
+ Thankfully, you don't have to worry too hard, because Django comes with
+ very easy-to-use system for protecting against it. In short, all POST
+ forms that are targetted at internal URLs need the ``{% csrf_token %}``
+ template tag adding.
+
+The ``{% csrf_token %}`` tag requires information from the request object, which
+is not normally accessible from within the template context. To fix this, a
+small adjustment needs to be made to the ``detail`` view, so that it looks like
+the following::
+
+ from django.template import RequestContext
+ # ...
+ def detail(request, poll_id):
+ p = get_object_or_404(Poll, pk=poll_id)
+ return render_to_response('polls/detail.html', {'poll': p},
+ context_instance=RequestContext(request))
+
+The details of how this works are explained in the documentation for
+:ref:`RequestContext <subclassing-context-requestcontext>`.
+
Now, let's create a Django view that handles the submitted data and does
something with it. Remember, in :ref:`Tutorial 3 <intro-tutorial03>`, we
@@ -53,9 +76,11 @@
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
-So let's create a ``vote()`` function in ``mysite/polls/views.py``::
+We also created a dummy implementation of the ``vote()`` function. Let's
+create a real version. Add the following to ``mysite/polls/views.py``::
from django.shortcuts import get_object_or_404, render_to_response
- from django.http import HttpResponseRedirect
+ from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
+ from django.template import RequestContext
from mysite.polls.models import Choice, Poll
# ...
@@ -69,5 +94,5 @@
'poll': p,
'error_message': "You didn't select a choice.",
- })
+ }, context_instance=RequestContext(request))
else:
selected_choice.votes += 1
Index: /django/branches/soc2009/model-validation/docs/internals/committers.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/internals/committers.txt (revision 11504)
+++ /django/branches/soc2009/model-validation/docs/internals/committers.txt (revision 11725)
@@ -149,5 +149,4 @@
.. _brian rosner: http://oebfare.com/
.. _eldarion: http://eldarion.com/
-.. _pinax: http://pinaxproject.com/
.. _django dose: http://djangodose.com/
@@ -190,4 +189,16 @@
Karen lives in Apex, NC, USA.
+`Jannis Leidel`_
+ Jannis graduated in media design from `Bauhaus-University Weimar`_,
+ is the author of a number of pluggable Django apps and likes to
+ contribute to Open Source projects like Pinax_. He currently works as
+ a freelance web developer and designer.
+
+ Jannis lives in Berlin, Germany.
+
+.. _Jannis Leidel: http://jezdez.com/
+.. _Bauhaus-University Weimar: http://www.uni-weimar.de/
+.. _pinax: http://pinaxproject.com/
+
Specialists
-----------
Index: /django/branches/soc2009/model-validation/docs/internals/release-process.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/internals/release-process.txt (revision 11395)
+++ /django/branches/soc2009/model-validation/docs/internals/release-process.txt (revision 11725)
@@ -57,5 +57,5 @@
feature in version ``A.B`` is deprecated, it will continue to work in version
``A.B+1``. In version ``A.B+2``, use of the feature will raise a
-``PendingDeprecationWarning`` but will continue to work. Version ``A.B+3`` will
+``DeprecationWarning`` but will continue to work. Version ``A.B+3`` will
remove the feature entirely.
Index: /django/branches/soc2009/model-validation/docs/internals/deprecation.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/internals/deprecation.txt (revision 11395)
+++ /django/branches/soc2009/model-validation/docs/internals/deprecation.txt (revision 11725)
@@ -14,4 +14,19 @@
release.
+ * 1.4
+ * ``CsrfResponseMiddleware``. This has been deprecated since the 1.2
+ release, in favour of the template tag method for inserting the CSRF
+ token. ``CsrfMiddleware``, which combines ``CsrfResponseMiddleware``
+ and ``CsrfViewMiddleware``, is also deprecated.
+
+ * The old imports for CSRF functionality (``django.contrib.csrf.*``),
+ which moved to core in 1.2, will be removed.
+
+ * ``SMTPConnection``. The 1.2 release deprecated the ``SMTPConnection``
+ class in favor of a generic E-mail backend API.
+
+ * The many to many SQL generation functions on the database backends
+ will be removed. These have been deprecated since the 1.2 release.
+
* 2.0
* ``django.views.defaults.shortcut()``. This function has been moved
Index: /django/branches/soc2009/model-validation/docs/topics/http/middleware.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/topics/http/middleware.txt (revision 11083)
+++ /django/branches/soc2009/model-validation/docs/topics/http/middleware.txt (revision 11725)
@@ -30,4 +30,5 @@
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
Index: /django/branches/soc2009/model-validation/docs/topics/auth.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/topics/auth.txt (revision 11617)
+++ /django/branches/soc2009/model-validation/docs/topics/auth.txt (revision 11725)
@@ -263,6 +263,6 @@
The :attr:`~django.contrib.auth.models.User.username`,
:attr:`~django.contrib.auth.models.User.email` and
- :attr:`~django.contrib.auth.models.User.password` are set as given, and
- the :class:`~django.contrib.auth.models.User` gets ``is_active=True``.
+ :attr:`~django.contrib.auth.models.User.password` are set as given, and the
+ :class:`~django.contrib.auth.models.User` gets ``is_active=True``.
If no password is provided,
@@ -706,5 +706,5 @@
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
-.. function:: views.login(request, [template_name, redirect_field_name])
+.. function:: views.login(request, [template_name, redirect_field_name, authentication_form])
Here's what ``django.contrib.auth.views.login`` does:
@@ -768,5 +768,5 @@
{% endif %}
- <form method="post" action="{% url django.contrib.auth.views.login %}">
+ <form method="post" action="{% url django.contrib.auth.views.login %}">{% csrf_token %}
<table>
<tr>
@@ -786,4 +786,13 @@
{% endblock %}
+ .. versionadded:: 1.2
+
+ If you are using alternate authentication (see
+ :ref:`authentication-backends`) you can pass a custom authentication form
+ to the login view via the ``authentication_form`` parameter. This form must
+ accept a ``request`` keyword argument in its ``__init__`` method, and
+ provide a ``get_user`` argument which returns the authenticated user object
+ (this method is only ever called after successful form validation).
+
.. _forms documentation: ../forms/
.. _site framework docs: ../sites/
@@ -825,5 +834,5 @@
default to :setting:`settings.LOGIN_URL <LOGIN_URL>` if not supplied.
-.. function:: views.password_change(request[, template_name, post_change_redirect])
+.. function:: views.password_change(request[, template_name, post_change_redirect, password_change_form])
Allows a user to change their password.
@@ -837,4 +846,11 @@
* ``post_change_redirect``: The URL to redirect to after a successful
password change.
+
+ * .. versionadded:: 1.2
+
+ ``password_change_form``: A custom "change password" form which must
+ accept a ``user`` keyword argument. The form is responsible for
+ actually changing the user's password.
+
**Template context:**
@@ -1031,13 +1047,5 @@
login page (:setting:`settings.LOGIN_URL <LOGIN_URL>` by default).
- Example in Python 2.3 syntax::
-
- from django.contrib.auth.decorators import user_passes_test
-
- def my_view(request):
- # ...
- my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')(my_view)
-
- Example in Python 2.4 syntax::
+ For example::
from django.contrib.auth.decorators import user_passes_test
Index: /django/branches/soc2009/model-validation/docs/topics/cache.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/topics/cache.txt (revision 11617)
+++ /django/branches/soc2009/model-validation/docs/topics/cache.txt (revision 11725)
@@ -617,10 +617,4 @@
from django.views.decorators.vary import vary_on_headers
- # Python 2.3 syntax.
- def my_view(request):
- # ...
- my_view = vary_on_headers(my_view, 'User-Agent')
-
- # Python 2.4+ decorator syntax.
@vary_on_headers('User-Agent')
def my_view(request):
Index: /django/branches/soc2009/model-validation/docs/topics/install.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/topics/install.txt (revision 11158)
+++ /django/branches/soc2009/model-validation/docs/topics/install.txt (revision 11725)
@@ -12,5 +12,5 @@
Being a Python Web framework, Django requires Python.
-It works with any Python version from 2.3 to 2.6 (due to backwards
+It works with any Python version from 2.4 to 2.6 (due to backwards
incompatibilities in Python 3.0, Django does not currently work with
Python 3.0; see :ref:`the Django FAQ <faq-install>` for more
@@ -94,8 +94,8 @@
backend <ref-databases>`.
-* If you're using SQLite and either Python 2.3 or Python 2.4, you'll need
- pysqlite_. Use version 2.0.3 or higher. Python 2.5 ships with an SQLite
- wrapper in the standard library, so you don't need to install anything extra
- in that case. Please read the SQLite backend :ref:`notes<sqlite-notes>`.
+* If you're using SQLite and Python 2.4, you'll need pysqlite_. Use version
+ 2.0.3 or higher. Python 2.5 ships with an SQLite wrapper in the standard
+ library, so you don't need to install anything extra in that case. Please
+ read the SQLite backend :ref:`notes<sqlite-notes>`.
* If you're using Oracle, you'll need a copy of cx_Oracle_, but please
Index: /django/branches/soc2009/model-validation/docs/topics/testing.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/topics/testing.txt (revision 11617)
+++ /django/branches/soc2009/model-validation/docs/topics/testing.txt (revision 11725)
@@ -1105,4 +1105,6 @@
point of the redirect chain.
+.. _topics-testing-email:
+
E-mail services
---------------
@@ -1118,5 +1120,5 @@
The test runner accomplishes this by transparently replacing the normal
-:class:`~django.core.mail.SMTPConnection` class with a different version.
+email backend with a testing backend.
(Don't worry -- this has no effect on any other e-mail senders outside of
Django, such as your machine's mail server, if you're running one.)
@@ -1129,12 +1131,6 @@
``django.core.mail.outbox``. This is a simple list of all
:class:`~django.core.mail.EmailMessage` instances that have been sent.
-It does not exist under normal execution conditions, i.e., when you're not
-running unit tests. The outbox is created during test setup, along with the
-dummy :class:`~django.core.mail.SMTPConnection`. When the test framework is
-torn down, the standard :class:`~django.core.mail.SMTPConnection` class is
-restored, and the test outbox is destroyed.
-
The ``outbox`` attribute is a special attribute that is created *only* when
-the tests are run. It doesn't normally exist as part of the
+the ``locmem`` e-mail backend is used. It doesn't normally exist as part of the
:mod:`django.core.mail` module and you can't import it directly. The code
below shows how to access this attribute correctly.
Index: /django/branches/soc2009/model-validation/docs/topics/conditional-view-processing.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/topics/conditional-view-processing.txt (revision 10642)
+++ /django/branches/soc2009/model-validation/docs/topics/conditional-view-processing.txt (revision 11725)
@@ -95,11 +95,4 @@
def front_page(request, blog_id):
...
-
-Of course, if you're using Python 2.3 or prefer not to use the decorator
-syntax, you can write the same code as follows, there is no difference::
-
- def front_page(request, blog_id):
- ...
- front_page = condition(last_modified_func=latest_entry)(front_page)
Shortcuts for only computing one value
Index: /django/branches/soc2009/model-validation/docs/topics/email.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/topics/email.txt (revision 9793)
+++ /django/branches/soc2009/model-validation/docs/topics/email.txt (revision 11725)
@@ -8,9 +8,11 @@
:synopsis: Helpers to easily send e-mail.
-Although Python makes sending e-mail relatively easy via the `smtplib library`_,
-Django provides a couple of light wrappers over it, to make sending e-mail
-extra quick.
-
-The code lives in a single module: ``django.core.mail``.
+Although Python makes sending e-mail relatively easy via the `smtplib
+library`_, Django provides a couple of light wrappers over it. These wrappers
+are provided to make sending e-mail extra quick, to make it easy to test
+email sending during development, and to provide support for platforms that
+can't use SMTP.
+
+The code lives in the ``django.core.mail`` module.
.. _smtplib library: http://docs.python.org/library/smtplib.html
@@ -26,9 +28,9 @@
['to@example.com'], fail_silently=False)
-Mail is sent using the SMTP host and port specified in the :setting:`EMAIL_HOST`
-and :setting:`EMAIL_PORT` settings. The :setting:`EMAIL_HOST_USER` and
-:setting:`EMAIL_HOST_PASSWORD` settings, if set, are used to authenticate to the
-SMTP server, and the :setting:`EMAIL_USE_TLS` setting controls whether a secure
-connection is used.
+Mail is sent using the SMTP host and port specified in the
+:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
+:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
+set, are used to authenticate to the SMTP server, and the
+:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
.. note::
@@ -43,5 +45,5 @@
``django.core.mail.send_mail()``. Here's its definition:
- .. function:: send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None)
+ .. function:: send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None)
The ``subject``, ``message``, ``from_email`` and ``recipient_list`` parameters
@@ -63,4 +65,8 @@
SMTP server. If this isn't provided, Django will use the value of the
``EMAIL_HOST_PASSWORD`` setting.
+ * ``connection``: The optional email backend to use to send the mail.
+ If unspecified, an instance of the default backend will be used.
+ See the documentation on :ref:`E-mail backends <topic-email-backends>`
+ for more details.
.. _smtplib docs: http://docs.python.org/library/smtplib.html
@@ -72,5 +78,5 @@
Here's the definition:
- .. function:: send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None)
+ .. function:: send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)
``datatuple`` is a tuple in which each element is in this format::
@@ -79,17 +85,20 @@
``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
-as in ``send_mail()``.
+as in :meth:`~django.core.mail.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' "To:" field.
+As in :meth:`~django.core.mail.send_mail()`, recipients in the same
+``recipient_list`` will all see the other addresses in the e-mail messages'
+"To:" field.
send_mass_mail() vs. send_mail()
--------------------------------
-The main difference between ``send_mass_mail()`` and ``send_mail()`` is that
-``send_mail()`` opens a connection to the mail server each time it's executed,
-while ``send_mass_mail()`` uses a single connection for all of its messages.
-This makes ``send_mass_mail()`` slightly more efficient.
+The main difference between :meth:`~django.core.mail.send_mass_mail()` and
+:meth:`~django.core.mail.send_mail()` is that
+:meth:`~django.core.mail.send_mail()` opens a connection to the mail server
+each time it's executed, while :meth:`~django.core.mail.send_mass_mail()` uses
+a single connection for all of its messages. This makes
+:meth:`~django.core.mail.send_mass_mail()` slightly more efficient.
mail_admins()
@@ -99,5 +108,5 @@
site admins, as defined in the :setting:`ADMINS` setting. Here's the definition:
- .. function:: mail_admins(subject, message, fail_silently=False)
+ .. function:: mail_admins(subject, message, fail_silently=False, connection=None)
``mail_admins()`` prefixes the subject with the value of the
@@ -116,5 +125,5 @@
setting. Here's the definition:
- .. function:: mail_managers(subject, message, fail_silently=False)
+ .. function:: mail_managers(subject, message, fail_silently=False, connection=None)
Examples
@@ -146,5 +155,5 @@
by forbidding newlines in header values. If any ``subject``, ``from_email`` or
``recipient_list`` contains a newline (in either Unix, Windows or Mac style),
-the e-mail function (e.g. ``send_mail()``) will raise
+the e-mail function (e.g. :meth:`~django.core.mail.send_mail()`) will raise
``django.core.mail.BadHeaderError`` (a subclass of ``ValueError``) and, hence,
will not send the e-mail. It's your responsibility to validate all data before
@@ -179,30 +188,35 @@
.. _emailmessage-and-smtpconnection:
-The EmailMessage and SMTPConnection classes
-===========================================
+The EmailMessage class
+======================
.. versionadded:: 1.0
-Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
-wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
-in ``django.core.mail``. If you ever need to customize the way Django sends
-e-mail, you can subclass these two classes to suit your needs.
+Django's :meth:`~django.core.mail.send_mail()` and
+:meth:`~django.core.mail.send_mass_mail()` functions are actually thin
+wrappers that make use of the :class:`~django.core.mail.EmailMessage` class.
+
+Not all features of the :class:`~django.core.mail.EmailMessage` class are
+available through the :meth:`~django.core.mail.send_mail()` and related
+wrapper functions. If you wish to use advanced features, such as BCC'ed
+recipients, file attachments, or multi-part e-mail, you'll need to create
+:class:`~django.core.mail.EmailMessage` instances directly.
.. note::
- Not all features of the ``EmailMessage`` class are available through the
- ``send_mail()`` and related wrapper functions. If you wish to use advanced
- features, such as BCC'ed recipients, file attachments, or multi-part
- e-mail, you'll need to create ``EmailMessage`` instances directly.
-
- This is a design feature. ``send_mail()`` and related functions were
- originally the only interface Django provided. However, the list of
- parameters they accepted was slowly growing over time. It made sense to
- move to a more object-oriented design for e-mail messages and retain the
- original functions only for backwards compatibility.
-
-In general, ``EmailMessage`` is responsible for creating the e-mail message
-itself. ``SMTPConnection`` is responsible for the network connection side of
-the operation. This means you can reuse the same connection (an
-``SMTPConnection`` instance) for multiple messages.
+ This is a design feature. :meth:`~django.core.mail.send_mail()` and
+ related functions were originally the only interface Django provided.
+ However, the list of parameters they accepted was slowly growing over
+ time. It made sense to move to a more object-oriented design for e-mail
+ messages and retain the original functions only for backwards
+ compatibility.
+
+:class:`~django.core.mail.EmailMessage` is responsible for creating the e-mail
+message itself. The :ref:`e-mail backend <topic-email-backends>` is then
+responsible for sending the e-mail.
+
+For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
+``send()`` method for sending a single email. If you need to send multiple
+messages, the email backend API :ref:`provides an alternative
+<topics-sending-multiple-emails>`.
EmailMessage Objects
@@ -211,7 +225,8 @@
.. class:: EmailMessage
-The ``EmailMessage`` class is initialized with the following parameters (in
-the given order, if positional arguments are used). All parameters are
-optional and can be set at any time prior to calling the ``send()`` method.
+The :class:`~django.core.mail.EmailMessage` class is initialized with the
+following parameters (in the given order, if positional arguments are used).
+All parameters are optional and can be set at any time prior to calling the
+``send()`` method.
* ``subject``: The subject line of the e-mail.
@@ -228,5 +243,5 @@
sending the e-mail.
- * ``connection``: An ``SMTPConnection`` instance. Use this parameter if
+ * ``connection``: An e-mail backend instance. Use this parameter if
you want to use the same connection for multiple messages. If omitted, a
new connection is created when ``send()`` is called.
@@ -249,16 +264,16 @@
The class has the following methods:
- * ``send(fail_silently=False)`` sends the message, using either
- the connection that is specified in the ``connection``
- attribute, or creating a new connection if none already
- exists. If the keyword argument ``fail_silently`` is ``True``,
- exceptions raised while sending the message will be quashed.
+ * ``send(fail_silently=False)`` sends the message. If a connection was
+ specified when the email was constructed, that connection will be used.
+ Otherwise, an instance of the default backend will be instantiated and
+ used. If the keyword argument ``fail_silently`` is ``True``, exceptions
+ raised while sending the message will be quashed.
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
subclass of Python's ``email.MIMEText.MIMEText`` class) or a
- ``django.core.mail.SafeMIMEMultipart`` object holding the
- message to be sent. If you ever need to extend the ``EmailMessage`` class,
- you'll probably want to override this method to put the content you want
- into the MIME object.
+ ``django.core.mail.SafeMIMEMultipart`` object holding the message to be
+ sent. If you ever need to extend the
+ :class:`~django.core.mail.EmailMessage` class, you'll probably want to
+ override this method to put the content you want into the MIME object.
* ``recipients()`` returns a list of all the recipients of the message,
@@ -300,11 +315,11 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-It can be useful to include multiple versions of the content in an e-mail;
-the classic example is to send both text and HTML versions of a message. With
+It can be useful to include multiple versions of the content in an e-mail; the
+classic example is to send both text and HTML versions of a message. With
Django's e-mail library, you can do this using the ``EmailMultiAlternatives``
-class. This subclass of ``EmailMessage`` has an ``attach_alternative()`` method
-for including extra versions of the message body in the e-mail. All the other
-methods (including the class initialization) are inherited directly from
-``EmailMessage``.
+class. This subclass of :class:`~django.core.mail.EmailMessage` has an
+``attach_alternative()`` method for including extra versions of the message
+body in the e-mail. All the other methods (including the class initialization)
+are inherited directly from :class:`~django.core.mail.EmailMessage`.
To send a text and HTML combination, you could write::
@@ -319,12 +334,13 @@
msg.send()
-By default, the MIME type of the ``body`` parameter in an ``EmailMessage`` is
-``"text/plain"``. It is good practice to leave this alone, because it
-guarantees that any recipient will be able to read the e-mail, regardless of
-their mail client. However, if you are confident that your recipients can
-handle an alternative content type, you can use the ``content_subtype``
-attribute on the ``EmailMessage`` class to change the main content type. The
-major type will always be ``"text"``, but you can change it to the subtype. For
-example::
+By default, the MIME type of the ``body`` parameter in an
+:class:`~django.core.mail.EmailMessage` is ``"text/plain"``. It is good
+practice to leave this alone, because it guarantees that any recipient will be
+able to read the e-mail, regardless of their mail client. However, if you are
+confident that your recipients can handle an alternative content type, you can
+use the ``content_subtype`` attribute on the
+:class:`~django.core.mail.EmailMessage` class to change the main content type.
+The major type will always be ``"text"``, but you can change it to the
+subtype. For example::
msg = EmailMessage(subject, html_content, from_email, [to])
@@ -332,26 +348,215 @@
msg.send()
-SMTPConnection Objects
-----------------------
-
-.. class:: SMTPConnection
-
-The ``SMTPConnection`` class is initialized with the host, port, username and
-password for the SMTP server. If you don't specify one or more of those
-options, they are read from your settings file.
-
-If you're sending lots of messages at once, the ``send_messages()`` method of
-the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage``
-instances (or subclasses) and sends them over a single connection. For example,
-if you have a function called ``get_notification_email()`` that returns a
-list of ``EmailMessage`` objects representing some periodic e-mail you wish to
-send out, you could send this with::
-
- connection = SMTPConnection() # Use default settings for connection
+.. _topic-email-backends:
+
+E-Mail Backends
+===============
+
+.. versionadded:: 1.2
+
+The actual sending of an e-mail is handled by the e-mail backend.
+
+The e-mail backend class has the following methods:
+
+ * ``open()`` instantiates an long-lived email-sending connection.
+
+ * ``close()`` closes the current email-sending connection.
+
+ * ``send_messages(email_messages)`` sends a list of
+ :class:`~django.core.mail.EmailMessage` objects. If the connection is
+ not open, this call will implicitly open the connection, and close the
+ connection afterwards. If the connection is already open, it will be
+ left open after mail has been sent.
+
+Obtaining an instance of an e-mail backend
+------------------------------------------
+
+The :meth:`get_connection` function in ``django.core.mail`` returns an
+instance of the e-mail backend that you can use.
+
+.. currentmodule:: django.core.mail
+
+.. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs)
+
+By default, a call to ``get_connection()`` will return an instance of the
+email backend specified in :setting:`EMAIL_BACKEND`. If you specify the
+``backend`` argument, an instance of that backend will be instantiated.
+
+The ``fail_silently`` argument controls how the backend should handle errors.
+If ``fail_silently`` is True, exceptions during the email sending process
+will be silently ignored.
+
+All other arguments are passed directly to the constructor of the
+e-mail backend.
+
+Django ships with several e-mail sending backends. With the exception of the
+SMTP backend (which is the default), these backends are only useful during
+testing and development. If you have special email sending requirements, you
+can :ref:`write your own email backend <topic-custom-email-backend>`.
+
+.. _topic-email-smtp-backend:
+
+SMTP backend
+~~~~~~~~~~~~
+
+This is the default backend. E-mail will be sent through a SMTP server.
+The server address and authentication credentials are set in the
+:setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`,
+:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
+settings file.
+
+The SMTP backend is the default configuration inherited by Django. If you
+want to specify it explicitly, put the following in your settings::
+
+ EMAIL_BACKEND = 'django.core.mail.backends.smtp'
+
+.. admonition:: SMTPConnection objects
+
+ Prior to version 1.2, Django provided a
+ :class:`~django.core.mail.SMTPConnection` class. This class provided a way
+ to directly control the use of SMTP to send email. This class has been
+ deprecated in favor of the generic email backend API.
+
+ For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
+ still available in ``django.core.mail`` as an alias for the SMTP backend.
+ New code should use :meth:`~django.core.mail.get_connection` instead.
+
+Console backend
+~~~~~~~~~~~~~~~
+
+Instead of sending out real e-mails the console backend just writes the
+e-mails that would be send to the standard output. By default, the console
+backend writes to ``stdout``. You can use a different stream-like object by
+providing the ``stream`` keyword argument when constructing the connection.
+
+To specify this backend, put the following in your settings::
+
+ EMAIL_BACKEND = 'django.core.mail.backends.console'
+
+This backend is not intended for use in production -- it is provided as a
+convenience that can be used during development.
+
+File backend
+~~~~~~~~~~~~
+
+The file backend writes e-mails to a file. A new file is created for each new
+session that is opened on this backend. The directory to which the files are
+written is either taken from the :setting:`EMAIL_FILE_PATH` setting or from
+the ``file_path`` keyword when creating a connection with
+:meth:`~django.core.mail.get_connection`.
+
+To specify this backend, put the following in your settings::
+
+ EMAIL_BACKEND = 'django.core.mail.backends.filebased'
+ EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
+
+This backend is not intended for use in production -- it is provided as a
+convenience that can be used during development.
+
+In-memory backend
+~~~~~~~~~~~~~~~~~
+
+The ``'locmem'`` backend stores messages in a special attribute of the
+``django.core.mail`` module. The ``outbox`` attribute is created when the
+first message is send. It's a list with an
+:class:`~django.core.mail.EmailMessage` instance for each message that would
+be send.
+
+To specify this backend, put the following in your settings::
+
+ EMAIL_BACKEND = 'django.core.mail.backends.locmem'
+
+This backend is not intended for use in production -- it is provided as a
+convenience that can be used during development and testing.
+
+Dummy backend
+~~~~~~~~~~~~~
+
+As the name suggests the dummy backend does nothing with your messages. To
+specify this backend, put the following in your settings::
+
+ EMAIL_BACKEND = 'django.core.mail.backends.dummy'
+
+This backend is not intended for use in production -- it is provided as a
+convenience that can be used during development.
+
+.. _topic-custom-email-backend:
+
+Defining a custom e-mail backend
+--------------------------------
+
+If you need to change how e-mails are send you can write your own e-mail
+backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
+Python import path for your backend.
+
+Custom e-mail backends should subclass ``BaseEmailBackend`` that is located in
+the ``django.core.mail.backends.base`` module. A custom e-mail backend must
+implement the ``send_messages(email_messages)`` method. This method receives a
+list of :class:`~django.core.mail.EmailMessage` instances and returns the
+number of successfully delivered messages. If your backend has any concept of
+a persistent session or connection, you should also implement the ``open()``
+and ``close()`` methods. Refer to ``SMTPEmailBackend`` for a reference
+implementation.
+
+.. _topics-sending-multiple-emails:
+
+Sending multiple emails
+-----------------------
+
+Establishing and closing an SMTP connection (or any other network connection,
+for that matter) is an expensive process. If you have a lot of emails to send,
+it makes sense to reuse an SMTP connection, rather than creating and
+destroying a connection every time you want to send an email.
+
+There are two ways you tell an email backend to reuse a connection.
+
+Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes
+a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses),
+and sends them all using a single connection.
+
+For example, if you have a function called ``get_notification_email()`` that
+returns a list of :class:`~django.core.mail.EmailMessage` objects representing
+some periodic e-mail you wish to send out, you could send these emails using
+a single call to send_messages::
+
+ from django.core import mail
+ connection = mail.get_connection() # Use default email connection
messages = get_notification_email()
connection.send_messages(messages)
+In this example, the call to ``send_messages()`` opens a connection on the
+backend, sends the list of messages, and then closes the connection again.
+
+The second approach is to use the ``open()`` and ``close()`` methods on the
+email backend to manually control the connection. ``send_messages()`` will not
+manually open or close the connection if it is already open, so if you
+manually open the connection, you can control when it is closed. For example::
+
+ from django.core import mail
+ connection = mail.get_connection()
+
+ # Manually open the connection
+ connection.open()
+
+ # Construct an email message that uses the connection
+ email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
+ ['to1@example.com'], connection=connection)
+ email1.send() # Send the email
+
+ # Construct two more messages
+ email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
+ ['to2@example.com'])
+ email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
+ ['to3@example.com'])
+
+ # Send the two emails in a single call -
+ connection.send_messages([email2, email3])
+ # The connection was already open so send_messages() doesn't close it.
+ # We need to manually close the connection.
+ connection.close()
+
+
Testing e-mail sending
-----------------------
+======================
The are times when you do not want Django to send e-mails at all. For example,
@@ -361,18 +566,40 @@
correct content.
-The easiest way to test your project's use of e-mail is to use a "dumb" e-mail
-server that receives the e-mails locally and displays them to the terminal,
-but does not actually send anything. Python has a built-in way to accomplish
-this with a single command::
+The easiest way to test your project's use of e-mail is to use the ``console``
+email backend. This backend redirects all email to stdout, allowing you to
+inspect the content of mail.
+
+The ``file`` email backend can also be useful during development -- this backend
+dumps the contents of every SMTP connection to a file that can be inspected
+at your leisure.
+
+Another approach is to use a "dumb" SMTP server that receives the e-mails
+locally and displays them to the terminal, but does not actually send
+anything. Python has a built-in way to accomplish this with a single command::
python -m smtpd -n -c DebuggingServer localhost:1025
This command will start a simple SMTP server listening on port 1025 of
-localhost. This server simply prints to standard output all email headers and
-the email body. You then only need to set the :setting:`EMAIL_HOST` and
+localhost. This server simply prints to standard output all e-mail headers and
+the e-mail body. You then only need to set the :setting:`EMAIL_HOST` and
:setting:`EMAIL_PORT` accordingly, and you are set.
-For more entailed testing and processing of e-mails locally, see the Python
-documentation on the `SMTP Server`_.
+For a more detailed discussion of testing and processing of e-mails locally,
+see the Python documentation on the `SMTP Server`_.
.. _SMTP Server: http://docs.python.org/library/smtpd.html
+
+SMTPConnection
+==============
+
+.. class:: SMTPConnection
+
+.. deprecated:: 1.2
+
+The ``SMTPConnection`` class has been deprecated in favor of the generic email
+backend API.
+
+For backwards compatibility ``SMTPConnection`` is still available in
+``django.core.mail`` as an alias for the :ref:`SMTP backend
+<topic-email-smtp-backend>`. New code should use
+:meth:`~django.core.mail.get_connection` instead.
Index: /django/branches/soc2009/model-validation/docs/releases/1.2-alpha.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/releases/1.2-alpha.txt (revision 11725)
+++ /django/branches/soc2009/model-validation/docs/releases/1.2-alpha.txt (revision 11725)
@@ -0,0 +1,52 @@
+
+Backwards-incompatible changes
+==============================
+
+CSRF Protection
+---------------
+
+There have been large changes to the way that CSRF protection works, detailed in
+:ref:`the CSRF documentaton <ref-contrib-csrf>`. The following are the major
+changes that developers must be aware of:
+
+ * ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and
+ will be removed completely in Django 1.4, in favour of a template tag that
+ should be inserted into forms.
+
+ * All contrib apps use a ``csrf_protect`` decorator to protect the view. This
+ requires the use of the csrf_token template tag in the template, so if you
+ have used custom templates for contrib views, you MUST READ THE UPGRADE
+ INSTRUCTIONS to fix those templates.
+
+ * ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by
+ default. This turns on CSRF protection by default, so that views that accept
+ POST requests need to be written to work with the middleware. Instructions
+ on how to do this are found in the CSRF docs.
+
+ * All of the CSRF has moved from contrib to core (with backwards compatible
+ imports in the old locations, which are deprecated).
+
+LazyObject
+----------
+
+``LazyObject`` is an undocumented utility class used for lazily wrapping other
+objects of unknown type. In Django 1.1 and earlier, it handled introspection in
+a non-standard way, depending on wrapped objects implementing a public method
+``get_all_members()``. Since this could easily lead to name clashes, it has been
+changed to use the standard method, involving ``__members__`` and ``__dir__()``.
+If you used ``LazyObject`` in your own code, and implemented the
+``get_all_members()`` method for wrapped objects, you need to make the following
+changes:
+
+ * If your class does not have special requirements for introspection (i.e. you
+ have not implemented ``__getattr__()`` or other methods that allow for
+ attributes not discoverable by normal mechanisms), you can simply remove the
+ ``get_all_members()`` method. The default implementation on ``LazyObject``
+ will do the right thing.
+
+ * If you have more complex requirements for introspection, first rename the
+ ``get_all_members()`` method to ``__dir__()``. This is the standard method,
+ from Python 2.6 onwards, for supporting introspection. If you are require
+ support for Python < 2.6, add the following code to the class::
+
+ __members__ = property(lambda self: self.__dir__())
Index: /django/branches/soc2009/model-validation/docs/faq/install.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/faq/install.txt (revision 11083)
+++ /django/branches/soc2009/model-validation/docs/faq/install.txt (revision 11725)
@@ -19,5 +19,5 @@
--------------------------------
-Django requires Python_, specifically any version of Python from 2.3
+Django requires Python_, specifically any version of Python from 2.4
through 2.6. No other Python libraries are required for basic Django
usage.
@@ -43,27 +43,32 @@
.. _Oracle: http://www.oracle.com/
-Do I lose anything by using Python 2.3 versus newer Python versions, such as Python 2.5?
-----------------------------------------------------------------------------------------
+Do I lose anything by using Python 2.4 versus newer Python versions, such as Python 2.5 or 2.6?
+-----------------------------------------------------------------------------------------------
-Not in the core framework. Currently, Django itself officially
-supports any version of Python from 2.3 through 2.6,
-inclusive. However, some add-on components may require a more recent
-Python version; the ``django.contrib.gis`` component, for example,
-requires at least Python 2.4, and third-party applications for use
-with Django are, of course, free to set their own version
-requirements.
+Not in the core framework. Currently, Django itself officially supports any
+version of Python from 2.4 through 2.6, inclusive. However, newer versions of
+Python are often faster, have more features, and are better supported.
+Third-party applications for use with Django are, of course, free to set their
+own version requirements.
-Please note, however, that over the next year or two Django will begin
-dropping support for older Python versions as part of a migration
-which will end with Django running on Python 3.0 (see next question
-for details). So if you're just starting out with Python, it's
-recommended that you use the latest 2.x release (currently, Python
-2.6). This will let you take advantage of the numerous improvements
-and optimizations to the Python language since version 2.3, and will
-help ease the process of dropping support for older Python versions on
-the road to Python 3.0.
+Over the next year or two Django will begin dropping support for older Python
+versions as part of a migration which will end with Django running on Python 3
+(see below for details).
-Can I use Django with Python 3.0?
+All else being equal, we recommend that you use the latest 2.x release
+(currently Python 2.6). This will let you take advantage of the numerous
+improvements and optimizations to the Python language since version 2.4, and
+will help ease the process of dropping support for older Python versions on
+the road to Python 3.
+
+Can I use Django with Python 2.3?
---------------------------------
+
+Django 1.1 (and earlier) supported Python 2.3. Django 1.2 and newer does not.
+We highly recommend you upgrade Python if at all possible, but Django 1.1 will
+continue to work on Python 2.3.
+
+Can I use Django with Python 3?
+-------------------------------
Not at the moment. Python 3.0 introduced a number of
Index: /django/branches/soc2009/model-validation/docs/ref/contrib/formtools/form-wizard.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/contrib/formtools/form-wizard.txt (revision 11504)
+++ /django/branches/soc2009/model-validation/docs/ref/contrib/formtools/form-wizard.txt (revision 11725)
@@ -178,5 +178,5 @@
{% block content %}
<p>Step {{ step }} of {{ step_count }}</p>
- <form action="." method="post">
+ <form action="." method="post">{% csrf_token %}
<table>
{{ form }}
Index: /django/branches/soc2009/model-validation/docs/ref/contrib/comments/index.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/contrib/comments/index.txt (revision 11617)
+++ /django/branches/soc2009/model-validation/docs/ref/contrib/comments/index.txt (revision 11725)
@@ -217,4 +217,11 @@
template you should be sure to do the same.
+The comments app also depends on the more general :ref:`Cross Site Request
+Forgery protection < ref-contrib-csrf>` that comes with Django. As described in
+the documentation, it is best to use ``CsrfViewMiddleware``. However, if you
+are not using that, you will need to use the ``csrf_protect`` decorator on any
+views that include the comment form, in order for those views to be able to
+output the CSRF token and cookie.
+
.. _honeypot: http://en.wikipedia.org/wiki/Honeypot_(computing)
Index: /django/branches/soc2009/model-validation/docs/ref/contrib/admin/index.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/contrib/admin/index.txt (revision 11617)
+++ /django/branches/soc2009/model-validation/docs/ref/contrib/admin/index.txt (revision 11725)
@@ -771,5 +771,5 @@
problems:
- * It will *not* perform and permission checks, so it will be accessible to
+ * It will *not* perform any permission checks, so it will be accessible to
the general public.
* It will *not* provide any header details to prevent caching. This means if
@@ -1049,14 +1049,68 @@
]
+Working with Many-to-Many Models
+--------------------------------
+
+.. versionadded:: 1.2
+
+By default, admin widgets for many-to-many relations will be displayed
+on whichever model contains the actual reference to the ``ManyToManyField``.
+Depending on your ``ModelAdmin`` definition, each many-to-many field in your
+model will be represented by a standard HTML ``<select multiple>``, a
+horizontal or vertical filter, or a ``raw_id_admin`` widget. However, it is
+also possible to to replace these widgets with inlines.
+
+Suppose we have the following models::
+
+ class Person(models.Model):
+ name = models.CharField(max_length=128)
+
+ class Group(models.Model):
+ name = models.CharField(max_length=128)
+ members = models.ManyToManyField(Person, related_name='groups')
+
+If you want to display many-to-many relations using an inline, you can do
+so by defining an ``InlineModelAdmin`` object for the relationship::
+
+ class MembershipInline(admin.TabularInline):
+ model = Group.members.through
+
+ class PersonAdmin(admin.ModelAdmin):
+ inlines = [
+ MembershipInline,
+ ]
+
+ class GroupAdmin(admin.ModelAdmin):
+ inlines = [
+ MembershipInline,
+ ]
+ exclude = ('members',)
+
+There are two features worth noting in this example.
+
+Firstly - the ``MembershipInline`` class references ``Group.members.through``.
+The ``through`` attribute is a reference to the model that manages the
+many-to-many relation. This model is automatically created by Django when you
+define a many-to-many field.
+
+Secondly, the ``GroupAdmin`` must manually exclude the ``members`` field.
+Django displays an admin widget for a many-to-many field on the model that
+defines the relation (in this case, ``Group``). If you want to use an inline
+model to represent the many-to-many relationship, you must tell Django's admin
+to *not* display this widget - otherwise you will end up with two widgets on
+your admin page for managing the relation.
+
+In all other respects, the ``InlineModelAdmin`` is exactly the same as any
+other. You can customize the appearance using any of the normal
+``InlineModelAdmin`` properties.
+
Working with Many-to-Many Intermediary Models
----------------------------------------------
-By default, admin widgets for many-to-many relations will be displayed inline
-on whichever model contains the actual reference to the ``ManyToManyField``.
-However, when you specify an intermediary model using the ``through``
-argument to a ``ManyToManyField``, the admin will not display a widget by
-default. This is because each instance of that intermediary model requires
-more information than could be displayed in a single widget, and the layout
-required for multiple widgets will vary depending on the intermediate model.
+When you specify an intermediary model using the ``through`` argument to a
+``ManyToManyField``, the admin will not display a widget by default. This is
+because each instance of that intermediary model requires more information
+than could be displayed in a single widget, and the layout required for
+multiple widgets will vary depending on the intermediate model.
However, we still want to be able to edit that information inline. Fortunately,
Index: /django/branches/soc2009/model-validation/docs/ref/contrib/csrf.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/contrib/csrf.txt (revision 10128)
+++ /django/branches/soc2009/model-validation/docs/ref/contrib/csrf.txt (revision 11725)
@@ -5,16 +5,23 @@
=====================================
-.. module:: django.contrib.csrf
+.. module:: django.middleware.csrf
:synopsis: Protects against Cross Site Request Forgeries
-The CsrfMiddleware class provides easy-to-use protection against
+The CSRF middleware and template tag provides easy-to-use protection against
`Cross Site Request Forgeries`_. This type of attack occurs when a malicious
-Web site creates a link or form button that is intended to perform some action
-on your Web site, using the credentials of a logged-in user who is tricked
-into clicking on the link in their browser.
-
-The first defense against CSRF attacks is to ensure that GET requests
-are side-effect free. POST requests can then be protected by adding this
-middleware into your list of installed middleware.
+Web site contains a link, a form button or some javascript that is intended to
+perform some action on your Web site, using the credentials of a logged-in user
+who visits the malicious site in their browser. A related type of attack,
+'login CSRF', where an attacking site tricks a user's browser into logging into
+a site with someone else's credentials, is also covered.
+
+The first defense against CSRF attacks is to ensure that GET requests are
+side-effect free. POST requests can then be protected by following the steps
+below.
+
+.. versionadded:: 1.2
+ The 'contrib' apps, including the admin, use the functionality described
+ here. Because it is security related, a few things have been added to core
+ functionality to allow this to happen without any required upgrade steps.
.. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF
@@ -23,20 +30,212 @@
=============
-Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
-your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process
-the response after the SessionMiddleware, so must come before it in the
-list. It also must process the response before things like compression
-happen to the response, so it must come after GZipMiddleware in the
-list.
-
-The ``CsrfMiddleware`` class is actually composed of two middleware:
-``CsrfViewMiddleware`` which performs the checks on incoming requests,
-and ``CsrfResponseMiddleware`` which performs post-processing of the
-result. This allows the individual components to be used and/or
-replaced instead of using ``CsrfMiddleware``.
-
-.. versionchanged:: 1.1
- (previous versions of Django did not provide these two components
- of ``CsrfMiddleware`` as described above)
+.. versionchanged:: 1.2
+ The template tag functionality (the recommended way to use this) was added
+ in version 1.2. The previous method (still available) is described under
+ `Legacy method`_.
+
+To enable CSRF protection for your views, follow these steps:
+
+ 1. Add the middleware
+ ``'django.middleware.csrf.CsrfViewMiddleware'`` to your list of
+ middleware classes, :setting:`MIDDLEWARE_CLASSES`. (It should come
+ before ``CsrfResponseMiddleware`` if that is being used, and before any
+ view middleware that assume that CSRF attacks have been dealt with.)
+
+ Alternatively, you can use the decorator
+ ``django.views.decorators.csrf.csrf_protect`` on particular views you
+ want to protect (see below).
+
+ 2. In any template that uses a POST form, use the ``csrf_token`` tag inside
+ the ``<form>`` element if the form is for an internal URL, e.g.::
+
+ <form action="" method="POST">{% csrf_token %}
+
+ This should not be done for POST forms that target external URLs, since
+ that would cause the CSRF token to be leaked, leading to a vulnerability.
+
+ 3. In the corresponding view functions, ensure that the
+ ``'django.core.context_processors.csrf'`` context processor is
+ being used. Usually, this can be done in one of two ways:
+
+ 1. Use RequestContext, which always uses
+ ``'django.core.context_processors.csrf'`` (no matter what your
+ TEMPLATE_CONTEXT_PROCESSORS setting). If you are using
+ generic views or contrib apps, you are covered already, since these
+ apps use RequestContext throughout.
+
+ 2. Manually import and use the processor to generate the CSRF token and
+ add it to the template context. e.g.::
+
+ from django.core.context_processors import csrf
+ from django.shortcuts import render_to_response
+
+ def my_view(request):
+ c = {}
+ c.update(csrf(request))
+ # ... view code here
+ return render_to_response("a_template.html", c)
+
+ You may want to write your own ``render_to_response`` wrapper that
+ takes care of this step for you.
+
+The utility script ``extras/csrf_migration_helper.py`` can help to automate the
+finding of code and templates that may need to be upgraded. It contains full
+help on how to use it.
+
+The decorator method
+--------------------
+
+Rather than adding ``CsrfViewMiddleware`` as a blanket protection, you can use
+the ``csrf_protect`` decorator, which has exactly the same functionality, on
+particular views that need the protection. It must be used **both** on views
+that insert the CSRF token in the output, and on those that accept the POST form
+data. (These are often the same view function, but not always). It is used like
+this::
+
+ from django.views.decorators.csrf import csrf_protect
+ from django.template import RequestContext
+
+ @csrf_protect
+ def my_view(request):
+ c = {}
+ # ...
+ return render_to_response("a_template.html", c,
+ context_instance=RequestContext(request))
+
+Use of the decorator is **not recommended** by itself, since if you forget to
+use it, you will have a security hole. The 'belt and braces' strategy of using
+both is fine, and will incur minimal overhead.
+
+Legacy method
+-------------
+
+In Django 1.1, the template tag did not exist. Instead, a post-processing
+middleware that re-wrote POST forms to include the CSRF token was used. If you
+are upgrading a site from version 1.1 or earlier, please read this section and
+the `Upgrading notes`_ below. The post-processing middleware is still available
+as ``CsrfResponseMiddleware``, and it can be used by following these steps:
+
+ 1. Follow step 1 above to install ``CsrfViewMiddleware``.
+
+ 2. Add ``'django.middleware.csrf.CsrfResponseMiddleware'`` to your
+ :setting:`MIDDLEWARE_CLASSES` setting.
+
+ ``CsrfResponseMiddleware`` needs to process the response before things
+ like compression or setting ofETags happen to the response, so it must
+ come after ``GZipMiddleware``, ``CommonMiddleware`` and
+ ``ConditionalGetMiddleware`` in the list. It also must come after
+ ``CsrfViewMiddleware``.
+
+Use of the ``CsrfResponseMiddleware`` is not recommended because of the
+performance hit it imposes, and because of a potential security problem (see
+below). It can be used as an interim measure until applications have been
+updated to use the ``{% csrf_token %}`` tag. It is deprecated and will be
+removed in Django 1.4.
+
+Django 1.1 and earlier provided a single ``CsrfMiddleware`` class. This is also
+still available for backwards compatibility. It combines the functions of the
+two middleware.
+
+Note also that previous versions of these classes depended on the sessions
+framework, but this dependency has now been removed, with backward compatibility
+support so that upgrading will not produce any issues.
+
+Security of legacy method
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The post-processing ``CsrfResponseMiddleware`` adds the CSRF token to all POST
+forms (unless the view has been decorated with ``csrf_response_exempt``). If
+the POST form has an external untrusted site as its target, rather than an
+internal page, that site will be sent the CSRF token when the form is submitted.
+Armed with this leaked information, that site will then be able to successfully
+launch a CSRF attack on your site against that user. The
+``@csrf_response_exempt`` decorator can be used to fix this, but only if the
+page doesn't also contain internal forms that require the token.
+
+Upgrading notes
+---------------
+
+When upgrading to version 1.2 or later, you may have applications that rely on
+the old post-processing functionality for CSRF protection, or you may not have
+enabled any CSRF protection. This section outlines the steps necessary for a
+smooth upgrade, without having to fix all the applications to use the new
+template tag method immediately.
+
+First of all, the location of the middleware and related functions have
+changed. There are backwards compatible stub files so that old imports will
+continue to work for now, but they are deprecated and will be removed in Django
+1.4. The following changes have been made:
+
+ * Middleware have been moved to ``django.middleware.csrf``
+ * Decorators have been moved to ``django.views.decorators.csrf``
+
+====================================================== ==============================================
+ Old New
+====================================================== ==============================================
+django.contrib.csrf.middleware.CsrfMiddleware django.middleware.csrf.CsrfMiddleware
+django.contrib.csrf.middleware.CsrfViewMiddleware django.middleware.csrf.CsrfViewMiddleware
+django.contrib.csrf.middleware.CsrfResponseMiddleware django.middleware.csrf.CsrfResponseMiddleware
+django.contrib.csrf.middleware.csrf_exempt django.views.decorators.csrf_exempt
+django.contrib.csrf.middleware.csrf_view_exempt django.views.decorators.csrf_view_exempt
+django.contrib.csrf.middleware.csrf_response_exempt django.views.decorators.csrf_response_exempt
+====================================================== ==============================================
+
+You should update any imports, and also the paths in your
+:setting:`MIDDLEWARE_CLASSES`.
+
+If you have ``CsrfMiddleware`` in your :setting:`MIDDLEWARE_CLASSES`, you will now
+have a working installation with CSRF protection. It is recommended at this
+point that you replace ``CsrfMiddleware`` with its two components,
+``CsrfViewMiddleware`` and ``CsrfResponseMiddleware`` (in that order).
+
+If you do not have any of the middleware in your :setting:`MIDDLEWARE_CLASSES`,
+you will have a working installation but without any CSRF protection for your
+views (just as you had before). It is strongly recommended to install
+``CsrfViewMiddleware`` and ``CsrfResponseMiddleware``, as described above.
+
+Note that contrib apps, such as the admin, have been updated to use the
+``csrf_protect`` decorator, so that they are secured even if you do not add the
+``CsrfViewMiddleware`` to your settings. However, if you have supplied
+customised templates to any of the view functions of contrib apps (whether
+explicitly via a keyword argument, or by overriding built-in templates), **you
+MUST update them** to include the ``csrf_token`` template tag as described
+above, or they will stop working. (If you cannot update these templates for
+some reason, you will be forced to use ``CsrfResponseMiddleware`` for these
+views to continue working).
+
+Note also, if you are using the comments app, and you are not going to add
+``CsrfViewMiddleware`` to your settings (not recommended), you will need to add
+the ``csrf_protect`` decorator to any views that include the comment forms and
+target the comment views (usually using the :ttag:`comment_form_target` template
+tag).
+
+Assuming you have followed the above, all views in your Django site will now be
+protected by the ``CsrfViewMiddleware``. Contrib apps meet the requirements
+imposed by the ``CsrfViewMiddleware`` using the template tag, and other
+applications in your project will meet its requirements by virtue of the
+``CsrfResponseMiddleware``.
+
+The next step is to update all your applications to use the template tag, as
+described in `How to use it`_, steps 2-3. This can be done as soon as is
+practical. Any applications that are updated will now require Django 1.1.2 or
+later, since they will use the CSRF template tag which was not available in
+earlier versions. (The template tag in 1.1.2 is actually a no-op that exists
[[BINARY DATA]]
+CSRF protection under 1.2 without requiring users of the apps to upgrade to the
+Django 1.2.X series).
+
+The utility script ``extras/csrf_migration_helper.py`` can help to automate the
+finding of code and templates that may need to be upgraded. It contains full
+help on how to use it.
+
+Finally, once all applications are upgraded, ``CsrfResponseMiddleware`` can be
+removed from your settings.
+
+While ``CsrfResponseMiddleware`` is still in use, the ``csrf_response_exempt``
+decorator, described in `Exceptions`_, may be useful. The post-processing
+middleware imposes a performance hit and a potential vulnerability, and any
+views that have been upgraded to use the new template tag method no longer need
+it.
Exceptions
@@ -45,80 +244,181 @@
.. versionadded:: 1.1
-To manually exclude a view function from being handled by the
-CsrfMiddleware, you can use the ``csrf_exempt`` decorator, found in
-the ``django.contrib.csrf.middleware`` module. For example::
-
- from django.contrib.csrf.middleware import csrf_exempt
-
+To manually exclude a view function from being handled by either of the two CSRF
+middleware, you can use the ``csrf_exempt`` decorator, found in the
+``django.views.decorators.csrf`` module. For example::
+
+ from django.views.decorators.csrf import csrf_exempt
+
+ @csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
- my_view = csrf_exempt(my_view)
-
-Like the middleware itself, the ``csrf_exempt`` decorator is composed
-of two parts: a ``csrf_view_exempt`` decorator and a
-``csrf_response_exempt`` decorator, found in the same module. These
-disable the view protection mechanism (``CsrfViewMiddleware``) and the
-response post-processing (``CsrfResponseMiddleware``) respectively.
-They can be used individually if required.
-
-You don't have to worry about doing this for most AJAX views. Any
-request sent with "X-Requested-With: XMLHttpRequest" is automatically
-exempt. (See the next section.)
+
+Like the middleware, the ``csrf_exempt`` decorator is composed of two parts: a
+``csrf_view_exempt`` decorator and a ``csrf_response_exempt`` decorator, found
+in the same module. These disable the view protection mechanism
+(``CsrfViewMiddleware``) and the response post-processing
+(``CsrfResponseMiddleware``) respectively. They can be used individually if
+required.
+
+You don't have to worry about doing this for most AJAX views. Any request sent
+with "X-Requested-With: XMLHttpRequest" is automatically exempt. (See the `How
+it works`_ section.)
+
+Subdomains
+----------
+
+By default, CSRF cookies are specific to the subdomain they are set for. This
+means that a form served from one subdomain (e.g. server1.example.com) will not
+be able to have a target on another subdomain (e.g. server2.example.com). This
+restriction can be removed by setting :setting:`CSRF_COOKIE_DOMAIN` to be
+something like ``".example.com"``.
+
+Please note that, with or without use of this setting, this CSRF protection
+mechanism is not safe against cross-subdomain attacks -- see `Limitations`_.
+
+Rejected requests
+=================
+
+By default, a '403 Forbidden' response is sent to the user if an incoming
+request fails the checks performed by ``CsrfViewMiddleware``. This should
+usually only be seen when there is a genuine Cross Site Request Forgery, or
+when, due to a programming error, the CSRF token has not been included with a
+POST form.
+
+No logging is done, and the error message is not very friendly, so you may want
+to provide your own page for handling this condition. To do this, simply set
+the :setting:`CSRF_FAILURE_VIEW` setting to a dotted path to your own view
+function, which should have the following signature::
+
+ def csrf_failure(request, reason="")
+
+where ``reason`` is a short message (intended for developers or logging, not for
+end users) indicating the reason the request was rejected.
How it works
============
-CsrfMiddleware does two things:
-
-1. It modifies outgoing requests by adding a hidden form field to all
- 'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
- a hash of the session ID plus a secret. If there is no session ID set,
- this modification of the response isn't done, so there is very little
- performance penalty for those requests that don't have a session.
- (This is done by ``CsrfResponseMiddleware``).
-
-2. On all incoming POST requests that have the session cookie set, it
- checks that the 'csrfmiddlewaretoken' is present and correct. If it
- isn't, the user will get a 403 error. (This is done by
- ``CsrfViewMiddleware``)
-
-This ensures that only forms that have originated from your Web site
-can be used to POST data back.
+The CSRF protection is based on the following things:
+
+1. A CSRF cookie that is set to a random value (a session independent nonce, as
+ it is called), which other sites will not have access to.
+
+ This cookie is set by ``CsrfViewMiddleware``. It is meant to be permanent,
+ but since there is no way to set a cookie that never expires, it is sent with
+ every response that has called ``django.middleware.csrf.get_token()``
+ (the function used internally to retrieve the CSRF token).
+
+2. A hidden form field with the name 'csrfmiddlewaretoken' present in all
+ outgoing POST forms. The value of this field is the value of the CSRF
+ cookie.
+
+ This part is done by the template tag (and with the legacy method, it is done
+ by ``CsrfResponseMiddleware``).
+
+3. For all incoming POST requests, a CSRF cookie must be present, and the
+ 'csrfmiddlewaretoken' field must be present and correct. If it isn't, the
+ user will get a 403 error.
+
+ This check is done by ``CsrfViewMiddleware``.
+
+4. In addition, for HTTPS requests, strict referer checking is done by
+ ``CsrfViewMiddleware``. This is necessary to address a Man-In-The-Middle
+ attack that is possible under HTTPS when using a session independent nonce,
+ due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted
+ by clients that are talking to a site under HTTPS. (Referer checking is not
+ done for HTTP requests because the presence of the Referer header is not
+ reliable enough under HTTP.)
+
+This ensures that only forms that have originated from your Web site can be used
+to POST data back.
It deliberately only targets HTTP POST requests (and the corresponding POST
-forms). GET requests ought never to have any potentially dangerous side
-effects (see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a
-CSRF attack with a GET request ought to be harmless.
-
-POST requests that are not accompanied by a session cookie are not protected,
-but they do not need to be protected, since the 'attacking' Web site
-could make these kind of requests anyway.
-
-The Content-Type is checked before modifying the response, and only
-pages that are served as 'text/html' or 'application/xml+xhtml'
-are modified.
-
-The middleware tries to be smart about requests that come in via AJAX. Many
-JavaScript toolkits send an "X-Requested-With: XMLHttpRequest" HTTP header;
-these requests are detected and automatically *not* handled by this middleware.
-We can do this safely because, in the context of a browser, the header can only
-be added by using ``XMLHttpRequest``, and browsers already implement a
-same-domain policy for ``XMLHttpRequest``. (Note that this is not secure if you
-don't trust content within the same domain or subdomains.)
-
+forms). GET requests ought never to have any potentially dangerous side effects
+(see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a CSRF attack with a GET
+request ought to be harmless.
+
+``CsrfResponseMiddleware`` checks the Content-Type before modifying the
+response, and only pages that are served as 'text/html' or
+'application/xml+xhtml' are modified.
+
+AJAX
+----
+
+The middleware tries to be smart about requests that come in via AJAX. Most
+modern JavaScript toolkits send an "X-Requested-With: XMLHttpRequest" HTTP
+header; these requests are detected and automatically *not* handled by this
+middleware. We can do this safely because, in the context of a browser, the
+header can only be added by using ``XMLHttpRequest``, and browsers already
+implement a same-domain policy for ``XMLHttpRequest``.
+
+For the more recent browsers that relax this same-domain policy, custom headers
+like "X-Requested-With" are only allowed after the browser has done a
+'preflight' check to the server to see if the cross-domain request is allowed,
[[BINARY DATA]]
+the developer has specifically opted in to allowing cross-site AJAX POST
+requests on a specific URL, they obviously don't want the middleware to disallow
+exactly that.
.. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+
+Caching
+=======
+
+If the ``csrf_token`` template tag is used by a template (or the ``get_token``
+function is called some other way), ``CsrfViewMiddleware`` will add a cookie and
+a ``Vary: Cookie`` header to the response. Similarly,
+``CsrfResponseMiddleware`` will send the ``Vary: Cookie`` header if it inserted
+a token. This means that these middleware will play well with the cache
+middleware if it is used as instructed (``UpdateCacheMiddleware`` goes before
+all other middleware).
+
+However, if you use cache decorators on individual views, the CSRF middleware
+will not yet have been able to set the Vary header. In this case, on any views
+that will require a CSRF token to be inserted you should use the
+:func:`django.views.decorators.vary.vary_on_cookie` decorator first::
+
+ from django.views.decorators.cache import cache_page
+ from django.views.decorators.vary import vary_on_cookie
+
+ @cache_page(60 * 15)
+ @vary_on_cookie
+ def my_view(request):
+ # ...
+
+
+Testing
+=======
+
+The ``CsrfViewMiddleware`` will usually be a big hindrance to testing view
+functions, due to the need for the CSRF token which must be sent with every POST
+request. For this reason, Django's HTTP client for tests has been modified to
+set a flag on requests which relaxes the middleware and the ``csrf_protect``
+decorator so that they no longer rejects requests. In every other respect
+(e.g. sending cookies etc.), they behave the same.
Limitations
===========
-CsrfMiddleware requires Django's session framework to work. If you have
-a custom authentication system that manually sets cookies and the like,
-it won't help you.
-
-If your app creates HTML pages and forms in some unusual way, (e.g.
-it sends fragments of HTML in JavaScript document.write statements)
-you might bypass the filter that adds the hidden field to the form,
-in which case form submission will always fail. It may still be possible
-to use the middleware, provided you can find some way to get the
-CSRF token and ensure that is included when your form is submitted.
+Subdomains within a site will be able to set cookies on the client for the whole
+domain. By setting the cookie and using a corresponding token, subdomains will
+be able to circumvent the CSRF protection. The only way to avoid this is to
+ensure that subdomains are controlled by trusted users (or, are at least unable
+to set cookies). Note that even without CSRF, there are other vulnerabilities,
+such as session fixation, that make giving subdomains to untrusted parties a bad
+idea, and these vulnerabilities cannot easily be fixed with current browsers.
+
+If you are using ``CsrfResponseMiddleware`` and your app creates HTML pages and
+forms in some unusual way, (e.g. it sends fragments of HTML in JavaScript
+document.write statements) you might bypass the filter that adds the hidden
+field to the form, in which case form submission will always fail. You should
+use the template tag or :meth:`django.middleware.csrf.get_token` to get
+the CSRF token and ensure it is included when your form is submitted.
+
+Contrib and reusable apps
+=========================
+
+Because it is possible for the developer to turn off the ``CsrfViewMiddleware``,
+all relevant views in contrib apps use the ``csrf_protect`` decorator to ensure
+the security of these applications against CSRF. It is recommended that the
+developers of other reusable apps that want the same guarantees also use the
+``csrf_protect`` decorator on their views.
Index: /django/branches/soc2009/model-validation/docs/ref/models/querysets.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/models/querysets.txt (revision 11617)
+++ /django/branches/soc2009/model-validation/docs/ref/models/querysets.txt (revision 11725)
@@ -1115,4 +1115,15 @@
.. _field-lookups:
+``exists()``
+~~~~~~~~~~~~
+
+.. versionadded:: 1.2
+
+Returns ``True`` if the :class:`QuerySet` contains any results, and ``False``
+if not. This tries to perform the query in the simplest and fastest way
+possible, but it *does* execute nearly the same query. This means that calling
+:meth:`QuerySet.exists()` is faster that ``bool(some_query_set)``, but not by
+a large degree.
+
Field lookups
-------------
Index: /django/branches/soc2009/model-validation/docs/ref/middleware.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/middleware.txt (revision 11395)
+++ /django/branches/soc2009/model-validation/docs/ref/middleware.txt (revision 11725)
@@ -166,9 +166,9 @@
--------------------------
-.. module:: django.contrib.csrf.middleware
+.. module:: django.middleware.csrf
:synopsis: Middleware adding protection against Cross Site Request
Forgeries.
-.. class:: django.contrib.csrf.middleware.CsrfMiddleware
+.. class:: django.middleware.csrf.CsrfMiddleware
.. versionadded:: 1.0
Index: /django/branches/soc2009/model-validation/docs/ref/settings.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/settings.txt (revision 11083)
+++ /django/branches/soc2009/model-validation/docs/ref/settings.txt (revision 11725)
@@ -145,4 +145,49 @@
``cache_page()`` decorator is used.
+.. setting:: CSRF_COOKIE_NAME
+
+CSRF_COOKIE_NAME
+----------------
+
+.. versionadded:: 1.2
+
+Default: ``'csrftoken'``
+
+The name of the cookie to use for the CSRF authentication token. This can be whatever you
+want. See :ref:`ref-contrib-csrf`.
+
+.. setting:: CSRF_COOKIE_DOMAIN
+
+CSRF_COOKIE_DOMAIN
+------------------
+
+.. versionadded:: 1.2
+
+Default: ``None``
+
+The domain to be used when setting the CSRF cookie. This can be useful for
+allowing cross-subdomain requests to be exluded from the normal cross site
+request forgery protection. It should be set to a string such as
+``".lawrence.com"`` to allow a POST request from a form on one subdomain to be
+accepted by accepted by a view served from another subdomain.
+
+.. setting:: CSRF_FAILURE_VIEW
+
+CSRF_FAILURE_VIEW
+-----------------
+
+.. versionadded:: 1.2
+
+Default: ``'django.views.csrf.csrf_failure'``
+
+A dotted path to the view function to be used when an incoming request
+is rejected by the CSRF protection. The function should have this signature::
+
+ def csrf_failure(request, reason="")
+
+where ``reason`` is a short message (intended for developers or logging, not for
+end users) indicating the reason the request was rejected. See
+:ref:`ref-contrib-csrf`.
+
.. setting:: DATABASE_ENGINE
@@ -379,4 +424,27 @@
This is only used if ``CommonMiddleware`` is installed (see
:ref:`topics-http-middleware`).
+
+.. setting:: EMAIL_BACKEND
+
+EMAIL_BACKEND
+-------------
+
+.. versionadded:: 1.2
+
+Default: ``'django.core.mail.backends.smtp'``
+
+The backend to use for sending emails. For the list of available backends see
+:ref:`topics-email`.
+
+.. setting:: EMAIL_FILE_PATH
+
+EMAIL_FILE_PATH
+---------------
+
+.. versionadded:: 1.2
+
+Default: Not defined
+
+The directory used by the ``file`` email backend to store output files.
.. setting:: EMAIL_HOST
@@ -752,4 +820,5 @@
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',)
Index: /django/branches/soc2009/model-validation/docs/ref/templates/api.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/templates/api.txt (revision 11395)
+++ /django/branches/soc2009/model-validation/docs/ref/templates/api.txt (revision 11725)
@@ -314,4 +314,11 @@
"django.core.context_processors.media")
+.. versionadded:: 1.2
+ In addition to these, ``RequestContext`` always uses
+ ``'django.core.context_processors.csrf'``. This is a security
+ related context processor required by the admin and other contrib apps, and,
+ in case of accidental misconfiguration, it is deliberately hardcoded in and
+ cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
+
Each processor is applied in order. That means, if one processor adds a
variable to the context and a second processor adds a variable with the same
@@ -404,4 +411,12 @@
``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
value of the :setting:`MEDIA_URL` setting.
+
+django.core.context_processors.csrf
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.2
+
+This processor adds a token that is needed by the ``csrf_token`` template tag
+for protection against :ref:`Cross Site Request Forgeries <ref-contrib-csrf>`.
django.core.context_processors.request
Index: /django/branches/soc2009/model-validation/docs/ref/templates/builtins.txt
===================================================================
--- /django/branches/soc2009/model-validation/docs/ref/templates/builtins.txt (revision 11395)
+++ /django/branches/soc2009/model-validation/docs/ref/templates/builtins.txt (revision 11725)
@@ -53,4 +53,14 @@
.. templatetag:: cycle
+
+csrf_token
+~~~~~~~~~~
+
+.. versionadded:: 1.1.2
+
+In the Django 1.1.X series, this is a no-op tag that returns an empty string for
+future compatibility purposes. In Django 1.2 and later, it is used for CSRF
+protection, as described in the documentation for :ref:`Cross Site Request
+Forgeries <ref-contrib-csrf>`.
cycle