Merge remote-tracking branch 'django/master' into t3011

This commit is contained in:
Russell Keith-Magee 2012-09-15 14:31:54 +08:00
commit 57ac6e3d32
42 changed files with 195 additions and 356 deletions

View File

@ -31,6 +31,8 @@ The PRIMARY AUTHORS are (and/or have been):
* Claude Paroz * Claude Paroz
* Anssi Kääriäinen * Anssi Kääriäinen
* Florian Apolloner * Florian Apolloner
* Jeremy Dunck
* Bryan Veloso
More information on the main contributors to Django can be found in More information on the main contributors to Django can be found in
docs/internals/committers.txt. docs/internals/committers.txt.
@ -167,7 +169,6 @@ answer newbie questions, and generally made Django that much better:
dready <wil@mojipage.com> dready <wil@mojipage.com>
Maximillian Dornseif <md@hudora.de> Maximillian Dornseif <md@hudora.de>
Daniel Duan <DaNmarner@gmail.com> Daniel Duan <DaNmarner@gmail.com>
Jeremy Dunck <http://dunck.us/>
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>

View File

@ -24,7 +24,9 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
if test_func(request.user): if test_func(request.user):
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
path = request.build_absolute_uri() path = request.build_absolute_uri()
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL) # urlparse chokes on lazy objects in Python 3, force to str
resolved_login_url = force_str(
resolve_url(login_url or settings.LOGIN_URL))
# If the login url is the same scheme and net location then just # If the login url is the same scheme and net location then just
# use the path as the "next" url. # use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2] login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
@ -33,7 +35,8 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
(not login_netloc or login_netloc == current_netloc)): (not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path() path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
return redirect_to_login(path, login_url, redirect_field_name) return redirect_to_login(
path, resolved_login_url, redirect_field_name)
return _wrapped_view return _wrapped_view
return decorator return decorator

View File

@ -11,7 +11,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, identify_hasher from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.models import get_current_site from django.contrib.sites.models import get_current_site
@ -24,16 +24,16 @@ mask_password = lambda p: "%s%s" % (p[:UNMASKED_DIGITS_TO_SHOW], "*" * max(len(p
class ReadOnlyPasswordHashWidget(forms.Widget): class ReadOnlyPasswordHashWidget(forms.Widget):
def render(self, name, value, attrs): def render(self, name, value, attrs):
encoded = value encoded = value
if not is_password_usable(encoded):
return "None"
final_attrs = self.build_attrs(attrs) final_attrs = self.build_attrs(attrs)
if encoded == '' or encoded == UNUSABLE_PASSWORD:
summary = mark_safe("<strong>%s</strong>" % ugettext("No password set."))
else:
try: try:
hasher = identify_hasher(encoded) hasher = identify_hasher(encoded)
except ValueError: except ValueError:
summary = mark_safe("<strong>Invalid password format or unknown hashing algorithm.</strong>") summary = mark_safe("<strong>%s</strong>" % ugettext(
"Invalid password format or unknown hashing algorithm."))
else: else:
summary = format_html_join('', summary = format_html_join('',
"<strong>{0}</strong>: {1} ", "<strong>{0}</strong>: {1} ",

View File

@ -28,7 +28,13 @@ def reset_hashers(**kwargs):
def is_password_usable(encoded): def is_password_usable(encoded):
return (encoded is not None and encoded != UNUSABLE_PASSWORD) if encoded is None or encoded == UNUSABLE_PASSWORD:
return False
try:
hasher = identify_hasher(encoded)
except ValueError:
return False
return True
def check_password(password, encoded, setter=None, preferred='default'): def check_password(password, encoded, setter=None, preferred='default'):

View File

@ -240,23 +240,29 @@ class UserChangeFormTest(TestCase):
# Just check we can create it # Just check we can create it
form = MyUserForm({}) form = MyUserForm({})
def test_unsuable_password(self):
user = User.objects.get(username='empty_password')
user.set_unusable_password()
user.save()
form = UserChangeForm(instance=user)
self.assertIn(_("No password set."), form.as_table())
def test_bug_17944_empty_password(self): def test_bug_17944_empty_password(self):
user = User.objects.get(username='empty_password') user = User.objects.get(username='empty_password')
form = UserChangeForm(instance=user) form = UserChangeForm(instance=user)
# Just check that no error is raised. self.assertIn(_("No password set."), form.as_table())
form.as_table()
def test_bug_17944_unmanageable_password(self): def test_bug_17944_unmanageable_password(self):
user = User.objects.get(username='unmanageable_password') user = User.objects.get(username='unmanageable_password')
form = UserChangeForm(instance=user) form = UserChangeForm(instance=user)
# Just check that no error is raised. self.assertIn(_("Invalid password format or unknown hashing algorithm."),
form.as_table() form.as_table())
def test_bug_17944_unknown_password_algorithm(self): def test_bug_17944_unknown_password_algorithm(self):
user = User.objects.get(username='unknown_password') user = User.objects.get(username='unknown_password')
form = UserChangeForm(instance=user) form = UserChangeForm(instance=user)
# Just check that no error is raised. self.assertIn(_("Invalid password format or unknown hashing algorithm."),
form.as_table() form.as_table())
@skipIfCustomUser @skipIfCustomUser

View File

@ -100,6 +100,10 @@ class TestUtilsHashPass(unittest.TestCase):
self.assertRaises(ValueError, doit) self.assertRaises(ValueError, doit)
self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash") self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash")
def test_bad_encoded(self):
self.assertFalse(is_password_usable('letmein_badencoded'))
self.assertFalse(is_password_usable(''))
def test_low_level_pkbdf2(self): def test_low_level_pkbdf2(self):
hasher = PBKDF2PasswordHasher() hasher = PBKDF2PasswordHasher()
encoded = hasher.encode('letmein', 'seasalt') encoded = hasher.encode('letmein', 'seasalt')

View File

@ -90,8 +90,6 @@ class BaseSpatialOperations(object):
# For quoting column values, rather than columns. # For quoting column values, rather than columns.
def geo_quote_name(self, name): def geo_quote_name(self, name):
if isinstance(name, six.text_type):
name = name.encode('ascii')
return "'%s'" % name return "'%s'" % name
# GeometryField operations # GeometryField operations

View File

@ -3,20 +3,6 @@ A collection of utility routines and classes used by the spatial
backends. backends.
""" """
from django.utils import six
def gqn(val):
"""
The geographic quote name function; used for quoting tables and
geometries (they use single rather than the double quotes of the
backend quotename function).
"""
if isinstance(val, six.string_types):
if isinstance(val, six.text_type): val = val.encode('ascii')
return "'%s'" % val
else:
return str(val)
class SpatialOperation(object): class SpatialOperation(object):
""" """
Base class for generating spatial SQL. Base class for generating spatial SQL.

View File

@ -181,7 +181,11 @@ class DataSourceTest(unittest.TestCase):
# Making sure the SpatialReference is as expected. # Making sure the SpatialReference is as expected.
if hasattr(source, 'srs_wkt'): if hasattr(source, 'srs_wkt'):
self.assertEqual(source.srs_wkt, g.srs.wkt) self.assertEqual(
source.srs_wkt,
# Depending on lib versions, WGS_84 might be WGS_1984
g.srs.wkt.replace('SPHEROID["WGS_84"', 'SPHEROID["WGS_1984"')
)
def test06_spatial_filter(self): def test06_spatial_filter(self):
"Testing the Layer.spatial_filter property." "Testing the Layer.spatial_filter property."

View File

@ -1,3 +1,4 @@
import json
from binascii import b2a_hex from binascii import b2a_hex
try: try:
from django.utils.six.moves import cPickle as pickle from django.utils.six.moves import cPickle as pickle
@ -111,8 +112,9 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin):
for g in self.geometries.json_geoms: for g in self.geometries.json_geoms:
geom = OGRGeometry(g.wkt) geom = OGRGeometry(g.wkt)
if not hasattr(g, 'not_equal'): if not hasattr(g, 'not_equal'):
self.assertEqual(g.json, geom.json) # Loading jsons to prevent decimal differences
self.assertEqual(g.json, geom.geojson) self.assertEqual(json.loads(g.json), json.loads(geom.json))
self.assertEqual(json.loads(g.json), json.loads(geom.geojson))
self.assertEqual(OGRGeometry(g.wkt), OGRGeometry(geom.json)) self.assertEqual(OGRGeometry(g.wkt), OGRGeometry(geom.json))
def test02_points(self): def test02_points(self):

View File

@ -110,7 +110,7 @@ def geos_version_info():
is a release candidate (and what number release candidate), and the C API is a release candidate (and what number release candidate), and the C API
version. version.
""" """
ver = geos_version() ver = geos_version().decode()
m = version_regex.match(ver) m = version_regex.match(ver)
if not m: raise GEOSException('Could not parse version info string "%s"' % ver) if not m: raise GEOSException('Could not parse version info string "%s"' % ver)
return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')) return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor'))

View File

@ -215,15 +215,18 @@ class ListMixin(object):
"Standard list reverse method" "Standard list reverse method"
self[:] = self[-1::-1] self[:] = self[-1::-1]
def sort(self, cmp=cmp, key=None, reverse=False): def sort(self, cmp=None, key=None, reverse=False):
"Standard list sort method" "Standard list sort method"
if key: if key:
temp = [(key(v),v) for v in self] temp = [(key(v),v) for v in self]
temp.sort(cmp=cmp, key=lambda x: x[0], reverse=reverse) temp.sort(key=lambda x: x[0], reverse=reverse)
self[:] = [v[1] for v in temp] self[:] = [v[1] for v in temp]
else: else:
temp = list(self) temp = list(self)
if cmp is not None:
temp.sort(cmp=cmp, reverse=reverse) temp.sort(cmp=cmp, reverse=reverse)
else:
temp.sort(reverse=reverse)
self[:] = temp self[:] = temp
### Private routines ### ### Private routines ###

View File

@ -16,7 +16,8 @@ test_suites = [
def suite(): def suite():
"Builds a test suite for the GEOS tests." "Builds a test suite for the GEOS tests."
s = TestSuite() s = TestSuite()
map(s.addTest, test_suites) for suite in test_suites:
s.addTest(suite)
return s return s
def run(verbosity=1): def run(verbosity=1):

View File

@ -1,4 +1,5 @@
import ctypes import ctypes
import json
import random import random
from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry,
@ -204,8 +205,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
for g in self.geometries.json_geoms: for g in self.geometries.json_geoms:
geom = GEOSGeometry(g.wkt) geom = GEOSGeometry(g.wkt)
if not hasattr(g, 'not_equal'): if not hasattr(g, 'not_equal'):
self.assertEqual(g.json, geom.json) # Loading jsons to prevent decimal differences
self.assertEqual(g.json, geom.geojson) self.assertEqual(json.loads(g.json), json.loads(geom.json))
self.assertEqual(json.loads(g.json), json.loads(geom.geojson))
self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json)) self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
def test_fromfile(self): def test_fromfile(self):

View File

@ -55,14 +55,14 @@ class ListMixinTest(unittest.TestCase):
def lists_of_len(self, length=None): def lists_of_len(self, length=None):
if length is None: length = self.limit if length is None: length = self.limit
pl = range(length) pl = list(range(length))
return pl, self.listType(pl) return pl, self.listType(pl)
def limits_plus(self, b): def limits_plus(self, b):
return range(-self.limit - b, self.limit + b) return range(-self.limit - b, self.limit + b)
def step_range(self): def step_range(self):
return range(-1 - self.limit, 0) + range(1, 1 + self.limit) return list(range(-1 - self.limit, 0)) + list(range(1, 1 + self.limit))
def test01_getslice(self): def test01_getslice(self):
'Slice retrieval' 'Slice retrieval'
@ -160,13 +160,13 @@ class ListMixinTest(unittest.TestCase):
del pl[i:j] del pl[i:j]
del ul[i:j] del ul[i:j]
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i,j)) self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i,j))
for k in range(-Len - 1,0) + range(1,Len): for k in list(range(-Len - 1, 0)) + list(range(1, Len)):
pl, ul = self.lists_of_len(Len) pl, ul = self.lists_of_len(Len)
del pl[i:j:k] del pl[i:j:k]
del ul[i:j:k] del ul[i:j:k]
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i,j,k)) self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i,j,k))
for k in range(-Len - 1,0) + range(1,Len): for k in list(range(-Len - 1, 0)) + list(range(1, Len)):
pl, ul = self.lists_of_len(Len) pl, ul = self.lists_of_len(Len)
del pl[:i:k] del pl[:i:k]
del ul[:i:k] del ul[:i:k]
@ -177,7 +177,7 @@ class ListMixinTest(unittest.TestCase):
del ul[i::k] del ul[i::k]
self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i,k)) self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i,k))
for k in range(-Len - 1,0) + range(1,Len): for k in list(range(-Len - 1, 0)) + list(range(1, Len)):
pl, ul = self.lists_of_len(Len) pl, ul = self.lists_of_len(Len)
del pl[::k] del pl[::k]
del ul[::k] del ul[::k]
@ -320,7 +320,7 @@ class ListMixinTest(unittest.TestCase):
pl.sort() pl.sort()
ul.sort() ul.sort()
self.assertEqual(pl[:], ul[:], 'sort') self.assertEqual(pl[:], ul[:], 'sort')
mid = pl[len(pl) / 2] mid = pl[len(pl) // 2]
pl.sort(key=lambda x: (mid-x)**2) pl.sort(key=lambda x: (mid-x)**2)
ul.sort(key=lambda x: (mid-x)**2) ul.sort(key=lambda x: (mid-x)**2)
self.assertEqual(pl[:], ul[:], 'sort w/ key') self.assertEqual(pl[:], ul[:], 'sort w/ key')
@ -330,7 +330,7 @@ class ListMixinTest(unittest.TestCase):
pl.sort(reverse=True) pl.sort(reverse=True)
ul.sort(reverse=True) ul.sort(reverse=True)
self.assertEqual(pl[:], ul[:], 'sort w/ reverse') self.assertEqual(pl[:], ul[:], 'sort w/ reverse')
mid = pl[len(pl) / 2] mid = pl[len(pl) // 2]
pl.sort(key=lambda x: (mid-x)**2) pl.sort(key=lambda x: (mid-x)**2)
ul.sort(key=lambda x: (mid-x)**2) ul.sort(key=lambda x: (mid-x)**2)
self.assertEqual(pl[:], ul[:], 'sort w/ key') self.assertEqual(pl[:], ul[:], 'sort w/ key')
@ -338,7 +338,7 @@ class ListMixinTest(unittest.TestCase):
def test_12_arithmetic(self): def test_12_arithmetic(self):
'Arithmetic' 'Arithmetic'
pl, ul = self.lists_of_len() pl, ul = self.lists_of_len()
al = range(10,14) al = list(range(10,14))
self.assertEqual(list(pl + al), list(ul + al), 'add') self.assertEqual(list(pl + al), list(ul + al), 'add')
self.assertEqual(type(ul), type(ul + al), 'type of add result') self.assertEqual(type(ul), type(ul + al), 'type of add result')
self.assertEqual(list(al + pl), list(al + ul), 'radd') self.assertEqual(list(al + pl), list(al + ul), 'radd')

View File

@ -8,9 +8,11 @@ from django.utils import unittest
test_srs = ({'srid' : 4326, test_srs = ({'srid' : 4326,
'auth_name' : ('EPSG', True), 'auth_name' : ('EPSG', True),
'auth_srid' : 4326, 'auth_srid' : 4326,
'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]', # Only the beginning, because there are differences depending on installed libs
'srtext14' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]', 'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"',
'proj4' : '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ', 'proj4' : ['+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ',
# +ellps=WGS84 has been removed in the 4326 proj string in proj-4.8
'+proj=longlat +datum=WGS84 +no_defs '],
'spheroid' : 'WGS 84', 'name' : 'WGS 84', 'spheroid' : 'WGS 84', 'name' : 'WGS 84',
'geographic' : True, 'projected' : False, 'spatialite' : True, 'geographic' : True, 'projected' : False, 'spatialite' : True,
'ellipsoid' : (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only) 'ellipsoid' : (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
@ -19,9 +21,9 @@ test_srs = ({'srid' : 4326,
{'srid' : 32140, {'srid' : 32140,
'auth_name' : ('EPSG', False), 'auth_name' : ('EPSG', False),
'auth_srid' : 32140, 'auth_srid' : 32140,
'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32140"]]', 'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
'srtext14': 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],AUTHORITY["EPSG","32140"],AXIS["X",EAST],AXIS["Y",NORTH]]', 'proj4' : ['+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ',
'proj4' : '+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ', '+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs '],
'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central', 'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central',
'geographic' : False, 'projected' : True, 'spatialite' : False, 'geographic' : False, 'projected' : True, 'spatialite' : False,
'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only) 'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
@ -51,17 +53,12 @@ class SpatialRefSysTest(unittest.TestCase):
# No proj.4 and different srtext on oracle backends :( # No proj.4 and different srtext on oracle backends :(
if postgis: if postgis:
if connection.ops.spatial_version >= (1, 4, 0): self.assertTrue(srs.wkt.startswith(sd['srtext']))
srtext = sd['srtext14'] self.assertTrue(srs.proj4text in sd['proj4'])
else:
srtext = sd['srtext']
self.assertEqual(srtext, srs.wkt)
self.assertEqual(sd['proj4'], srs.proj4text)
@no_mysql @no_mysql
def test02_osr(self): def test02_osr(self):
"Testing getting OSR objects from SpatialRefSys model objects." "Testing getting OSR objects from SpatialRefSys model objects."
from django.contrib.gis.gdal import GDAL_VERSION
for sd in test_srs: for sd in test_srs:
sr = SpatialRefSys.objects.get(srid=sd['srid']) sr = SpatialRefSys.objects.get(srid=sd['srid'])
self.assertEqual(True, sr.spheroid.startswith(sd['spheroid'])) self.assertEqual(True, sr.spheroid.startswith(sd['spheroid']))
@ -76,15 +73,10 @@ class SpatialRefSysTest(unittest.TestCase):
# Testing the SpatialReference object directly. # Testing the SpatialReference object directly.
if postgis or spatialite: if postgis or spatialite:
srs = sr.srs srs = sr.srs
if GDAL_VERSION <= (1, 8): self.assertTrue(srs.proj4 in sd['proj4'])
self.assertEqual(sd['proj4'], srs.proj4)
# No `srtext` field in the `spatial_ref_sys` table in SpatiaLite # No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
if not spatialite: if not spatialite:
if connection.ops.spatial_version >= (1, 4, 0): self.assertTrue(srs.wkt.startswith(sd['srtext']))
srtext = sd['srtext14']
else:
srtext = sd['srtext']
self.assertEqual(srtext, srs.wkt)
@no_mysql @no_mysql
def test03_ellipsoid(self): def test03_ellipsoid(self):

View File

@ -222,7 +222,6 @@ class WSGIHandler(base.BaseHandler):
set_script_prefix(base.get_script_name(environ)) set_script_prefix(base.get_script_name(environ))
signals.request_started.send(sender=self.__class__) signals.request_started.send(sender=self.__class__)
try:
try: try:
request = self.request_class(environ) request = self.request_class(environ)
except UnicodeDecodeError: except UnicodeDecodeError:

View File

@ -1,4 +1,5 @@
from optparse import make_option from optparse import make_option
from datetime import datetime
import os import os
import re import re
import sys import sys
@ -90,10 +91,12 @@ class Command(BaseCommand):
self.stdout.write("Validating models...\n\n") self.stdout.write("Validating models...\n\n")
self.validate(display_num_errors=True) self.validate(display_num_errors=True)
self.stdout.write(( self.stdout.write((
"%(started_at)s\n"
"Django version %(version)s, using settings %(settings)r\n" "Django version %(version)s, using settings %(settings)r\n"
"Development server is running at http://%(addr)s:%(port)s/\n" "Development server is running at http://%(addr)s:%(port)s/\n"
"Quit the server with %(quit_command)s.\n" "Quit the server with %(quit_command)s.\n"
) % { ) % {
"started_at": datetime.now().strftime('%B %d, %Y - %X'),
"version": self.get_version(), "version": self.get_version(),
"settings": settings.SETTINGS_MODULE, "settings": settings.SETTINGS_MODULE,
"addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr, "addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr,

View File

@ -37,6 +37,7 @@ from django.db.backends.mysql.client import DatabaseClient
from django.db.backends.mysql.creation import DatabaseCreation from django.db.backends.mysql.creation import DatabaseCreation
from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation from django.db.backends.mysql.validation import DatabaseValidation
from django.utils.encoding import force_str
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeBytes, SafeText from django.utils.safestring import SafeBytes, SafeText
from django.utils import six from django.utils import six
@ -390,7 +391,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if settings_dict['NAME']: if settings_dict['NAME']:
kwargs['db'] = settings_dict['NAME'] kwargs['db'] = settings_dict['NAME']
if settings_dict['PASSWORD']: if settings_dict['PASSWORD']:
kwargs['passwd'] = settings_dict['PASSWORD'] kwargs['passwd'] = force_str(settings_dict['PASSWORD'])
if settings_dict['HOST'].startswith('/'): if settings_dict['HOST'].startswith('/'):
kwargs['unix_socket'] = settings_dict['HOST'] kwargs['unix_socket'] = settings_dict['HOST']
elif settings_dict['HOST']: elif settings_dict['HOST']:

View File

@ -13,6 +13,7 @@ from django.db.backends.postgresql_psycopg2.client import DatabaseClient
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.version import get_version
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
from django.utils.encoding import force_str
from django.utils.log import getLogger from django.utils.log import getLogger
from django.utils.safestring import SafeText, SafeBytes from django.utils.safestring import SafeText, SafeBytes
from django.utils import six from django.utils import six
@ -172,7 +173,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if settings_dict['USER']: if settings_dict['USER']:
conn_params['user'] = settings_dict['USER'] conn_params['user'] = settings_dict['USER']
if settings_dict['PASSWORD']: if settings_dict['PASSWORD']:
conn_params['password'] = settings_dict['PASSWORD'] conn_params['password'] = force_str(settings_dict['PASSWORD'])
if settings_dict['HOST']: if settings_dict['HOST']:
conn_params['host'] = settings_dict['HOST'] conn_params['host'] = settings_dict['HOST']
if settings_dict['PORT']: if settings_dict['PORT']:

View File

@ -199,7 +199,7 @@ class CharField(Field):
def widget_attrs(self, widget): def widget_attrs(self, widget):
attrs = super(CharField, self).widget_attrs(widget) attrs = super(CharField, self).widget_attrs(widget)
if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): if self.max_length is not None and isinstance(widget, TextInput):
# The HTML attribute is maxlength, not max_length. # The HTML attribute is maxlength, not max_length.
attrs.update({'maxlength': str(self.max_length)}) attrs.update({'maxlength': str(self.max_length)})
return attrs return attrs

View File

@ -260,10 +260,17 @@ class Input(Widget):
final_attrs['value'] = force_text(self._format_value(value)) final_attrs['value'] = force_text(self._format_value(value))
return format_html('<input{0} />', flatatt(final_attrs)) return format_html('<input{0} />', flatatt(final_attrs))
class TextInput(Input): class TextInput(Input):
input_type = 'text' input_type = 'text'
class PasswordInput(Input): def __init__(self, attrs=None):
if attrs is not None:
self.input_type = attrs.pop('type', self.input_type)
super(TextInput, self).__init__(attrs)
class PasswordInput(TextInput):
input_type = 'password' input_type = 'password'
def __init__(self, attrs=None, render_value=False): def __init__(self, attrs=None, render_value=False):
@ -400,9 +407,8 @@ class Textarea(Widget):
flatatt(final_attrs), flatatt(final_attrs),
force_text(value)) force_text(value))
class DateInput(Input):
input_type = 'text'
class DateInput(TextInput):
def __init__(self, attrs=None, format=None): def __init__(self, attrs=None, format=None):
super(DateInput, self).__init__(attrs) super(DateInput, self).__init__(attrs)
if format: if format:
@ -431,9 +437,8 @@ class DateInput(Input):
pass pass
return super(DateInput, self)._has_changed(self._format_value(initial), data) return super(DateInput, self)._has_changed(self._format_value(initial), data)
class DateTimeInput(Input):
input_type = 'text'
class DateTimeInput(TextInput):
def __init__(self, attrs=None, format=None): def __init__(self, attrs=None, format=None):
super(DateTimeInput, self).__init__(attrs) super(DateTimeInput, self).__init__(attrs)
if format: if format:
@ -462,9 +467,8 @@ class DateTimeInput(Input):
pass pass
return super(DateTimeInput, self)._has_changed(self._format_value(initial), data) return super(DateTimeInput, self)._has_changed(self._format_value(initial), data)
class TimeInput(Input):
input_type = 'text'
class TimeInput(TextInput):
def __init__(self, attrs=None, format=None): def __init__(self, attrs=None, format=None):
super(TimeInput, self).__init__(attrs) super(TimeInput, self).__init__(attrs)
if format: if format:

View File

@ -105,7 +105,7 @@ class CsrfViewMiddleware(object):
if getattr(callback, 'csrf_exempt', False): if getattr(callback, 'csrf_exempt', False):
return None return None
# Assume that anything not defined as 'safe' by RC2616 needs protection # Assume that anything not defined as 'safe' by RFC2616 needs protection
if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if getattr(request, '_dont_enforce_csrf_checks', False): if getattr(request, '_dont_enforce_csrf_checks', False):
# Mechanism to turn off CSRF checks for test suite. # Mechanism to turn off CSRF checks for test suite.

View File

@ -531,11 +531,9 @@ def cycle(parser, token):
The optional flag "silent" can be used to prevent the cycle declaration The optional flag "silent" can be used to prevent the cycle declaration
from returning any value:: from returning any value::
{% cycle 'row1' 'row2' as rowcolors silent %}{# no value here #}
{% for o in some_list %} {% for o in some_list %}
<tr class="{% cycle rowcolors %}">{# first value will be "row1" #} {% cycle 'row1' 'row2' as rowcolors silent %}
... <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr>
</tr>
{% endfor %} {% endfor %}
""" """

View File

@ -5,8 +5,7 @@ import sys
current_version = sys.version_info current_version = sys.version_info
use_workaround = ( use_workaround = (
(current_version < (2, 6, 8)) or (current_version < (2, 7, 3)) or
(current_version >= (2, 7) and current_version < (2, 7, 3)) or
(current_version >= (3, 0) and current_version < (3, 2, 3)) (current_version >= (3, 0) and current_version < (3, 2, 3))
) )

View File

@ -28,14 +28,3 @@ Indices, glossary and tables
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`glossary` * :ref:`glossary`
Deprecated/obsolete documentation
=================================
The following documentation covers features that have been deprecated or that
have been replaced in newer versions of Django.
.. toctree::
:maxdepth: 2
obsolete/index

View File

@ -91,8 +91,7 @@ The dynamically-generated admin site is ugly! How can I change it?
We like it, but if you don't agree, you can modify the admin site's We like it, but if you don't agree, you can modify the admin site's
presentation by editing the CSS stylesheet and/or associated image files. The presentation by editing the CSS stylesheet and/or associated image files. The
site is built using semantic HTML and plenty of CSS hooks, so any changes you'd site is built using semantic HTML and plenty of CSS hooks, so any changes you'd
like to make should be possible by editing the stylesheet. We've got a like to make should be possible by editing the stylesheet.
:doc:`guide to the CSS used in the admin </obsolete/admin-css>` to get you started.
What browsers are supported for using the admin? What browsers are supported for using the admin?
------------------------------------------------ ------------------------------------------------

View File

@ -5,7 +5,7 @@
Django documentation Django documentation
==================== ====================
.. rubric:: Everything you need to know about Django (and then some). .. rubric:: Everything you need to know about Django.
Getting help Getting help
============ ============

View File

@ -379,6 +379,34 @@ Florian Apolloner
.. _Graz University of Technology: http://tugraz.at/ .. _Graz University of Technology: http://tugraz.at/
.. _Ubuntuusers webteam: http://wiki.ubuntuusers.de/ubuntuusers/Webteam .. _Ubuntuusers webteam: http://wiki.ubuntuusers.de/ubuntuusers/Webteam
Jeremy Dunck
Jeremy was rescued from corporate IT drudgery by Free Software and, in part,
Django. Many of Jeremy's interests center around access to information.
Jeremy was the lead developer of Pegasus News, one of the first uses of
Django outside World Online, and has since joined Votizen, a startup intent
on reducing the influence of money in politics.
He serves as DSF Secretary, organizes and helps organize sprints, cares
about the health and equity of the Django community. He has gone an
embarrassingly long time without a working blog.
Jeremy lives in Mountain View, CA, USA.
`Bryan Veloso`_
Bryan found Django 0.96 through a fellow designer who was evangelizing
its use. It was his first foray outside of the land that was PHP-based
templating. Although he has only ever used Django for personal projects,
it is the very reason he considers himself a designer/developer
hybrid and is working to further design within the Django community.
Bryan works as a designer at GitHub by day, and masquerades as a `vlogger`_
and `shoutcaster`_ in the after-hours. Bryan lives in Los Angeles, CA, USA.
.. _bryan veloso: http://avalonstar.com/
.. _vlogger: http://youtube.com/bryanveloso/
.. _shoutcaster: http://twitch.tv/vlogalonstar/
Specialists Specialists
----------- -----------
@ -403,16 +431,6 @@ Ian Kelly
Matt Boersma Matt Boersma
Matt is also responsible for Django's Oracle support. Matt is also responsible for Django's Oracle support.
Jeremy Dunck
Jeremy is the lead developer of Pegasus News, a personalized local site based
in Dallas, Texas. An early contributor to Greasemonkey and Django, he sees
technology as a tool for communication and access to knowledge.
Jeremy helped kick off GeoDjango development, and is mostly responsible for
the serious speed improvements that signals received in Django 1.0.
Jeremy lives in Dallas, Texas, USA.
`Simon Meers`_ `Simon Meers`_
Simon discovered Django 0.96 during his Computer Science PhD research and Simon discovered Django 0.96 during his Computer Science PhD research and
has been developing with it full-time ever since. His core code has been developing with it full-time ever since. His core code

View File

@ -67,8 +67,7 @@ different needs:
whathaveyou. whathaveyou.
* Finally, there's some "specialized" documentation not usually relevant to * Finally, there's some "specialized" documentation not usually relevant to
most developers. This includes the :doc:`release notes </releases/index>`, most developers. This includes the :doc:`release notes </releases/index>` and
:doc:`documentation of obsolete features </obsolete/index>`,
:doc:`internals documentation </internals/index>` for those who want to add :doc:`internals documentation </internals/index>` for those who want to add
code to Django itself, and a :doc:`few other things that simply don't fit code to Django itself, and a :doc:`few other things that simply don't fit
elsewhere </misc/index>`. elsewhere </misc/index>`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,186 +0,0 @@
======================================
Customizing the Django admin interface
======================================
.. warning::
The design of the admin has changed somewhat since this document was
written, and parts may not apply any more. This document is no longer
maintained since an official API for customizing the Django admin interface
is in development.
Django's dynamic admin interface gives you a fully-functional admin for free
with no hand-coding required. The dynamic admin is designed to be
production-ready, not just a starting point, so you can use it as-is on a real
site. While the underlying format of the admin pages is built in to Django, you
can customize the look and feel by editing the admin stylesheet and images.
Here's a quick and dirty overview some of the main styles and classes used in
the Django admin CSS.
Modules
=======
The ``.module`` class is a basic building block for grouping content in the
admin. It's generally applied to a ``div`` or a ``fieldset``. It wraps the content
group in a box and applies certain styles to the elements within. An ``h2``
within a ``div.module`` will align to the top of the ``div`` as a header for the
whole group.
.. image:: _images/module.png
:alt: Example use of module class on admin homepage
Column Types
============
.. note::
All admin pages (except the dashboard) are fluid-width. All fixed-width
classes from previous Django versions have been removed.
The base template for each admin page has a block that defines the column
structure for the page. This sets a class on the page content area
(``div#content``) so everything on the page knows how wide it should be. There
are three column types available.
colM
This is the default column setting for all pages. The "M" stands for "main".
Assumes that all content on the page is in one main column
(``div#content-main``).
colMS
This is for pages with one main column and a sidebar on the right. The "S"
stands for "sidebar". Assumes that main content is in ``div#content-main``
and sidebar content is in ``div#content-related``. This is used on the main
admin page.
colSM
Same as above, with the sidebar on the left. The source order of the columns
doesn't matter.
For instance, you could stick this in a template to make a two-column page with
the sidebar on the right:
.. code-block:: html+django
{% block coltype %}colMS{% endblock %}
Text Styles
===========
Font Sizes
----------
Most HTML elements (headers, lists, etc.) have base font sizes in the stylesheet
based on context. There are three classes are available for forcing text to a
certain size in any context.
small
11px
tiny
10px
mini
9px (use sparingly)
Font Styles and Alignment
-------------------------
There are also a few styles for styling text.
.quiet
Sets font color to light gray. Good for side notes in instructions. Combine
with ``.small`` or ``.tiny`` for sheer excitement.
.help
This is a custom class for blocks of inline help text explaining the
function of form elements. It makes text smaller and gray, and when applied
to ``p`` elements within ``.form-row`` elements (see Form Styles below),
it will offset the text to align with the form field. Use this for help
text, instead of ``small quiet``. It works on other elements, but try to
put the class on a ``p`` whenever you can.
.align-left
It aligns the text left. Only works on block elements containing inline
elements.
.align-right
Are you paying attention?
.nowrap
Keeps text and inline objects from wrapping. Comes in handy for table
headers you want to stay on one line.
Floats and Clears
-----------------
float-left
floats left
float-right
floats right
clear
clears all
Object Tools
============
Certain actions which apply directly to an object are used in form and
changelist pages. These appear in a "toolbar" row above the form or changelist,
to the right of the page. The tools are wrapped in a ``ul`` with the class
``object-tools``. There are two custom tool types which can be defined with an
additional class on the ``a`` for that tool. These are ``.addlink`` and
``.viewsitelink``.
Example from a changelist page:
.. code-block:: html+django
<ul class="object-tools">
<li><a href="/stories/add/" class="addlink">Add redirect</a></li>
</ul>
.. image:: _images/objecttools_01.png
:alt: Object tools on a changelist page
and from a form page:
.. code-block:: html+django
<ul class="object-tools">
<li><a href="/history/303/152383/">History</a></li>
<li><a href="/r/303/152383/" class="viewsitelink">View on site</a></li>
</ul>
.. image:: _images/objecttools_02.png
:alt: Object tools on a form page
Form Styles
===========
Fieldsets
---------
Admin forms are broken up into groups by ``fieldset`` elements. Each form fieldset
should have a class ``.module``. Each fieldset should have a header ``h2`` within the
fieldset at the top (except the first group in the form, and in some cases where the
group of fields doesn't have a logical label).
Each fieldset can also take extra classes in addition to ``.module`` to apply
appropriate formatting to the group of fields.
.aligned
This will align the labels and inputs side by side on the same line.
.wide
Used in combination with ``.aligned`` to widen the space available for the
labels.
Form Rows
---------
Each row of the form (within the ``fieldset``) should be enclosed in a ``div``
with class ``form-row``. If the field in the row is required, a class of
``required`` should also be added to the ``div.form-row``.
.. image:: _images/formrow.png
:alt: Example use of form-row class
Labels
------
Form labels should always precede the field, except in the case
of checkboxes and radio buttons, where the ``input`` should come first. Any
explanation or help text should follow the ``label`` in a ``p`` with class
``.help``.

View File

@ -1,12 +0,0 @@
Deprecated/obsolete documentation
=================================
These documents cover features that have been deprecated or that have been
replaced in newer versions of Django. They're preserved here for folks using old
versions of Django or those still using deprecated APIs. No new code based on
these APIs should be written.
.. toctree::
:maxdepth: 1
admin-css

View File

@ -126,8 +126,9 @@ provided for each widget will be rendered exactly the same::
On a real Web page, you probably don't want every widget to look the same. You On a real Web page, you probably don't want every widget to look the same. You
might want a larger input element for the comment, and you might want the might want a larger input element for the comment, and you might want the
'name' widget to have some special CSS class. To do this, you use the 'name' widget to have some special CSS class. It is also possible to specify
:attr:`Widget.attrs` argument when creating the widget: the 'type' attribute to take advantage of the new HTML5 input types. To do
this, you use the :attr:`Widget.attrs` argument when creating the widget:
For example:: For example::
@ -245,7 +246,7 @@ commonly used groups of widgets:
Date input as a simple text box: ``<input type='text' ...>`` Date input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes same arguments as :class:`TextInput`, with one more optional argument:
.. attribute:: DateInput.format .. attribute:: DateInput.format
@ -262,7 +263,7 @@ commonly used groups of widgets:
Date/time input as a simple text box: ``<input type='text' ...>`` Date/time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes same arguments as :class:`TextInput`, with one more optional argument:
.. attribute:: DateTimeInput.format .. attribute:: DateTimeInput.format
@ -279,7 +280,7 @@ commonly used groups of widgets:
Time input as a simple text box: ``<input type='text' ...>`` Time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes same arguments as :class:`TextInput`, with one more optional argument:
.. attribute:: TimeInput.format .. attribute:: TimeInput.format

View File

@ -14,6 +14,7 @@ up to and including the new version.
Final releases Final releases
============== ==============
.. _development_release_notes:
1.5 release 1.5 release
----------- -----------

View File

@ -257,15 +257,14 @@ Installing the development version
If you decide to use the latest development version of Django, If you decide to use the latest development version of Django,
you'll want to pay close attention to `the development timeline`_, you'll want to pay close attention to `the development timeline`_,
and you'll want to keep an eye on `the list of and you'll want to keep an eye on the :ref:`release notes for the
backwards-incompatible changes`_. This will help you stay on top upcoming release <development_release_notes>`. This will help you stay
of any new features you might want to use, as well as any changes on top of any new features you might want to use, as well as any changes
you'll need to make to your code when updating your copy of Django. you'll need to make to your code when updating your copy of Django.
(For stable releases, any necessary changes are documented in the (For stable releases, any necessary changes are documented in the
release notes.) release notes.)
.. _the development timeline: https://code.djangoproject.com/timeline .. _the development timeline: https://code.djangoproject.com/timeline
.. _the list of backwards-incompatible changes: https://code.djangoproject.com/wiki/BackwardsIncompatibleChanges
If you'd like to be able to update your Django code occasionally with the If you'd like to be able to update your Django code occasionally with the
latest bug fixes and improvements, follow these instructions: latest bug fixes and improvements, follow these instructions:

View File

@ -401,6 +401,19 @@ class BackendTestCase(TestCase):
self.assertEqual(list(cursor.fetchmany(2)), [('Jane', 'Doe'), ('John', 'Doe')]) self.assertEqual(list(cursor.fetchmany(2)), [('Jane', 'Doe'), ('John', 'Doe')])
self.assertEqual(list(cursor.fetchall()), [('Mary', 'Agnelline'), ('Peter', 'Parker')]) self.assertEqual(list(cursor.fetchall()), [('Mary', 'Agnelline'), ('Peter', 'Parker')])
def test_unicode_password(self):
old_password = connection.settings_dict['PASSWORD']
connection.settings_dict['PASSWORD'] = "françois"
try:
cursor = connection.cursor()
except backend.Database.DatabaseError:
# As password is probably wrong, a database exception is expected
pass
except Exception as e:
self.fail("Unexpected error raised with unicode password: %s" % e)
finally:
connection.settings_dict['PASSWORD'] = old_password
def test_database_operations_helper_class(self): def test_database_operations_helper_class(self):
# Ticket #13630 # Ticket #13630
self.assertTrue(hasattr(connection, 'ops')) self.assertTrue(hasattr(connection, 'ops'))

View File

@ -31,9 +31,9 @@ class FormsWidgetTestCase(TestCase):
self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '<input type="text" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" class="fun" />') self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '<input type="text" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" class="fun" />')
# You can also pass 'attrs' to the constructor: # You can also pass 'attrs' to the constructor:
w = TextInput(attrs={'class': 'fun'}) w = TextInput(attrs={'class': 'fun', 'type': 'email'})
self.assertHTMLEqual(w.render('email', ''), '<input type="text" class="fun" name="email" />') self.assertHTMLEqual(w.render('email', ''), '<input type="email" class="fun" name="email" />')
self.assertHTMLEqual(w.render('email', 'foo@example.com'), '<input type="text" class="fun" value="foo@example.com" name="email" />') self.assertHTMLEqual(w.render('email', 'foo@example.com'), '<input type="email" class="fun" value="foo@example.com" name="email" />')
# 'attrs' passed to render() get precedence over those passed to the constructor: # 'attrs' passed to render() get precedence over those passed to the constructor:
w = TextInput(attrs={'class': 'pretty'}) w = TextInput(attrs={'class': 'pretty'})
@ -915,8 +915,8 @@ beatle J R Ringo False""")
self.assertHTMLEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), '<input type="text" name="date" value="2007-09-17 12:51:00" />') self.assertHTMLEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), '<input type="text" name="date" value="2007-09-17 12:51:00" />')
# Use 'format' to change the way a value is displayed. # Use 'format' to change the way a value is displayed.
w = DateTimeInput(format='%d/%m/%Y %H:%M') w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'})
self.assertHTMLEqual(w.render('date', d), '<input type="text" name="date" value="17/09/2007 12:51" />') self.assertHTMLEqual(w.render('date', d), '<input type="datetime" name="date" value="17/09/2007 12:51" />')
self.assertFalse(w._has_changed(d, '17/09/2007 12:51')) self.assertFalse(w._has_changed(d, '17/09/2007 12:51'))
# Make sure a custom format works with _has_changed. The hidden input will use # Make sure a custom format works with _has_changed. The hidden input will use
@ -938,8 +938,8 @@ beatle J R Ringo False""")
self.assertHTMLEqual(w.render('date', '2007-09-17'), '<input type="text" name="date" value="2007-09-17" />') self.assertHTMLEqual(w.render('date', '2007-09-17'), '<input type="text" name="date" value="2007-09-17" />')
# Use 'format' to change the way a value is displayed. # Use 'format' to change the way a value is displayed.
w = DateInput(format='%d/%m/%Y') w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'})
self.assertHTMLEqual(w.render('date', d), '<input type="text" name="date" value="17/09/2007" />') self.assertHTMLEqual(w.render('date', d), '<input type="date" name="date" value="17/09/2007" />')
self.assertFalse(w._has_changed(d, '17/09/2007')) self.assertFalse(w._has_changed(d, '17/09/2007'))
# Make sure a custom format works with _has_changed. The hidden input will use # Make sure a custom format works with _has_changed. The hidden input will use
@ -963,8 +963,8 @@ beatle J R Ringo False""")
self.assertHTMLEqual(w.render('time', '13:12:11'), '<input type="text" name="time" value="13:12:11" />') self.assertHTMLEqual(w.render('time', '13:12:11'), '<input type="text" name="time" value="13:12:11" />')
# Use 'format' to change the way a value is displayed. # Use 'format' to change the way a value is displayed.
w = TimeInput(format='%H:%M') w = TimeInput(format='%H:%M', attrs={'type': 'time'})
self.assertHTMLEqual(w.render('time', t), '<input type="text" name="time" value="12:51" />') self.assertHTMLEqual(w.render('time', t), '<input type="time" name="time" value="12:51" />')
self.assertFalse(w._has_changed(t, '12:51')) self.assertFalse(w._has_changed(t, '12:51'))
# Make sure a custom format works with _has_changed. The hidden input will use # Make sure a custom format works with _has_changed. The hidden input will use

View File

@ -1,21 +1,26 @@
import os
from django.utils import unittest from django.utils import unittest
from django.utils._os import safe_join from django.utils._os import safe_join
class SafeJoinTests(unittest.TestCase): class SafeJoinTests(unittest.TestCase):
def test_base_path_ends_with_sep(self): def test_base_path_ends_with_sep(self):
drive, path = os.path.splitdrive(safe_join("/abc/", "abc"))
self.assertEqual( self.assertEqual(
safe_join("/abc/", "abc"), path,
"/abc/abc", "{0}abc{0}abc".format(os.path.sep)
) )
def test_root_path(self): def test_root_path(self):
drive, path = os.path.splitdrive(safe_join("/", "path"))
self.assertEqual( self.assertEqual(
safe_join("/", "path"), path,
"/path", "{0}path".format(os.path.sep),
) )
drive, path = os.path.splitdrive(safe_join("/", ""))
self.assertEqual( self.assertEqual(
safe_join("/", ""), path,
"/", os.path.sep,
) )