Permalink
Browse files

Upgrade Django to 1.4.2. Replace sphinxcontrip_httpdomain with python…

… 2.6 version
  • Loading branch information...
1 parent 4998c71 commit 9f29294ecb8463685e98747f3d9e406c0fdab031 @camd camd committed Nov 10, 2012
@@ -1,4 +1,4 @@
-VERSION = (1, 4, 1, 'final', 0)
+VERSION = (1, 4, 2, 'final', 0)
def get_version(version=None):
"""Derives a PEP386-compliant version number from VERSION."""
@@ -8,13 +8,13 @@
import datetime
from django.db import models
-from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
-
from django.contrib.admin.util import (get_model_from_relation,
reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value)
+from django.contrib.admin.options import IncorrectLookupParameters
class ListFilter(object):
title = None # Human-readable title to appear in the right sidebar.
@@ -129,7 +129,10 @@ def has_output(self):
return True
def queryset(self, request, queryset):
- return queryset.filter(**self.used_parameters)
+ try:
+ return queryset.filter(**self.used_parameters)
+ except ValidationError, e:
+ raise IncorrectLookupParameters(e)
@classmethod
def register(cls, test, list_filter_class, take_priority=False):
@@ -302,7 +305,7 @@ def __init__(self, field, request, params, model, model_admin, field_path):
else: # field is a models.DateField
today = now.date()
tomorrow = today + datetime.timedelta(days=1)
-
+
self.lookup_kwarg_since = '%s__gte' % field_path
self.lookup_kwarg_until = '%s__lt' % field_path
self.links = (
@@ -25,9 +25,9 @@ def setUpClass(cls):
@classmethod
def tearDownClass(cls):
- super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
if hasattr(cls, 'selenium'):
cls.selenium.quit()
+ super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
def wait_until(self, callback, timeout=10):
"""
@@ -102,4 +102,4 @@ def has_css_class(self, selector, klass):
`klass`.
"""
return (self.selenium.find_element_by_css_selector(selector)
- .get_attribute('class').find(klass) != -1)
+ .get_attribute('class').find(klass) != -1)
@@ -11,6 +11,11 @@ def __repr__(self):
def __getitem__(self, perm_name):
return self.user.has_perm("%s.%s" % (self.module_name, perm_name))
+ def __iter__(self):
+ # To fix 'item in perms.someapp' and __getitem__ iteraction we need to
+ # define __iter__. See #18979 for details.
+ raise TypeError("PermLookupDict is not iterable.")
+
def __nonzero__(self):
return self.user.has_module_perms(self.module_name)
@@ -2,12 +2,58 @@
from django.conf import global_settings
from django.contrib.auth import authenticate
+from django.contrib.auth.context_processors import PermWrapper, PermLookupDict
from django.db.models import Q
from django.template import context
from django.test import TestCase
from django.test.utils import override_settings
+class MockUser(object):
+ def has_module_perm(self, perm):
+ if perm == 'mockapp.someapp':
+ return True
+ return False
+
+ def has_perm(self, perm):
+ if perm == 'someperm':
+ return True
+ return False
+
+
+class PermWrapperTests(TestCase):
+ """
+ Test some details of the PermWrapper implementation.
+ """
+ class EQLimiterObject(object):
+ """
+ This object makes sure __eq__ will not be called endlessly.
+ """
+ def __init__(self):
+ self.eq_calls = 0
+
+ def __eq__(self, other):
+ if self.eq_calls > 0:
+ return True
+ self.eq_calls += 1
+ return False
+
+ def test_permwrapper_in(self):
+ """
+ Test that 'something' in PermWrapper doesn't end up in endless loop.
+ """
+ perms = PermWrapper(MockUser())
+ def raises():
+ self.EQLimiterObject() in perms
+ self.assertRaises(raises, TypeError)
+
+ def test_permlookupdict_in(self):
+ pldict = PermLookupDict(MockUser(), 'mockapp')
+ def raises():
+ self.EQLimiterObject() in pldict
+ self.assertRaises(raises, TypeError)
+
+
class AuthContextProcessorTests(TestCase):
"""
Tests for the ``django.contrib.auth.context_processors.auth`` processor
@@ -51,6 +51,7 @@ def userpage(request):
(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
(r'^remote_user/$', remote_user_auth_view),
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
+ (r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
(r'^login_required/$', login_required(password_reset)),
(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
@@ -7,6 +7,7 @@
from django.contrib.sites.models import Site, RequestSite
from django.contrib.auth.models import User
from django.core import mail
+from django.core.exceptions import SuspiciousOperation
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict
from django.utils.encoding import force_unicode
@@ -106,6 +107,42 @@ def test_email_found_custom_from(self):
self.assertEqual(len(mail.outbox), 1)
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
+ def test_admin_reset(self):
+ "If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
+ response = self.client.post('/admin_password_reset/',
+ {'email': 'staffmember@example.com'},
+ HTTP_HOST='adminsite.com'
+ )
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertTrue("http://adminsite.com" in mail.outbox[0].body)
+ self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
+
+ def test_poisoned_http_host(self):
+ "Poisoned HTTP_HOST headers can't be used for reset emails"
+ # This attack is based on the way browsers handle URLs. The colon
+ # should be used to separate the port, but if the URL contains an @,
+ # the colon is interpreted as part of a username for login purposes,
+ # making 'evil.com' the request domain. Since HTTP_HOST is used to
+ # produce a meaningful reset URL, we need to be certain that the
+ # HTTP_HOST header isn't poisoned. This is done as a check when get_host()
+ # is invoked, but we check here as a practical consequence.
+ with self.assertRaises(SuspiciousOperation):
+ self.client.post('/password_reset/',
+ {'email': 'staffmember@example.com'},
+ HTTP_HOST='www.example:dr.frankenstein@evil.tld'
+ )
+ self.assertEqual(len(mail.outbox), 0)
+
+ def test_poisoned_http_host_admin_site(self):
+ "Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
+ with self.assertRaises(SuspiciousOperation):
+ self.client.post('/admin_password_reset/',
+ {'email': 'staffmember@example.com'},
+ HTTP_HOST='www.example:dr.frankenstein@evil.tld'
+ )
+ self.assertEqual(len(mail.outbox), 0)
+
def _test_confirm_start(self):
# Start by creating the email
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
@@ -156,7 +156,7 @@ def password_reset(request, is_admin_site=False,
'request': request,
}
if is_admin_site:
- opts = dict(opts, domain_override=request.META['HTTP_HOST'])
+ opts = dict(opts, domain_override=request.get_host())
form.save(**opts)
return HttpResponseRedirect(post_reset_redirect)
else:
@@ -470,6 +470,14 @@ def autoinc_sql(self, table, column):
"""
return None
+ def bulk_batch_size(self, fields, objs):
+ """
+ Returns the maximum allowed batch size for the backend. The fields
+ are the fields going to be inserted in the batch, the objs contains
+ all the objects to be inserted.
+ """
+ return len(objs)
+
def date_extract_sql(self, lookup_type, field_name):
"""
Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
@@ -507,6 +515,17 @@ def deferrable_sql(self):
"""
return ''
+ def distinct_sql(self, fields):
+ """
+ Returns an SQL DISTINCT clause which removes duplicate rows from the
+ result set. If any fields are given, only the given fields are being
+ checked for duplicates.
+ """
+ if fields:
+ raise NotImplementedError('DISTINCT ON fields is not supported by this database backend')
+ else:
+ return 'DISTINCT'
+
def drop_foreignkey_sql(self):
"""
Returns the SQL command that drops a foreign key.
@@ -562,17 +581,6 @@ def fulltext_search_sql(self, field_name):
"""
raise NotImplementedError('Full-text search is not implemented for this database backend')
- def distinct_sql(self, fields):
- """
- Returns an SQL DISTINCT clause which removes duplicate rows from the
- result set. If any fields are given, only the given fields are being
- checked for duplicates.
- """
- if fields:
- raise NotImplementedError('DISTINCT ON fields is not supported by this database backend')
- else:
- return 'DISTINCT'
-
def last_executed_query(self, cursor, sql, params):
"""
Returns a string of the query last executed by the given cursor, with
@@ -83,7 +83,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_1000_query_parameters = False
supports_mixed_date_datetime_comparisons = False
has_bulk_insert = True
- can_combine_inserts_with_and_without_auto_increment_pk = True
+ can_combine_inserts_with_and_without_auto_increment_pk = False
def _supports_stddev(self):
"""Confirm support for STDDEV and related stats functions
@@ -104,6 +104,13 @@ def _supports_stddev(self):
return has_support
class DatabaseOperations(BaseDatabaseOperations):
+ def bulk_batch_size(self, fields, objs):
+ """
+ SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of
+ 999 variables per query.
+ """
+ return (999 // len(fields)) if len(fields) > 0 else len(objs)
+
def date_extract_sql(self, lookup_type, field_name):
# sqlite doesn't support extract, so we fake it with the user-defined
# function django_extract that's registered in connect(). Note that
@@ -1023,13 +1023,14 @@ class GenericIPAddressField(Field):
description = _("IP address")
default_error_messages = {}
- def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs):
+ def __init__(self, verbose_name=None, name=None, protocol='both',
+ unpack_ipv4=False, *args, **kwargs):
self.unpack_ipv4 = unpack_ipv4
self.default_validators, invalid_error_message = \
validators.ip_address_validators(protocol, unpack_ipv4)
self.default_error_messages['invalid'] = invalid_error_message
kwargs['max_length'] = 39
- Field.__init__(self, *args, **kwargs)
+ Field.__init__(self, verbose_name, name, *args, **kwargs)
def get_internal_type(self):
return "GenericIPAddressField"
@@ -377,7 +377,7 @@ def create(self, **kwargs):
obj.save(force_insert=True, using=self.db)
return obj
- def bulk_create(self, objs):
+ def bulk_create(self, objs, batch_size=None):
"""
Inserts each of the instances into the database. This does *not* call
save() on each of the instances, does not send any pre/post save
@@ -390,8 +390,10 @@ def bulk_create(self, objs):
# this could be implemented if you didn't have an autoincrement pk,
# and 2) you could do it by doing O(n) normal inserts into the parent
# tables to get the primary keys back, and then doing a single bulk
- # insert into the childmost table. We're punting on these for now
- # because they are relatively rare cases.
+ # insert into the childmost table. Some databases might allow doing
+ # this by using RETURNING clause for the insert query. We're punting
+ # on these for now because they are relatively rare cases.
+ assert batch_size is None or batch_size > 0
if self.model._meta.parents:
raise ValueError("Can't bulk create an inherited model")
if not objs:
@@ -407,13 +409,14 @@ def bulk_create(self, objs):
try:
if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
and self.model._meta.has_auto_field):
- self.model._base_manager._insert(objs, fields=fields, using=self.db)
+ self._batched_insert(objs, fields, batch_size)
else:
objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs)
if objs_with_pk:
- self.model._base_manager._insert(objs_with_pk, fields=fields, using=self.db)
+ self._batched_insert(objs_with_pk, fields, batch_size)
if objs_without_pk:
- self.model._base_manager._insert(objs_without_pk, fields=[f for f in fields if not isinstance(f, AutoField)], using=self.db)
+ fields= [f for f in fields if not isinstance(f, AutoField)]
+ self._batched_insert(objs_without_pk, fields, batch_size)
if forced_managed:
transaction.commit(using=self.db)
else:
@@ -849,6 +852,20 @@ def db(self):
###################
# PRIVATE METHODS #
###################
+ def _batched_insert(self, objs, fields, batch_size):
+ """
+ A little helper method for bulk_insert to insert the bulk one batch
+ at a time. Inserts recursively a batch from the front of the bulk and
+ then _batched_insert() the remaining objects again.
+ """
+ if not objs:
+ return
+ ops = connections[self.db].ops
+ batch_size = (batch_size or max(ops.bulk_batch_size(fields, objs), 1))
+ for batch in [objs[i:i+batch_size]
+ for i in range(0, len(objs), batch_size)]:
+ self.model._base_manager._insert(batch, fields=fields,
+ using=self.db)
def _clone(self, klass=None, setup=False, **kwargs):
if klass is None:
@@ -212,6 +212,11 @@ def get_host(self):
server_port = str(self.META['SERVER_PORT'])
if server_port != (self.is_secure() and '443' or '80'):
host = '%s:%s' % (host, server_port)
+
+ # Disallow potentially poisoned hostnames.
+ if set(';/?@&=+$,').intersection(host):
+ raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)
+
return host
def get_full_path(self):
@@ -183,3 +183,15 @@ def filepath_to_uri(path):
codecs.lookup(DEFAULT_LOCALE_ENCODING)
except:
DEFAULT_LOCALE_ENCODING = 'ascii'
+
+# Forwards compatibility with Django 1.5
+
+def python_2_unicode_compatible(klass):
+ # Always use the Python 2 branch of the decorator here in Django 1.4
+ klass.__unicode__ = klass.__str__
+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+ return klass
+
+smart_text = smart_unicode
+force_text = force_unicode
+smart_bytes = smart_str
Oops, something went wrong.

0 comments on commit 9f29294

Please sign in to comment.