1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

[soc2009/multidb] Merged up to trunk r11858.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11860 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Alex Gaynor 2009-12-13 22:15:08 +00:00
parent 049dc42bde
commit 2794cceb5f
53 changed files with 1658 additions and 1621 deletions

View File

@ -55,6 +55,7 @@ answer newbie questions, and generally made Django that much better:
Niran Babalola <niran@niran.org> Niran Babalola <niran@niran.org>
Morten Bagai <m@bagai.com> Morten Bagai <m@bagai.com>
Mikaël Barbero <mikael.barbero nospam at nospam free.fr> Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
Randy Barlow <randy@electronsweatshop.com>
Scott Barr <scott@divisionbyzero.com.au> Scott Barr <scott@divisionbyzero.com.au>
Jiri Barton Jiri Barton
Ned Batchelder <http://www.nedbatchelder.com/> Ned Batchelder <http://www.nedbatchelder.com/>
@ -136,7 +137,7 @@ answer newbie questions, and generally made Django that much better:
Andrew Durdin <adurdin@gmail.com> Andrew Durdin <adurdin@gmail.com>
dusk@woofle.net dusk@woofle.net
Andy Dustman <farcepest@gmail.com> Andy Dustman <farcepest@gmail.com>
J. Clifford Dyer <jcd@unc.edu> J. Clifford Dyer <jcd@sdf.lonestar.org>
Clint Ecker Clint Ecker
Nick Efford <nick@efford.org> Nick Efford <nick@efford.org>
eibaan@gmail.com eibaan@gmail.com
@ -172,6 +173,7 @@ answer newbie questions, and generally made Django that much better:
Alex Gaynor <alex.gaynor@gmail.com> Alex Gaynor <alex.gaynor@gmail.com>
Andy Gayton <andy-django@thecablelounge.com> Andy Gayton <andy-django@thecablelounge.com>
Idan Gazit Idan Gazit
geber@datacollect.com
Baishampayan Ghose Baishampayan Ghose
Dimitris Glezos <dimitris@glezos.com> Dimitris Glezos <dimitris@glezos.com>
glin@seznam.cz glin@seznam.cz
@ -267,6 +269,7 @@ answer newbie questions, and generally made Django that much better:
Finn Gruwier Larsen <finn@gruwier.dk> Finn Gruwier Larsen <finn@gruwier.dk>
Lau Bech Lauritzen Lau Bech Lauritzen
Rune Rønde Laursen <runerl@skjoldhoej.dk> Rune Rønde Laursen <runerl@skjoldhoej.dk>
Mark Lavin <markdlavin@gmail.com>
Eugene Lazutkin <http://lazutkin.com/blog/> Eugene Lazutkin <http://lazutkin.com/blog/>
lcordier@point45.com lcordier@point45.com
Jeong-Min Lee <falsetru@gmail.com> Jeong-Min Lee <falsetru@gmail.com>
@ -300,6 +303,7 @@ answer newbie questions, and generally made Django that much better:
Jason McBrayer <http://www.carcosa.net/jason/> Jason McBrayer <http://www.carcosa.net/jason/>
Kevin McConnell <kevin.mcconnell@gmail.com> Kevin McConnell <kevin.mcconnell@gmail.com>
mccutchen@gmail.com mccutchen@gmail.com
Paul McLanahan <paul@mclanahan.net>
Tobias McNulty <http://www.caktusgroup.com/blog> Tobias McNulty <http://www.caktusgroup.com/blog>
Christian Metts Christian Metts
michael.mcewan@gmail.com michael.mcewan@gmail.com
@ -382,6 +386,7 @@ answer newbie questions, and generally made Django that much better:
Massimo Scamarcia <massimo.scamarcia@gmail.com> Massimo Scamarcia <massimo.scamarcia@gmail.com>
David Schein David Schein
Bernd Schlapsi Bernd Schlapsi
schwank@gmail.com
scott@staplefish.com scott@staplefish.com
Ilya Semenov <semenov@inetss.com> Ilya Semenov <semenov@inetss.com>
serbaut@gmail.com serbaut@gmail.com
@ -393,6 +398,7 @@ answer newbie questions, and generally made Django that much better:
Jozko Skrablin <jozko.skrablin@gmail.com> Jozko Skrablin <jozko.skrablin@gmail.com>
Ben Slavin <benjamin.slavin@gmail.com> Ben Slavin <benjamin.slavin@gmail.com>
sloonz <simon.lipp@insa-lyon.fr> sloonz <simon.lipp@insa-lyon.fr>
Paul Smith <blinkylights23@gmail.com>
Warren Smith <warren@wandrsmith.net> Warren Smith <warren@wandrsmith.net>
smurf@smurf.noris.de smurf@smurf.noris.de
Vsevolod Solovyov Vsevolod Solovyov

2
README
View File

@ -27,7 +27,7 @@ http://code.djangoproject.com/newticket
To get more help: To get more help:
* Join the #django channel on irc.freenode.net. Lots of helpful people * Join the #django channel on irc.freenode.net. Lots of helpful people
hang out there. Read the archives at http://oebfare.com/logger/django/. hang out there. Read the archives at http://botland.oebfare.com/logger/django/.
* Join the django-users mailing list, or read the archives, at * Join the django-users mailing list, or read the archives, at
http://groups.google.com/group/django-users. http://groups.google.com/group/django-users.

View File

@ -692,6 +692,9 @@ class ModelAdmin(BaseModelAdmin):
# perform an action on it, so bail. # perform an action on it, so bail.
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
if not selected: if not selected:
# Reminder that something needs to be selected or nothing will happen
msg = "Items must be selected in order to perform actions on them. No items have been changed."
self.message_user(request, _(msg))
return None return None
response = func(self, request, queryset.filter(pk__in=selected)) response = func(self, request, queryset.filter(pk__in=selected))
@ -703,6 +706,9 @@ class ModelAdmin(BaseModelAdmin):
return response return response
else: else:
return HttpResponseRedirect(".") return HttpResponseRedirect(".")
else:
msg = "No action selected."
self.message_user(request, _(msg))
@csrf_protect @csrf_protect
@transaction.commit_on_success @transaction.commit_on_success

View File

@ -0,0 +1 @@
# Empty models.py to allow for specifying admindocs as a test label.

View File

@ -0,0 +1,36 @@
import unittest
from django.contrib.admindocs import views
import fields
from django.db.models import fields as builtin_fields
class TestFieldType(unittest.TestCase):
def setUp(self):
pass
def test_field_name(self):
self.assertRaises(AttributeError,
views.get_readable_field_data_type, "NotAField"
)
def test_builtin_fields(self):
self.assertEqual(
views.get_readable_field_data_type(builtin_fields.BooleanField()),
u'Boolean (Either True or False)'
)
def test_custom_fields(self):
self.assertEqual(
views.get_readable_field_data_type(fields.CustomField()),
u'A custom field type'
)
self.assertEqual(
views.get_readable_field_data_type(fields.DocstringLackingField()),
u'Field of type: DocstringLackingField'
)
def test_multiline_custom_field_truncation(self):
self.assertEqual(
views.get_readable_field_data_type(fields.ManyLineDocstringField()),
u'Many-line custom field'
)

View File

@ -0,0 +1,13 @@
from django.db import models
class CustomField(models.Field):
"""A custom field type"""
class ManyLineDocstringField(models.Field):
"""Many-line custom field
This docstring has many lines. Lorum ipsem etc. etc. Four score
and seven years ago, and so on and so forth."""
class DocstringLackingField(models.Field):
pass

View File

@ -326,43 +326,20 @@ def get_return_data_type(func_name):
return 'Integer' return 'Integer'
return '' return ''
# Maps Field objects to their human-readable data types, as strings.
# Column-type strings can contain format strings; they'll be interpolated
# against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
DATA_TYPE_MAPPING = {
'AutoField' : _('Integer'),
'BooleanField' : _('Boolean (Either True or False)'),
'CharField' : _('String (up to %(max_length)s)'),
'CommaSeparatedIntegerField': _('Comma-separated integers'),
'DateField' : _('Date (without time)'),
'DateTimeField' : _('Date (with time)'),
'DecimalField' : _('Decimal number'),
'EmailField' : _('E-mail address'),
'FileField' : _('File path'),
'FilePathField' : _('File path'),
'FloatField' : _('Floating point number'),
'ForeignKey' : _('Integer'),
'ImageField' : _('File path'),
'IntegerField' : _('Integer'),
'IPAddressField' : _('IP address'),
'ManyToManyField' : '',
'NullBooleanField' : _('Boolean (Either True, False or None)'),
'OneToOneField' : _('Relation to parent model'),
'PhoneNumberField' : _('Phone number'),
'PositiveIntegerField' : _('Integer'),
'PositiveSmallIntegerField' : _('Integer'),
'SlugField' : _('String (up to %(max_length)s)'),
'SmallIntegerField' : _('Integer'),
'TextField' : _('Text'),
'TimeField' : _('Time'),
'URLField' : _('URL'),
'USStateField' : _('U.S. state (two uppercase letters)'),
'XMLField' : _('XML text'),
}
def get_readable_field_data_type(field): def get_readable_field_data_type(field):
return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__ """Returns the first line of a doc string for a given field type, if it
exists. Fields' docstrings can contain format strings, which will be
interpolated against the values of Field.__dict__ before being output.
If no docstring is given, a sensible value will be auto-generated from
the field's class name."""
if field.__doc__:
doc = field.__doc__.split('\n')[0]
return _(doc) % field.__dict__
else:
return _(u'Field of type: %(field_type)s') % {
'field_type': field.__class__.__name__
}
def extract_views_from_urlpatterns(urlpatterns, base=''): def extract_views_from_urlpatterns(urlpatterns, base=''):
""" """

View File

@ -40,8 +40,8 @@ def get_srid_info(srid, connection):
return _srid_cache[name][srid] return _srid_cache[name][srid]
class GeometryField(Field): class GeometryField(SpatialBackend.Field):
"The base GIS field -- maps to the OpenGIS Specification Geometry type." """The base GIS field -- maps to the OpenGIS Specification Geometry type."""
# The OpenGIS Geometry name. # The OpenGIS Geometry name.
geom_type = 'GEOMETRY' geom_type = 'GEOMETRY'
@ -285,22 +285,29 @@ class GeometryField(Field):
# The OpenGIS Geometry Type Fields # The OpenGIS Geometry Type Fields
class PointField(GeometryField): class PointField(GeometryField):
"""Point"""
geom_type = 'POINT' geom_type = 'POINT'
class LineStringField(GeometryField): class LineStringField(GeometryField):
"""Line string"""
geom_type = 'LINESTRING' geom_type = 'LINESTRING'
class PolygonField(GeometryField): class PolygonField(GeometryField):
"""Polygon"""
geom_type = 'POLYGON' geom_type = 'POLYGON'
class MultiPointField(GeometryField): class MultiPointField(GeometryField):
"""Multi-point"""
geom_type = 'MULTIPOINT' geom_type = 'MULTIPOINT'
class MultiLineStringField(GeometryField): class MultiLineStringField(GeometryField):
"""Multi-line string"""
geom_type = 'MULTILINESTRING' geom_type = 'MULTILINESTRING'
class MultiPolygonField(GeometryField): class MultiPolygonField(GeometryField):
"""Multi polygon"""
geom_type = 'MULTIPOLYGON' geom_type = 'MULTIPOLYGON'
class GeometryCollectionField(GeometryField): class GeometryCollectionField(GeometryField):
"""Geometry collection"""
geom_type = 'GEOMETRYCOLLECTION' geom_type = 'GEOMETRYCOLLECTION'

View File

@ -12,13 +12,20 @@ phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$") sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$")
class CAPostalCodeField(RegexField): class CAPostalCodeField(RegexField):
"""Canadian postal code field.""" """
Canadian postal code field.
Validates against known invalid characters: D, F, I, O, Q, U
Additionally the first character cannot be Z or W.
For more info see:
http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1402170
"""
default_error_messages = { default_error_messages = {
'invalid': _(u'Enter a postal code in the format XXX XXX.'), 'invalid': _(u'Enter a postal code in the format XXX XXX.'),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$', super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] \d[ABCEGHJKLMNPRSTVWXYZ]\d$',
max_length=None, min_length=None, *args, **kwargs) max_length=None, min_length=None, *args, **kwargs)
class CAPhoneNumberField(Field): class CAPhoneNumberField(Field):

View File

@ -1,22 +1,16 @@
from django.db.models.fields import Field from django.conf import settings
from django.db.models.fields import Field, CharField
class USStateField(Field): from django.contrib.localflavor.us.us_states import STATE_CHOICES
def get_internal_type(self):
return "USStateField" class USStateField(CharField):
"""U.S. state (two uppercase letters)"""
def db_type(self, connection): def __init__(self, *args, **kwargs):
if connection.settings_dict['ENGINE'] == 'django.db.backends.oracle': kwargs['choices'] = STATE_CHOICES
return 'CHAR(2)' kwargs['max_length'] = 2
else: super(USStateField, self).__init__(*args, **kwargs)
return 'varchar(2)'
def formfield(self, **kwargs):
from django.contrib.localflavor.us.forms import USStateSelect
defaults = {'widget': USStateSelect}
defaults.update(kwargs)
return super(USStateField, self).formfield(**defaults)
class PhoneNumberField(Field): class PhoneNumberField(Field):
"""Phone number"""
def get_internal_type(self): def get_internal_type(self):
return "PhoneNumberField" return "PhoneNumberField"

View File

@ -1,7 +1,7 @@
import hmac import hmac
from django.conf import settings from django.conf import settings
from django.utils.hashcompat import sha_constructor from django.utils.hashcompat import sha_hmac
from django.contrib.messages import constants from django.contrib.messages import constants
from django.contrib.messages.storage.base import BaseStorage, Message from django.contrib.messages.storage.base import BaseStorage, Message
from django.utils import simplejson as json from django.utils import simplejson as json
@ -41,7 +41,6 @@ class MessageDecoder(json.JSONDecoder):
decoded = super(MessageDecoder, self).decode(s, **kwargs) decoded = super(MessageDecoder, self).decode(s, **kwargs)
return self.process_messages(decoded) return self.process_messages(decoded)
class CookieStorage(BaseStorage): class CookieStorage(BaseStorage):
""" """
Stores messages in a cookie. Stores messages in a cookie.
@ -103,7 +102,7 @@ class CookieStorage(BaseStorage):
SECRET_KEY, modified to make it unique for the present purpose. SECRET_KEY, modified to make it unique for the present purpose.
""" """
key = 'django.contrib.messages' + settings.SECRET_KEY key = 'django.contrib.messages' + settings.SECRET_KEY
return hmac.new(key, value, sha_constructor).hexdigest() return hmac.new(key, value, sha_hmac).hexdigest()
def _encode(self, messages, encode_empty=False): def _encode(self, messages, encode_empty=False):
""" """

View File

@ -218,9 +218,9 @@ class BaseTest(TestCase):
response = self.client.post(add_url, data, follow=True) response = self.client.post(add_url, data, follow=True)
self.assertRedirects(response, show_url) self.assertRedirects(response, show_url)
self.assertTrue('messages' in response.context) self.assertTrue('messages' in response.context)
self.assertEqual(list(response.context['messages']), context_messages = list(response.context['messages'])
data['messages'])
for msg in data['messages']: for msg in data['messages']:
self.assertTrue(msg in context_messages)
self.assertContains(response, msg) self.assertContains(response, msg)
def test_middleware_disabled_anon_user(self): def test_middleware_disabled_anon_user(self):

View File

@ -46,7 +46,28 @@ class CacheClass(BaseCache):
self._cache.disconnect_all() self._cache.disconnect_all()
def incr(self, key, delta=1): def incr(self, key, delta=1):
return self._cache.incr(key, delta) try:
val = self._cache.incr(key, delta)
# python-memcache responds to incr on non-existent keys by
# raising a ValueError. Cmemcache returns None. In both
# cases, we should raise a ValueError though.
except ValueError:
val = None
if val is None:
raise ValueError("Key '%s' not found" % key)
return val
def decr(self, key, delta=1): def decr(self, key, delta=1):
return self._cache.decr(key, delta) try:
val = self._cache.decr(key, delta)
# python-memcache responds to decr on non-existent keys by
# raising a ValueError. Cmemcache returns None. In both
# cases, we should raise a ValueError though.
except ValueError:
val = None
if val is None:
raise ValueError("Key '%s' not found" % key)
return val

View File

@ -6,6 +6,8 @@ class Command(BaseCommand):
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--noinput', action='store_false', dest='interactive', default=True, make_option('--noinput', action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.'), help='Tells Django to NOT prompt the user for input of any kind.'),
make_option('--failfast', action='store_true', dest='failfast', default=False,
help='Tells Django to stop running the test suite after first failed test.')
) )
help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.' help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.'
args = '[appname ...]' args = '[appname ...]'
@ -15,11 +17,18 @@ class Command(BaseCommand):
def handle(self, *test_labels, **options): def handle(self, *test_labels, **options):
from django.conf import settings from django.conf import settings
from django.test.utils import get_runner from django.test.utils import get_runner
verbosity = int(options.get('verbosity', 1)) verbosity = int(options.get('verbosity', 1))
interactive = options.get('interactive', True) interactive = options.get('interactive', True)
failfast = options.get('failfast', False)
test_runner = get_runner(settings) test_runner = get_runner(settings)
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive) # Some custom test runners won't accept the failfast flag, so let's make sure they accept it before passing it to them
if 'failfast' in test_runner.func_code.co_varnames:
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive,
failfast=failfast)
else:
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
if failures: if failures:
sys.exit(failures) sys.exit(failures)

View File

@ -257,9 +257,8 @@ class RegexURLResolver(object):
def _resolve_special(self, view_type): def _resolve_special(self, view_type):
callback = getattr(self.urlconf_module, 'handler%s' % view_type) callback = getattr(self.urlconf_module, 'handler%s' % view_type)
mod_name, func_name = get_mod_func(callback)
try: try:
return getattr(import_module(mod_name), func_name), {} return get_callable(callback), {}
except (ImportError, AttributeError), e: except (ImportError, AttributeError), e:
raise ViewDoesNotExist, "Tried %s. Error was: %s" % (callback, str(e)) raise ViewDoesNotExist, "Tried %s. Error was: %s" % (callback, str(e))

View File

@ -3,10 +3,6 @@ import datetime
import os import os
import re import re
import time import time
try:
import decimal
except ImportError:
from django.utils import _decimal as decimal # for Python 2.3
from django.db import connection from django.db import connection
from django.db.models import signals from django.db.models import signals
@ -50,7 +46,9 @@ class FieldDoesNotExist(Exception):
# getattr(obj, opts.pk.attname) # getattr(obj, opts.pk.attname)
class Field(object): class Field(object):
"""Base class for all field types"""
__metaclass__ = LegacyConnection __metaclass__ = LegacyConnection
# Designates whether empty strings fundamentally are allowed at the # Designates whether empty strings fundamentally are allowed at the
# database level. # database level.
empty_strings_allowed = True empty_strings_allowed = True
@ -370,7 +368,10 @@ class Field(object):
return getattr(obj, self.attname) return getattr(obj, self.attname)
class AutoField(Field): class AutoField(Field):
"""Integer"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__ assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
kwargs['blank'] = True kwargs['blank'] = True
@ -400,7 +401,10 @@ class AutoField(Field):
return None return None
class BooleanField(Field): class BooleanField(Field):
"""Boolean (Either True or False)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['blank'] = True kwargs['blank'] = True
if 'default' not in kwargs and not kwargs.get('null'): if 'default' not in kwargs and not kwargs.get('null'):
@ -443,6 +447,8 @@ class BooleanField(Field):
return super(BooleanField, self).formfield(**defaults) return super(BooleanField, self).formfield(**defaults)
class CharField(Field): class CharField(Field):
"""String (up to %(max_length)s)"""
def get_internal_type(self): def get_internal_type(self):
return "CharField" return "CharField"
@ -464,6 +470,8 @@ class CharField(Field):
# TODO: Maybe move this into contrib, because it's specialized. # TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField): class CommaSeparatedIntegerField(CharField):
"""Comma-separated integers"""
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = { defaults = {
'form_class': forms.RegexField, 'form_class': forms.RegexField,
@ -479,7 +487,10 @@ class CommaSeparatedIntegerField(CharField):
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$') ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
class DateField(Field): class DateField(Field):
"""Date (without time)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add self.auto_now, self.auto_now_add = auto_now, auto_now_add
#HACKs : auto_now_add/auto_now should be done as a default or a pre_save. #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@ -559,6 +570,8 @@ class DateField(Field):
return super(DateField, self).formfield(**defaults) return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField): class DateTimeField(DateField):
"""Date (with time)"""
def get_internal_type(self): def get_internal_type(self):
return "DateTimeField" return "DateTimeField"
@ -623,7 +636,10 @@ class DateTimeField(DateField):
return super(DateTimeField, self).formfield(**defaults) return super(DateTimeField, self).formfield(**defaults)
class DecimalField(Field): class DecimalField(Field):
"""Decimal number"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
@ -677,6 +693,8 @@ class DecimalField(Field):
return super(DecimalField, self).formfield(**defaults) return super(DecimalField, self).formfield(**defaults)
class EmailField(CharField): class EmailField(CharField):
"""E-mail address"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75) kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs) CharField.__init__(self, *args, **kwargs)
@ -687,6 +705,8 @@ class EmailField(CharField):
return super(EmailField, self).formfield(**defaults) return super(EmailField, self).formfield(**defaults)
class FilePathField(Field): class FilePathField(Field):
"""File path"""
def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive self.path, self.match, self.recursive = path, match, recursive
kwargs['max_length'] = kwargs.get('max_length', 100) kwargs['max_length'] = kwargs.get('max_length', 100)
@ -706,6 +726,8 @@ class FilePathField(Field):
return "FilePathField" return "FilePathField"
class FloatField(Field): class FloatField(Field):
"""Floating point number"""
empty_strings_allowed = False empty_strings_allowed = False
def get_prep_value(self, value): def get_prep_value(self, value):
@ -731,8 +753,15 @@ class FloatField(Field):
return super(FloatField, self).formfield(**defaults) return super(FloatField, self).formfield(**defaults)
class IntegerField(Field): class IntegerField(Field):
"""Integer"""
empty_strings_allowed = False empty_strings_allowed = False
<<<<<<< HEAD:django/db/models/fields/__init__.py
def get_prep_value(self, value): def get_prep_value(self, value):
=======
def get_db_prep_value(self, value):
>>>>>>> master:django/db/models/fields/__init__.py
if value is None: if value is None:
return None return None
return int(value) return int(value)
@ -755,7 +784,10 @@ class IntegerField(Field):
return super(IntegerField, self).formfield(**defaults) return super(IntegerField, self).formfield(**defaults)
class IPAddressField(Field): class IPAddressField(Field):
"""IP address"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 15 kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
@ -769,7 +801,10 @@ class IPAddressField(Field):
return super(IPAddressField, self).formfield(**defaults) return super(IPAddressField, self).formfield(**defaults)
class NullBooleanField(Field): class NullBooleanField(Field):
"""Boolean (Either True, False or None)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['null'] = True kwargs['null'] = True
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
@ -809,6 +844,8 @@ class NullBooleanField(Field):
return super(NullBooleanField, self).formfield(**defaults) return super(NullBooleanField, self).formfield(**defaults)
class PositiveIntegerField(IntegerField): class PositiveIntegerField(IntegerField):
"""Integer"""
def get_internal_type(self): def get_internal_type(self):
return "PositiveIntegerField" return "PositiveIntegerField"
@ -818,6 +855,8 @@ class PositiveIntegerField(IntegerField):
return super(PositiveIntegerField, self).formfield(**defaults) return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField): class PositiveSmallIntegerField(IntegerField):
"""Integer"""
def get_internal_type(self): def get_internal_type(self):
return "PositiveSmallIntegerField" return "PositiveSmallIntegerField"
@ -827,6 +866,8 @@ class PositiveSmallIntegerField(IntegerField):
return super(PositiveSmallIntegerField, self).formfield(**defaults) return super(PositiveSmallIntegerField, self).formfield(**defaults)
class SlugField(CharField): class SlugField(CharField):
"""String (up to %(max_length)s)"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 50) kwargs['max_length'] = kwargs.get('max_length', 50)
# Set db_index=True unless it's been set manually. # Set db_index=True unless it's been set manually.
@ -843,10 +884,14 @@ class SlugField(CharField):
return super(SlugField, self).formfield(**defaults) return super(SlugField, self).formfield(**defaults)
class SmallIntegerField(IntegerField): class SmallIntegerField(IntegerField):
"""Integer"""
def get_internal_type(self): def get_internal_type(self):
return "SmallIntegerField" return "SmallIntegerField"
class TextField(Field): class TextField(Field):
"""Text"""
def get_internal_type(self): def get_internal_type(self):
return "TextField" return "TextField"
@ -856,7 +901,10 @@ class TextField(Field):
return super(TextField, self).formfield(**defaults) return super(TextField, self).formfield(**defaults)
class TimeField(Field): class TimeField(Field):
"""Time"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add: if auto_now or auto_now_add:
@ -933,6 +981,8 @@ class TimeField(Field):
return super(TimeField, self).formfield(**defaults) return super(TimeField, self).formfield(**defaults)
class URLField(CharField): class URLField(CharField):
"""URL"""
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 200) kwargs['max_length'] = kwargs.get('max_length', 200)
self.verify_exists = verify_exists self.verify_exists = verify_exists
@ -944,6 +994,8 @@ class URLField(CharField):
return super(URLField, self).formfield(**defaults) return super(URLField, self).formfield(**defaults)
class XMLField(TextField): class XMLField(TextField):
"""XML text"""
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
self.schema_path = schema_path self.schema_path = schema_path
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)

View File

@ -209,6 +209,8 @@ class FileDescriptor(object):
instance.__dict__[self.field.name] = value instance.__dict__[self.field.name] = value
class FileField(Field): class FileField(Field):
"""File path"""
# The class to wrap instance attributes in. Accessing the file object off # The class to wrap instance attributes in. Accessing the file object off
# the instance will always return an instance of attr_class. # the instance will always return an instance of attr_class.
attr_class = FieldFile attr_class = FieldFile
@ -323,6 +325,8 @@ class ImageFieldFile(ImageFile, FieldFile):
super(ImageFieldFile, self).delete(save) super(ImageFieldFile, self).delete(save)
class ImageField(FileField): class ImageField(FileField):
"""File path"""
attr_class = ImageFieldFile attr_class = ImageFieldFile
descriptor_class = ImageFileDescriptor descriptor_class = ImageFileDescriptor

View File

@ -708,6 +708,8 @@ class ManyToManyRel(object):
return self.to._meta.pk return self.to._meta.pk
class ForeignKey(RelatedField, Field): class ForeignKey(RelatedField, Field):
"""Foreign Key (type determined by related field)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try: try:
@ -806,12 +808,13 @@ class ForeignKey(RelatedField, Field):
return rel_field.db_type(connection=connection) return rel_field.db_type(connection=connection)
class OneToOneField(ForeignKey): class OneToOneField(ForeignKey):
""" """One-to-one relationship
A OneToOneField is essentially the same as a ForeignKey, with the exception A OneToOneField is essentially the same as a ForeignKey, with the exception
that always carries a "unique" constraint with it and the reverse relation that always carries a "unique" constraint with it and the reverse relation
always returns the object pointed to (since there will only ever be one), always returns the object pointed to (since there will only ever be one),
rather than returning a list. rather than returning a list."""
"""
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True kwargs['unique'] = True
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs) super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@ -865,6 +868,8 @@ def create_many_to_many_intermediary_model(field, klass):
}) })
class ManyToManyField(RelatedField, Field): class ManyToManyField(RelatedField, Field):
"""Many-to-many relationship"""
def __init__(self, to, **kwargs): def __init__(self, to, **kwargs):
try: try:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)

View File

@ -138,6 +138,8 @@ class BaseForm(StrAndUnicode):
"Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
top_errors = self.non_field_errors() # Errors that should be displayed above all fields. top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
output, hidden_fields = [], [] output, hidden_fields = [], []
html_class_attr = ''
for name, field in self.fields.items(): for name, field in self.fields.items():
bf = BoundField(self, field, name) bf = BoundField(self, field, name)
bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable.
@ -146,8 +148,15 @@ class BaseForm(StrAndUnicode):
top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
hidden_fields.append(unicode(bf)) hidden_fields.append(unicode(bf))
else: else:
# Create a 'class="..."' atribute if the row should have any
# CSS classes applied.
css_classes = bf.css_classes()
if css_classes:
html_class_attr = ' class="%s"' % css_classes
if errors_on_separate_row and bf_errors: if errors_on_separate_row and bf_errors:
output.append(error_row % force_unicode(bf_errors)) output.append(error_row % force_unicode(bf_errors))
if bf.label: if bf.label:
label = conditional_escape(force_unicode(bf.label)) label = conditional_escape(force_unicode(bf.label))
# Only add the suffix if the label does not end in # Only add the suffix if the label does not end in
@ -158,13 +167,23 @@ class BaseForm(StrAndUnicode):
label = bf.label_tag(label) or '' label = bf.label_tag(label) or ''
else: else:
label = '' label = ''
if field.help_text: if field.help_text:
help_text = help_text_html % force_unicode(field.help_text) help_text = help_text_html % force_unicode(field.help_text)
else: else:
help_text = u'' help_text = u''
output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
output.append(normal_row % {
'errors': force_unicode(bf_errors),
'label': force_unicode(label),
'field': unicode(bf),
'help_text': help_text,
'html_class_attr': html_class_attr
})
if top_errors: if top_errors:
output.insert(0, error_row % force_unicode(top_errors)) output.insert(0, error_row % force_unicode(top_errors))
if hidden_fields: # Insert any hidden fields in the last row. if hidden_fields: # Insert any hidden fields in the last row.
str_hidden = u''.join(hidden_fields) str_hidden = u''.join(hidden_fields)
if output: if output:
@ -176,7 +195,9 @@ class BaseForm(StrAndUnicode):
# that users write): if there are only top errors, we may # that users write): if there are only top errors, we may
# not be able to conscript the last row for our purposes, # not be able to conscript the last row for our purposes,
# so insert a new, empty row. # so insert a new, empty row.
last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''} last_row = (normal_row % {'errors': '', 'label': '',
'field': '', 'help_text':'',
'html_class_attr': html_class_attr})
output.append(last_row) output.append(last_row)
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
else: else:
@ -187,15 +208,30 @@ class BaseForm(StrAndUnicode):
def as_table(self): def as_table(self):
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>." "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False) return self._html_output(
normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
error_row = u'<tr><td colspan="2">%s</td></tr>',
row_ender = u'</td></tr>',
help_text_html = u'<br />%s',
errors_on_separate_row = False)
def as_ul(self): def as_ul(self):
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False) return self._html_output(
normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
error_row = u'<li>%s</li>',
row_ender = '</li>',
help_text_html = u' %s',
errors_on_separate_row = False)
def as_p(self): def as_p(self):
"Returns this form rendered as HTML <p>s." "Returns this form rendered as HTML <p>s."
return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True) return self._html_output(
normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
error_row = u'%s',
row_ender = '</p>',
help_text_html = u' %s',
errors_on_separate_row = True)
def non_field_errors(self): def non_field_errors(self):
""" """
@ -343,6 +379,7 @@ class BoundField(StrAndUnicode):
self.name = name self.name = name
self.html_name = form.add_prefix(name) self.html_name = form.add_prefix(name)
self.html_initial_name = form.add_initial_prefix(name) self.html_initial_name = form.add_initial_prefix(name)
self.html_initial_id = form.add_initial_prefix(self.auto_id)
if self.field.label is None: if self.field.label is None:
self.label = pretty_name(name) self.label = pretty_name(name)
else: else:
@ -374,7 +411,10 @@ class BoundField(StrAndUnicode):
attrs = attrs or {} attrs = attrs or {}
auto_id = self.auto_id auto_id = self.auto_id
if auto_id and 'id' not in attrs and 'id' not in widget.attrs: if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
attrs['id'] = auto_id if not only_initial:
attrs['id'] = auto_id
else:
attrs['id'] = self.html_initial_id
if not self.form.is_bound: if not self.form.is_bound:
data = self.form.initial.get(self.name, self.field.initial) data = self.form.initial.get(self.name, self.field.initial)
if callable(data): if callable(data):
@ -429,6 +469,19 @@ class BoundField(StrAndUnicode):
contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents)) contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
return mark_safe(contents) return mark_safe(contents)
def css_classes(self, extra_classes=None):
"""
Returns a string of space-separated CSS classes for this field.
"""
if hasattr(extra_classes, 'split'):
extra_classes = extra_classes.split()
extra_classes = set(extra_classes or [])
if self.errors and hasattr(self.form, 'error_css_class'):
extra_classes.add(self.form.error_css_class)
if self.field.required and hasattr(self.form, 'required_css_class'):
extra_classes.add(self.form.required_css_class)
return ' '.join(extra_classes)
def _is_hidden(self): def _is_hidden(self):
"Returns True if this BoundField's widget is hidden." "Returns True if this BoundField's widget is hidden."
return self.field.widget.is_hidden return self.field.widget.is_hidden

View File

@ -912,6 +912,9 @@ class ModelChoiceIterator(object):
for obj in self.queryset.all(): for obj in self.queryset.all():
yield self.choice(obj) yield self.choice(obj)
def __len__(self):
return len(self.queryset)
def choice(self, obj): def choice(self, obj):
if self.field.to_field_name: if self.field.to_field_name:
key = obj.serializable_value(self.field.to_field_name) key = obj.serializable_value(self.field.to_field_name)

View File

@ -1,6 +1,6 @@
import os import os
import re import re
from Cookie import SimpleCookie, CookieError from Cookie import BaseCookie, SimpleCookie, CookieError
from pprint import pformat from pprint import pformat
from urllib import urlencode from urllib import urlencode
from urlparse import urljoin from urlparse import urljoin
@ -251,13 +251,15 @@ class QueryDict(MultiValueDict):
def parse_cookie(cookie): def parse_cookie(cookie):
if cookie == '': if cookie == '':
return {} return {}
try: if not isinstance(cookie, BaseCookie):
c = SimpleCookie() try:
c.load(cookie) c = SimpleCookie()
except CookieError: c.load(cookie)
# Invalid cookie except CookieError:
return {} # Invalid cookie
return {}
else:
c = cookie
cookiedict = {} cookiedict = {}
for key in c.keys(): for key in c.keys():
cookiedict[key] = c.get(key).value cookiedict[key] = c.get(key).value

View File

@ -249,7 +249,8 @@ stringformat.is_safe = True
def title(value): def title(value):
"""Converts a string into titlecase.""" """Converts a string into titlecase."""
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)
title.is_safe = True title.is_safe = True
title = stringfilter(title) title = stringfilter(title)

View File

@ -10,6 +10,26 @@ TEST_MODULE = 'tests'
doctestOutputChecker = OutputChecker() doctestOutputChecker = OutputChecker()
class DjangoTestRunner(unittest.TextTestRunner):
def __init__(self, verbosity=0, failfast=False, **kwargs):
super(DjangoTestRunner, self).__init__(verbosity=verbosity, **kwargs)
self.failfast = failfast
def _makeResult(self):
result = super(DjangoTestRunner, self)._makeResult()
failfast = self.failfast
def stoptest_override(func):
def stoptest(test):
if failfast and not result.wasSuccessful():
result.stop()
func(test)
return stoptest
setattr(result, 'stopTest', stoptest_override(result.stopTest))
return result
def get_tests(app_module): def get_tests(app_module):
try: try:
app_path = app_module.__name__.split('.')[:-1] app_path = app_module.__name__.split('.')[:-1]
@ -146,7 +166,7 @@ def reorder_suite(suite, classes):
bins[0].addTests(bins[i+1]) bins[0].addTests(bins[i+1])
return bins[0] return bins[0]
def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]): def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=[]):
""" """
Run the unit tests for all the test labels in the provided list. Run the unit tests for all the test labels in the provided list.
Labels must be of the form: Labels must be of the form:
@ -186,13 +206,13 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
suite = reorder_suite(suite, (TestCase,)) suite = reorder_suite(suite, (TestCase,))
old_names = []
from django.db import connections from django.db import connections
old_names = []
for alias in connections: for alias in connections:
connection = connections[alias] connection = connections[alias]
old_names.append((connection, connection.settings_dict['NAME'])) old_names.append((connection, connection.settings_dict['NAME']))
connection.creation.create_test_db(verbosity, autoclobber=not interactive) connection.creation.create_test_db(verbosity, autoclobber=not interactive)
result = unittest.TextTestRunner(verbosity=verbosity).run(suite) result = DjangoTestRunner(verbosity=verbosity, failfast=failfast).run(suite)
for connection, old_name in old_names: for connection, old_name in old_names:
connection.creation.destroy_test_db(old_name, verbosity) connection.creation.destroy_test_db(old_name, verbosity)

View File

@ -1,3 +1,6 @@
from copy import deepcopy
class MergeDict(object): class MergeDict(object):
""" """
A simple class for creating new "virtual" dictionaries that actually look A simple class for creating new "virtual" dictionaries that actually look
@ -72,22 +75,20 @@ class SortedDict(dict):
self.keyOrder.append(key) self.keyOrder.append(key)
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
from copy import deepcopy
return self.__class__([(key, deepcopy(value, memo)) return self.__class__([(key, deepcopy(value, memo))
for key, value in self.iteritems()]) for key, value in self.iteritems()])
def __setitem__(self, key, value): def __setitem__(self, key, value):
super(SortedDict, self).__setitem__(key, value) if key not in self:
if key not in self.keyOrder:
self.keyOrder.append(key) self.keyOrder.append(key)
super(SortedDict, self).__setitem__(key, value)
def __delitem__(self, key): def __delitem__(self, key):
super(SortedDict, self).__delitem__(key) super(SortedDict, self).__delitem__(key)
self.keyOrder.remove(key) self.keyOrder.remove(key)
def __iter__(self): def __iter__(self):
for k in self.keyOrder: return iter(self.keyOrder)
yield k
def pop(self, k, *args): def pop(self, k, *args):
result = super(SortedDict, self).pop(k, *args) result = super(SortedDict, self).pop(k, *args)
@ -108,7 +109,7 @@ class SortedDict(dict):
def iteritems(self): def iteritems(self):
for key in self.keyOrder: for key in self.keyOrder:
yield key, super(SortedDict, self).__getitem__(key) yield key, self[key]
def keys(self): def keys(self):
return self.keyOrder[:] return self.keyOrder[:]
@ -117,18 +118,18 @@ class SortedDict(dict):
return iter(self.keyOrder) return iter(self.keyOrder)
def values(self): def values(self):
return map(super(SortedDict, self).__getitem__, self.keyOrder) return map(self.__getitem__, self.keyOrder)
def itervalues(self): def itervalues(self):
for key in self.keyOrder: for key in self.keyOrder:
yield super(SortedDict, self).__getitem__(key) yield self[key]
def update(self, dict_): def update(self, dict_):
for k, v in dict_.items(): for k, v in dict_.iteritems():
self.__setitem__(k, v) self[k] = v
def setdefault(self, key, default): def setdefault(self, key, default):
if key not in self.keyOrder: if key not in self:
self.keyOrder.append(key) self.keyOrder.append(key)
return super(SortedDict, self).setdefault(key, default) return super(SortedDict, self).setdefault(key, default)
@ -222,18 +223,18 @@ class MultiValueDict(dict):
dict.__setitem__(result, copy.deepcopy(key, memo), dict.__setitem__(result, copy.deepcopy(key, memo),
copy.deepcopy(value, memo)) copy.deepcopy(value, memo))
return result return result
def __getstate__(self): def __getstate__(self):
obj_dict = self.__dict__.copy() obj_dict = self.__dict__.copy()
obj_dict['_data'] = dict([(k, self.getlist(k)) for k in self]) obj_dict['_data'] = dict([(k, self.getlist(k)) for k in self])
return obj_dict return obj_dict
def __setstate__(self, obj_dict): def __setstate__(self, obj_dict):
data = obj_dict.pop('_data', {}) data = obj_dict.pop('_data', {})
for k, v in data.items(): for k, v in data.items():
self.setlist(k, v) self.setlist(k, v)
self.__dict__.update(obj_dict) self.__dict__.update(obj_dict)
def get(self, key, default=None): def get(self, key, default=None):
""" """
Returns the last data value for the passed key. If key doesn't exist Returns the last data value for the passed key. If key doesn't exist
@ -301,12 +302,12 @@ class MultiValueDict(dict):
def values(self): def values(self):
"""Returns a list of the last value on every key list.""" """Returns a list of the last value on every key list."""
return [self[key] for key in self.keys()] return [self[key] for key in self.keys()]
def itervalues(self): def itervalues(self):
"""Yield the last value on every key list.""" """Yield the last value on every key list."""
for key in self.iterkeys(): for key in self.iterkeys():
yield self[key] yield self[key]
def copy(self): def copy(self):
"""Returns a copy of this object.""" """Returns a copy of this object."""
return self.__deepcopy__() return self.__deepcopy__()

View File

@ -277,6 +277,13 @@ class LazyObject(object):
self._setup() self._setup()
setattr(self._wrapped, name, value) setattr(self._wrapped, name, value)
def __delattr__(self, name):
if name == "_wrapped":
raise TypeError("can't delete _wrapped.")
if self._wrapped is None:
self._setup()
delattr(self._wrapped, name)
def _setup(self): def _setup(self):
""" """
Must be implemented by subclasses to initialise the wrapped object. Must be implemented by subclasses to initialise the wrapped object.

View File

@ -8,9 +8,13 @@ available.
try: try:
import hashlib import hashlib
md5_constructor = hashlib.md5 md5_constructor = hashlib.md5
md5_hmac = md5_constructor
sha_constructor = hashlib.sha1 sha_constructor = hashlib.sha1
sha_hmac = sha_constructor
except ImportError: except ImportError:
import md5 import md5
md5_constructor = md5.new md5_constructor = md5.new
md5_hmac = md5
import sha import sha
sha_constructor = sha.new sha_constructor = sha.new
sha_hmac = sha

View File

@ -39,6 +39,8 @@ are traditionally called *north*, *east*, *south* and *west*. Our class looks
something like this:: something like this::
class Hand(object): class Hand(object):
"""A hand of cards (bridge style)"""
def __init__(self, north, east, south, west): def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc) # Input parameters are lists of cards ('Ah', '9s', etc)
self.north = north self.north = north
@ -163,6 +165,8 @@ behave like any existing field, so we'll subclass directly from
from django.db import models from django.db import models
class HandField(models.Field): class HandField(models.Field):
"""A hand of cards (bridge style)"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104 kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs) super(HandField, self).__init__(*args, **kwargs)
@ -244,6 +248,8 @@ simple: make sure your field subclass uses a special metaclass:
For example:: For example::
class HandField(models.Field): class HandField(models.Field):
"""A hand of cards (bridge style)"""
__metaclass__ = models.SubfieldBase __metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -252,6 +258,21 @@ For example::
This ensures that the :meth:`to_python` method, documented below, will always be This ensures that the :meth:`to_python` method, documented below, will always be
called when the attribute is initialized. called when the attribute is initialized.
Documenting your Custom Field
-----------------------------
As always, you should document your field type, so users will know what it is.
The best way to do this is to simply provide a docstring for it. This will
automatically be picked up by ``django.contrib.admindocs``, if you have it
installed, and the first line of it will show up as the field type in the
documentation for any model that uses your field. In the above examples, it
will show up as 'A hand of cards (bridge style)'. Note that if you provide a
more verbose docstring, only the first line will show up in
``django.contrib.admindocs``. The full docstring will, of course, still be
available through ``pydoc`` or the interactive interpreter's ``help()``
function.
Useful methods Useful methods
-------------- --------------

View File

@ -28,7 +28,7 @@ Having trouble? We'd like to help!
.. _archives of the django-users mailing list: http://groups.google.com/group/django-users/ .. _archives of the django-users mailing list: http://groups.google.com/group/django-users/
.. _post a question: http://groups.google.com/group/django-users/ .. _post a question: http://groups.google.com/group/django-users/
.. _#django IRC channel: irc://irc.freenode.net/django .. _#django IRC channel: irc://irc.freenode.net/django
.. _IRC logs: http://oebfare.com/logger/django/ .. _IRC logs: http://botland.oebfare.com/logger/django/
.. _ticket tracker: http://code.djangoproject.com/ .. _ticket tracker: http://code.djangoproject.com/
First steps First steps

View File

@ -802,6 +802,14 @@ test <app or test identifier>
Runs tests for all installed models. See :ref:`topics-testing` for more Runs tests for all installed models. See :ref:`topics-testing` for more
information. information.
--failfast
~~~~~~~~~~
.. versionadded:: 1.2
Use the ``--failfast`` option to stop running tests and report the failure
immediately after a test fails.
testserver <fixture fixture ...> testserver <fixture fixture ...>
-------------------------------- --------------------------------

View File

@ -366,6 +366,36 @@ calls its ``as_table()`` method behind the scenes::
<tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr> <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr> <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
Styling required or erroneous form rows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.2
It's pretty common to style form rows and fields that are required or have
errors. For example, you might want to present required form rows in bold and
highlight errors in red.
The :class:`Form` class has a couple of hooks you can use to add ``class``
attributes to required rows or to rows with errors: simple set the
:attr:`Form.error_css_class` and/or :attr:`Form.required_css_class`
attributes::
class ContactForm(Form):
error_css_class = 'error'
required_css_class = 'required'
# ... and the rest of your fields here
Once you've done that, rows will be given ``"error"`` and/or ``"required"``
classes, as needed. The HTML will look something like::
>>> f = ContactForm(data)
>>> print f.as_table()
<tr class="required"><th><label for="id_subject">Subject:</label> ...
<tr class="required"><th><label for="id_message">Message:</label> ...
<tr class="required error"><th><label for="id_sender">Sender:</label> ...
<tr><th><label for="id_cc_myself">Cc myself:<label> ...
.. _ref-forms-api-configuring-label: .. _ref-forms-api-configuring-label:
Configuring HTML ``<label>`` tags Configuring HTML ``<label>`` tags

View File

@ -253,24 +253,30 @@ handler404
.. data:: handler404 .. data:: handler404
A string representing the full Python import path to the view that should be A callable, or a string representing the full Python import path to the view
called if none of the URL patterns match. that should be called if none of the URL patterns match.
By default, this is ``'django.views.defaults.page_not_found'``. That default By default, this is ``'django.views.defaults.page_not_found'``. That default
value should suffice. value should suffice.
.. versionchanged:: 1.2
Previous versions of Django only accepted strings representing import paths.
handler500 handler500
---------- ----------
.. data:: handler500 .. data:: handler500
A string representing the full Python import path to the view that should be A callable, or a string representing the full Python import path to the view
called in case of server errors. Server errors happen when you have runtime that should be called in case of server errors. Server errors happen when you
errors in view code. have runtime errors in view code.
By default, this is ``'django.views.defaults.server_error'``. That default By default, this is ``'django.views.defaults.server_error'``. That default
value should suffice. value should suffice.
.. versionchanged:: 1.2
Previous versions of Django only accepted strings representing import paths.
include include
------- -------

View File

@ -885,7 +885,7 @@ False
>>> form = formset.forms[0] # this formset only has one form >>> form = formset.forms[0] # this formset only has one form
>>> now = form.fields['date_joined'].initial >>> now = form.fields['date_joined'].initial
>>> print form.as_p() >>> print form.as_p()
<p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /></p> <p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="..." id="initial-membership_set-0-id_membership_set-0-date_joined" /></p>
<p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="1" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p> <p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="1" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p>
# test for validation with callable defaults. Validations rely on hidden fields # test for validation with callable defaults. Validations rely on hidden fields

View File

@ -1157,7 +1157,6 @@ class AdminActionsTest(TestCase):
self.assert_('action-checkbox-column' in response.content, self.assert_('action-checkbox-column' in response.content,
"Expected an action-checkbox-column in response") "Expected an action-checkbox-column in response")
def test_multiple_actions_form(self): def test_multiple_actions_form(self):
""" """
Test that actions come from the form whose submit button was pressed (#10618). Test that actions come from the form whose submit button was pressed (#10618).
@ -1175,6 +1174,35 @@ class AdminActionsTest(TestCase):
self.assertEquals(len(mail.outbox), 1) self.assertEquals(len(mail.outbox), 1)
self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action') self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action')
def test_user_message_on_none_selected(self):
"""
User should see a warning when 'Go' is pressed and no items are selected.
"""
action_data = {
ACTION_CHECKBOX_NAME: [],
'action' : 'delete_selected',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
msg = """Items must be selected in order to perform actions on them. No items have been changed."""
self.assertContains(response, msg)
self.failUnlessEqual(Subscriber.objects.count(), 2)
def test_user_message_on_no_action(self):
"""
User should see a warning when 'Go' is pressed and no action is selected.
"""
action_data = {
ACTION_CHECKBOX_NAME: [1, 2],
'action' : '',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
msg = """No action selected."""
self.assertContains(response, msg)
self.failUnlessEqual(Subscriber.objects.count(), 2)
class TestInlineNotEditable(TestCase): class TestInlineNotEditable(TestCase):
fixtures = ['admin-views-users.xml'] fixtures = ['admin-views-users.xml']

File diff suppressed because it is too large Load Diff

View File

@ -1807,4 +1807,43 @@ True
>>> [f.name for f in form.visible_fields()] >>> [f.name for f in form.visible_fields()]
['artist', 'name'] ['artist', 'name']
# Hidden initial input gets its own unique id ################################
>>> class MyForm(Form):
... field1 = CharField(max_length=50, show_hidden_initial=True)
>>> print MyForm()
<tr><th><label for="id_field1">Field1:</label></th><td><input id="id_field1" type="text" name="field1" maxlength="50" /><input type="hidden" name="initial-field1" id="initial-id_field1" /></td></tr>
# The error_html_class and required_html_class attributes ####################
>>> p = Person({})
>>> p.error_css_class = 'error'
>>> p.required_css_class = 'required'
>>> print p.as_ul()
<li class="required error"><ul class="errorlist"><li>This field is required.</li></ul><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
<li class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool">
<option value="1" selected="selected">Unknown</option>
<option value="2">Yes</option>
<option value="3">No</option>
</select></li>
>>> print p.as_p()
<ul class="errorlist"><li>This field is required.</li></ul>
<p class="required error"><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
<p class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool">
<option value="1" selected="selected">Unknown</option>
<option value="2">Yes</option>
<option value="3">No</option>
</select></p>
>>> print p.as_table()
<tr class="required error"><th><label for="id_name">Name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="name" id="id_name" /></td></tr>
<tr class="required"><th><label for="id_is_cool">Is cool:</label></th><td><select name="is_cool" id="id_is_cool">
<option value="1" selected="selected">Unknown</option>
<option value="2">Yes</option>
<option value="3">No</option>
</select></td></tr>
""" """

View File

@ -60,6 +60,50 @@ ValidationError: [u'Enter a postal code in the format XXX XXX.']
u'' u''
>>> f.clean('') >>> f.clean('')
u'' u''
>>> f.clean('W2S 2H3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('T2W 2H7')
u'T2W 2H7'
>>> f.clean('T2S 2W7')
u'T2S 2W7'
>>> f.clean('Z2S 2H3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('T2Z 2H7')
u'T2Z 2H7'
>>> f.clean('T2S 2Z7')
u'T2S 2Z7'
>>> f.clean('F2S 2H3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('A2S 2D3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('A2I 2R3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('A2I 2R3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('A2Q 2R3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('U2B 2R3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
>>> f.clean('O2B 2R3')
Traceback (most recent call last):
...
ValidationError: [u'Enter a postal code in the format XXX XXX.']
# CAPhoneNumberField ########################################################## # CAPhoneNumberField ##########################################################

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from extra import tests as extra_tests from extra import tests as extra_tests
from fields import tests as fields_tests
from forms import tests as form_tests from forms import tests as form_tests
from error_messages import tests as custom_error_message_tests from error_messages import tests as custom_error_message_tests
from localflavor.ar import tests as localflavor_ar_tests from localflavor.ar import tests as localflavor_ar_tests
@ -32,9 +31,10 @@ from widgets import tests as widgets_tests
from formsets import tests as formset_tests from formsets import tests as formset_tests
from media import media_tests from media import media_tests
from fields import FieldsTests
__test__ = { __test__ = {
'extra_tests': extra_tests, 'extra_tests': extra_tests,
'fields_tests': fields_tests,
'form_tests': form_tests, 'form_tests': form_tests,
'custom_error_message_tests': custom_error_message_tests, 'custom_error_message_tests': custom_error_message_tests,
'localflavor_ar_tests': localflavor_ar_tests, 'localflavor_ar_tests': localflavor_ar_tests,

View File

@ -0,0 +1,14 @@
from django.forms import ModelForm
from models import Place
class PlaceForm(ModelForm):
"""docstring for PlaceForm"""
class Meta:
model = Place
from django.forms import ModelForm
from models import Place
class PlaceForm(ModelForm):
"""docstring for PlaceForm"""
class Meta:
model = Place

View File

@ -0,0 +1,16 @@
from django.db import models
from django.contrib.localflavor.us.models import USStateField
class Place(models.Model):
state = USStateField(blank=True)
state_req = USStateField()
state_default = USStateField(default="CA", blank=True)
name = models.CharField(max_length=20)
from django.db import models
from django.contrib.localflavor.us.models import USStateField
class Place(models.Model):
state = USStateField(blank=True)
state_req = USStateField()
state_default = USStateField(default="CA", blank=True)
name = models.CharField(max_length=20)

View File

@ -0,0 +1,166 @@
from django.test import TestCase
from models import Place
from forms import PlaceForm
class USLocalflavorTests(TestCase):
def setUp(self):
self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'})
def test_get_display_methods(self):
"""Test that the get_*_display() methods are added to the model instances."""
place = self.form.save()
self.assertEqual(place.get_state_display(), 'Georgia')
self.assertEqual(place.get_state_req_display(), 'North Carolina')
def test_required(self):
"""Test that required USStateFields throw appropriate errors."""
form = PlaceForm({'state':'GA', 'name':'Place in GA'})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['state_req'], [u'This field is required.'])
def test_field_blank_option(self):
"""Test that the empty option is there."""
state_select_html = """\
<select name="state" id="id_state">
<option value="">---------</option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AS">American Samoa</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="DC">District of Columbia</option>
<option value="FL">Florida</option>
<option value="GA" selected="selected">Georgia</option>
<option value="GU">Guam</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>
<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="MP">Northern Mariana Islands</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="PR">Puerto Rico</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VI">Virgin Islands</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>"""
self.assertEqual(str(self.form['state']), state_select_html)
from django.test import TestCase
from models import Place
from forms import PlaceForm
class USLocalflavorTests(TestCase):
def setUp(self):
self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'})
def test_get_display_methods(self):
"""Test that the get_*_display() methods are added to the model instances."""
place = self.form.save()
self.assertEqual(place.get_state_display(), 'Georgia')
self.assertEqual(place.get_state_req_display(), 'North Carolina')
def test_required(self):
"""Test that required USStateFields throw appropriate errors."""
form = PlaceForm({'state':'GA', 'name':'Place in GA'})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['state_req'], [u'This field is required.'])
def test_field_blank_option(self):
"""Test that the empty option is there."""
state_select_html = """\
<select name="state" id="id_state">
<option value="">---------</option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AS">American Samoa</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="DC">District of Columbia</option>
<option value="FL">Florida</option>
<option value="GA" selected="selected">Georgia</option>
<option value="GU">Guam</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>
<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="MP">Northern Mariana Islands</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="PR">Puerto Rico</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VI">Virgin Islands</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>"""
self.assertEqual(str(self.form['state']), state_select_html)

View File

@ -100,4 +100,16 @@ class CustomFieldSaveTests(TestCase):
# It's enough that the form saves without error -- the custom save routine will # It's enough that the form saves without error -- the custom save routine will
# generate an AssertionError if it is called more than once during save. # generate an AssertionError if it is called more than once during save.
form = CFFForm(data = {'f': None}) form = CFFForm(data = {'f': None})
form.save() form.save()
class ModelChoiceIteratorTests(TestCase):
def test_len(self):
class Form(forms.ModelForm):
class Meta:
model = Article
fields = ["publications"]
Publication.objects.create(title="Pravda",
date_published=date(1991, 8, 22))
f = Form()
self.assertEqual(len(f.fields["publications"].choices), 1)

View File

@ -0,0 +1,17 @@
import unittest
from django.conf import settings
class SettingsTests(unittest.TestCase):
#
# Regression tests for #10130: deleting settings.
#
def test_settings_delete(self):
settings.TEST = 'test'
self.assertEqual('test', settings.TEST)
del settings.TEST
self.assertRaises(AttributeError, getattr, settings, 'TEST')
def test_settings_delete_wrapped(self):
self.assertRaises(TypeError, delattr, settings, '_wrapped')

View File

@ -120,13 +120,19 @@ def get_filter_tests():
# Notice that escaping is applied *after* any filters, so the string # Notice that escaping is applied *after* any filters, so the string
# formatting here only needs to deal with pre-escaped characters. # formatting here only needs to deal with pre-escaped characters.
'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."), 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}',
'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, u". a&lt;b. . a<b."), {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."),
'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")},
u". a&lt;b. . a<b."),
# XXX No test for "title" filter; needs an actual object. # Test the title filter
'filter-title1' : ('{{ a|title }}', {'a' : 'JOE\'S CRAB SHACK'}, u'Joe&#39;s Crab Shack'),
'filter-title2' : ('{{ a|title }}', {'a' : '555 WEST 53RD STREET'}, u'555 West 53rd Street'),
'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, u"alpha & ... alpha &amp; ..."), 'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}',
'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, u"alpha &amp; ... alpha &amp; ..."), {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, u"alpha & ... alpha &amp; ..."),
'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}',
{"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, u"alpha &amp; ... alpha &amp; ..."),
# The "upper" filter messes up entities (which are case-sensitive), # The "upper" filter messes up entities (which are case-sensitive),
# so it's not safe for non-escaping purposes. # so it's not safe for non-escaping purposes.

View File

@ -0,0 +1,29 @@
"""
Tests for django test runner
"""
import StringIO
import unittest
import django
from django.test import TestCase, TransactionTestCase, simple
class DjangoTestRunnerTests(TestCase):
def test_failfast(self):
class MockTestOne(TransactionTestCase):
def runTest(self):
assert False
class MockTestTwo(TransactionTestCase):
def runTest(self):
assert False
suite = unittest.TestSuite([MockTestOne(), MockTestTwo()])
mock_stream = StringIO.StringIO()
dtr = simple.DjangoTestRunner(verbosity=0, failfast=False, stream=mock_stream)
result = dtr.run(suite)
self.assertEqual(2, result.testsRun)
self.assertEqual(2, len(result.failures))
dtr = simple.DjangoTestRunner(verbosity=0, failfast=True, stream=mock_stream)
result = dtr.run(suite)
self.assertEqual(1, result.testsRun)
self.assertEqual(1, len(result.failures))

View File

@ -244,7 +244,6 @@ class NamespaceTests(TestCase):
self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1')) self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1')) self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
class RequestURLconfTests(TestCase): class RequestURLconfTests(TestCase):
def setUp(self): def setUp(self):
self.root_urlconf = settings.ROOT_URLCONF self.root_urlconf = settings.ROOT_URLCONF
@ -276,3 +275,25 @@ class RequestURLconfTests(TestCase):
response = self.client.get('/second_test/') response = self.client.get('/second_test/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'outer:,inner:/second_test/') self.assertEqual(response.content, 'outer:,inner:/second_test/')
class ErrorHandlerResolutionTests(TestCase):
"""Tests for handler404 and handler500"""
def setUp(self):
from django.core.urlresolvers import RegexURLResolver
urlconf = 'regressiontests.urlpatterns_reverse.urls_error_handlers'
urlconf_callables = 'regressiontests.urlpatterns_reverse.urls_error_handlers_callables'
self.resolver = RegexURLResolver(r'^$', urlconf)
self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables)
def test_named_handlers(self):
from views import empty_view
handler = (empty_view, {})
self.assertEqual(self.resolver.resolve404(), handler)
self.assertEqual(self.resolver.resolve500(), handler)
def test_callable_handers(self):
from views import empty_view
handler = (empty_view, {})
self.assertEqual(self.callable_resolver.resolve404(), handler)
self.assertEqual(self.callable_resolver.resolve500(), handler)

View File

@ -0,0 +1,8 @@
# Used by the ErrorHandlerResolutionTests test case.
from django.conf.urls.defaults import patterns
urlpatterns = patterns('')
handler404 = 'regressiontests.urlpatterns_reverse.views.empty_view'
handler500 = 'regressiontests.urlpatterns_reverse.views.empty_view'

View File

@ -0,0 +1,9 @@
# Used by the ErrorHandlerResolutionTests test case.
from django.conf.urls.defaults import patterns
from views import empty_view
urlpatterns = patterns('')
handler404 = empty_view
handler500 = empty_view

View File

@ -86,7 +86,7 @@ class InvalidModelTestCase(unittest.TestCase):
self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected)) self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
self.assert_(not missing, "Missing Errors: " + '\n'.join(missing)) self.assert_(not missing, "Missing Errors: " + '\n'.join(missing))
def django_tests(verbosity, interactive, test_labels): def django_tests(verbosity, interactive, failfast, test_labels):
from django.conf import settings from django.conf import settings
old_installed_apps = settings.INSTALLED_APPS old_installed_apps = settings.INSTALLED_APPS
@ -159,7 +159,8 @@ def django_tests(verbosity, interactive, test_labels):
settings.TEST_RUNNER = 'django.test.simple.run_tests' settings.TEST_RUNNER = 'django.test.simple.run_tests'
test_runner = get_runner(settings) test_runner = get_runner(settings)
failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests) failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, failfast=failfast,
extra_tests=extra_tests)
if failures: if failures:
sys.exit(failures) sys.exit(failures)
@ -181,6 +182,8 @@ if __name__ == "__main__":
help='Verbosity level; 0=minimal output, 1=normal output, 2=all output') help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
parser.add_option('--noinput', action='store_false', dest='interactive', default=True, parser.add_option('--noinput', action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.') help='Tells Django to NOT prompt the user for input of any kind.')
parser.add_option('--failfast', action='store_true', dest='failfast', default=False,
help='Tells Django to stop running the test suite after first failed test.')
parser.add_option('--settings', parser.add_option('--settings',
help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.') help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
options, args = parser.parse_args() options, args = parser.parse_args()
@ -189,4 +192,4 @@ if __name__ == "__main__":
elif "DJANGO_SETTINGS_MODULE" not in os.environ: elif "DJANGO_SETTINGS_MODULE" not in os.environ:
parser.error("DJANGO_SETTINGS_MODULE is not set in the environment. " parser.error("DJANGO_SETTINGS_MODULE is not set in the environment. "
"Set it or use --settings.") "Set it or use --settings.")
django_tests(int(options.verbosity), options.interactive, args) django_tests(int(options.verbosity), options.interactive, options.failfast, args)