1
0
mirror of https://github.com/django/django.git synced 2025-07-07 03:09:22 +00:00

[soc2009/model-validation] Merged to trunk at r11499

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11513 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Honza Král 2009-09-11 21:23:55 +00:00
parent 78d75b86e3
commit 9222091de8
16 changed files with 102 additions and 39 deletions

View File

@ -300,7 +300,7 @@ class AdminSite(object):
user = authenticate(username=username, password=password) user = authenticate(username=username, password=password)
if user is None: if user is None:
message = ERROR_MESSAGE message = ERROR_MESSAGE
if u'@' in username: if username is not None and u'@' in username:
# Mistakenly entered e-mail address instead of username? Look it up. # Mistakenly entered e-mail address instead of username? Look it up.
try: try:
user = User.objects.get(email=username) user = User.objects.get(email=username)

View File

@ -15,6 +15,7 @@
{% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %} {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %}
</tr></thead> </tr></thead>
<tbody>
{% for inline_admin_form in inline_admin_formset %} {% for inline_admin_form in inline_admin_formset %}
{% if inline_admin_form.form.non_field_errors %} {% if inline_admin_form.form.non_field_errors %}
<tr><td colspan="{{ inline_admin_form.field_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr> <tr><td colspan="{{ inline_admin_form.field_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
@ -57,7 +58,7 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody>
</table> </table>
</fieldset> </fieldset>

View File

@ -22,7 +22,7 @@ def paginator_number(cl,i):
elif i == cl.page_num: elif i == cl.page_num:
return mark_safe(u'<span class="this-page">%d</span> ' % (i+1)) return mark_safe(u'<span class="this-page">%d</span> ' % (i+1))
else: else:
return mark_safe(u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) return mark_safe(u'<a href="%s"%s>%d</a> ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1))
paginator_number = register.simple_tag(paginator_number) paginator_number = register.simple_tag(paginator_number)
def pagination(cl): def pagination(cl):
@ -265,7 +265,7 @@ def date_hierarchy(cl):
day_lookup = cl.params.get(day_field) day_lookup = cl.params.get(day_field)
year_month_format, month_day_format = get_partial_date_formats() year_month_format, month_day_format = get_partial_date_formats()
link = lambda d: mark_safe(cl.get_query_string(d, [field_generic])) link = lambda d: cl.get_query_string(d, [field_generic])
if year_lookup and month_lookup and day_lookup: if year_lookup and month_lookup and day_lookup:
day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup)) day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))

View File

@ -7,6 +7,7 @@ import copy
from django import forms from django import forms
from django.forms.widgets import RadioFieldRenderer from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt from django.forms.util import flatatt
from django.utils.html import escape
from django.utils.text import truncate_words from django.utils.text import truncate_words
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -148,7 +149,7 @@ class ForeignKeyRawIdWidget(forms.TextInput):
def label_for_value(self, value): def label_for_value(self, value):
key = self.rel.get_related_field().name key = self.rel.get_related_field().name
obj = self.rel.to._default_manager.get(**{key: value}) obj = self.rel.to._default_manager.get(**{key: value})
return '&nbsp;<strong>%s</strong>' % truncate_words(obj, 14) return '&nbsp;<strong>%s</strong>' % escape(truncate_words(obj, 14))
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
""" """

View File

@ -10,10 +10,6 @@ from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS
__test__ = { __test__ = {
'BASIC_TESTS': BASIC_TESTS, 'BASIC_TESTS': BASIC_TESTS,
'PASSWORDRESET_TESTS': PasswordResetTest,
'FORM_TESTS': FORM_TESTS, 'FORM_TESTS': FORM_TESTS,
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS,
'CHANGEPASSWORD_TESTS': ChangePasswordTest,
'LOGIN_TESTS': LoginTest,
'LOGOUT_TESTS': LogoutTest,
} }

View File

@ -398,7 +398,9 @@ def copy_helper(style, app_or_project, name, directory, other_name=''):
if subdir.startswith('.'): if subdir.startswith('.'):
del subdirs[i] del subdirs[i]
for f in files: for f in files:
if f.endswith('.pyc'): if not f.endswith('.py'):
# Ignore .pyc, .pyo, .py.class etc, as they cause various
# breakages.
continue continue
path_old = os.path.join(d, f) path_old = os.path.join(d, f)
path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)) path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name))

View File

@ -287,8 +287,17 @@ class RegexURLResolver(object):
candidate = result % unicode_kwargs candidate = result % unicode_kwargs
if re.search(u'^%s' % pattern, candidate, re.UNICODE): if re.search(u'^%s' % pattern, candidate, re.UNICODE):
return candidate return candidate
# lookup_view can be URL label, or dotted path, or callable, Any of
# these can be passed in at the top, but callables are not friendly in
# error messages.
m = getattr(lookup_view, '__module__', None)
n = getattr(lookup_view, '__name__', None)
if m is not None and n is not None:
lookup_view_s = "%s.%s" % (m, n)
else:
lookup_view_s = lookup_view
raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword " raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
"arguments '%s' not found." % (lookup_view, args, kwargs)) "arguments '%s' not found." % (lookup_view_s, args, kwargs))
def resolve(path, urlconf=None): def resolve(path, urlconf=None):
return get_resolver(urlconf).resolve(path) return get_resolver(urlconf).resolve(path)

View File

@ -275,9 +275,10 @@ class FileInput(Input):
class Textarea(Widget): class Textarea(Widget):
def __init__(self, attrs=None): def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness. # The 'rows' and 'cols' attributes are required for HTML correctness.
self.attrs = {'cols': '40', 'rows': '10'} default_attrs = {'cols': '40', 'rows': '10'}
if attrs: if attrs:
self.attrs.update(attrs) default_attrs.update(attrs)
super(Textarea, self).__init__(default_attrs)
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if value is None: value = '' if value is None: value = ''

View File

@ -117,7 +117,7 @@ class SortedDict(dict):
return iter(self.keyOrder) return iter(self.keyOrder)
def values(self): def values(self):
return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder] return map(super(SortedDict, self).__getitem__, self.keyOrder)
def itervalues(self): def itervalues(self):
for key in self.keyOrder: for key in self.keyOrder:

View File

@ -159,6 +159,16 @@ commonly used groups of widgets:
.. versionchanged:: 1.1 .. versionchanged:: 1.1
The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0. The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0.
.. class:: SelectDateWidget
Wrapper around three select widgets: one each for month, day, and year.
Note that this widget lives in a separate file from the standard widgets.
.. code-block:: python
from django.forms.extras.widgets import SelectDateWidget
date = forms.DateField(widget=SelectDateWidget())
Specifying widgets Specifying widgets
------------------ ------------------

View File

@ -232,16 +232,7 @@ Methods
Returns ``True`` if the request was made via an ``XMLHttpRequest``, by Returns ``True`` if the request was made via an ``XMLHttpRequest``, by
checking the ``HTTP_X_REQUESTED_WITH`` header for the string checking the ``HTTP_X_REQUESTED_WITH`` header for the string
``'XMLHttpRequest'``. The following major JavaScript libraries all send this ``'XMLHttpRequest'``. Most modern JavaScript libraries send this header.
header:
* jQuery
* Dojo
* MochiKit
* MooTools
* Prototype
* YUI
If you write your own XMLHttpRequest call (on the browser side), you'll If you write your own XMLHttpRequest call (on the browser side), you'll
have to set this header manually if you want ``is_ajax()`` to work. have to set this header manually if you want ``is_ajax()`` to work.

View File

@ -29,13 +29,16 @@ Installation
Authentication support is bundled as a Django application in Authentication support is bundled as a Django application in
``django.contrib.auth``. To install it, do the following: ``django.contrib.auth``. To install it, do the following:
1. Put ``'django.contrib.auth'`` in your :setting:`INSTALLED_APPS` setting. 1. Put ``'django.contrib.auth'`` and ``'django.contrib.contenttypes'`` in
your :setting:`INSTALLED_APPS` setting.
(The :class:`~django.contrib.auth.models.Permisson` model in
:mod:`django.contrib.auth` depends on :mod:`django.contrib.contenttypes`.)
2. Run the command ``manage.py syncdb``. 2. Run the command ``manage.py syncdb``.
Note that the default :file:`settings.py` file created by Note that the default :file:`settings.py` file created by
:djadmin:`django-admin.py startproject` includes ``'django.contrib.auth'`` in :djadmin:`django-admin.py startproject` includes ``'django.contrib.auth'`` and
:setting:`INSTALLED_APPS` for convenience. If your :setting:`INSTALLED_APPS` ``'django.contrib.contenttypes'`` in :setting:`INSTALLED_APPS` for convenience.
already contains ``'django.contrib.auth'``, feel free to run If your :setting:`INSTALLED_APPS` already contains these apps, feel free to run
:djadmin:`manage.py syncdb` again; you can run that command as many times as :djadmin:`manage.py syncdb` again; you can run that command as many times as
you'd like, and each time it'll only install what's needed. you'd like, and each time it'll only install what's needed.

View File

@ -150,7 +150,7 @@ be using these models::
publisher = models.ForeignKey(Publisher) publisher = models.ForeignKey(Publisher)
publication_date = models.DateField() publication_date = models.DateField()
To build a list page of all books, we'd use a URLconf along these lines:: To build a list page of all publishers, we'd use a URLconf along these lines::
from django.conf.urls.defaults import * from django.conf.urls.defaults import *
from django.views.generic import list_detail from django.views.generic import list_detail
@ -176,7 +176,7 @@ version of the model's name.
.. highlightlang:: html+django .. highlightlang:: html+django
This template will be rendered against a context containing a variable called This template will be rendered against a context containing a variable called
``object_list`` that contains all the book objects. A very simple template ``object_list`` that contains all the publisher objects. A very simple template
might look like the following:: might look like the following::
{% extends "base.html" %} {% extends "base.html" %}
@ -217,7 +217,7 @@ Making "friendly" template contexts
You might have noticed that our sample publisher list template stores all the You might have noticed that our sample publisher list template stores all the
books in a variable named ``object_list``. While this works just fine, it isn't books in a variable named ``object_list``. While this works just fine, it isn't
all that "friendly" to template authors: they have to "just know" that they're all that "friendly" to template authors: they have to "just know" that they're
dealing with books here. A better name for that variable would be dealing with publishers here. A better name for that variable would be
``publisher_list``; that variable's content is pretty obvious. ``publisher_list``; that variable's content is pretty obvious.
We can change the name of that variable easily with the ``template_object_name`` We can change the name of that variable easily with the ``template_object_name``
@ -241,14 +241,14 @@ Adding extra context
-------------------- --------------------
Often you simply need to present some extra information beyond that provided by Often you simply need to present some extra information beyond that provided by
the generic view. For example, think of showing a list of all the other the generic view. For example, think of showing a list of all the books on each
publishers on each publisher detail page. The ``object_detail`` generic view publisher detail page. The ``object_detail`` generic view provides the
provides the publisher to the context, but it seems there's no way to get a list publisher to the context, but it seems there's no way to get additional
of *all* publishers in that template. information in that template.
But there is: all generic views take an extra optional parameter, But there is: all generic views take an extra optional parameter,
``extra_context``. This is a dictionary of extra objects that will be added to ``extra_context``. This is a dictionary of extra objects that will be added to
the template's context. So, to provide the list of all publishers on the detail the template's context. So, to provide the list of all books on the detail
detail view, we'd use an info dict like this: detail view, we'd use an info dict like this:
.. parsed-literal:: .. parsed-literal::
@ -268,9 +268,9 @@ generic view. It's very handy.
However, there's actually a subtle bug here -- can you spot it? However, there's actually a subtle bug here -- can you spot it?
The problem has to do with when the queries in ``extra_context`` are evaluated. The problem has to do with when the queries in ``extra_context`` are evaluated.
Because this example puts ``Publisher.objects.all()`` in the URLconf, it will Because this example puts ``Book.objects.all()`` in the URLconf, it will
be evaluated only once (when the URLconf is first loaded). Once you add or be evaluated only once (when the URLconf is first loaded). Once you add or
remove publishers, you'll notice that the generic view doesn't reflect those remove books, you'll notice that the generic view doesn't reflect those
changes until you reload the Web server (see :ref:`caching-and-querysets` changes until you reload the Web server (see :ref:`caching-and-querysets`
for more information about when QuerySets are cached and evaluated). for more information about when QuerySets are cached and evaluated).

View File

@ -353,6 +353,9 @@ class AdminViewPermissionsTest(TestCase):
LOGIN_FORM_KEY: 1, LOGIN_FORM_KEY: 1,
'username': 'joepublic', 'username': 'joepublic',
'password': 'secret'} 'password': 'secret'}
self.no_username_login = {
LOGIN_FORM_KEY: 1,
'password': 'secret'}
def testLogin(self): def testLogin(self):
""" """
@ -416,6 +419,14 @@ class AdminViewPermissionsTest(TestCase):
# Login.context is a list of context dicts we just need to check the first one. # Login.context is a list of context dicts we just need to check the first one.
self.assert_(login.context[0].get('error_message')) self.assert_(login.context[0].get('error_message'))
# Requests without username should not return 500 errors.
request = self.client.get('/test_admin/admin/')
self.failUnlessEqual(request.status_code, 200)
login = self.client.post('/test_admin/admin/', self.no_username_login)
self.failUnlessEqual(login.status_code, 200)
# Login.context is a list of context dicts we just need to check the first one.
self.assert_(login.context[0].get('error_message'))
def testLoginSuccessfullyRedirectsToOriginalUrl(self): def testLoginSuccessfullyRedirectsToOriginalUrl(self):
request = self.client.get('/test_admin/admin/') request = self.client.get('/test_admin/admin/')
self.failUnlessEqual(request.status_code, 200) self.failUnlessEqual(request.status_code, 200)

View File

@ -358,4 +358,42 @@ ValidationError: [u'NOT A LIST OF VALUES']
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'4 IS INVALID CHOICE'] ValidationError: [u'4 IS INVALID CHOICE']
# Subclassing ErrorList #######################################################
>>> from django.utils.safestring import mark_safe
>>>
>>> class TestForm(Form):
... first_name = CharField()
... last_name = CharField()
... birthday = DateField()
...
... def clean(self):
... raise ValidationError("I like to be awkward.")
...
>>> class CustomErrorList(util.ErrorList):
... def __unicode__(self):
... return self.as_divs()
... def as_divs(self):
... if not self: return u''
... return mark_safe(u'<div class="error">%s</div>'
... % ''.join([u'<p>%s</p>' % e for e in self]))
...
This form should print errors the default way.
>>> form1 = TestForm({'first_name': 'John'})
>>> print form1['last_name'].errors
<ul class="errorlist"><li>This field is required.</li></ul>
>>> print form1.errors['__all__']
<ul class="errorlist"><li>I like to be awkward.</li></ul>
This one should wrap error groups in the customized way.
>>> form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList)
>>> print form2['last_name'].errors
<div class="error"><p>This field is required.</p></div>
>>> print form2.errors['__all__']
<div class="error"><p>I like to be awkward.</p></div>
""" """

View File

@ -196,7 +196,7 @@ def get_filter_tests():
'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"), 'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"),
'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&amp;y"), 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&amp;y"),
'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"), 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"),
'filter-force-escape07': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&amp;y"), 'filter-force-escape08': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&amp;y"),
# The contents in "linebreaks" and "linebreaksbr" are escaped # The contents in "linebreaks" and "linebreaksbr" are escaped
# according to the current autoescape setting. # according to the current autoescape setting.