I watch following beautiful codes ...

  • CherryPy
  • Django
  • Jinja
  • Pylons
  • SQLAlchemy
  • Tornado
  • TurboGears
  • Twisted
  • Werkzeug

You can following ...

  • atom feed
  • twitter

Django: Changeset [11725]: [soc2009/model-validation] Merged to trunk at r11724

Django

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

TurboGears: Changeset [6943]: re-organize the code - move custom methods into into a Scheduler class - …

TurboGears

6943
at 2009-11-06 23:01:29
by vinces1979@gmail.com

Index: /projects/TGScheduler/trunk/tgscheduler/scheduler.py
===================================================================
--- /projects/TGScheduler/trunk/tgscheduler/scheduler.py (revision 6943)
+++ /projects/TGScheduler/trunk/tgscheduler/scheduler.py (revision 6943)
@@ -0,0 +1,91 @@
+""" create the scheduler object and expose function """
+
+import kronos
+from kronos import method
+import atexit
+import logging
+
+log = logging.getLogger("tgscheduler")
+
+class Scheduler:
+    """ Handle Task Scheduling """
+    
+    def __init__(self):
+        self._scheduler_instance = None
+    
+    def start_scheduler(self):
+        """ start the scheduler, and register shutdown at exit """
+        
+        log.info("Starting Scheduler")
+        si = self._get_scheduler()
+        si.start()
+        atexit.register(self.stop_scheduler)
+        return si
+
+    def stop_scheduler(self):
+        """ stop the scheduler """
+        
+        log.info("Shutting down scheduler")
+        if not self._scheduler_instance:
+            return
+        si = self._get_scheduler()
+        si.stop()
+
+    def _get_scheduler(self):
+        """ find available scheduler """
+        
+        if not self._scheduler_instance:
+            if hasattr(kronos, "ThreadedScheduler"):
+                log.debug("Using threaded scheduler")
+                self._scheduler_instance = kronos.ThreadedScheduler()
+            elif hasattr(kronos, "ForkedScheduler"):
+                log.debug("Using Forked scheduler")
+                self._scheduler_instance = kronos.ForkedScheduler()
+            else:
+                log.debug("Using Sequential scheduler")
+                self._scheduler_instance = kronos.Scheduler()
+                
+        return self._scheduler_instance
+
+    def add_interval_task(self, action, interval, args=None, kw=None, initialdelay=0, 
+            processmethod=method.threaded, taskname=None):
+        si = self._get_scheduler()
+        return si.add_interval_task(action=action, interval=interval, args=args,
+            kw=kw, initialdelay=initialdelay, processmethod=processmethod, 
+            taskname=taskname)
+    
+    def add_single_task(self, action, args=None, kw=None, initialdelay=0, 
+            processmethod=method.threaded, taskname=None):
+        si = self._get_scheduler()
+        return si.add_single_task(action=action, args=args, kw=kw, 
+            initialdelay=initialdelay, processmethod=processmethod, 
+            taskname=taskname)
+    
+    def add_weekday_task(self, action, weekdays, timeonday, args=None, kw=None,
+            processmethod=method.threaded, taskname=None):
+        si = self._get_scheduler()
+        return si.add_daytime_task(action=action, taskname=taskname, 
+            weekdays=weekdays, monthdays=None, timeonday=timeonday,
+            processmethod=processmethod, args=args, kw=kw)
+    
+    def add_monthday_task(self, action, monthdays, timeonday, args=None, kw=None,
+            processmethod=method.threaded, taskname=None):
+        si = self._get_scheduler()
+        return si.add_daytime_task(action=action, taskname=taskname,
+            weekdays=None, monthdays=monthdays, timeonday=timeonday,
+            processmethod=processmethod, args=args, kw=kw)
+    
+    def cancel(self, task):
+        si = self._get_scheduler()
+        si.cancel(task)    
+
+scheduler = Scheduler()
+
+#: backwards compatibility 
+start_scheduler = scheduler.start_scheduler
+stop_scheduler = scheduler.stop_scheduler
+
+add_monthly_task = scheduler.add_monthday_task
+add_weekday_task = scheduler.add_weekday_task
+add_single_task = scheduler.add_single_task
+add_interval_task = scheduler.add_interval_task
Index: /projects/TGScheduler/trunk/tgscheduler/__init__.py
===================================================================
--- /projects/TGScheduler/trunk/tgscheduler/__init__.py (revision 6940)
+++ /projects/TGScheduler/trunk/tgscheduler/__init__.py (revision 6943)
@@ -1,73 +1,5 @@
 """  Scheduled background tasks using kronos.py """
 
-import kronos as scheduler
-from kronos import method
-import atexit
-import logging
-
-log = logging.getLogger("tgscheduler")
-
-_scheduler_instance = None
-
-def start_scheduler():
-    log.info("Starting Scheduler")
-    si = _get_scheduler()
-    si.start()
-    atexit.register(stop_scheduler)
-
-def stop_scheduler():
-    log.info("Shutting down scheduler")
-    if not _scheduler_instance:
-        return
-    si = _get_scheduler()
-    si.stop()
-
-def _get_scheduler():
-    """ find available scheduler """
-    global _scheduler_instance
-    si = _scheduler_instance
-    if not si:
-        if hasattr(scheduler, "ThreadedScheduler"):
-            log.debug("Using threaded scheduler")
-            si = scheduler.ThreadedScheduler()
-        elif hasattr(scheduler, "ForkedScheduler"):
-            log.debug("Using Forked scheduler")
-            si = scheduler.ForkedScheduler()
-        else:
-            log.debug("Using Sequential scheduler")
-            si = scheduler.Sceduler()
-            
-        _scheduler_instance = si
-    return si
-
-def add_interval_task(action, interval, args=None, kw=None, initialdelay=0, 
-        processmethod=method.threaded, taskname=None):
-    si = _get_scheduler()
-    return si.add_interval_task(action=action, interval=interval, args=args,
-        kw=kw, initialdelay=initialdelay, processmethod=processmethod, 
-        taskname=taskname)
-
-def add_single_task(action, args=None, kw=None, initialdelay=0, 
-        processmethod=method.threaded, taskname=None):
-    si = _get_scheduler()
-    return si.add_single_task(action=action, args=args, kw=kw, 
-        initialdelay=initialdelay, processmethod=processmethod, 
-        taskname=taskname)
-
-def add_weekday_task(action, weekdays, timeonday, args=None, kw=None,
-        processmethod=method.threaded, taskname=None):
-    si = _get_scheduler()
-    return si.add_daytime_task(action=action, taskname=taskname, 
-        weekdays=weekdays, monthdays=None, timeonday=timeonday,
-        processmethod=processmethod, args=args, kw=kw)
-
-def add_monthday_task(action, monthdays, timeonday, args=None, kw=None,
-        processmethod=method.threaded, taskname=None):
-    si = _get_scheduler()
-    return si.add_daytime_task(action=action, taskname=taskname,
-        weekdays=None, monthdays=monthdays, timeonday=timeonday,
-        processmethod=processmethod, args=args, kw=kw)
-
-def cancel(task):
-    si = _get_scheduler()
-    si.cancel(task)    
+from scheduler import scheduler, start_scheduler, stop_scheduler, \
+                add_interval_task, add_monthly_task, add_single_task, \
+                add_weekday_task

TurboGears: Changeset [6942]: implementing changes noted in …

TurboGears

6942
at 2009-11-06 22:31:58
by vinces1979@gmail.com

Index: /projects/TGScheduler/trunk/tgscheduler/kronos.py
===================================================================
--- /projects/TGScheduler/trunk/tgscheduler/kronos.py (revision 6940)
+++ /projects/TGScheduler/trunk/tgscheduler/kronos.py (revision 6942)
@@ -44,5 +44,5 @@
 """
 
-__version__="2.0"
+__version__ = "2.0"
 
 __all__ = [
@@ -82,7 +82,7 @@
 
 class method:
-    sequential="sequential"
-    forked="forked"
-    threaded="threaded"
+    sequential = "sequential"
+    forked = "forked"
+    threaded = "threaded"
 
 class Scheduler:
@@ -90,13 +90,13 @@
 
     def __init__(self):
-        self.running=True
+        self.running = True
         self.sched = sched.scheduler(time.time, self.__delayfunc)
 
     def __delayfunc(self, delay):
-        # This delay function is basically a time.sleep() that is
-        # divided up, so that we can check the self.running flag while delaying.
-        # there is an additional check in here to ensure that the top item of
-        # the queue hasn't changed
-        if delay<10:
+        """ This delay function is basically a time.sleep() that is
+         divided up, so that we can check the self.running flag while delaying.
+         there is an additional check in here to ensure that the top item of
+         the queue hasn't changed """
+        if delay < 10:
             time.sleep(delay)
         else:
@@ -191,5 +191,5 @@
             else:
                 raise ValueError("Invalid processmethod")
-            task=TaskClass(taskname, weekdays, timeonday, action, args, kw)
+            task = TaskClass(taskname, weekdays, timeonday, action, args, kw)
         if monthdays:
             # Select the correct MonthdayTask class.
@@ -203,6 +203,6 @@
             else:
                 raise ValueError("Invalid processmethod")
-            task=TaskClass(taskname, monthdays, timeonday, action, args, kw)
-        firsttime=task.get_schedule_time(True)
+            task = TaskClass(taskname, monthdays, timeonday, action, args, kw)
+        firsttime = task.get_schedule_time(True)
         self.schedule_task_abs(task, firsttime)
         return task
@@ -257,5 +257,5 @@
         self.sched.cancel(task.event)
 
-    if sys.version_info>=(2,6):
+    if sys.version_info >= (2,6):
         # code for sched module of python 2.6+
         def _getqueuetoptime(self):
@@ -271,13 +271,13 @@
 
     def _run(self):
-        # Low-level run method to do the actual scheduling loop.
+        """ Low-level run method to do the actual scheduling loop. """
         while self.running:
             try:
                 self.sched.run()
-            except Exception,x:
-                print >>sys.stderr, "ERROR DURING SCHEDULER EXECUTION",x
-                print >>sys.stderr, "".join(
+            except Exception, err:
+                print >> sys.stderr, "ERROR DURING SCHEDULER EXECUTION", err
+                print >> sys.stderr, "".join(
                     traceback.format_exception(*sys.exc_info()))
-                print >>sys.stderr, "-" * 20
+                print >> sys.stderr, "-" * 20
             # queue is empty; sleep a short while before checking again
             if self.running:
@@ -290,8 +290,8 @@
     def __init__(self, name, action, args, kw):
         """This is an abstract class!"""
-        self.name=name
-        self.action=action
-        self.args=args
-        self.kw=kw
+        self.name = name
+        self.action = action
+        self.args = args
+        self.kw = kw
 
     def __call__(self, schedulerref):
@@ -299,6 +299,6 @@
         try:
             self.execute()
-        except Exception,x:
-            self.handle_exception(x)
+        except Exception, err:
+            self.handle_exception(err)
         self.reschedule(schedulerref())
 
@@ -314,7 +314,7 @@
     def handle_exception(self, exc):
         """Handle any exception that occured during task execution."""
-        print >>sys.stderr, "ERROR DURING TASK EXECUTION", exc
-        print >>sys.stderr, "".join(traceback.format_exception(*sys.exc_info()))
-        print >>sys.stderr, "-" * 20
+        print >> sys.stderr, "ERROR DURING TASK EXECUTION", exc
+        print >> sys.stderr, "".join(traceback.format_exception(*sys.exc_info()))
+        print >> sys.stderr, "-" * 20
 
 
@@ -335,5 +335,6 @@
     def reschedule(self, scheduler):
         """Reschedule this task according to its interval (in seconds)."""
-        scheduler.schedule_task(self, self.interval)
+        if scheduler.running:
+            scheduler.schedule_task(self, self.interval)
 
 
@@ -369,6 +370,7 @@
         # (The execute method in the concrete Task classes will check
         # if the current day is a day on which the task must run).
-        abstime = self.get_schedule_time(False)
-        scheduler.schedule_task_abs(self, abstime)
+        if scheduler.running:
+            abstime = self.get_schedule_time(False)
+            scheduler.schedule_task_abs(self, abstime)
 
 
@@ -429,4 +431,5 @@
             Scheduler.__init__(self)
             # we require a lock around the task queue
+            self.thread = None
             self._lock = threading.Lock()
 
@@ -463,10 +466,10 @@
 
         def threadedcall(self):
-            # This method is run within its own thread, so we have to
-            # do the execute() call and exception handling here.
+            """ This method is run within its own thread, so we have to
+            do the execute() call and exception handling here."""
             try:
                 self.execute()
-            except Exception,x:
-                self.handle_exception(x)
+            except Exception, err:
+                self.handle_exception(err)
 
     class ThreadedIntervalTask(ThreadedTaskMixin, IntervalTask):
@@ -534,6 +537,6 @@
                 try:
                     self.execute()
-                except Exception,x:
-                    self.handle_exception(x)
+                except Exception, err:
+                    self.handle_exception(err)
                 os._exit(0)
             else:
@@ -560,11 +563,11 @@
 
 
-if __name__=="__main__":
+if __name__ == "__main__":
     def testaction(arg):
-        print ">>>TASK",arg,"sleeping 3 seconds"
+        print ">>>TASK", arg, "sleeping 3 seconds"
         time.sleep(3)
-        print "<<<END_TASK",arg
-
-    s=ThreadedScheduler()
+        print "<<<END_TASK", arg
+
+    s = ThreadedScheduler()
     s.add_interval_task( testaction, "test action 1", 0, 4, method.threaded, ["task 1"], None )
     s.start()

TurboGears: Changeset [6941]: Merging r6919-6939 from 1.5 branch into 1.5-quickstart-refactor branch

TurboGears

6941
at 2009-11-06 21:38:07
by sorry, but unknown...

Index: /branches/1.5-quickstart-refactor/CHANGELOG.txt
===================================================================
--- /branches/1.5-quickstart-refactor/CHANGELOG.txt (revision 6557)
+++ /branches/1.5-quickstart-refactor/CHANGELOG.txt (revision 6941)
@@ -60,4 +60,177 @@
 Mark Ramm, Sean O'Donnell, Bill Zingler, Ken Kuhlman
 
+1.1.1 (?)
+---------
+
+Fixes
+~~~~~
+
+* When mapping classes with ``__init__` method using `database.session_mapper`,
+  you could get an `AttributeError` (#2386).
+* It was not possible to enter Null values for optional fields using Catwalk.
+  Some fixes were made to solve this, particularly for numeric fields (#760).
+
+Features
+~~~~~~~~
+
+* The `database.session_mapper` now takes an optional `set_kwargs_on_init`
+  parameter allowing you to suppress the automatic setting of keyword args
+  as attributes for instances of the mapped class (#2386).
+
+
+1.1 (October 4, 2009)
+----------------------
+
+Fixes
+~~~~~
+
+* Name error in `identity.SecureResource` when `require` attribute not set
+  (r6686).
+* Unit test for model bootstrap functions in standard quickstart template
+  should not run when identity model can not be imported (r6692).
+* When a visitor requested a page shortly before the `visit.timeout` and
+  then again shortly afterwards, he could sometimes be logged of at the
+  second request because the visit manager had not yet updated the database.
+  This has now been fixed by first looking up visits cached in memory (#2346).
+* Added 'save_on_init' as alias for 'autoadd' kwarg of database.session_mapper
+  for backward-compatibility and fixed a bug occuring when mapping a class a
+  second time with session_mapper (r6747).
+* Genshi now adds correct doctype declarations to HTML and XHTML. By default,
+  the 'strict' variant is used (#1963).
+* Genshi engine now correctly changes the encoding when configured to do
+  so (r6787).
+* Added tests for Genshi template engine integration and separated test for
+  Kid engine in `test_view.py` from engine-independant tests (r6788).
+
+Features
+~~~~~~~~
+
+* There is a new config setting `visit.interval` allowing you to change
+  the visit manager time interval for updating the visit table in the database.
+  The setting must be specified in seconds; the default value is 30 seconds
+  (r6751).
+* The `genshi.default_doctype` configuration setting can now be a dictionary
+  mapping format names to doctype names. For details see the examples in
+  `app.cfg` in the default quickstart templates (r6787).
+
+Changes
+~~~~~~~
+
+* Updated PyPI trove classifiers to give stable status and supported Python
+  versions (r6691).
+* PasteScript version requirement is now >= 1.7 for Python 2.6 compatibility
+  (r6691).
+* TurboCheetah is not installed by default anymore (but Cheetah dependency is
+  pulled by PasteScipt). To install TurboGears with TurboCheetah support, use
+  `easy_install TurboGears[compat]` (r6691).
+* The `turbogears.feed` package is deprecated and using `FeedControlller` will
+  issue a deprecation warning (r6695).
+* The config setting `"tg.defaultview"` now has the default value `"genshi"`.
+  This means that to use kid templates, one has to set `tg.defaultview = "kid"`
+  in the application's configuration or prefix template names with `"kid:"`
+  in the expose decorator (r6696).
+* A missing or faulty dburi raises a `DatabaseConfigurationError` exception
+  in most cases when trying to establish a database connection (r6714).
+* Default identity provider code cleanup and aligning. Changes to
+  `identity.*.encryption_algorithm` are now effective immediately (r6715).
+* Identity tests now use `SqlAlchemyVisitManager` (r6716).
+* Instantiating `SqlObjectVisitManager` fails correctly if visit data model
+  tables can not be created (r6717).
+* Loading the template engine options from the configuration was changed so
+  that all options whose first part of the option name (i.e. everything before
+  the first dot) matches the start of the tempalte plugin name are passed to
+  the template engine when it is instantiated in `view.load_engines`. This
+  means, for example, that the plugin named `"genshi"` will receive all
+  `genshi.*` settings and the `"genshi-markup"` plugin will recieve those
+  as well and all `genshi-markup.*` settings too (r6756).
+* `expose` and `view.render` now take arbitrary keywords arguments, which are
+  passed as keyword args to the `render` method of template plugins, which
+  may use them as options for this rendering process. This replaces the
+  `mapping` keyword arg previously supported by `expose` and `view.render` but
+  which was never passed to the template renderer (r6783).
+* The config setting ``genshi.encoding`` is now ``genshi.default_encoding``.
+  If you used this setting and it was set to something other than `"utf-8"`,
+  you need to change your configuration (r6787).
+* New provisional favicon in quickstart templates. This may be replaced again
+  in a 1.1 bugfix release (r6789).
+
+Contributors (in alphabetical order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Christopher Arndt, Christoph Zwerschke
+
+See also the list of contributors for version 1.1rc1.
+
+
+1.1rc1 (September 20, 2009):
+----------------------------
+
+Fixes
+~~~~~
+
+* Elixir quickstart model now supports transparent passwort encryption as well
+  (#2047).
+* Hardcode kid as template engine for feed templates (#2348).
+* Rename 'commands' module in quickstarted projects to 'command' to avoid
+  conflict with 'commands' module from the standard library (#2141).
+* Adaption of the autocompletefield CSS file for fix #1914.
+* .po header lost when merging a .pot including genshi templates (#2256).
+* Lots of test fixes & improvements (r6318, r6623, r6647, r6648, r6652, r6658).
+* SQLAlchemy forward compatibility fixes (r6576).
+* Projects with no identity support do not include superfluous code (e.g. in
+  model.py) and files (``login.css`` and ``login.html``) any more (#2049).
+* Identity failures during a request to a controller exposed with
+  `tg_format=json` yielded a 500 error instead of 403. Additionally, when
+  no applicable controller method is found, Turbogears now returns HTTP
+  status 404 (#2036).
+* `tg-admin update` did not detect when a project uses Elixir and would
+  overwrite the model with the standard SQLAlchemy based one (#2046).
+* A "i18n merge" took the last message of the same id, while "i18n compile"
+  took the last nonempty message. To make this consistent, "merge" now also
+  takes the last nonempty message. (As suggested by rejoc in ticket #2258).
+
+Features
+~~~~~~~~
+
+* Backported the TG2 templates to 1.1 (r6611).
+* turbogears.database now supports its own version of a session-aware mapper,
+  since the SQLAlchemy version is deprecated and will be removed in the future
+  (r6638).
+* Added NotAny identity predicate (#1343) and unit tests for predicates (r6311).
+* Improved Number validator by adding a decimals parameter (r6316).
+* Minor standard quickstart model improvements (r6617).
+* Added requirements for tests to quickstart setup file (r6622).
+* Instead of a plugin name you can now specify a class in dotted-path notation
+  for the ``visit.manager`` config setting to use a custom visit manager
+  (r6651, thanks to Nic Bellamy fro provding the idea in #2260).
+* Support for JSONification of SQLAlchemy identity model objects in json.py
+  of quickstarted projects plus tests (r6657).
+* Many small improvements to the standard quickstart template files to enhance
+  clarity and conformance to Python coding standards (r6655, r6657).
+* Identity can now initiate HTTP basic authentication. Two new config settings
+  `identity.http_basic_auth` and `identity.http_auth_realm` have been added for
+  this purpose and are explained in the standard quickstart `app.cfg` (r6665).
+* Quickstarted projects now have a unit test case for the bootstrap functions
+  in model.py working for SQLObject and SQLAlchemy based model (r6673).
+
+Changes
+~~~~~~~
+
+* SQLAlchemy version requirement is now >= 0.4.3 (r6638).
+* Elixir version requirement is now >= 0.6.1 (r6640).
+* Made TG 1.1 completely independent of Session.mapper, but still export it as
+  database.mapper if available (r6633).
+* TGTest now automatically resets turbogears.config after every test (r6568).
+* Default visit manager plugin is now "sqlalchemy" instead of "sqlobject"
+  (r6650).
+* Identity failures again will cause a HTTP status code 403 to be returned.
+  Status 401 is only returned when HTTP basic authentication is used (r6665).
+
+Contributors (in alphabetical order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Florent Aide, Christopher Arndt, Ken Kuhlman, Jonathan Schemoul,
+Christoph Zwerschke
+
 
 1.1b3 (December 3, 2008):
@@ -70,4 +243,5 @@
 * Added the .mak file extension to list of supported files for translation
   in admi18n. (#1795)
+
 
 1.1b2 (December 2, 2008):
@@ -164,5 +338,5 @@
   (r5666).
 * 'testutil' does not choke when trying to load test configuration from a
-   bogus 'config' package (r5708).
+  bogus 'config' package (r5708).
 * Fix 'tg-admin update' command, which was broken due to not passing
   'elixirversion' to PasteScript (r5732).
@@ -212,4 +386,168 @@
 Florent Aide, Christopher Arndt, Roger Demetrescu, Jorge Godoy, Paul Johnston,
 Ken Kuhlman, Luke Macken, Diez R. Roggisch, Christoph Zwerschke
+
+
+1.0.9 (October 16, 2009):
+-------------------------
+
+Changes
+~~~~~~~
+
+* ``turbogears.validators`` supports the new ``formencode.national`` module.
+* Lists as return values of controllers are handled like generators again.
+* ``tg-admin i18n collect`` supports a new option ``-e|--js-encoding`` to
+  set the file encoding of the JavaScript source files (r6645).
+
+Fixes
+~~~~~
+
+* Fixed some problems with non-ascci user names and URLs (#1130 and #2118).
+* Static files were not found when project package was part of a larger
+  namespace package (#12).
+* Better error message if validator for option list couldn't be guessed (#978).
+* Fixed Modeldesigner exception (#1109).
+* Multi-level inheritance did not work in model designer (#1092).
+* The PackageHub for SQLObject now only establishes a connection/transaction
+  when really necessary (fixed tickets #763, #817 and #2160).
+* The tgsetup script failed when ``find_link`` was set in ``.pydistutils.cfg``
+  as a single string (#2098).
+* A form widget that is displayed without action parameter now produces an
+  empty action attribute instead of leaving it out which is invalid (#2292).
+* Wrapping the ``CatWalk`` controller with ``identity.SecureObject`` did not
+  protect catwalk's sub-controller ``Browser`` (#2207).
+* ``tg-admin sql help`` produced an error (#2361, thanks to Izhar Firdaus).
+* ``tg-admin i18n collect`` DecodingError when collecting strings from
+  JavaScript files with non-ascii characters (#2183).
+* Removed some obsolete code to handle deleting of related joins from CatWalk
+  (#2162, thanks , Daniel Fetchinson).
+* When a visitor requested a page shortly before the ``visit.timeout`` and
+  then again shortly afterwards, he could sometimes be logged of at the
+  second request because the visit manager had not yet updated the database.
+  This has now been fixed by first looking up visits cached in memory (#2346).
+* The Genshi Buffet interface is now correctly initialized with the
+  ``default_encoding`` setting, not ``encoding`` (see end of #1963).
+* It was not possible to enter Null values for optional fields using Catwalk.
+  Some fixes were made to solve this, particularly for numeric fields (#760).
+* Better error message in Catwalk if foreign key cannot be found (#1412).
+* Primary key strings did not work properly with Catwalk when updating (#1029).
+* Pagination did not work with SQLAlchemy >= 0.6.
+* We now only pass string values as hidden fields in the login form, skipping
+  possible file fields which cannot be passed anyway (#1761).
+* Fixed scheduler shutdown problem with method.sequential (#1702).
+
+Features
+~~~~~~~~
+
+* Added links to the home pages of the individual packages on the toolbox
+  info page (#1155).
+* TurboGears now makes internal use of the ``is_active`` property provided
+  by SQLAlchemy 0.4.9 and newer.
+* Compiled Kid templates are now added when building eggs (#1463).
+* Added default MANIFEST.in to quickstart template to ensure clean builds.
+* Allowed nested validation schemas to be defined by nesting schema class
+  definitions and allowed schema classes, not only instances, to be attached
+  to form widgets (#2393).
+
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Peter Russell, Christoph Zwerschke, Christopher Arndt
+
+
+1.0.8 (December 17, 2008):
+--------------------------
+
+Changes
+~~~~~~~
+
+* Charset parameters are now only added to content type headers when the
+  respective mime type supports this (e.g. all "text" types, but not "pdf").
+* Use "text/html" again instead of "application/xhtml+xml" for XHTML format,
+  as was done in version 1.0.7. Though it is more standards-compliant, IE
+  unfortunately does not cope with this content type, and we want to avoid
+  the problems of content negotiation (see ticket #1998).
+* Updated and fixed the 'tgsetup.py' script (#2053).
+
+Fixes
+~~~~~
+
+* Passing a list of arguments to the redirect method did not work anymore since
+  version 1.0.5 and is now working again (#2006).
+* Solved a small problem in the login controller of quickstarted projects since
+  version 1.0.5 that occurred when directly accessing a non-authorized resource.
+* ``tg-admin toolbox`` ignored the ``--config`` option and adopted problematic
+  config settings from the current project.
+* Fixed an issue with i18n string collection when using the lang option (#2012).
+* Made ``not_anonymous`` internally consistent with others predicates (#2029).
+* ``tg-admin sql`` did not work with SQLAlchemy versions before 0.4.3 (#2057).
+* Hidden fields are now always put in an invisible 'div' section since they
+  must be contained in a block-level element to be valid (X)HTML (#2052).
+* The id of widgets like datagrid could not be changed at render time (#2023).
+* Improved some error messages and warnings.
+
+Features
+~~~~~~~~
+
+* Made content type delivered for a template format configurable with the
+  ``tg.format_mime_types`` setting (see problem with XHTML content above).
+* TurboGears 1.0.8 now additionally includes MochiKit version 1.4.2, though
+  version 1.3.1 is still used by default. You can use the newer version by
+  setting ``tg.mochikit_version`` or ``tg_mochikit.version``  to '1.4' (#2018).
+* Forms have got a new ``use_name`` parameter that allows setting the id
+  attribute instead of the name attribute of the form, since the latter is
+  deprecated in HTML and invalid in XHTML (#2052).
+
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Florent Aide, Christopher Arndt, Christopher Gabriel, Eloi Notario, Peter Russel,
+George Sakkis, Christophe de Vienne, Matt Wilson, Christoph Zwerschke
+
+
+1.0.7 (September 15, 2008):
+---------------------------
+
+Changes
+~~~~~~~
+
+* The standard controller method handling identity.failure_url ("/login") now
+  returns the proper HTTP status code "401 Unauthorized" (instead of
+  "403 Forbidden") (#1787).
+* The required version of SQLObject was bumped up to 0.10.1 (#1765).
+* 'tgsetup.py' installation script does not set script-dir to "/usr/local/bin"
+  anymore on Unix-like systems (#1317).
+
+Fixes
+~~~~~
+
+* The Label widget now renders 'attrs' which had been missing from its template
+  (Andy Grover) (#1964).
+* The 'testutil.call_with_request' did not work with controller methods which
+  raise redirect (Felix Schwarz) (#1203).
+* "tg-admin i18n" and the adm18n toolbox tool now correctly strip whitespace
+  around strings collected from Kid templates so that they get translated
+  properly (#1695).
+* 'identity.get_failure_url' was not backported from 1.1 branch but used in
+  the identity providers (r5338).
+* XHTML templates were delivered with a content type of "text/html" while it
+  should be "application/xhtml+xml" (r5323, related to #1963).
+* CalendarPicker was positioned wrong when scrolled in IE7 (Nick Murdoch)
+  (#1875).
+* Make paginate work with tg.strict_parameters=False (#1889).
+* 'i18n.parse_decimal' broke when locale is unknown (#1104).
+* 'feed.FeedController.index' now passes keyword args when redirecting to the
+  feed handler methods (#1732).
+* 'config.config_defaults()' should now set 'package_dir' correctly on all
+  platforms (r5328).
+
+Features
+~~~~~~~~
+
+None
+
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Christopher Arndt, Christoph Zwerschke
 
 
@@ -254,6 +592,6 @@
 * Fixed several problems with toolbox admi18n (#1399, #1941, #1943).
 
-Contributors (in alphabetical order)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Florent Aide, Christopher Arndt, Ken Kuhlman, Tore Lundqvist, Christoph Zwerschke.
@@ -472,6 +810,6 @@
 
 
-1.0.4b3: (December, 2, 2007):
------------------------------
+1.0.4b3: (December 2, 2007):
+----------------------------
 
 Deprecations
@@ -488,13 +826,23 @@
   the shipped MochiKit 1.3.1. That allows to include custom mochikit versions.
 * PaginateDataGrid template now makes use of paginate attributes to render
-  the links for first/previous/next/last page.
+  the links for first/previous/next/last page (#1617).
 * ``paginate.href_last`` returns a special URL that allows paginate decorator
-  to compute the correct last page number at server-side.
-
-Features
-~~~~~~~~
-
-* Introduction of tg.mochikit_suppress to prevent the inclusion of
-  the shipped MochiKit 1.3.1. That allows to include custom mochikit versions.
+  to compute the correct last page number at server-side (#1617).
+* The ``start-<project>.py`` script in a quickstarted project is now only a
+  wrapper for the ``start()`` function in a new ``commands`` module in the
+  project's package. The ``setup.py`` in new project also creates a console
+  script entry point for this, so easy_install can create a start script
+  when the project's egg is installed. It also allows to package a default
+  configuration file in the egg. For details see ticket (#1386).
+* Installation of TurboGears now does not require installation of an ORM.
+  Instead, a project that relies on SQLObject or SQLAlchemy will have a
+  ``setup.py`` file written with the proper requirements (#1501, #1620).
+* Removed the hard SQLAlchemy/SQLObject dependecies and modified quickstart
+  to render the required ORM as requirement into a projects setup.py.
+
+Features
+~~~~~~~~
+
+* Introduction of tg.mochikit_suppress to (see "Changes").
 * Workaround in paginate for databases without ``OFFSET`` (#1601).
 * The database module exports a mapper which is either session.mapper
@@ -509,5 +857,5 @@
   last page is requested, respectively.
 * Paginate ``default_order`` can now be a string or a list of strings.
-  The list of string is used to specify the ordering of multiple columns.
+  The list of strings is used to specify the ordering of multiple columns.
   Every string starting with a dash (``-``) indicates that the column will
   have its default ordering reversed (#1618).
@@ -530,4 +878,11 @@
 * Fix pagination of out of bound pages (#1617).
 * ``tg-admin i18n`` now supports Unicode strings in Kid templates (#1397).
+* Fixed testutil to properly use the soClasses attribute in the model
+  in order to pick up only the classes defined in this list and not
+  the rest. Thanks to Gregor Horvath for this suggestion. (#1586).
+* Fixed the command line interface to i18n collection. Command line now
+  processes also the templates (#1436).
+* Identity bug when using non-ASCII characters in the URL (#1598, #1407,
+  #1022).
 
 Project Updates
@@ -538,12 +893,13 @@
 * TurboKid 1.0.4
 
-Contributors
-~~~~~~~~~~~~
-
-Joel Pearson, Christoph Zwerschke, Roger Demetrescu, Juan Germano
-
-
-1.0.4b2 (October, 27, 2007):
-----------------------------
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Florent Aide, Christopher Arndt, Roger Demetrescu, Juan Germano, Joel Pearson,
+Diez B. Roggisch, Christoph Zwerschke.
+
+
+1.0.4b2 (October 27, 2007):
+---------------------------
 
 Changes
@@ -713,5 +1069,5 @@
 
 Simon Wittber, Christopher Arndt, Christoph Zwerschke, Paul Johnston, FredLin,
[[BINARY DATA]]
 Florent Aide.
 
@@ -1197,5 +1553,5 @@
 
 Max Ischenko, Claudio Martinez, Matt Good, Rune Hansen, Michele Cella,
[[BINARY DATA]]
 Mark Ramm-Christensen, Ronald Jaramillo,
 Richard Standbrook, Roger Demetrescu, Patrick Lewis, Hal Wine,
@@ -1332,5 +1688,5 @@
 ~~~~~~~~~~~~
 
[[BINARY DATA]]
 Simon Belak, Jorge Godoy, Patrick Lewis, Jorge Vargas, Joost Moesker,
 Joseph Tate, Philip Walls, Bob Ippolito, Steve Bergman, Andrey Lebedev,
@@ -1548,5 +1904,5 @@
 This release comes to you thanks to the work of Michele Cella,
 Elvelind Grandin, Ronald Jaramillo, Simon Belak, Jeff Watkins,
[[BINARY DATA]]
 Dan Weeks, Dennis Brakhane, Heikichi Umahara, Patrick Lewis,
 Joost Moesker, Roger Demetrescu, Liza Daly.
@@ -1710,8 +2066,8 @@
 Joey Smith, David Bernard, Simon Davy, Gary Godfrey, Jason Chu,
 Liza Daly, Ryan Forsythe, Jeremy Jones, Egor Cheshkov, Claudio Martinez,
[[BINARY DATA]]
 Brian Bockelman, Stephen Thorne, Robin Bryce, Ksenia Marasanova,
 Ori Avtalion, Martina Oefelein, Ian Bicking, Rick Richardson,
[[BINARY DATA]]
 A special thanks to Cliff Wells of Develix for sponsoring a bug bounty!
Index: /branches/1.5-quickstart-refactor/test.cfg
===================================================================
--- /branches/1.5-quickstart-refactor/test.cfg (revision 6084)
+++ /branches/1.5-quickstart-refactor/test.cfg (revision 6941)
@@ -3,4 +3,6 @@
 sqlobject.dburi = 'sqlite:///:memory:'
 sqlalchemy.dburi = 'sqlite:///:memory:'
+
+log.screen = False
 
 [logging]
@@ -22,2 +24,9 @@
 handlers=['test_out']
 
+[[[cp_error]]]
+level="DEBUG"
+qualname="cherrypy.error"
+handlers=['test_out']
+propgate=0
+
+
Index: /branches/1.5-quickstart-refactor/turbogears/identity/conditions.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/conditions.py (revision 6737)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/conditions.py (revision 6941)
@@ -196,9 +196,7 @@
     if not request_available():
         raise RequestRequiredException()
-    ip = request.headers.get('X-Forwarded-For',
-        request.headers.get('Remote-Addr'))
-    if ip:
-        return ip.rsplit(',', 1)[-1].strip()
-    return None
+    else:
+        return request.headers.get('X-Forwarded-For', request.headers.get(
+            'Remote-Addr', '')).rsplit(',', 1)[-1].strip() or None
 
 class from_host(Predicate, IdentityPredicateHelper):
Index: /branches/1.5-quickstart-refactor/turbogears/identity/base.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/base.py (revision 6710)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/base.py (revision 6941)
@@ -27,6 +27,9 @@
 
 import logging
-import md5
-import sha
+try:
+    from hashlib import md5, sha1
+except ImportError:
+    from sha import new as sha1
+    from md5 import new as md5
 
 import cherrypy
@@ -111,7 +114,7 @@
         password_8bit = password
     if algorithm == 'md5':
-        hashed_password =  md5.new(password_8bit).hexdigest()
+        hashed_password =  md5(password_8bit).hexdigest()
     elif algorithm == 'sha1':
-        hashed_password = sha.new(password_8bit).hexdigest()
+        hashed_password = sha1(password_8bit).hexdigest()
     elif algorithm == 'custom':
         custom_encryption_path = turbogears.config.get(
@@ -211,7 +214,14 @@
     """
 
-    if (cherrypy.response.status < '400'):
-        new_status = cherrypy.request.wsgi_environ.get('identity_status', None)
+    if (str(cherrypy.response.status) < '400'):
+        new_status = cherrypy.request.wsgi_environ.get('identity_status')
         if new_status:
             cherrypy.response.status = new_status
-
+        auth_realm = cherrypy.request.wsgi_environ.get('identity_auth_realm')
+        if auth_realm:
+            cherrypy.response.headers['WWW-Authenticate'] = auth_realm
+            # During the finalize stage, CP stops looking at response.headers,
+            #  so populate header_list as well.
+            if not cherrypy.response.header_list:
+                cherrypy.response.header_list = []
+            cherrypy.response.header_list.append(('WWW-Authenticate', auth_realm))
Index: /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_identity.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_identity.py (revision 6843)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_identity.py (revision 6941)
@@ -14,5 +14,7 @@
 from turbogears.identity.conditions import _remoteHost
 from turbogears.identity.soprovider import TG_User, TG_Group, TG_Permission
-from turbogears.identity.exceptions import RequestRequiredException
+from turbogears.identity.exceptions import (IdentityConfigurationException,
+    RequestRequiredException)
+from turbogears.identity.visitor import IdentityVisitPlugin
 
 #hub = database.AutoConnectHub("sqlite:///:memory:")
@@ -139,4 +141,8 @@
             return 'in_other_group'
 
+    @expose(format='json')
+    def json(self):
+        return dict(json="restricted_json")
+
 
 class IdentityRoot(RootController):
@@ -146,6 +152,7 @@
         return dict()
 
-    @expose()
+    @expose(format='html')
     def identity_failed(self, **kw):
+        """Identity failure - this usually returns a login form."""
         return 'identity_failed_answer'
 
@@ -167,5 +174,4 @@
         assert identity.current.group_ids == group_ids
         assert "samIam" == cherrypy.serving.request.identity.user_name
-
         return 'in_peon_group'
 
@@ -256,7 +262,13 @@
         return 'is_anonymous'
 
+    @expose(format='json')
+    @identity.require(in_group('peon'))
+    def json(self):
+        return dict(json="restricted_json")
 
 
 class TestIdentity(testutil.TGTest):
+
+    stop_tg_only = True
 
     def setUp(self):
@@ -266,5 +278,9 @@
             'visit.manager': 'sqlalchemy',
             'identity.failure_url': '/identity_failed',
-            'identity.soprovider.encryption_algorithm': None}}
+            'identity.soprovider.encryption_algorithm': None,
+            'identity.http_basic_auth': False,
+            'identity.http_auth_realm': None,
+            'tg.strict_parameters': True,
+            'tg.allow_json': False}}
         original_config = dict()
         for key in test_config['global']:
@@ -302,9 +318,10 @@
 
     def test_user_password_parameters(self):
-        """Controller can receive user_name and password parameters."""
+        """Test that controller can receive user_name and password parameters."""
         response = self.app.get('/new_user_setup?user_name=test&password=pw')
         assert 'test pw' in response, response
 
     def test_user_exists(self):
+        """Test that test user is present in data base."""
         u = TG_User.by_user_name('samIam')
         assert u.email_address == 'samiam@example.com'
@@ -409,4 +426,5 @@
 
     def test_user_password_raw_unicode(self):
+        """Test that unicode passwords are encrypted correctly"""
         config.update({'identity.soprovider.encryption_algorithm':'sha1'})
         self.app.get('/')
@@ -440,5 +458,5 @@
     def test_deny_anonymous(self):
         """Test that we have secured an url from anonymous users."""
-        response = self.app.get('/logged_in_only', status=401)
+        response = self.app.get('/logged_in_only', status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -450,16 +468,25 @@
         assert 'logged_in_only' in response, response
 
+    def test_authenticate_header(self):
+        """Test that identity returns correct WWW-Authenticate header."""
+        response = self.app.get('/logged_in_only', status=403)
+        assert 'WWW-Authenticate' not in response.headers
+        config.update({'identity.http_basic_auth': True,
+                       'identity.http_auth_realm': 'test realm'})
+        response = self.app.get('/logged_in_only', status=401)
+        assert response.headers['WWW-Authenticate'] == 'Basic realm="test realm"'
+
     def test_basic_authentication(self):
         """Test HTTP basic authentication mechanism."""
         credentials = base64.encodestring('samIam')[:-1]
         response = self.app.get('/logged_in_only', headers={
-            'Authorization': 'Basic %s' % credentials}, status=401)
+            'Authorization': 'Basic %s' % credentials}, status=403)
         assert 'identity_failed_answer' in response, response
         credentials = base64.encodestring('samIam:secret:appendix')[:-1]
         response = self.app.get('/logged_in_only', headers={
-            'Authorization': 'Basic %s' % credentials}, status=401)
+            'Authorization': 'Basic %s' % credentials}, status=403)
         assert 'identity_failed_answer' in response, response
         response = self.app.get('/logged_in_only', headers={
-            'Authorization': 'Basic samIam:secret'}, status=401)
+            'Authorization': 'Basic samIam:secret'}, status=403)
         assert 'identity_failed_answer' in response, response
         credentials = base64.encodestring('samIam:secret')[:-1]
@@ -480,5 +507,5 @@
         """Test that a anonymous user can not access resource protected by
         require(in_group(...))"""
-        response = self.app.get('/in_peon_group', status=401)
+        response = self.app.get('/in_peon_group', status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -508,5 +535,5 @@
         """
         response = self.app.get('/in_admin_group?'
-            'user_name=samIam&password=secret&login=Login', status=401)
+            'user_name=samIam&password=secret&login=Login', status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -515,5 +542,5 @@
         restricted url.
         """
-        response = self.app.get('/has_chopper_permission', status=401)
+        response = self.app.get('/has_chopper_permission', status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -529,5 +556,5 @@
         """
         response = self.app.get('/has_boss_permission?'
-            'user_name=samIam&password=secret&login=Login', status=401)
+            'user_name=samIam&password=secret&login=Login', status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -541,10 +568,10 @@
         """Test that we are denied access if we provide a bad login."""
         response = self.app.get('/logged_in_only?'
-            'user_name=samIam&password=wrong&login=Login', status=401)
+            'user_name=samIam&password=wrong&login=Login', status=403)
         assert 'identity_failed_answer' in response, response
 
     def test_restricted_subdirectory(self):
         """Test that we can restrict access to a whole subdirectory."""
-        response = self.app.get('/peon_area/index', status=401)
+        response = self.app.get('/peon_area/index', status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -567,5 +594,5 @@
         in a restricted subdirectory"""
         response = self.app.get('/peon_area/in_admin_group?'
-            'user_name=samIam&password=secret&login=Login', status=401)
+            'user_name=samIam&password=secret&login=Login', status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -581,5 +608,5 @@
         directory is handled as expected"""
         response = self.app.get('/peon_area/in_admin_group_explicit_check?'
-            'user_name=samIam&password=secret&login=Login', status=401)
+            'user_name=samIam&password=secret&login=Login', status=403)
         assert 'identity_failed' in response, response
 
@@ -589,5 +616,5 @@
         params = urllib.quote(IdentityRoot._test_encoded_params.decode(
             'utf-8').encode('latin-1'), '=&')
-        response = self.app.get('/test_params?' + params, status=401)
+        response = self.app.get('/test_params?' + params, status=403)
         assert 'identity_failed_answer' in response, response
 
@@ -614,8 +641,33 @@
         assert 'logged_in_only' in response, response
 
+    def test_json(self):
+        """Test that JSON controllers return the right status codes."""
+        # We check that we get an authorization error, not the server error
+        # caused by the identity_failure controller not accepting JSON.
+        response = self.app.get('/json', status=403)
+        # we get the output of the identity_failure controller in this case
+        assert 'identity_failed_answer' in response, response
+        response = self.app.get('/json?tg_format=json', status=403)
+        # we get the right status code, but not the output of identity_failure
+        assert 'identity_failed_answer' not in response, response
+        assert 'Forbidden' in response, response
+        response = self.app.get('/peon_area/json', status=403)
+        assert 'identity_failed_answer' in response, response
+        response = self.app.get('/peon_area/json?tg_format=json', status=403)
+        assert 'identity_failed_answer' not in response, response
+        response = self.app.get('/in_peon_group?'
+            'user_name=samIam&password=secret&login=Login')
+        assert 'in_peon_group' in response, response
+        response = self.app.get('/json', status=200)
+        assert 'restricted_json' in response, response
+        response = self.app.get('/json?tg_format=json', status=200)
+        assert 'restricted_json' in response, response
+        response = self.app.get('/peon_area/json', status=200)
+        assert 'restricted_json' in response, response
+        response = self.app.get('/peon_area/json?tg_format=json', status=200)
+        assert 'restricted_json' in response, response
 
     def test_remote_ip(self):
         """Test that our client IP is detected correctly."""
-        r = self.app.get('/remote_ip', status=200)
         r = self.app.get('/remote_ip', headers={'Remote-Addr': '127.0.0.1'},
             status=200)
@@ -629,5 +681,5 @@
         """Test we can connect from 127.0.0.1."""
         r = self.app.get('/from_localhost', headers={'Remote-Addr': '192.168.4.100'},
-            status=401)
+            status=403)
         assert 'identity_failed_answer' in r
         r = self.app.get('/from_localhost', headers={'Remote-Addr': '127.0.0.1'},
@@ -638,5 +690,5 @@
         """Test we can connect from any host in an IP list."""
         r = self.app.get('/from_any_host', headers={'Remote-Addr': '192.168.4.100'},
-            status=401)
+            status=403)
         assert 'identity_failed_answer' in r
         r = self.app.get('/from_any_host', headers={'Remote-Addr': '127.0.0.1'},
@@ -650,4 +702,5 @@
 class TestTGUser(testutil.DBTest):
     model = TG_User
+    stop_tg_only = True
 
     def setUp(self):
@@ -655,5 +708,4 @@
         config.update({'identity.on': False})
         super(TestTGUser, self).setUp()
-        testutil.start_server()
 
     def tearDown(self):
@@ -663,7 +715,41 @@
 
     def test_create_user(self):
-        """Check that User can be created outside of a running identity provider."""
+        """Test that User can be created outside of a running identity provider."""
         u = TG_User(user_name='testcase',
             email_address='testcase@example.com',
             display_name='Test Me', password='test')
         assert u.password=='test', u.password
+
+
+class TestIdentityVisitPlugin(unittest.TestCase):
+    def setUp(self):
+        test_config = {'global': {
+            'tools.visit.on': True,
+            'identity.on': True,
+            'identity.source': 'form, http_auth, visit, '}}
+        original_config = dict()
+        for key in test_config['global']:
+            original_config[key] = config.get(key)
+        self._original_config = original_config
+        config.update(test_config['global'])
+
+    def tearDown(self):
+        config.update(self._original_config)
+
+    def test_identity_source(self):
+        """Test that identity.source setting is parsed correctly."""
+        plug = IdentityVisitPlugin()
+        assert plug.identity_from_form in plug.identity_sources
+        assert plug.identity_from_http_auth in plug.identity_sources
+        assert plug.identity_from_visit in plug.identity_sources
+
+    def test_unknown_identity_source(self):
+        """Test that unknown identity source raises identity configuration error."""
+        config.update({'identity.source': 'bogus'})
+        self.assertRaises(IdentityConfigurationException, IdentityVisitPlugin)
+
+    def test_decode_credentials(self):
+        """Test that HTTP basic auth credentials are decoded correctly."""
+        plug = IdentityVisitPlugin()
+        credentials = base64.encodestring('samIam:secret')[:-1]
+        assert plug.decode_basic_credentials(credentials) == ['samIam', 'secret']
Index: /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_visit.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_visit.py (revision 6745)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_visit.py (revision 6941)
@@ -13,5 +13,4 @@
     """Returns a dict containing cookie information to pass to a server."""
     return dict(Cookie=response.headers['Set-Cookie'])
-
 
 def setup_module():
Index: /branches/1.5-quickstart-refactor/turbogears/identity/visitor.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/visitor.py (revision 6704)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/visitor.py (revision 6941)
@@ -70,7 +70,9 @@
         # checked. These terms are mapped to methods by prepending
         # "identity_from_".
-        sources = get('identity.source', 'form,http_auth,visit').split(',')
+        sources = [s.strip() for s in
+                   get('identity.source', 'form,http_auth,visit').split(',')]
         self.identity_sources = list()
         for s in sources:
+            if not s: continue
             try:
                 source_method = getattr(self, 'identity_from_' + s)
@@ -189,3 +191,2 @@
         set_current_identity(identity)
         set_current_provider(self.provider)
-
Index: /branches/1.5-quickstart-refactor/turbogears/identity/exceptions.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/exceptions.py (revision 5539)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/exceptions.py (revision 6941)
@@ -16,4 +16,5 @@
 import turbogears
 
+from turbogears import config
 
 def set_identity_errors(errors):
@@ -71,9 +72,7 @@
     args = ()
 
-    def __init__(self, message):
-        self.message = message
-
     def __str__(self):
-        return self.message
+        return (self.args and self.args[0] or 
+            'Unknown Identity configuration error')
 
 
@@ -92,7 +91,13 @@
         # that a simple GET to /login receives a 401 http code.
 
-        # This needs to be saved into the wsgi environment because 
-        #  request & reposne are reset when we redirect.
-        cherrypy.request.wsgi_environ['identity_status'] = '401 Unauthorized'
+        if config.get('identity.http_basic_auth', False):
+            # This needs to be saved into the wsgi environment because 
+            #  request & reposne are reset when we redirect.
+            cherrypy.request.wsgi_environ['identity_status'] = '401 Unauthorized'
+            cherrypy.request.wsgi_environ['identity_auth_realm'] = \
+                'Basic realm="%s"' % config.get('identity.http_auth_realm',
+                'TurboGears')
+        else:
+            cherrypy.request.wsgi_environ['identity_status'] = '403 Forbidden'
 
         if turbogears.config.get('identity.force_external_redirect', False):
@@ -106,3 +111,5 @@
         else:
             # use internal redirect which is quicker
+            if cherrypy.request.query_string:
+                url += '?' + cherrypy.request.query_string
             cherrypy.InternalRedirect.__init__(self, url)
Index: /branches/1.5-quickstart-refactor/turbogears/config.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/config.py (revision 6880)
+++ /branches/1.5-quickstart-refactor/turbogears/config.py (revision 6941)
@@ -247,5 +247,6 @@
                 server[key] = value
 
-                if key in ['visit.on', 'toscawidgets.on']:
+                if key in ['visit.on', 'toscawidgets.on'] and \
+                   'tools.%s' % key not in server:
                     warnings.warn("Config key %s is deprecated.  "
                         "Use tools.%s instead." % (key, key),
Index: /branches/1.5-quickstart-refactor/turbogears/controllers.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/controllers.py (revision 6903)
+++ /branches/1.5-quickstart-refactor/turbogears/controllers.py (revision 6941)
@@ -226,6 +226,6 @@
     ruleparts = ['kw.get("tg_format", "default") == "%s"' % as_format]
     if accept_format:
-        ruleparts.append('(accept == "%s" and kw.get("tg_format"'
-            ', "default") == "default")' % accept_format)
+        ruleparts.append('(accept == "%s" and '
+            'kw.get("tg_format", "default") == "default")' % accept_format)
     rule = " or ".join(ruleparts)
     log.debug("Generated rule %s", rule)
@@ -240,9 +240,8 @@
 
     if func._allow_json:
-        log.debug("Adding allow_json rule: "
-            'allow_json and (kw.get("tg_format", None) == "json"'
+        rule = ('allow_json and (kw.get("tg_format", None) == "json"'
             ' or accept in ("application/json", "text/javascript"))')
-        first(_expose, 'allow_json and (kw.get("tg_format", None) == "json"'
-            ' or accept in ("application/json", "text/javascript"))')(
+        log.debug("Adding allow_json rule for %s: %s", func, rule)
+        first(_expose, rule)(
             lambda _func, accept, allow_json, *args, **kw:
                 _execute_func(_func, "json", "json", "application/json",
@@ -363,7 +362,7 @@
                         # error unless a specific error status was already set
                         # (e.g. "unauthorized" was set by the identity provider):
-                        status = cherrypy.response.status
-                        if status and status // 100 == 4:
-                            raise cherrypy.HTTPError(status)
+                        status = cherrypy.request.wsgi_environ.get('identity_status')
+                        if status and str(status) >= '400':
+                            raise cherrypy.HTTPError(*str(status).split(None, 1))
                         raise cherrypy.NotFound
                     # If the error was raised elsewhere inside the controller,
@@ -440,5 +439,5 @@
 
         if template and template.startswith("."):
-            template = func.__module__[:func.__module__.rfind('.')]+template
+            template = func.__module__[:func.__module__.rfind('.')] + template
 
         return _process_output(output, template, format, content_type,
@@ -522,5 +521,4 @@
         except AttributeError:
             username = "-"
-
         request_date = time.strftime('%d/%b/%Y:%H:%M:%S +0000',time.gmtime())
         request_info = {
Index: /branches/1.5-quickstart-refactor/turbogears/visit/api.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/visit/api.py (revision 6866)
+++ /branches/1.5-quickstart-refactor/turbogears/visit/api.py (revision 6941)
@@ -14,6 +14,10 @@
 ]
 
+
 import logging
-import sha
+try:
+    from hashlib import sha1
+except ImportError:
+    from sha import new as sha1
 import threading
 import time
@@ -83,4 +87,5 @@
         return
 
+    log.info("Visit Tracking starting.")
     # How long may the visit be idle before a new visit ID is assigned?
     # The default is 20 minutes.
@@ -121,8 +126,9 @@
 
 class VisitTool(object):
-    """A filter that automatically tracks visitors."""
+    """A tool that automatically tracks visitors."""
 
     def __init__(self):
-        log.info("Visit filter initialised.")
+        log.info("Visit tool initialised.")
+        get = config.get
 
     def __call__(self, **kw):
@@ -153,4 +159,5 @@
         self.cookie_max_age = get("cookie.permanent",
             False) and int(get("timeout", "20")) * 60 or None
+        log.info("Visit filter initialised")
 
         cpreq = cherrypy.request
@@ -200,5 +207,5 @@
         key_string = '%s%s%s%s' % (random(), datetime.now(),
             cherrypy.request.remote.ip, cherrypy.request.remote.port)
-        return sha.new(key_string).hexdigest()
+        return sha1(key_string).hexdigest()
     _generate_key = staticmethod(_generate_key)
 
Index: /branches/1.5-quickstart-refactor/turbogears/visit/sovisit.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/visit/sovisit.py (revision 6856)
+++ /branches/1.5-quickstart-refactor/turbogears/visit/sovisit.py (revision 6941)
@@ -27,5 +27,5 @@
         visit_class = load_class(visit_class_path)
         if visit_class:
-            log.info("Succesfully loaded \"%s\"" % visit_class_path)
+            log.info('Succesfully loaded "%s"', visit_class_path)
         # base-class' __init__ triggers self.create_model, so mappers need to
         # be initialized before.
@@ -33,19 +33,14 @@
 
     def create_model(self):
-        try:
-            # Create the Visit table if it doesn't already exist
-            hub.begin()
-            visit_class.createTable(ifNotExists=True)
-            hub.commit()
-            hub.end()
-        except KeyError:
-            # No database configured...
-            log.info("No database is configured: Visit Tracking is disabled.")
-            return
+        hub.begin()
+        visit_class.createTable(ifNotExists=True)
+        hub.commit()
+        hub.end()
+        log.debug("Visit model database table(s) created.")
 
     def new_visit_with_key(self, visit_key):
         hub.begin()
         visit = visit_class(visit_key=visit_key,
-            expiry=datetime.now()+self.timeout)
+            expiry = datetime.now() + self.timeout)
         hub.commit()
         hub.end()
@@ -69,5 +64,5 @@
             return None
         # Visit hasn't expired, extend it
-        self.update_visit(visit_key, now+self.timeout)
+        self.update_visit(visit_key, now + self.timeout)
         return Visit(visit_key, False)
 
Index: /branches/1.5-quickstart-refactor/turbogears/visit/savisit.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/visit/savisit.py (revision 6856)
+++ /branches/1.5-quickstart-refactor/turbogears/visit/savisit.py (revision 6941)
@@ -9,5 +9,5 @@
 
 from turbogears import config
-from turbogears.database import get_engine, bind_metadata, metadata, session
+from turbogears.database import bind_metadata, metadata, session
 from turbogears.util import load_class
 from turbogears.visit.api import BaseVisitManager, Visit
@@ -30,8 +30,9 @@
             msg += ', did you run setup.py develop?'
             log.error(msg)
+        else:
+            log.info('Succesfully loaded "%s"', visit_class_path)
 
-        bind_metadata()
         if visit_class is TG_Visit:
-            # Do not try to map TG_Visit if already done
+            # Handle it gracefully when TG_Visit is already mapped.
             # May happen, when the visit manager is shutdown and started again
             try:
@@ -47,4 +48,5 @@
         bind_metadata()
         class_mapper(visit_class).local_table.create(checkfirst=True)
+        log.debug("Visit model database table(s) created.")
 
     def new_visit_with_key(self, visit_key):
Index: /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/model.py_tmpl
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/model.py_tmpl (revision 6882)
+++ /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/model.py_tmpl (revision 6941)
@@ -477,5 +477,5 @@
     @property
     def permissions(self):
-        """Return all permissions os all groups the user belongs to."""
+        """Return all permissions of all groups the user belongs to."""
         p = set()
         for g in self.groups:
Index: /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/config/app.cfg_tmpl
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/config/app.cfg_tmpl (revision 6914)
+++ /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/config/app.cfg_tmpl (revision 6941)
@@ -185,7 +185,19 @@
 identity.on = True
 
+# Should the server request the client to provide credentials via HTTP basic
+# authentication? This is off by default, since identity identity uses
+# form/cookie-based authentication.
+# identity.http_basic_auth = False
+
+# The authentication realm to use for HTTP authentication
+identity.http_realm = "${project}"
+
 # [REQUIRED] URL to which CherryPy will internally redirect when an access
 # control check fails. If Identity management is turned on, a value for this
 # option must be specified.
+# If you use HTTP basic auth and you don't want to allow form-based login
+# you should set this to the URL of a controller which just returns an
+# "Authorization required" error message. You probably also want to change
+# the 'identity.source' setting below.
 identity.failure_url = "/login"
 
@@ -212,5 +224,9 @@
 # What sources should the identity provider consider when determining the
 # identity associated with a request? Comma separated list of identity sources.
-# Valid sources: form, visit, http_auth
+# Valid sources:
+#     form = User and password supplied as GET or POST request parameters
+#     visit = Session cookie
+#     http_auth = User and password supplied via HTTP Authorization header.
+#                 Only HTTP Basic Authentication is currently supported.
 # identity.source = "form,http_auth,visit"
 
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_controllers.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_controllers.py (revision 6842)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_controllers.py (revision 6941)
@@ -7,4 +7,11 @@
     error_handler, exception_handler, expose, flash, redirect, startup,
     testutil, url, util, validate, validators)
+
+
+def setup_module():
+    testutil.start_server()
+
+def teardown_module():
+    testutil.stop_server()
 
 
@@ -282,4 +289,5 @@
 
 class TestRoot(testutil.TGTest):
+    stop_tg_only = True
 
     def setUp(self):
@@ -634,4 +642,5 @@
 
 class TestURLs(testutil.TGTest):
+    stop_tg_only = True
 
     def setUp(self):
@@ -757,11 +766,5 @@
 class TestAbsoluteURLs(testutil.TGTest):
     root = MyRoot
-
-    #def setUp(self):
-    #    super(TestAbsoluteURLs, self).setUp()
-    #
-    #def tearDown(self):
-    #    super(TestAbsoluteURLs, self).tearDown()
-    #    config.update({"server.webpath": ""})
+    stop_tg_only = True
 
     def _config_update(self, data=None, **kw):
@@ -869,5 +872,4 @@
         url = self.app.get('/absolute_url/?url=%2Ffoo',
             headers={'Host': ''}).body
-        print "CONFIG:", self.config_backup
         self._config_reset()
         print url
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_util.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_util.py (revision 6848)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_util.py (revision 6941)
@@ -214,35 +214,9 @@
 
 
-def test_get_template_encoding_default():
-    assert util.get_template_encoding_default() == 'utf-8'
-
-
-def test_get_mime_type_for_format():
-    assert util.get_mime_type_for_format('plain') == 'text/plain'
-    assert util.get_mime_type_for_format('text') == 'text/plain'
-    assert util.get_mime_type_for_format('html') == 'text/html'
-    assert util.get_mime_type_for_format('xhtml') == 'text/html'
-    assert util.get_mime_type_for_format('xml') == 'text/xml'
-    assert util.get_mime_type_for_format('json') == 'application/json'
-    assert util.get_mime_type_for_format('foo') == 'text/html'
-    config.update({'global': {'tg.format_mime_types':
-        {'xhtml': 'application/xhtml+xml', 'foo': 'text/bar'}}})
-    assert util.get_mime_type_for_format('xhtml') == 'application/xhtml+xml'
-    assert util.get_mime_type_for_format('json') == 'application/json'
-    assert util.get_mime_type_for_format('foo') == 'text/bar'
-    config.update({'global': {'tg.format_mime_types': {}}})
-    assert util.get_mime_type_for_format('xhtml') == 'text/html'
-    assert util.get_mime_type_for_format('foo') == 'text/html'
-
-
-def test_mime_type_has_charset():
-    assert not util.mime_type_has_charset(None)
-    assert not util.mime_type_has_charset('foo/bar')
-    assert util.mime_type_has_charset('text/foobar')
-    assert util.mime_type_has_charset('application/xml')
-    assert util.mime_type_has_charset('application/foo+xml')
-    assert util.mime_type_has_charset('application/javascript')
-    assert not util.mime_type_has_charset('application/foobar')
-    assert not util.mime_type_has_charset('application/json')
+def test_unquote_cookie():
+    assert util.unquote_cookie ('Hello%2C%20W\xf6rld!') \
+        == 'Hello, W\xf6rld!'
+    assert util.unquote_cookie ('%241%3B%09insert%20coin!') \
+        == '$1;\tinsert coin!'
 
 
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_session_mapper.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_session_mapper.py (revision 6804)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_session_mapper.py (revision 6941)
@@ -1,2 +1,4 @@
+"""Unit tests for turbogears.database.session_mapper."""
+
 import unittest
 
@@ -7,4 +9,5 @@
 except ImportError: # SQLAlchemy < 0.5
     from sqlalchemy.exceptions import ArgumentError
+
 
 from turbogears import config
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_paginate.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_paginate.py (revision 6849)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_paginate.py (revision 6941)
@@ -1055,4 +1055,5 @@
     _so_dburi = config.get("sqlobject.dburi", "sqlite:///:memory:")
     _sa_dburi = config.get("sqlalchemy.dburi", "sqlite:///:memory:")
+    # sqlalchemy setup
     database.set_db_uri("sqlite:///:memory:", 'sqlalchemy')
     sqlalchemy_cleanup()
Index: /branches/1.5-quickstart-refactor/turbogears/testutil.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/testutil.py (revision 6555)
+++ /branches/1.5-quickstart-refactor/turbogears/testutil.py (revision 6941)
@@ -109,5 +109,5 @@
 def stop_server(tg_only = False):
     """Stop the server and unmount the application.
-    
+
     Use tg_only = True to leave CherryPy running (for faster tests).
 
Index: /branches/1.5-quickstart-refactor/turbogears/widgets/tests/test_nested_widgets.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/widgets/tests/test_nested_widgets.py (revision 6887)
+++ /branches/1.5-quickstart-refactor/turbogears/widgets/tests/test_nested_widgets.py (revision 6941)
@@ -330,4 +330,5 @@
 
 
+
 class InnerSchema(validators.Schema):
     ignore_key_missing = True

TurboGears: Changeset [6940]: pulled in kronos.py V2, and moved the custom function into init.py to …

TurboGears

6940
at 2009-11-06 21:34:04
by vinces1979@gmail.com

Index: /projects/TGScheduler/trunk/tgscheduler.egg-info/SOURCES.txt
===================================================================
--- /projects/TGScheduler/trunk/tgscheduler.egg-info/SOURCES.txt (revision 6938)
+++ /projects/TGScheduler/trunk/tgscheduler.egg-info/SOURCES.txt (revision 6940)
@@ -1,7 +1,6 @@
 setup.py
 tgscheduler/__init__.py
+tgscheduler/kronos.py
 tgscheduler/release.py
-tgscheduler/scheduler.py
-tgscheduler/util.py
 tgscheduler.egg-info/PKG-INFO
 tgscheduler.egg-info/SOURCES.txt
Index: /projects/TGScheduler/trunk/tgscheduler.egg-info/PKG-INFO
===================================================================
--- /projects/TGScheduler/trunk/tgscheduler.egg-info/PKG-INFO (revision 6938)
+++ /projects/TGScheduler/trunk/tgscheduler.egg-info/PKG-INFO (revision 6940)
@@ -1,5 +1,5 @@
 Metadata-Version: 1.0
 Name: tgscheduler
-Version: 1.1
+Version: 1.5
 Summary: Turbogears Scheduler
 Home-page: http://tgscheduler.googlecode.com
Index: /projects/TGScheduler/trunk/tgscheduler/kronos.py
===================================================================
--- /projects/TGScheduler/trunk/tgscheduler/kronos.py (revision 6940)
+++ /projects/TGScheduler/trunk/tgscheduler/kronos.py (revision 6940)
@@ -0,0 +1,578 @@
+"""Module that provides a cron-like task scheduler.
+
+This task scheduler is designed to be used from inside your own program.
+You can schedule Python functions to be called at specific intervals or
+days. It uses the standard 'sched' module for the actual task scheduling,
+but provides much more:
+
+* repeated tasks (at intervals, or on specific days)
+* error handling (exceptions in tasks don't kill the scheduler)
+* optional to run scheduler in its own thread or separate process
+* optional to run a task in its own thread or separate process
+
+If the threading module is available, you can use the various Threaded
+variants of the scheduler and associated tasks. If threading is not
+available, you could still use the forked variants. If fork is also
+not available, all processing is done in a single process, sequentially.
+
+There are three Scheduler classes:
+
+    Scheduler    ThreadedScheduler    ForkedScheduler
+
+You usually add new tasks to a scheduler using the add_interval_task or
+add_daytime_task methods, with the appropriate processmethod argument
+to select sequential, threaded or forked processing. NOTE: it is impossible
+to add new tasks to a ForkedScheduler, after the scheduler has been started!
+For more control you can use one of the following Task classes
+and use schedule_task or schedule_task_abs:
+
+    IntervalTask    ThreadedIntervalTask    ForkedIntervalTask
+    SingleTask      ThreadedSingleTask      ForkedSingleTask 
+    WeekdayTask     ThreadedWeekdayTask     ForkedWeekdayTask
+    MonthdayTask    ThreadedMonthdayTask    ForkedMonthdayTask
+
+Kronos is the Greek God of Time.
+
+Kronos scheduler (c) Irmen de Jong.
+This version has been extracted from the Turbogears source repository
+and slightly changed to be completely stand-alone again. Also some fixes
+have been made to make it work on Python 2.6 (sched module changes).
+The version in Turbogears is based on the original stand-alone Kronos.
+This is open-source software, released under the MIT Software License:
+http://www.opensource.org/licenses/mit-license.php
+
+"""
+
+__version__="2.0"
+
+__all__ = [
+    "DayTaskRescheduler",
+    "ForkedIntervalTask",
+    "ForkedMonthdayTask",
+    "ForkedScheduler",
+    "ForkedSingleTask",
+    "ForkedTaskMixin",
+    "ForkedWeekdayTask",
+    "IntervalTask",
+    "MonthdayTask",
+    "Scheduler",
+    "SingleTask",
+    "Task",
+    "ThreadedIntervalTask",
+    "ThreadedMonthdayTask",
+    "ThreadedScheduler",
+    "ThreadedSingleTask",
+    "ThreadedTaskMixin",
+    "ThreadedWeekdayTask",
+    "WeekdayTask",
+    "add_interval_task",
+    "add_monthday_task",
+    "add_single_task",
+    "add_weekday_task",
+    "cancel",
+    "method",
+]
+
+import os
+import sys
+import sched
+import time
+import traceback
+import weakref
+
+class method:
+    sequential="sequential"
+    forked="forked"
+    threaded="threaded"
+
+class Scheduler:
+    """The Scheduler itself."""
+
+    def __init__(self):
+        self.running=True
+        self.sched = sched.scheduler(time.time, self.__delayfunc)
+
+    def __delayfunc(self, delay):
+        # This delay function is basically a time.sleep() that is
+        # divided up, so that we can check the self.running flag while delaying.
+        # there is an additional check in here to ensure that the top item of
+        # the queue hasn't changed
+        if delay<10:
+            time.sleep(delay)
+        else:
+            toptime = self._getqueuetoptime()
+            endtime = time.time() + delay
+            period = 5
+            stoptime = endtime - period
+            while self.running and stoptime > time.time() and \
+                self._getqueuetoptime() == toptime:
+                time.sleep(period)
+            if not self.running or self._getqueuetoptime() != toptime:
+                return
+            now = time.time()
+            if endtime > now:
+                time.sleep(endtime - now)
+
+    def _acquire_lock(self):
+        pass
+
+    def _release_lock(self):
+        pass
+
+    def add_interval_task(self, action, taskname, initialdelay, interval,
+            processmethod, args, kw):
+        """Add a new Interval Task to the schedule.
+        
+        A very short initialdelay or one of zero cannot be honored, you will 
+        see a slight delay before the task is first executed. This is because
+        the scheduler needs to pick it up in its loop.
+
+        """
+        if initialdelay < 0 or interval < 1:
+            raise ValueError("Delay or interval must be >0")
+        # Select the correct IntervalTask class. Not all types may be available!
+        if processmethod == method.sequential:
+            TaskClass = IntervalTask
+        elif processmethod == method.threaded:
+            TaskClass = ThreadedIntervalTask
+        elif processmethod == method.forked:
+            TaskClass = ForkedIntervalTask
+        else:
+            raise ValueError("Invalid processmethod")
+        if not args:
+            args = []
+        if not kw:
+            kw = {}
+        task = TaskClass(taskname, interval, action, args, kw)
+        self.schedule_task(task, initialdelay)
+        return task
+
+    def add_single_task(self, action, taskname, initialdelay, processmethod, 
+            args, kw):
+        """Add a new task to the scheduler that will only be executed once."""
+        if initialdelay < 0:
+            raise ValueError("Delay must be >0")
+        # Select the correct SingleTask class. Not all types may be available!
+        if processmethod == method.sequential:
+            TaskClass = SingleTask
+        elif processmethod == method.threaded:
+            TaskClass = ThreadedSingleTask
+        elif processmethod == method.forked:
+            TaskClass = ForkedSingleTask
+        else:
+            raise ValueError("Invalid processmethod")
+        if not args:
+            args = []
+        if not kw:
+            kw = {}
+        task = TaskClass(taskname, action, args, kw)
+        self.schedule_task(task, initialdelay)
+        return task
+
+    def add_daytime_task(self, action, taskname, weekdays, monthdays, timeonday, 
+            processmethod, args, kw):
+        """Add a new Day Task (Weekday or Monthday) to the schedule."""
+        if weekdays and monthdays:
+            raise ValueError("You can only specify weekdays or monthdays, "
+                "not both")
+        if not args:
+            args = []
+        if not kw:
+            kw = {}
+        if weekdays:
+            # Select the correct WeekdayTask class.
+            # Not all types may be available!
+            if processmethod == method.sequential:
+                TaskClass = WeekdayTask
+            elif processmethod == method.threaded:
+                TaskClass = ThreadedWeekdayTask
+            elif processmethod == method.forked:
+                TaskClass = ForkedWeekdayTask
+            else:
+                raise ValueError("Invalid processmethod")
+            task=TaskClass(taskname, weekdays, timeonday, action, args, kw)
+        if monthdays:
+            # Select the correct MonthdayTask class.
+            # Not all types may be available!
+            if processmethod == method.sequential:
+                TaskClass = MonthdayTask
+            elif processmethod == method.threaded:
+                TaskClass = ThreadedMonthdayTask
+            elif processmethod == method.forked:
+                TaskClass = ForkedMonthdayTask
+            else:
+                raise ValueError("Invalid processmethod")
+            task=TaskClass(taskname, monthdays, timeonday, action, args, kw)
+        firsttime=task.get_schedule_time(True)
+        self.schedule_task_abs(task, firsttime)
+        return task
+
+    def schedule_task(self, task, delay):
+        """Add a new task to the scheduler with the given delay (seconds).
+        
+        Low-level method for internal use.
+        
+        """
+        if self.running:
+            # lock the sched queue, if needed
+            self._acquire_lock()
+            try:
+                task.event = self.sched.enter(delay, 0, task,
+                            (weakref.ref(self),) )
+            finally:
+                self._release_lock()
+        else:
+            task.event = self.sched.enter(delay, 0, task,
+                        (weakref.ref(self),) )
+
+    def schedule_task_abs(self, task, abstime):
+        """Add a new task to the scheduler for the given absolute time value.
+        
+        Low-level method for internal use.
+        
+        """
+        if self.running:
+            # lock the sched queue, if needed
+            self._acquire_lock()
+            try:
+                task.event = self.sched.enterabs(abstime, 0, task,
+                                    (weakref.ref(self),) )
+            finally:
+                self._release_lock()
+        else:
+            task.event = self.sched.enterabs(abstime, 0, task,
+                                (weakref.ref(self),) )
+
+    def start(self):
+        """Start the scheduler."""
+        self._run()
+
+    def stop(self):
+        """Remove all pending tasks and stop the Scheduler.&qu