diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index abcff14cd1..5f397ecb01 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -300,7 +300,7 @@ class AdminSite(object): 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) diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index ecc14a46d3..211573ebfc 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -15,6 +15,7 @@ {% if inline_admin_formset.formset.can_delete %}{% trans "Delete?" %}{% endif %} + {% for inline_admin_form in inline_admin_formset %} {% if inline_admin_form.form.non_field_errors %} {{ inline_admin_form.form.non_field_errors }} @@ -57,7 +58,7 @@ {% endfor %} - + diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index d97c36a207..9a4ce3b266 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -22,7 +22,7 @@ def paginator_number(cl,i): elif i == cl.page_num: return mark_safe(u'%d ' % (i+1)) else: - return mark_safe(u'%d ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) + return mark_safe(u'%d ' % (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): @@ -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)) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index eacea44a31..fb5acb5295 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -7,6 +7,7 @@ import copy 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 @@ -148,7 +149,7 @@ class ForeignKeyRawIdWidget(forms.TextInput): def label_for_value(self, value): key = self.rel.get_related_field().name obj = self.rel.to._default_manager.get(**{key: value}) - return ' %s' % truncate_words(obj, 14) + return ' %s' % escape(truncate_words(obj, 14)) class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): """ diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index 9a381ef93d..14428d0fc8 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -10,10 +10,6 @@ from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS __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, } diff --git a/django/core/management/base.py b/django/core/management/base.py index b5efdbc108..76b9c0967c 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -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)) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 4f9eb982e2..eedb8f126c 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -287,8 +287,17 @@ class RegexURLResolver(object): 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) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index cecafa880b..b1d2cb7cda 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -275,9 +275,10 @@ class FileInput(Input): 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 = '' diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index ce2424065d..85cdd443f8 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -117,7 +117,7 @@ class SortedDict(dict): 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: diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index e2ba0d7889..1fc2bfa85d 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -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 ------------------ diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 8701c76235..9df156a6f2 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -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. diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 7858e44962..615382bb07 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -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. diff --git a/docs/topics/generic-views.txt b/docs/topics/generic-views.txt index c7d751a86b..e4094ac000 100644 --- a/docs/topics/generic-views.txt +++ b/docs/topics/generic-views.txt @@ -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 @@ -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" %} @@ -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`` @@ -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:: @@ -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). diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index aafa303cec..669755051e 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -353,6 +353,9 @@ class AdminViewPermissionsTest(TestCase): LOGIN_FORM_KEY: 1, 'username': 'joepublic', 'password': 'secret'} + self.no_username_login = { + LOGIN_FORM_KEY: 1, + 'password': 'secret'} 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. 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) diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py index 3f6cfcac80..12ec8a1585 100644 --- a/tests/regressiontests/forms/error_messages.py +++ b/tests/regressiontests/forms/error_messages.py @@ -358,4 +358,42 @@ ValidationError: [u'NOT A LIST OF VALUES'] 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'
%s
' +... % ''.join([u'

%s

' % e for e in self])) +... + +This form should print errors the default way. + +>>> form1 = TestForm({'first_name': 'John'}) +>>> print form1['last_name'].errors + +>>> print form1.errors['__all__'] + + +This one should wrap error groups in the customized way. + +>>> form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList) +>>> print form2['last_name'].errors +

This field is required.

+>>> print form2.errors['__all__'] +

I like to be awkward.

+ """ diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index e8b1dbe135..2b448574f7 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -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-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': ('{{ 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 # according to the current autoescape setting.