Skip to content

Commit

Permalink
[soc2009/model-validation] Merged to trunk at r11499
Browse files Browse the repository at this point in the history
  • Loading branch information
honzakral committed Sep 11, 2009
1 parent 78d75b8 commit 9222091
Show file tree
Hide file tree
Showing 16 changed files with 102 additions and 39 deletions.
2 changes: 1 addition & 1 deletion django/contrib/admin/sites.py
Expand Up @@ -300,7 +300,7 @@ def login(self, request):
user = authenticate(username=username, password=password)
if user is None:
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.
try:
user = User.objects.get(email=username)
Expand Down
Expand Up @@ -15,6 +15,7 @@ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
{% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %}
</tr></thead>

<tbody>
{% for inline_admin_form in inline_admin_formset %}
{% 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>
Expand Down Expand Up @@ -57,7 +58,7 @@ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
</tr>

{% endfor %}

</tbody>
</table>

</fieldset>
Expand Down
4 changes: 2 additions & 2 deletions django/contrib/admin/templatetags/admin_list.py
Expand Up @@ -22,7 +22,7 @@ def paginator_number(cl,i):
elif i == cl.page_num:
return mark_safe(u'<span class="this-page">%d</span> ' % (i+1))
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)

def pagination(cl):
Expand Down Expand Up @@ -265,7 +265,7 @@ def date_hierarchy(cl):
day_lookup = cl.params.get(day_field)
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:
day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))
Expand Down
3 changes: 2 additions & 1 deletion django/contrib/admin/widgets.py
Expand Up @@ -7,6 +7,7 @@
from django import forms
from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt
from django.utils.html import escape
from django.utils.text import truncate_words
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
Expand Down Expand Up @@ -148,7 +149,7 @@ def url_parameters(self):
def label_for_value(self, value):
key = self.rel.get_related_field().name
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):
"""
Expand Down
4 changes: 0 additions & 4 deletions django/contrib/auth/tests/__init__.py
Expand Up @@ -10,10 +10,6 @@

__test__ = {
'BASIC_TESTS': BASIC_TESTS,
'PASSWORDRESET_TESTS': PasswordResetTest,
'FORM_TESTS': FORM_TESTS,
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS,
'CHANGEPASSWORD_TESTS': ChangePasswordTest,
'LOGIN_TESTS': LoginTest,
'LOGOUT_TESTS': LogoutTest,
}
4 changes: 3 additions & 1 deletion django/core/management/base.py
Expand Up @@ -398,7 +398,9 @@ def copy_helper(style, app_or_project, name, directory, other_name=''):
if subdir.startswith('.'):
del subdirs[i]
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
path_old = os.path.join(d, f)
path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name))
Expand Down
11 changes: 10 additions & 1 deletion django/core/urlresolvers.py
Expand Up @@ -287,8 +287,17 @@ def reverse(self, lookup_view, *args, **kwargs):
candidate = result % unicode_kwargs
if re.search(u'^%s' % pattern, candidate, re.UNICODE):
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 "
"arguments '%s' not found." % (lookup_view, args, kwargs))
"arguments '%s' not found." % (lookup_view_s, args, kwargs))

def resolve(path, urlconf=None):
return get_resolver(urlconf).resolve(path)
Expand Down
5 changes: 3 additions & 2 deletions django/forms/widgets.py
Expand Up @@ -275,9 +275,10 @@ def _has_changed(self, initial, data):
class Textarea(Widget):
def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness.
self.attrs = {'cols': '40', 'rows': '10'}
default_attrs = {'cols': '40', 'rows': '10'}
if attrs:
self.attrs.update(attrs)
default_attrs.update(attrs)
super(Textarea, self).__init__(default_attrs)

def render(self, name, value, attrs=None):
if value is None: value = ''
Expand Down
2 changes: 1 addition & 1 deletion django/utils/datastructures.py
Expand Up @@ -117,7 +117,7 @@ def iterkeys(self):
return iter(self.keyOrder)

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):
for key in self.keyOrder:
Expand Down
10 changes: 10 additions & 0 deletions docs/ref/forms/widgets.txt
Expand Up @@ -159,6 +159,16 @@ commonly used groups of widgets:
.. versionchanged:: 1.1
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
------------------
Expand Down
11 changes: 1 addition & 10 deletions docs/ref/request-response.txt
Expand Up @@ -232,16 +232,7 @@ Methods

Returns ``True`` if the request was made via an ``XMLHttpRequest``, by
checking the ``HTTP_X_REQUESTED_WITH`` header for the string
``'XMLHttpRequest'``. The following major JavaScript libraries all send this
header:

* jQuery
* Dojo
* MochiKit
* MooTools
* Prototype
* YUI

``'XMLHttpRequest'``. Most modern JavaScript libraries send this header.
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.

Expand Down
11 changes: 7 additions & 4 deletions docs/topics/auth.txt
Expand Up @@ -29,13 +29,16 @@ Installation
Authentication support is bundled as a Django application in
``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``.

Note that the default :file:`settings.py` file created by
:djadmin:`django-admin.py startproject` includes ``'django.contrib.auth'`` in
:setting:`INSTALLED_APPS` for convenience. If your :setting:`INSTALLED_APPS`
already contains ``'django.contrib.auth'``, feel free to run
:djadmin:`django-admin.py startproject` includes ``'django.contrib.auth'`` and
``'django.contrib.contenttypes'`` in :setting:`INSTALLED_APPS` for convenience.
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
you'd like, and each time it'll only install what's needed.

Expand Down
20 changes: 10 additions & 10 deletions docs/topics/generic-views.txt
Expand Up @@ -150,7 +150,7 @@ be using these models::
publisher = models.ForeignKey(Publisher)
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.views.generic import list_detail
Expand All @@ -176,7 +176,7 @@ version of the model's name.
.. highlightlang:: html+django

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::

{% extends "base.html" %}
Expand Down Expand Up @@ -217,7 +217,7 @@ Making "friendly" template contexts
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
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.

We can change the name of that variable easily with the ``template_object_name``
Expand All @@ -241,14 +241,14 @@ Adding extra context
--------------------

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
publishers on each publisher detail page. The ``object_detail`` generic view
provides the publisher to the context, but it seems there's no way to get a list
of *all* publishers in that template.
the generic view. For example, think of showing a list of all the books on each
publisher detail page. The ``object_detail`` generic view provides the
publisher to the context, but it seems there's no way to get additional
information in that template.

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
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:

.. parsed-literal::
Expand All @@ -268,9 +268,9 @@ generic view. It's very handy.
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.
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
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`
for more information about when QuerySets are cached and evaluated).

Expand Down
11 changes: 11 additions & 0 deletions tests/regressiontests/admin_views/tests.py
Expand Up @@ -353,6 +353,9 @@ def setUp(self):
LOGIN_FORM_KEY: 1,
'username': 'joepublic',
'password': 'secret'}
self.no_username_login = {
LOGIN_FORM_KEY: 1,
'password': 'secret'}

def testLogin(self):
"""
Expand Down Expand Up @@ -416,6 +419,14 @@ def testLogin(self):
# Login.context is a list of context dicts we just need to check the first one.
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):
request = self.client.get('/test_admin/admin/')
self.failUnlessEqual(request.status_code, 200)
Expand Down
38 changes: 38 additions & 0 deletions tests/regressiontests/forms/error_messages.py
Expand Up @@ -358,4 +358,42 @@
Traceback (most recent call last):
...
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>
"""
2 changes: 1 addition & 1 deletion tests/regressiontests/templates/filters.py
Expand Up @@ -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-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': ('{{ 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
# according to the current autoescape setting.
Expand Down

0 comments on commit 9222091

Please sign in to comment.