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:
parent
78d75b86e3
commit
9222091de8
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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))
|
||||||
|
@ -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 ' <strong>%s</strong>' % truncate_words(obj, 14)
|
return ' <strong>%s</strong>' % escape(truncate_words(obj, 14))
|
||||||
|
|
||||||
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
||||||
"""
|
"""
|
||||||
|
@ -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,
|
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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 = ''
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
------------------
|
------------------
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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).
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -196,7 +196,7 @@ def get_filter_tests():
|
|||||||
'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
||||||
'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&y"),
|
'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&y"),
|
||||||
'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
||||||
'filter-force-escape07': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&y"),
|
'filter-force-escape08': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user