Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Patch #16774 -- Views can raise DoesNotResolve to make the URL resolver continue resolving. #378

Closed
wants to merge 14 commits into from

9 participants

@meric

This pull request allows views to raise urlresolvers.DoesNotResolve to make the URL resolver to ignore the view and keep resolving the URL.

https://code.djangoproject.com/ticket/16774
https://groups.google.com/forum/#!topic/django-developers/BAk5pGb7zE8/discussion

@charettes

I don't think this check is required anymore since you're already breaking at line 115.

The break at line 115 was part of a nested loop... I added the check back in the next commit, but now it uses the for-loop else operator.

6ba42de

@charettes

No need to assign the exception to e since it's not used.

@charettes

This should be replaced by an else statement.

@charettes

DoesNotResolve should be imported before get_resolver.

@meric

Thanks @charettes!

I've followed your comments and fixed up the code:

a3905d9

https://github.com/meric/django/compare/ticket_16774

@gilsondev

Congratulations!

@timgraham
Owner

It looks like there is a more up-to-date patch on the ticket.

@timgraham timgraham closed this
@alanjds

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 10, 2012
  1. @alex

    Merge pull request #366 from collinanderson/patch-6

    alex authored
    fixed rfc comment typo in middleware/csrf.py
  2. @claudep

    Document and test 'type' usage in Widget attrs

    claudep authored
    Refs #16630.
Commits on Sep 11, 2012
  1. @jphalip
  2. @jphalip

    Removed a colloquialism ("and then some") from the documentation inde…

    jphalip authored
    …x page that would be confusing to non-native English speakers.
Commits on Sep 12, 2012
  1. @claudep

    Fixed #18790 -- Encoded database password on Python 2

    claudep authored
    Thanks thcourbon@gmail.com for the report.
  2. @claudep

    Fixed #18182 -- Made is_usable_password check if hashing algorithm is…

    claudep authored
    … correct
    
    The display of the ReadOnlyPasswordHashWidget has also been improved to
    distinguish empty/unusable password from erroneous password.
    Fixed #18453 also.
    Thanks danielr and Leo for the reports and Moritz Sichert for the
    initial patch.
  3. @claudep
  4. @claudep
  5. @claudep
  6. Fixed my terribly outdated profile in committers.txt in celebration o…

    Jeremy Dunck authored
    …f my commit bit.
Commits on Sep 13, 2012
  1. @meric

    Fixed #16774 -- Allow view to raise DoesNotResolve to make URL Resolv…

    meric authored
    …er to keep searching for a URL.
Commits on Sep 18, 2012
  1. @meric
Commits on Mar 27, 2013
  1. @meric
  2. @meric

    Refs #16774 Made `RegexURLResolver.resolve` retain tried URLs state i…

    meric authored
    …f new argument `reset` is False.
This page is out of date. Refresh to see the latest.
Showing with 182 additions and 302 deletions.
  1. +1 −1  AUTHORS
  2. +14 −14 django/contrib/auth/forms.py
  3. +7 −1 django/contrib/auth/hashers.py
  4. +12 −6 django/contrib/auth/tests/forms.py
  5. +4 −0 django/contrib/auth/tests/hashers.py
  6. +1 −1  django/contrib/gis/geos/libgeos.py
  7. +6 −3 django/contrib/gis/geos/mutable_list.py
  8. +2 −1  django/contrib/gis/geos/tests/__init__.py
  9. +8 −8 django/contrib/gis/geos/tests/test_mutable_list.py
  10. +26 −15 django/core/handlers/base.py
  11. +8 −2 django/core/urlresolvers.py
  12. +2 −1  django/db/backends/mysql/base.py
  13. +2 −1  django/db/backends/postgresql_psycopg2/base.py
  14. +1 −1  django/forms/fields.py
  15. +11 −7 django/forms/widgets.py
  16. +0 −11 docs/contents.txt
  17. +2 −3 docs/faq/admin.txt
  18. +1 −1  docs/index.txt
  19. +14 −10 docs/internals/committers.txt
  20. +1 −2  docs/intro/whatsnext.txt
  21. BIN  docs/obsolete/_images/formrow.png
  22. BIN  docs/obsolete/_images/module.png
  23. BIN  docs/obsolete/_images/objecttools_01.png
  24. BIN  docs/obsolete/_images/objecttools_02.png
  25. +0 −186 docs/obsolete/admin-css.txt
  26. +0 −12 docs/obsolete/index.txt
  27. +6 −5 docs/ref/forms/widgets.txt
  28. +13 −0 tests/regressiontests/backends/tests.py
  29. +9 −9 tests/regressiontests/forms/tests/widgets.py
  30. +4 −0 tests/regressiontests/views/generic_urls.py
  31. +14 −0 tests/regressiontests/views/tests/specials.py
  32. +13 −1 tests/regressiontests/views/views.py
View
2  AUTHORS
@@ -31,6 +31,7 @@ The PRIMARY AUTHORS are (and/or have been):
* Claude Paroz
* Anssi Kääriäinen
* Florian Apolloner
+ * Jeremy Dunck
More information on the main contributors to Django can be found in
docs/internals/committers.txt.
@@ -167,7 +168,6 @@ answer newbie questions, and generally made Django that much better:
dready <wil@mojipage.com>
Maximillian Dornseif <md@hudora.de>
Daniel Duan <DaNmarner@gmail.com>
- Jeremy Dunck <http://dunck.us/>
Andrew Durdin <adurdin@gmail.com>
dusk@woofle.net
Andy Dustman <farcepest@gmail.com>
View
28 django/contrib/auth/forms.py
@@ -11,7 +11,7 @@
from django.contrib.auth import authenticate
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.sites.models import get_current_site
@@ -24,22 +24,22 @@
class ReadOnlyPasswordHashWidget(forms.Widget):
def render(self, name, value, attrs):
encoded = value
-
- if not is_password_usable(encoded):
- return "None"
-
final_attrs = self.build_attrs(attrs)
- try:
- hasher = identify_hasher(encoded)
- except ValueError:
- summary = mark_safe("<strong>Invalid password format or unknown hashing algorithm.</strong>")
+ if encoded == '' or encoded == UNUSABLE_PASSWORD:
+ summary = mark_safe("<strong>%s</strong>" % ugettext("No password set."))
else:
- summary = format_html_join('',
- "<strong>{0}</strong>: {1} ",
- ((ugettext(key), value)
- for key, value in hasher.safe_summary(encoded).items())
- )
+ try:
+ hasher = identify_hasher(encoded)
+ except ValueError:
+ summary = mark_safe("<strong>%s</strong>" % ugettext(
+ "Invalid password format or unknown hashing algorithm."))
+ else:
+ summary = format_html_join('',
+ "<strong>{0}</strong>: {1} ",
+ ((ugettext(key), value)
+ for key, value in hasher.safe_summary(encoded).items())
+ )
return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)
View
8 django/contrib/auth/hashers.py
@@ -28,7 +28,13 @@ def reset_hashers(**kwargs):
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'):
View
18 django/contrib/auth/tests/forms.py
@@ -236,23 +236,29 @@ class Meta(UserChangeForm.Meta):
# Just check we can create it
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):
user = User.objects.get(username='empty_password')
form = UserChangeForm(instance=user)
- # Just check that no error is raised.
- form.as_table()
+ self.assertIn(_("No password set."), form.as_table())
def test_bug_17944_unmanageable_password(self):
user = User.objects.get(username='unmanageable_password')
form = UserChangeForm(instance=user)
- # Just check that no error is raised.
- form.as_table()
+ self.assertIn(_("Invalid password format or unknown hashing algorithm."),
+ form.as_table())
def test_bug_17944_unknown_password_algorithm(self):
user = User.objects.get(username='unknown_password')
form = UserChangeForm(instance=user)
- # Just check that no error is raised.
- form.as_table()
+ self.assertIn(_("Invalid password format or unknown hashing algorithm."),
+ form.as_table())
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
View
4 django/contrib/auth/tests/hashers.py
@@ -100,6 +100,10 @@ def doit():
self.assertRaises(ValueError, doit)
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):
hasher = PBKDF2PasswordHasher()
encoded = hasher.encode('letmein', 'seasalt')
View
2  django/contrib/gis/geos/libgeos.py
@@ -110,7 +110,7 @@ def geos_version_info():
is a release candidate (and what number release candidate), and the C API
version.
"""
- ver = geos_version()
+ ver = geos_version().decode()
m = version_regex.match(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'))
View
9 django/contrib/gis/geos/mutable_list.py
@@ -215,15 +215,18 @@ def reverse(self):
"Standard list reverse method"
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"
if key:
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]
else:
temp = list(self)
- temp.sort(cmp=cmp, reverse=reverse)
+ if cmp is not None:
+ temp.sort(cmp=cmp, reverse=reverse)
+ else:
+ temp.sort(reverse=reverse)
self[:] = temp
### Private routines ###
View
3  django/contrib/gis/geos/tests/__init__.py
@@ -16,7 +16,8 @@
def suite():
"Builds a test suite for the GEOS tests."
s = TestSuite()
- map(s.addTest, test_suites)
+ for suite in test_suites:
+ s.addTest(suite)
return s
def run(verbosity=1):
View
16 django/contrib/gis/geos/tests/test_mutable_list.py
@@ -55,14 +55,14 @@ class ListMixinTest(unittest.TestCase):
def lists_of_len(self, length=None):
if length is None: length = self.limit
- pl = range(length)
+ pl = list(range(length))
return pl, self.listType(pl)
def limits_plus(self, b):
return range(-self.limit - b, self.limit + b)
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):
'Slice retrieval'
@@ -160,13 +160,13 @@ def test03_delslice(self):
del pl[i:j]
del ul[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)
del pl[i:j:k]
del ul[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)
del pl[:i:k]
del ul[:i:k]
@@ -177,7 +177,7 @@ def test03_delslice(self):
del ul[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)
del pl[::k]
del ul[::k]
@@ -320,7 +320,7 @@ def test_11_sorting(self):
pl.sort()
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)
ul.sort(key=lambda x: (mid-x)**2)
self.assertEqual(pl[:], ul[:], 'sort w/ key')
@@ -330,7 +330,7 @@ def test_11_sorting(self):
pl.sort(reverse=True)
ul.sort(reverse=True)
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)
ul.sort(key=lambda x: (mid-x)**2)
self.assertEqual(pl[:], ul[:], 'sort w/ key')
@@ -338,7 +338,7 @@ def test_11_sorting(self):
def test_12_arithmetic(self):
'Arithmetic'
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(type(ul), type(ul + al), 'type of add result')
self.assertEqual(list(al + pl), list(al + ul), 'radd')
View
41 django/core/handlers/base.py
@@ -101,28 +101,39 @@ def get_response(self, request):
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
+ # Use generators and try to resolve matching URL patterns one by
+ # one
+ url_patterns = (pattern for pattern in resolver.url_patterns)
+ resolver = urlresolvers.RegexURLResolver(r'^/', url_patterns)
+
+ while response is None:
callback, callback_args, callback_kwargs = resolver.resolve(
- request.path_info)
+ request.path_info, reset=False)
# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break
-
- if response is None:
- try:
- response = callback(request, *callback_args, **callback_kwargs)
- except Exception as e:
- # If the view raised an exception, run it through exception
- # middleware, and if the exception middleware returns a
- # response, use that. Otherwise, reraise the exception.
- for middleware_method in self._exception_middleware:
- response = middleware_method(request, e)
- if response:
- break
- if response is None:
- raise
+ else:
+ try:
+ response = callback(request, *callback_args, **callback_kwargs)
+ break
+ except urlresolvers.DoesNotResolve:
+ # Continue resolve URLs if the view raises
+ # urlresolvers.DoesNotResolve exception to indicate
+ # the url pattern does not match.
+ continue
+ except Exception as e:
+ # If the view raised an exception, run it through exception
+ # middleware, and if the exception middleware returns a
+ # response, use that. Otherwise, reraise the exception.
+ for middleware_method in self._exception_middleware:
+ response = middleware_method(request, e)
+ if response:
+ break
+ else:
+ raise
# Complain if the view returned None (a common error).
if response is None:
View
10 django/core/urlresolvers.py
@@ -73,6 +73,9 @@ def __repr__(self):
class Resolver404(Http404):
pass
+class DoesNotResolve(Http404):
+ pass
+
class NoReverseMatch(Exception):
# Don't make this raise an error when used in a template.
silent_variable_failure = True
@@ -243,6 +246,7 @@ def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, name
self._reverse_dict = {}
self._namespace_dict = {}
self._app_dict = {}
+ self._tried = []
def __repr__(self):
if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
@@ -310,8 +314,10 @@ def app_dict(self):
self._populate()
return self._app_dict[language_code]
- def resolve(self, path):
- tried = []
+ def resolve(self, path, reset=True):
+ if reset:
+ self._tried = []
+ tried = self._tried
match = self.regex.search(path)
if match:
new_path = path[match.end():]
View
3  django/db/backends/mysql/base.py
@@ -37,6 +37,7 @@
from django.db.backends.mysql.creation import DatabaseCreation
from django.db.backends.mysql.introspection import DatabaseIntrospection
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.safestring import SafeBytes, SafeText
from django.utils import six
@@ -390,7 +391,7 @@ def _cursor(self):
if settings_dict['NAME']:
kwargs['db'] = settings_dict['NAME']
if settings_dict['PASSWORD']:
- kwargs['passwd'] = settings_dict['PASSWORD']
+ kwargs['passwd'] = force_str(settings_dict['PASSWORD'])
if settings_dict['HOST'].startswith('/'):
kwargs['unix_socket'] = settings_dict['HOST']
elif settings_dict['HOST']:
View
3  django/db/backends/postgresql_psycopg2/base.py
@@ -13,6 +13,7 @@
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.introspection import DatabaseIntrospection
+from django.utils.encoding import force_str
from django.utils.log import getLogger
from django.utils.safestring import SafeText, SafeBytes
from django.utils import six
@@ -172,7 +173,7 @@ def _cursor(self):
if settings_dict['USER']:
conn_params['user'] = settings_dict['USER']
if settings_dict['PASSWORD']:
- conn_params['password'] = settings_dict['PASSWORD']
+ conn_params['password'] = force_str(settings_dict['PASSWORD'])
if settings_dict['HOST']:
conn_params['host'] = settings_dict['HOST']
if settings_dict['PORT']:
View
2  django/forms/fields.py
@@ -199,7 +199,7 @@ def to_python(self, value):
def widget_attrs(self, 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.
attrs.update({'maxlength': str(self.max_length)})
return attrs
View
18 django/forms/widgets.py
@@ -260,10 +260,17 @@ def render(self, name, value, attrs=None):
final_attrs['value'] = force_text(self._format_value(value))
return format_html('<input{0} />', flatatt(final_attrs))
+
class TextInput(Input):
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'
def __init__(self, attrs=None, render_value=False):
@@ -400,9 +407,8 @@ def render(self, name, value, attrs=None):
flatatt(final_attrs),
force_text(value))
-class DateInput(Input):
- input_type = 'text'
+class DateInput(TextInput):
def __init__(self, attrs=None, format=None):
super(DateInput, self).__init__(attrs)
if format:
@@ -431,9 +437,8 @@ def _has_changed(self, initial, data):
pass
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):
super(DateTimeInput, self).__init__(attrs)
if format:
@@ -462,9 +467,8 @@ def _has_changed(self, initial, data):
pass
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):
super(TimeInput, self).__init__(attrs)
if format:
View
11 docs/contents.txt
@@ -28,14 +28,3 @@ Indices, glossary and tables
* :ref:`genindex`
* :ref:`modindex`
* :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
5 docs/faq/admin.txt
@@ -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
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
-like to make should be possible by editing the stylesheet. We've got a
-:doc:`guide to the CSS used in the admin </obsolete/admin-css>` to get you started.
+like to make should be possible by editing the stylesheet.
What browsers are supported for using the admin?
------------------------------------------------
@@ -104,5 +103,5 @@ There *may* be minor stylistic differences between supported browsers—for
example, some browsers may not support rounded corners. These are considered
acceptable variations in rendering.
-.. _YUI's A-grade: http://yuilibrary.com/yui/docs/tutorials/gbs/
+.. _YUI's A-grade: http://yuilibrary.com/yui/docs/tutorials/gbs/
View
2  docs/index.txt
@@ -5,7 +5,7 @@
Django documentation
====================
-.. rubric:: Everything you need to know about Django (and then some).
+.. rubric:: Everything you need to know about Django.
Getting help
============
View
24 docs/internals/committers.txt
@@ -379,6 +379,20 @@ Florian Apolloner
.. _Graz University of Technology: http://tugraz.at/
.. _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.
+
Specialists
-----------
@@ -403,16 +417,6 @@ Ian Kelly
Matt Boersma
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 discovered Django 0.96 during his Computer Science PhD research and
has been developing with it full-time ever since. His core code
View
3  docs/intro/whatsnext.txt
@@ -67,8 +67,7 @@ different needs:
whathaveyou.
* Finally, there's some "specialized" documentation not usually relevant to
- most developers. This includes the :doc:`release notes </releases/index>`,
- :doc:`documentation of obsolete features </obsolete/index>`,
+ most developers. This includes the :doc:`release notes </releases/index>` and
: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
elsewhere </misc/index>`.
View
BIN  docs/obsolete/_images/formrow.png
Deleted file not rendered
View
BIN  docs/obsolete/_images/module.png
Deleted file not rendered
View
BIN  docs/obsolete/_images/objecttools_01.png
Deleted file not rendered
View
BIN  docs/obsolete/_images/objecttools_02.png
Deleted file not rendered
View
186 docs/obsolete/admin-css.txt
@@ -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
12 docs/obsolete/index.txt
@@ -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
11 docs/ref/forms/widgets.txt
@@ -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
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
-:attr:`Widget.attrs` argument when creating the widget:
+'name' widget to have some special CSS class. It is also possible to specify
+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::
@@ -245,7 +246,7 @@ commonly used groups of widgets:
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
@@ -262,7 +263,7 @@ commonly used groups of widgets:
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
@@ -279,7 +280,7 @@ commonly used groups of widgets:
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
View
13 tests/regressiontests/backends/tests.py
@@ -401,6 +401,19 @@ def test_unicode_fetches(self):
self.assertEqual(list(cursor.fetchmany(2)), [('Jane', 'Doe'), ('John', 'Doe')])
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):
# Ticket #13630
self.assertTrue(hasattr(connection, 'ops'))
View
18 tests/regressiontests/forms/tests/widgets.py
@@ -31,9 +31,9 @@ def test_textinput(self):
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:
- w = TextInput(attrs={'class': 'fun'})
- self.assertHTMLEqual(w.render('email', ''), '<input type="text" class="fun" name="email" />')
- self.assertHTMLEqual(w.render('email', 'foo@example.com'), '<input type="text" class="fun" value="foo@example.com" name="email" />')
+ w = TextInput(attrs={'class': 'fun', 'type': 'email'})
+ self.assertHTMLEqual(w.render('email', ''), '<input type="email" class="fun" 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:
w = TextInput(attrs={'class': 'pretty'})
@@ -915,8 +915,8 @@ def test_datetimeinput(self):
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.
- w = DateTimeInput(format='%d/%m/%Y %H:%M')
- self.assertHTMLEqual(w.render('date', d), '<input type="text" name="date" value="17/09/2007 12:51" />')
+ w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'})
+ 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'))
# Make sure a custom format works with _has_changed. The hidden input will use
@@ -938,8 +938,8 @@ def test_dateinput(self):
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.
- w = DateInput(format='%d/%m/%Y')
- self.assertHTMLEqual(w.render('date', d), '<input type="text" name="date" value="17/09/2007" />')
+ w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'})
+ self.assertHTMLEqual(w.render('date', d), '<input type="date" name="date" value="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
@@ -963,8 +963,8 @@ def test_timeinput(self):
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.
- w = TimeInput(format='%H:%M')
- self.assertHTMLEqual(w.render('time', t), '<input type="text" name="time" value="12:51" />')
+ w = TimeInput(format='%H:%M', attrs={'type': 'time'})
+ self.assertHTMLEqual(w.render('time', t), '<input type="time" name="time" value="12:51" />')
self.assertFalse(w._has_changed(t, '12:51'))
# Make sure a custom format works with _has_changed. The hidden input will use
View
4 tests/regressiontests/views/generic_urls.py
@@ -54,4 +54,8 @@
(r'^shortcuts/render/status/$', 'render_view_with_status'),
(r'^shortcuts/render/current_app/$', 'render_view_with_current_app'),
(r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
+ url(r'^overlapping_view/(?P<title>[a-z]+)/$', 'overlapping_view1', name='overlapping_view1'),
+ url(r'^overlapping_view/(?P<author>[a-z]+)/$', 'overlapping_view2', name='overlapping_view2'),
+ url(r'^overlapping_view/(?P<keywords>[a-z]+)/$', 'overlapping_view3', name='overlapping_view3'),
+ url(r'^no_overlapping_view/(?P<keywords>[a-z]+)/$', 'no_overlapping_view', name='no_overlapping_view'),
)
View
14 tests/regressiontests/views/tests/specials.py
@@ -37,3 +37,17 @@ def test_permanent_nonascii_redirect(self):
response = self.client.get('/permanent_nonascii_redirect/')
self.assertRedirects(response, self.redirect_target, status_code=301)
+ def test_overlapping_urls_reverse(self):
+ from django.core import urlresolvers
+ url = urlresolvers.reverse('overlapping_view1', kwargs={'title':'sometitle'})
+ self.assertEqual(url, '/overlapping_view/sometitle/')
+ url = urlresolvers.reverse('overlapping_view2', kwargs={'author':'someauthor'})
+ self.assertEqual(url, '/overlapping_view/someauthor/')
+
+ def test_overlapping_urls_resolve(self):
+ response = self.client.get('/overlapping_view/sometitle/')
+ self.assertContains(response, 'overlapping_view2')
+
+ def test_overlapping_urls_not_resolve(self):
+ response = self.client.get('/no_overlapping_view/sometitle/')
+ self.assertEqual(response.status_code, 404)
View
14 tests/regressiontests/views/views.py
@@ -3,7 +3,7 @@
import sys
from django.core.exceptions import PermissionDenied
-from django.core.urlresolvers import get_resolver
+from django.core.urlresolvers import DoesNotResolve, get_resolver
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, render
from django.template import Context, RequestContext, TemplateDoesNotExist
@@ -226,5 +226,17 @@ def method(self, request):
send_log(request, exc_info)
return technical_500_response(request, *exc_info)
+def overlapping_view1(request, title=None):
+ raise DoesNotResolve
+
+def overlapping_view2(request, author=None):
+ return HttpResponse("overlapping_view2")
+
+def overlapping_view3(request, keywords=None):
+ return HttpResponse("overlapping_view3")
+
+def no_overlapping_view(request, keywords=None):
+ raise DoesNotResolve
+
def sensitive_method_view(request):
return Klass().method(request)
Something went wrong with that request. Please try again.