From 7f13278f8619b1155fa51276bb63afa9997610da Mon Sep 17 00:00:00 2001
From: Boulder Sprinters
Date: Tue, 8 May 2007 17:46:05 +0000
Subject: boulder-oracle-sprint: Merged to [5173]
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5174 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
AUTHORS | 5 +-
django/conf/locale/de/LC_MESSAGES/django.mo | Bin 44060 -> 44063 bytes
django/conf/locale/de/LC_MESSAGES/django.po | 16 +-
django/contrib/admin/templatetags/log.py | 9 +-
django/contrib/contenttypes/generic.py | 260 +++++++++++++++++++++
django/core/cache/backends/base.py | 3 +
django/core/management.py | 4 +-
django/db/models/__init__.py | 1 -
django/db/models/base.py | 2 +-
django/db/models/fields/generic.py | 260 ---------------------
django/db/models/query.py | 9 +-
django/template/__init__.py | 9 +
django/test/testcases.py | 22 +-
django/test/utils.py | 32 ++-
docs/contributing.txt | 9 +-
docs/i18n.txt | 6 +-
docs/model-api.txt | 2 +-
docs/serialization.txt | 2 +-
docs/sitemaps.txt | 2 -
docs/templates_python.txt | 21 +-
docs/testing.txt | 104 +++++++--
tests/modeltests/generic_relations/models.py | 7 +-
tests/modeltests/test_client/models.py | 34 +++
tests/modeltests/test_client/urls.py | 4 +-
tests/modeltests/test_client/views.py | 26 +++
tests/regressiontests/cache/tests.py | 5 +
.../regressiontests/serializers_regress/models.py | 5 +-
tests/regressiontests/templates/tests.py | 13 +-
28 files changed, 531 insertions(+), 341 deletions(-)
create mode 100644 django/contrib/contenttypes/generic.py
delete mode 100644 django/db/models/fields/generic.py
diff --git a/AUTHORS b/AUTHORS
index 027dbc39ba..fb2d5f7112 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -43,12 +43,14 @@ answer newbie questions, and generally made Django that much better:
adurdin@gmail.com
alang@bright-green.com
+ Marty Alchin
Daniel Alves Barbosa de Oliveira Vaz
Andreas
andy@jadedplanet.net
Fabrice Aneche
ant9000@netwise.it
David Ascher
+ david@kazserve.org
Arthur
axiak@mit.edu
Jiri Barton
@@ -68,6 +70,7 @@ answer newbie questions, and generally made Django that much better:
Amit Chakradeo
ChaosKCW
ivan.chelubeev@gmail.com
+ Bryan Chow
Ian Clelland
crankycoder@gmail.com
Matt Croydon
@@ -145,7 +148,7 @@ answer newbie questions, and generally made Django that much better:
lerouxb@gmail.com
Waylan Limberg
limodou
- mattmcc
+ Matt McClanahan
Martin Maney
masonsimon+django@gmail.com
Manuzhai
diff --git a/django/conf/locale/de/LC_MESSAGES/django.mo b/django/conf/locale/de/LC_MESSAGES/django.mo
index 396fe5a432..5f2eee4f33 100644
Binary files a/django/conf/locale/de/LC_MESSAGES/django.mo and b/django/conf/locale/de/LC_MESSAGES/django.mo differ
diff --git a/django/conf/locale/de/LC_MESSAGES/django.po b/django/conf/locale/de/LC_MESSAGES/django.po
index 9237e18b2a..52b70bda00 100644
--- a/django/conf/locale/de/LC_MESSAGES/django.po
+++ b/django/conf/locale/de/LC_MESSAGES/django.po
@@ -367,11 +367,11 @@ msgstr "Abmelden"
#: contrib/admin/templates/admin/base_site.html:4
msgid "Django site admin"
-msgstr "Django Systemverwaltung"
+msgstr "Django-Systemverwaltung"
#: contrib/admin/templates/admin/base_site.html:7
msgid "Django administration"
-msgstr "Django Verwaltung"
+msgstr "Django-Verwaltung"
#: contrib/admin/templates/admin/change_form.html:15
#: contrib/admin/templates/admin/index.html:28
@@ -385,7 +385,7 @@ msgstr "Geschichte"
#: contrib/admin/templates/admin/change_form.html:22
msgid "View on site"
-msgstr "Im Web Anzeigen"
+msgstr "Im Web anzeigen"
#: contrib/admin/templates/admin/change_form.html:32
#: contrib/admin/templates/admin/auth/user/change_password.html:24
@@ -614,7 +614,7 @@ msgid ""
"your computer is \"internal\").
\n"
msgstr ""
"\n"
-"Um Bookmarklets zu installieren müssen diese Links in die\n"
+"
Um Bookmarklets zu installieren, müssen diese Links in die\n"
"Browser-Werkzeugleiste gezogen werden, oder mittels rechter Maustaste in "
"die\n"
"Bookmarks gespeichert werden. Danach können die Bookmarklets von jeder "
@@ -998,7 +998,7 @@ msgstr "%s ist scheinbar kein urlpattern Objekt"
#: contrib/admin/views/main.py:223
msgid "Site administration"
-msgstr "Website Verwaltung"
+msgstr "Website-Verwaltung"
#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356
#, python-format
@@ -1023,7 +1023,7 @@ msgstr "und"
#: contrib/admin/views/main.py:337
#, python-format
msgid "Changed %s."
-msgstr "%s geändert"
+msgstr "%s geändert."
#: contrib/admin/views/main.py:339
#, python-format
@@ -1490,8 +1490,8 @@ msgstr "Ihr Name:"
msgid ""
"This rating is required because you've entered at least one other rating."
msgstr ""
-"Diese Abstimmung ist zwingend erforderlich, da Du an mindestens einer "
-"weiteren Abstimmung teilnimmst."
+"Diese Abstimmung ist zwingend erforderlich, da Sie an mindestens einer "
+"weiteren Abstimmung teilnehmen."
#: contrib/comments/views/comments.py:111
#, python-format
diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py
index 5caba2b795..8d52d2e944 100644
--- a/django/contrib/admin/templatetags/log.py
+++ b/django/contrib/admin/templatetags/log.py
@@ -11,9 +11,12 @@ class AdminLogNode(template.Node):
return ""
def render(self, context):
- if self.user is not None and not self.user.isdigit():
- self.user = context[self.user].id
- context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
+ if self.user is None:
+ context[self.varname] = LogEntry.objects.all().select_related()[:self.limit]
+ else:
+ if not self.user.isdigit():
+ self.user = context[self.user].id
+ context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
return ''
class DoGetAdminLog:
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
new file mode 100644
index 0000000000..f995ab2044
--- /dev/null
+++ b/django/contrib/contenttypes/generic.py
@@ -0,0 +1,260 @@
+"""
+Classes allowing "generic" relations through ContentType and object-id fields.
+"""
+
+from django import oldforms
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import backend
+from django.db.models import signals
+from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
+from django.db.models.loading import get_model
+from django.dispatch import dispatcher
+from django.utils.functional import curry
+
+class GenericForeignKey(object):
+ """
+ Provides a generic relation to any object through content-type/object-id
+ fields.
+ """
+
+ def __init__(self, ct_field="content_type", fk_field="object_id"):
+ self.ct_field = ct_field
+ self.fk_field = fk_field
+
+ def contribute_to_class(self, cls, name):
+ # Make sure the fields exist (these raise FieldDoesNotExist,
+ # which is a fine error to raise here)
+ self.name = name
+ self.model = cls
+ self.cache_attr = "_%s_cache" % name
+
+ # For some reason I don't totally understand, using weakrefs here doesn't work.
+ dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
+
+ # Connect myself as the descriptor for this field
+ setattr(cls, name, self)
+
+ def instance_pre_init(self, signal, sender, args, kwargs):
+ # Handle initalizing an object with the generic FK instaed of
+ # content-type/object-id fields.
+ if self.name in kwargs:
+ value = kwargs.pop(self.name)
+ kwargs[self.ct_field] = self.get_content_type(value)
+ kwargs[self.fk_field] = value._get_pk_val()
+
+ def get_content_type(self, obj):
+ # Convenience function using get_model avoids a circular import when using this model
+ ContentType = get_model("contenttypes", "contenttype")
+ return ContentType.objects.get_for_model(obj)
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self.name
+
+ try:
+ return getattr(instance, self.cache_attr)
+ except AttributeError:
+ rel_obj = None
+ ct = getattr(instance, self.ct_field)
+ if ct:
+ try:
+ rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field))
+ except ObjectDoesNotExist:
+ pass
+ setattr(instance, self.cache_attr, rel_obj)
+ return rel_obj
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
+
+ ct = None
+ fk = None
+ if value is not None:
+ ct = self.get_content_type(value)
+ fk = value._get_pk_val()
+
+ setattr(instance, self.ct_field, ct)
+ setattr(instance, self.fk_field, fk)
+ setattr(instance, self.cache_attr, value)
+
+class GenericRelation(RelatedField, Field):
+ """Provides an accessor to generic related objects (i.e. comments)"""
+
+ def __init__(self, to, **kwargs):
+ kwargs['verbose_name'] = kwargs.get('verbose_name', None)
+ kwargs['rel'] = GenericRel(to,
+ related_name=kwargs.pop('related_name', None),
+ limit_choices_to=kwargs.pop('limit_choices_to', None),
+ symmetrical=kwargs.pop('symmetrical', True))
+
+ # Override content-type/object-id field names on the related class
+ self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
+ self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
+
+ kwargs['blank'] = True
+ kwargs['editable'] = False
+ kwargs['serialize'] = False
+ Field.__init__(self, **kwargs)
+
+ def get_manipulator_field_objs(self):
+ choices = self.get_choices_default()
+ return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
+
+ def get_choices_default(self):
+ return Field.get_choices(self, include_blank=False)
+
+ def flatten_data(self, follow, obj = None):
+ new_data = {}
+ if obj:
+ instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
+ new_data[self.name] = instance_ids
+ return new_data
+
+ def m2m_db_table(self):
+ return self.rel.to._meta.db_table
+
+ def m2m_column_name(self):
+ return self.object_id_field_name
+
+ def m2m_reverse_name(self):
+ return self.object_id_field_name
+
+ def contribute_to_class(self, cls, name):
+ super(GenericRelation, self).contribute_to_class(cls, name)
+
+ # Save a reference to which model this class is on for future use
+ self.model = cls
+
+ # Add the descriptor for the m2m relation
+ setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self))
+
+ def contribute_to_related_class(self, cls, related):
+ pass
+
+ def set_attributes_from_rel(self):
+ pass
+
+ def get_internal_type(self):
+ return "ManyToManyField"
+
+class ReverseGenericRelatedObjectsDescriptor(object):
+ """
+ This class provides the functionality that makes the related-object
+ managers available as attributes on a model class, for fields that have
+ multiple "remote" values and have a GenericRelation defined in their model
+ (rather than having another model pointed *at* them). In the example
+ "article.publications", the publications attribute is a
+ ReverseGenericRelatedObjectsDescriptor instance.
+ """
+ def __init__(self, field):
+ self.field = field
+
+ def __get__(self, instance, instance_type=None):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ # This import is done here to avoid circular import importing this module
+ from django.contrib.contenttypes.models import ContentType
+
+ # Dynamically create a class that subclasses the related model's
+ # default manager.
+ rel_model = self.field.rel.to
+ superclass = rel_model._default_manager.__class__
+ RelatedManager = create_generic_related_manager(superclass)
+
+ manager = RelatedManager(
+ model = rel_model,
+ instance = instance,
+ symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model),
+ join_table = backend.quote_name(self.field.m2m_db_table()),
+ source_col_name = backend.quote_name(self.field.m2m_column_name()),
+ target_col_name = backend.quote_name(self.field.m2m_reverse_name()),
+ content_type = ContentType.objects.get_for_model(self.field.model),
+ content_type_field_name = self.field.content_type_field_name,
+ object_id_field_name = self.field.object_id_field_name
+ )
+
+ return manager
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError, "Manager must be accessed via instance"
+
+ manager = self.__get__(instance)
+ manager.clear()
+ for obj in value:
+ manager.add(obj)
+
+def create_generic_related_manager(superclass):
+ """
+ Factory function for a manager that subclasses 'superclass' (which is a
+ Manager) and adds behavior for generic related objects.
+ """
+
+ class GenericRelatedObjectManager(superclass):
+ def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
+ join_table=None, source_col_name=None, target_col_name=None, content_type=None,
+ content_type_field_name=None, object_id_field_name=None):
+
+ super(GenericRelatedObjectManager, self).__init__()
+ self.core_filters = core_filters or {}
+ self.model = model
+ self.content_type = content_type
+ self.symmetrical = symmetrical
+ self.instance = instance
+ self.join_table = join_table
+ self.join_table = model._meta.db_table
+ self.source_col_name = source_col_name
+ self.target_col_name = target_col_name
+ self.content_type_field_name = content_type_field_name
+ self.object_id_field_name = object_id_field_name
+ self.pk_val = self.instance._get_pk_val()
+
+ def get_query_set(self):
+ query = {
+ '%s__pk' % self.content_type_field_name : self.content_type.id,
+ '%s__exact' % self.object_id_field_name : self.pk_val,
+ }
+ return superclass.get_query_set(self).filter(**query)
+
+ def add(self, *objs):
+ for obj in objs:
+ setattr(obj, self.content_type_field_name, self.content_type)
+ setattr(obj, self.object_id_field_name, self.pk_val)
+ obj.save()
+ add.alters_data = True
+
+ def remove(self, *objs):
+ for obj in objs:
+ obj.delete()
+ remove.alters_data = True
+
+ def clear(self):
+ for obj in self.all():
+ obj.delete()
+ clear.alters_data = True
+
+ def create(self, **kwargs):
+ kwargs[self.content_type_field_name] = self.content_type
+ kwargs[self.object_id_field_name] = self.pk_val
+ obj = self.model(**kwargs)
+ obj.save()
+ return obj
+ create.alters_data = True
+
+ return GenericRelatedObjectManager
+
+class GenericRel(ManyToManyRel):
+ def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True):
+ self.to = to
+ self.num_in_admin = 0
+ self.related_name = related_name
+ self.filter_interface = None
+ self.limit_choices_to = limit_choices_to or {}
+ self.edit_inline = False
+ self.raw_id_admin = False
+ self.symmetrical = symmetrical
+ self.multiple = True
+ assert not (self.raw_id_admin and self.filter_interface), \
+ "Generic relations may not use both raw_id_admin and filter_interface"
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index ef5f6a6b3e..bb67399f3b 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -54,3 +54,6 @@ class BaseCache(object):
Returns True if the key is in the cache and has not expired.
"""
return self.get(key) is not None
+
+ __contains__ = has_key
+
diff --git a/django/core/management.py b/django/core/management.py
index 47f44f8c8d..91de11f3b3 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -260,14 +260,14 @@ def _get_sql_for_pending_references(model, pending_references):
def _get_many_to_many_sql_for_model(model):
from django.db import backend, get_creation_module
- from django.db.models import GenericRel
+ from django.contrib.contenttypes import generic
data_types = get_creation_module().DATA_TYPES
opts = model._meta
final_output = []
for f in opts.many_to_many:
- if not isinstance(f.rel, GenericRel):
+ if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys:
tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace, inline=True)
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index ccd60023f9..6c3abb6b59 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -8,7 +8,6 @@ from django.db.models.manager import Manager
from django.db.models.base import Model, AdminOptions
from django.db.models.fields import *
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
-from django.db.models.fields.generic import GenericRelation, GenericRel, GenericForeignKey
from django.db.models import signals
from django.utils.functional import curry
from django.utils.text import capfirst
diff --git a/django/db/models/base.py b/django/db/models/base.py
index eb95aae4f2..a567f0ed37 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -42,11 +42,11 @@ class ModelBase(type):
new_class._meta.parents.append(base)
new_class._meta.parents.extend(base._meta.parents)
- model_module = sys.modules[new_class.__module__]
if getattr(new_class._meta, 'app_label', None) is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
+ model_module = sys.modules[new_class.__module__]
new_class._meta.app_label = model_module.__name__.split('.')[-2]
# Bail out early if we have already created this class.
diff --git a/django/db/models/fields/generic.py b/django/db/models/fields/generic.py
deleted file mode 100644
index f995ab2044..0000000000
--- a/django/db/models/fields/generic.py
+++ /dev/null
@@ -1,260 +0,0 @@
-"""
-Classes allowing "generic" relations through ContentType and object-id fields.
-"""
-
-from django import oldforms
-from django.core.exceptions import ObjectDoesNotExist
-from django.db import backend
-from django.db.models import signals
-from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
-from django.db.models.loading import get_model
-from django.dispatch import dispatcher
-from django.utils.functional import curry
-
-class GenericForeignKey(object):
- """
- Provides a generic relation to any object through content-type/object-id
- fields.
- """
-
- def __init__(self, ct_field="content_type", fk_field="object_id"):
- self.ct_field = ct_field
- self.fk_field = fk_field
-
- def contribute_to_class(self, cls, name):
- # Make sure the fields exist (these raise FieldDoesNotExist,
- # which is a fine error to raise here)
- self.name = name
- self.model = cls
- self.cache_attr = "_%s_cache" % name
-
- # For some reason I don't totally understand, using weakrefs here doesn't work.
- dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
-
- # Connect myself as the descriptor for this field
- setattr(cls, name, self)
-
- def instance_pre_init(self, signal, sender, args, kwargs):
- # Handle initalizing an object with the generic FK instaed of
- # content-type/object-id fields.
- if self.name in kwargs:
- value = kwargs.pop(self.name)
- kwargs[self.ct_field] = self.get_content_type(value)
- kwargs[self.fk_field] = value._get_pk_val()
-
- def get_content_type(self, obj):
- # Convenience function using get_model avoids a circular import when using this model
- ContentType = get_model("contenttypes", "contenttype")
- return ContentType.objects.get_for_model(obj)
-
- def __get__(self, instance, instance_type=None):
- if instance is None:
- raise AttributeError, "%s must be accessed via instance" % self.name
-
- try:
- return getattr(instance, self.cache_attr)
- except AttributeError:
- rel_obj = None
- ct = getattr(instance, self.ct_field)
- if ct:
- try:
- rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field))
- except ObjectDoesNotExist:
- pass
- setattr(instance, self.cache_attr, rel_obj)
- return rel_obj
-
- def __set__(self, instance, value):
- if instance is None:
- raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
-
- ct = None
- fk = None
- if value is not None:
- ct = self.get_content_type(value)
- fk = value._get_pk_val()
-
- setattr(instance, self.ct_field, ct)
- setattr(instance, self.fk_field, fk)
- setattr(instance, self.cache_attr, value)
-
-class GenericRelation(RelatedField, Field):
- """Provides an accessor to generic related objects (i.e. comments)"""
-
- def __init__(self, to, **kwargs):
- kwargs['verbose_name'] = kwargs.get('verbose_name', None)
- kwargs['rel'] = GenericRel(to,
- related_name=kwargs.pop('related_name', None),
- limit_choices_to=kwargs.pop('limit_choices_to', None),
- symmetrical=kwargs.pop('symmetrical', True))
-
- # Override content-type/object-id field names on the related class
- self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
- self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
-
- kwargs['blank'] = True
- kwargs['editable'] = False
- kwargs['serialize'] = False
- Field.__init__(self, **kwargs)
-
- def get_manipulator_field_objs(self):
- choices = self.get_choices_default()
- return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
-
- def get_choices_default(self):
- return Field.get_choices(self, include_blank=False)
-
- def flatten_data(self, follow, obj = None):
- new_data = {}
- if obj:
- instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
- new_data[self.name] = instance_ids
- return new_data
-
- def m2m_db_table(self):
- return self.rel.to._meta.db_table
-
- def m2m_column_name(self):
- return self.object_id_field_name
-
- def m2m_reverse_name(self):
- return self.object_id_field_name
-
- def contribute_to_class(self, cls, name):
- super(GenericRelation, self).contribute_to_class(cls, name)
-
- # Save a reference to which model this class is on for future use
- self.model = cls
-
- # Add the descriptor for the m2m relation
- setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self))
-
- def contribute_to_related_class(self, cls, related):
- pass
-
- def set_attributes_from_rel(self):
- pass
-
- def get_internal_type(self):
- return "ManyToManyField"
-
-class ReverseGenericRelatedObjectsDescriptor(object):
- """
- This class provides the functionality that makes the related-object
- managers available as attributes on a model class, for fields that have
- multiple "remote" values and have a GenericRelation defined in their model
- (rather than having another model pointed *at* them). In the example
- "article.publications", the publications attribute is a
- ReverseGenericRelatedObjectsDescriptor instance.
- """
- def __init__(self, field):
- self.field = field
-
- def __get__(self, instance, instance_type=None):
- if instance is None:
- raise AttributeError, "Manager must be accessed via instance"
-
- # This import is done here to avoid circular import importing this module
- from django.contrib.contenttypes.models import ContentType
-
- # Dynamically create a class that subclasses the related model's
- # default manager.
- rel_model = self.field.rel.to
- superclass = rel_model._default_manager.__class__
- RelatedManager = create_generic_related_manager(superclass)
-
- manager = RelatedManager(
- model = rel_model,
- instance = instance,
- symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model),
- join_table = backend.quote_name(self.field.m2m_db_table()),
- source_col_name = backend.quote_name(self.field.m2m_column_name()),
- target_col_name = backend.quote_name(self.field.m2m_reverse_name()),
- content_type = ContentType.objects.get_for_model(self.field.model),
- content_type_field_name = self.field.content_type_field_name,
- object_id_field_name = self.field.object_id_field_name
- )
-
- return manager
-
- def __set__(self, instance, value):
- if instance is None:
- raise AttributeError, "Manager must be accessed via instance"
-
- manager = self.__get__(instance)
- manager.clear()
- for obj in value:
- manager.add(obj)
-
-def create_generic_related_manager(superclass):
- """
- Factory function for a manager that subclasses 'superclass' (which is a
- Manager) and adds behavior for generic related objects.
- """
-
- class GenericRelatedObjectManager(superclass):
- def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
- join_table=None, source_col_name=None, target_col_name=None, content_type=None,
- content_type_field_name=None, object_id_field_name=None):
-
- super(GenericRelatedObjectManager, self).__init__()
- self.core_filters = core_filters or {}
- self.model = model
- self.content_type = content_type
- self.symmetrical = symmetrical
- self.instance = instance
- self.join_table = join_table
- self.join_table = model._meta.db_table
- self.source_col_name = source_col_name
- self.target_col_name = target_col_name
- self.content_type_field_name = content_type_field_name
- self.object_id_field_name = object_id_field_name
- self.pk_val = self.instance._get_pk_val()
-
- def get_query_set(self):
- query = {
- '%s__pk' % self.content_type_field_name : self.content_type.id,
- '%s__exact' % self.object_id_field_name : self.pk_val,
- }
- return superclass.get_query_set(self).filter(**query)
-
- def add(self, *objs):
- for obj in objs:
- setattr(obj, self.content_type_field_name, self.content_type)
- setattr(obj, self.object_id_field_name, self.pk_val)
- obj.save()
- add.alters_data = True
-
- def remove(self, *objs):
- for obj in objs:
- obj.delete()
- remove.alters_data = True
-
- def clear(self):
- for obj in self.all():
- obj.delete()
- clear.alters_data = True
-
- def create(self, **kwargs):
- kwargs[self.content_type_field_name] = self.content_type
- kwargs[self.object_id_field_name] = self.pk_val
- obj = self.model(**kwargs)
- obj.save()
- return obj
- create.alters_data = True
-
- return GenericRelatedObjectManager
-
-class GenericRel(ManyToManyRel):
- def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True):
- self.to = to
- self.num_in_admin = 0
- self.related_name = related_name
- self.filter_interface = None
- self.limit_choices_to = limit_choices_to or {}
- self.edit_inline = False
- self.raw_id_admin = False
- self.symmetrical = symmetrical
- self.multiple = True
- assert not (self.raw_id_admin and self.filter_interface), \
- "Generic relations may not use both raw_id_admin and filter_interface"
diff --git a/django/db/models/query.py b/django/db/models/query.py
index d31ccf003e..e3b9c794f8 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1,10 +1,9 @@
from django.db import backend, connection, transaction
from django.db.models.fields import DateField, FieldDoesNotExist
-from django.db.models.fields.generic import GenericRelation
-from django.db.models import signals
+from django.db.models import signals, loading
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
-from django.conf import settings
+from django.contrib.contenttypes import generic
import datetime
import operator
import re
@@ -1091,7 +1090,7 @@ def delete_objects(seen_objs):
pk_list = [pk for pk,instance in seen_objs[cls]]
for related in cls._meta.get_all_related_many_to_many_objects():
- if not isinstance(related.field, GenericRelation):
+ if not isinstance(related.field, generic.GenericRelation):
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
(qn(related.field.m2m_db_table()),
@@ -1099,7 +1098,7 @@ def delete_objects(seen_objs):
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
for f in cls._meta.many_to_many:
- if isinstance(f, GenericRelation):
+ if isinstance(f, generic.GenericRelation):
from django.contrib.contenttypes.models import ContentType
query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
args_extra = [ContentType.objects.get_for_model(cls).id]
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 4cb4f21156..9811a5649d 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -99,6 +99,10 @@ libraries = {}
# global list of libraries to load by default for a new parser
builtins = []
+# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
+# uninitialised.
+invalid_var_format_string = None
+
class TemplateSyntaxError(Exception):
def __str__(self):
try:
@@ -575,6 +579,11 @@ class FilterExpression(object):
obj = None
else:
if settings.TEMPLATE_STRING_IF_INVALID:
+ global invalid_var_format_string
+ if invalid_var_format_string is None:
+ invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
+ if invalid_var_format_string:
+ return settings.TEMPLATE_STRING_IF_INVALID % self.var
return settings.TEMPLATE_STRING_IF_INVALID
else:
obj = settings.TEMPLATE_STRING_IF_INVALID
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 80f55b20d3..153931f42a 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -1,7 +1,7 @@
import re, doctest, unittest
from urlparse import urlparse
from django.db import transaction
-from django.core import management
+from django.core import management, mail
from django.db.models import get_apps
from django.test.client import Client
@@ -33,23 +33,27 @@ class DocTestRunner(doctest.DocTestRunner):
transaction.rollback_unless_managed()
class TestCase(unittest.TestCase):
- def install_fixtures(self):
- """If the Test Case class has a 'fixtures' member, clear the database and
- install the named fixtures at the start of each test.
+ def _pre_setup(self):
+ """Perform any pre-test setup. This includes:
+ * If the Test Case class has a 'fixtures' member, clearing the
+ database and installing the named fixtures at the start of each test.
+ * Clearing the mail test outbox.
+
"""
management.flush(verbosity=0, interactive=False)
if hasattr(self, 'fixtures'):
management.load_data(self.fixtures, verbosity=0)
-
+ mail.outbox = []
+
def run(self, result=None):
- """Wrapper around default run method so that user-defined Test Cases
- automatically call install_fixtures without having to include a call to
- super().
+ """Wrapper around default run method to perform common Django test set up.
+ This means that user-defined Test Cases aren't required to include a call
+ to super().setUp().
"""
self.client = Client()
- self.install_fixtures()
+ self._pre_setup()
super(TestCase, self).run(result)
def assertRedirects(self, response, expected_path):
diff --git a/django/test/utils.py b/django/test/utils.py
index 70b7f3cdbe..da339bb808 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -1,7 +1,7 @@
import sys, time
from django.conf import settings
from django.db import connection, backend, get_creation_module
-from django.core import management
+from django.core import management, mail
from django.dispatch import dispatcher
from django.test import signals
from django.template import Template
@@ -18,24 +18,54 @@ def instrumented_test_render(self, context):
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
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)
+
def setup_test_environment():
"""Perform any global pre-test setup. This involves:
- Installing the instrumented test renderer
+ - Diverting the email sending functions to a test buffer
"""
Template.original_render = Template.render
Template.render = instrumented_test_render
+ mail.original_SMTPConnection = mail.SMTPConnection
+ mail.SMTPConnection = TestSMTPConnection
+
+ mail.outbox = []
+
def teardown_test_environment():
"""Perform any global post-test teardown. This involves:
- Restoring the original test renderer
+ - Restoring the email sending functions
"""
Template.render = Template.original_render
del Template.original_render
+ mail.SMTPConnection = mail.original_SMTPConnection
+ del mail.original_SMTPConnection
+
+ del mail.outbox
+
def _set_autocommit(connection):
"Make sure a connection is in autocommit mode."
if hasattr(connection.connection, "autocommit"):
diff --git a/docs/contributing.txt b/docs/contributing.txt
index 1d2b635b76..d05c166b37 100644
--- a/docs/contributing.txt
+++ b/docs/contributing.txt
@@ -396,10 +396,11 @@ To run the tests, ``cd`` to the ``tests/`` directory and type::
./runtests.py --settings=path.to.django.settings
Yes, the unit tests need a settings module, but only for database connection
-info -- the ``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD``.
-You will also need a ``ROOT_URLCONF`` setting (its value is ignored; it just
-needs to be present) and a ``SITE_ID`` setting (any integer value will do) in
-order for all the tests to pass.
+info -- the ``DATABASE_NAME`` (required, but will be ignored),
+``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD`` settings. You
+will also need a ``ROOT_URLCONF`` setting (its value is ignored; it just needs
+to be present) and a ``SITE_ID`` setting (any integer value will do) in order
+for all the tests to pass.
The unit tests will not touch your existing databases; they create a new
database, called ``django_test_db``, which is deleted when the tests are
diff --git a/docs/i18n.txt b/docs/i18n.txt
index 56e6f7e02c..1d7a0063b2 100644
--- a/docs/i18n.txt
+++ b/docs/i18n.txt
@@ -310,7 +310,7 @@ To create or update a message file, run this command::
...where ``de`` is the language code for the message file you want to create.
The language code, in this case, is in locale format. For example, it's
-``pt_BR`` for Brazilian and ``de_AT`` for Austrian German.
+``pt_BR`` for Brazilian Portugese and ``de_AT`` for Austrian German.
The script should be run from one of three places:
@@ -463,8 +463,8 @@ following this algorithm:
Notes:
* In each of these places, the language preference is expected to be in the
- standard language format, as a string. For example, Brazilian is
- ``pt-br``.
+ standard language format, as a string. For example, Brazilian Portugese
+ is ``pt-br``.
* If a base language is available but the sublanguage specified is not,
Django uses the base language. For example, if a user specifies ``de-at``
(Austrian German) but Django only has ``de`` available, Django uses
diff --git a/docs/model-api.txt b/docs/model-api.txt
index a14c469661..961269aebd 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -459,7 +459,7 @@ string, not ``NULL``.
``blank``
~~~~~~~~~
-If ``True``, the field is allowed to be blank.
+If ``True``, the field is allowed to be blank. Default is ``False``.
Note that this is different than ``null``. ``null`` is purely
database-related, whereas ``blank`` is validation-related. If a field has
diff --git a/docs/serialization.txt b/docs/serialization.txt
index 8af4da26a8..3216cb061e 100644
--- a/docs/serialization.txt
+++ b/docs/serialization.txt
@@ -109,7 +109,7 @@ serializer, you must pass ``ensure_ascii=False`` as a parameter to the
For example::
- json_serializer = serializers.get_serializer("json")
+ json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
Writing custom serializers
diff --git a/docs/sitemaps.txt b/docs/sitemaps.txt
index dafc009859..550f448de1 100644
--- a/docs/sitemaps.txt
+++ b/docs/sitemaps.txt
@@ -2,8 +2,6 @@
The sitemap framework
=====================
-**New in Django development version**.
-
Django comes with a high-level sitemap-generating framework that makes
creating sitemap_ XML files easy.
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index 1eeede1fe8..853707f58c 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -212,21 +212,24 @@ template tags. If an invalid variable is provided to one of these template
tags, the variable will be interpreted as ``None``. Filters are always
applied to invalid variables within these template tags.
+If ``TEMPLATE_STRING_IF_INVALID`` contains a ``'%s'``, the format marker will
+be replaced with the name of the invalid variable.
+
.. admonition:: For debug purposes only!
- While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
- it is a bad idea to turn it on as a 'development default'.
+ While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
+ it is a bad idea to turn it on as a 'development default'.
- Many templates, including those in the Admin site, rely upon the
- silence of the template system when a non-existent variable is
+ Many templates, including those in the Admin site, rely upon the
+ silence of the template system when a non-existent variable is
encountered. If you assign a value other than ``''`` to
- ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
+ ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
problems with these templates and sites.
- Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
- in order to debug a specific template problem, then cleared
+ Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
+ in order to debug a specific template problem, then cleared
once debugging is complete.
-
+
Playing with Context objects
----------------------------
@@ -866,7 +869,7 @@ current context, available in the ``render`` method::
try:
actual_date = resolve_variable(self.date_to_be_formatted, context)
return actual_date.strftime(self.format_string)
- except VariableDoesNotExist:
+ except template.VariableDoesNotExist:
return ''
``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
diff --git a/docs/testing.txt b/docs/testing.txt
index b3b33e9678..ba13dab67e 100644
--- a/docs/testing.txt
+++ b/docs/testing.txt
@@ -177,6 +177,7 @@ tools that can be used to establish tests and test conditions.
* `Test Client`_
* `TestCase`_
+* `Email services`_
Test Client
-----------
@@ -257,7 +258,7 @@ can be invoked on the ``Client`` instance.
need to manually close the file after it has been provided to the POST.
``login(**credentials)``
- ** New in Django development version **
+ **New in Django development version**
On a production site, it is likely that some views will be protected from
anonymous access through the use of the @login_required decorator, or some
@@ -289,9 +290,9 @@ can be invoked on the ``Client`` instance.
Testing Responses
~~~~~~~~~~~~~~~~~
-The ``get()``, ``post()`` and ``login()`` methods all return a Response
-object. This Response object has the following properties that can be used
-for testing purposes:
+The ``get()`` and ``post()`` methods both return a Response object. This
+Response object has the following properties that can be used for testing
+purposes:
=============== ==========================================================
Property Description
@@ -396,7 +397,7 @@ extra facilities.
Default Test Client
~~~~~~~~~~~~~~~~~~~
-** New in Django development version **
+**New in Django development version**
Every test case in a ``django.test.TestCase`` instance has access to an
instance of a Django `Test Client`_. This Client can be accessed as
@@ -453,9 +454,18 @@ This flush/load procedure is repeated for each test in the test case, so you
can be certain that the outcome of a test will not be affected by
another test, or the order of test execution.
+Emptying the test outbox
+~~~~~~~~~~~~~~~~~~~~~~~~
+**New in Django development version**
+
+At the start of each test case, in addition to installing fixtures,
+Django clears the contents of the test email outbox.
+
+For more detail on email services during tests, see `Email services`_.
+
Assertions
~~~~~~~~~~
-** New in Django development version **
+**New in Django development version**
Normal Python unit tests have a wide range of assertions, such as
``assertTrue`` and ``assertEquals`` that can be used to validate behavior.
@@ -468,30 +478,73 @@ that can be useful in testing the behavior of web sites.
times in the content of the response.
``assertFormError(response, form, field, errors)``
- Assert that a field on a form raised the provided list of errors when
- rendered on the form.
-
- ``form`` is the name the form object was given in the template context.
-
- ``field`` is the name of the field on the form to check. If ``field``
+ Assert that a field on a form raised the provided list of errors when
+ rendered on the form.
+
+ ``form`` is the name the form object was given in the template context.
+
+ ``field`` is the name of the field on the form to check. If ``field``
has a value of ``None``, non-field errors will be checked.
-
- ``errors`` is an error string, or a list of error strings, that are
- expected as a result of form validation.
-
+
+ ``errors`` is an error string, or a list of error strings, that are
+ expected as a result of form validation.
+
``assertTemplateNotUsed(response, template_name)``
- Assert that the template with the given name was *not* used in rendering
+ Assert that the template with the given name was *not* used in rendering
the response.
-
+
``assertRedirects(response, expected_path)``
Assert that the response received redirects the browser to the provided
- path, and that the expected_path can be retrieved.
+ path, and that the expected_path can be retrieved.
``assertTemplateUsed(response, template_name)``
Assert that the template with the given name was used in rendering the
response.
-
-
+
+Email services
+--------------
+**New in Django development version**
+
+If your view makes use of the `Django email services`_, you don't really
+want email to be sent every time you run a test using that view.
+
+When the Django test framework is initialized, it transparently replaces the
+normal `SMTPConnection`_ class with a dummy implementation that redirects all
+email to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``,
+is a simple list of all `EmailMessage`_ instances that have been sent.
+For example, during test conditions, it would be possible to run the following
+code::
+
+ from django.core import mail
+
+ # Send message
+ mail.send_mail('Subject here', 'Here is the message.', 'from@example.com',
+ ['to@example.com'], fail_silently=False)
+
+ # One message has been sent
+ self.assertEqual(len(mail.outbox), 1)
+ # Subject of first message is correct
+ self.assertEqual(mail.outbox[0].subject, 'Subject here')
+
+The ``mail.outbox`` object does not exist under normal execution conditions.
+The outbox is created during test setup, along with the dummy `SMTPConnection`_.
+When the test framework is torn down, the standard `SMTPConnection`_ class
+is restored, and the test outbox is destroyed.
+
+As noted `previously`_, the test outbox is emptied at the start of every
+test in a Django TestCase. To empty the outbox manually, assign the empty list
+to mail.outbox::
+
+ from django.core import mail
+
+ # Empty the test outbox
+ mail.outbox = []
+
+.. _`Django email services`: ../email/
+.. _`SMTPConnection`: ../email/#the-emailmessage-and-smtpconnection-classes
+.. _`EmailMessage`: ../email/#the-emailmessage-and-smtpconnection-classes
+.. _`previously`: #emptying-the-test-outbox
+
Running tests
=============
@@ -516,6 +569,10 @@ database settings will the same as they would be for the project normally.
If you wish to use a name other than the default for the test database,
you can use the ``TEST_DATABASE_NAME`` setting to provide a name.
+The test database is created by the user in the ``DATABASE_USER`` setting.
+This user needs to have sufficient privileges to create a new database on the
+system.
+
Once the test database has been established, Django will run your tests.
If everything goes well, at the end you'll see::
@@ -606,11 +663,12 @@ a number of utility methods in the ``django.test.utils`` module.
``setup_test_environment()``
Performs any global pre-test setup, such as the installing the
- instrumentation of the template rendering system.
+ instrumentation of the template rendering system and setting up
+ the dummy SMTPConnection.
``teardown_test_environment()``
Performs any global post-test teardown, such as removing the instrumentation
- of the template rendering system.
+ of the template rendering system and restoring normal email services.
``create_test_db(verbosity=1, autoclobber=False)``
Creates a new test database, and run ``syncdb`` against it.
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index 2b2f64165f..195f67db8f 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -11,6 +11,7 @@ from complete).
from django.db import models
from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
class TaggedItem(models.Model):
"""A tag on an item."""
@@ -18,7 +19,7 @@ class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
- content_object = models.GenericForeignKey()
+ content_object = generic.GenericForeignKey()
class Meta:
ordering = ["tag"]
@@ -30,7 +31,7 @@ class Animal(models.Model):
common_name = models.CharField(maxlength=150)
latin_name = models.CharField(maxlength=150)
- tags = models.GenericRelation(TaggedItem)
+ tags = generic.GenericRelation(TaggedItem)
def __str__(self):
return self.common_name
@@ -39,7 +40,7 @@ class Vegetable(models.Model):
name = models.CharField(maxlength=150)
is_yucky = models.BooleanField(default=True)
- tags = models.GenericRelation(TaggedItem)
+ tags = generic.GenericRelation(TaggedItem)
def __str__(self):
return self.name
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index cd8dbe37d2..34242ee0d8 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -20,6 +20,7 @@ rather than the HTML rendered to the end-user.
"""
from django.test import Client, TestCase
+from django.core import mail
class ClientTest(TestCase):
fixtures = ['testdata.json']
@@ -232,3 +233,36 @@ class ClientTest(TestCase):
self.fail('Should raise an error')
except KeyError:
pass
+
+ def test_mail_sending(self):
+ "Test that mail is redirected to a dummy outbox during test setup"
+
+ response = self.client.get('/test_client/mail_sending_view/')
+ self.assertEqual(response.status_code, 200)
+
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].subject, 'Test message')
+ self.assertEqual(mail.outbox[0].body, 'This is a test email')
+ self.assertEqual(mail.outbox[0].from_email, 'from@example.com')
+ self.assertEqual(mail.outbox[0].to[0], 'first@example.com')
+ self.assertEqual(mail.outbox[0].to[1], 'second@example.com')
+
+ def test_mass_mail_sending(self):
+ "Test that mass mail is redirected to a dummy outbox during test setup"
+
+ response = self.client.get('/test_client/mass_mail_sending_view/')
+ self.assertEqual(response.status_code, 200)
+
+ self.assertEqual(len(mail.outbox), 2)
+ self.assertEqual(mail.outbox[0].subject, 'First Test message')
+ self.assertEqual(mail.outbox[0].body, 'This is the first test email')
+ self.assertEqual(mail.outbox[0].from_email, 'from@example.com')
+ self.assertEqual(mail.outbox[0].to[0], 'first@example.com')
+ self.assertEqual(mail.outbox[0].to[1], 'second@example.com')
+
+ self.assertEqual(mail.outbox[1].subject, 'Second Test message')
+ self.assertEqual(mail.outbox[1].body, 'This is the second test email')
+ self.assertEqual(mail.outbox[1].from_email, 'from@example.com')
+ self.assertEqual(mail.outbox[1].to[0], 'second@example.com')
+ self.assertEqual(mail.outbox[1].to[1], 'third@example.com')
+
\ No newline at end of file
diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py
index f63c486d01..52fc8fe692 100644
--- a/tests/modeltests/test_client/urls.py
+++ b/tests/modeltests/test_client/urls.py
@@ -11,5 +11,7 @@ urlpatterns = patterns('',
(r'^form_view_with_template/$', views.form_view_with_template),
(r'^login_protected_view/$', views.login_protected_view),
(r'^session_view/$', views.session_view),
- (r'^broken_view/$', views.broken_view)
+ (r'^broken_view/$', views.broken_view),
+ (r'^mail_sending_view/$', views.mail_sending_view),
+ (r'^mass_mail_sending_view/$', views.mass_mail_sending_view)
)
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index 3b7a57f4d0..18d6a2dcd9 100644
--- a/tests/modeltests/test_client/views.py
+++ b/tests/modeltests/test_client/views.py
@@ -1,4 +1,5 @@
from xml.dom.minidom import parseString
+from django.core.mail import EmailMessage, SMTPConnection
from django.template import Context, Template
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
@@ -124,3 +125,28 @@ def session_view(request):
def broken_view(request):
"""A view which just raises an exception, simulating a broken view."""
raise KeyError("Oops! Looks like you wrote some bad code.")
+
+def mail_sending_view(request):
+ EmailMessage(
+ "Test message",
+ "This is a test email",
+ "from@example.com",
+ ['first@example.com', 'second@example.com']).send()
+ return HttpResponse("Mail sent")
+
+def mass_mail_sending_view(request):
+ m1 = EmailMessage(
+ 'First Test message',
+ 'This is the first test email',
+ 'from@example.com',
+ ['first@example.com', 'second@example.com'])
+ m2 = EmailMessage(
+ 'Second Test message',
+ 'This is the second test email',
+ 'from@example.com',
+ ['second@example.com', 'third@example.com'])
+
+ c = SMTPConnection()
+ c.send_messages([m1,m2])
+
+ return HttpResponse("Mail sent")
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index cf58ab321a..9dc7161c03 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -46,6 +46,11 @@ class Cache(unittest.TestCase):
self.assertEqual(cache.has_key("hello"), True)
self.assertEqual(cache.has_key("goodbye"), False)
+ def test_in(self):
+ cache.set("hello", "goodbye")
+ self.assertEqual("hello" in cache, True)
+ self.assertEqual("goodbye" in cache, False)
+
def test_data_types(self):
# test data types
stuff = {
diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
index d3415ac1b9..c287b6e0d6 100644
--- a/tests/regressiontests/serializers_regress/models.py
+++ b/tests/regressiontests/serializers_regress/models.py
@@ -6,6 +6,7 @@ This class sets up a model for each model field type
"""
from django.db import models
+from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
# The following classes are for testing basic data
@@ -80,7 +81,7 @@ class Tag(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
- content_object = models.GenericForeignKey()
+ content_object = generic.GenericForeignKey()
class Meta:
ordering = ["data"]
@@ -88,7 +89,7 @@ class Tag(models.Model):
class GenericData(models.Model):
data = models.CharField(maxlength=30)
- tags = models.GenericRelation(Tag)
+ tags = generic.GenericRelation(Tag)
# The following test classes are all for validation
# of related objects; in particular, forward, backward,
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 9be8f022f6..a5ed2dbf56 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -586,6 +586,8 @@ class Templates(unittest.TestCase):
'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
+ 'invalidstr05': ('{{ var }}', {}, ('', 'INVALID %s', 'var')),
+ 'invalidstr06': ('{{ var.prop }}', {'var': {}}, ('', 'INVALID %s', 'var.prop')),
### MULTILINE #############################################################
@@ -737,6 +739,7 @@ class Templates(unittest.TestCase):
# Set TEMPLATE_STRING_IF_INVALID to a known string
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
+ expected_invalid_str = 'INVALID'
for name, vals in tests:
install()
@@ -744,6 +747,10 @@ class Templates(unittest.TestCase):
if isinstance(vals[2], tuple):
normal_string_result = vals[2][0]
invalid_string_result = vals[2][1]
+ if '%s' in invalid_string_result:
+ expected_invalid_str = 'INVALID %s'
+ invalid_string_result = invalid_string_result % vals[2][2]
+ template.invalid_var_format_string = True
else:
normal_string_result = vals[2]
invalid_string_result = vals[2]
@@ -754,7 +761,7 @@ class Templates(unittest.TestCase):
activate('en-us')
for invalid_str, result in [('', normal_string_result),
- ('INVALID', invalid_string_result)]:
+ (expected_invalid_str, invalid_string_result)]:
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
try:
output = loader.get_template(name).render(template.Context(vals[1]))
@@ -768,6 +775,10 @@ class Templates(unittest.TestCase):
if 'LANGUAGE_CODE' in vals[1]:
deactivate()
+ if template.invalid_var_format_string:
+ expected_invalid_str = 'INVALID'
+ template.invalid_var_format_string = False
+
loader.template_source_loaders = old_template_loaders
deactivate()
settings.TEMPLATE_DEBUG = old_td
--
cgit v1.2.1