Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Applied nonrel changes from latest Bitbucket revision

  • Loading branch information...
commit 39c56d3528d7171c4aa2360ad30ce999fd69e33b 1 parent 61a1444
@jonashaag jonashaag authored
Showing with 294 additions and 91 deletions.
  1. +9 −2 .hgignore
  2. +1 −1  django/contrib/admin/sites.py
  3. +1 −1  django/contrib/admin/templates/registration/password_reset_email.html
  4. +2 −2 django/contrib/auth/forms.py
  5. +6 −5 django/contrib/auth/management/__init__.py
  6. +1 −1  django/contrib/auth/models.py
  7. +7 −0 django/contrib/auth/tests/auth_backends.py
  8. +3 −0  django/contrib/auth/tests/forms.py
  9. +1 −1  django/contrib/auth/tests/templates/registration/password_reset_email.html
  10. +2 −2 django/contrib/auth/tests/views.py
  11. +1 −1  django/contrib/auth/urls.py
  12. +6 −6 django/contrib/auth/views.py
  13. +5 −1 django/core/files/uploadhandler.py
  14. +16 −3 django/core/serializers/python.py
  15. +14 −0 django/db/backends/__init__.py
  16. +14 −0 django/db/backends/creation.py
  17. +1 −1  django/db/backends/mysql/base.py
  18. +1 −1  django/db/backends/sqlite3/base.py
  19. +34 −5 django/db/models/base.py
  20. +3 −0  django/db/models/deletion.py
  21. +39 −14 django/db/models/fields/__init__.py
  22. +3 −8 django/db/models/fields/related.py
  23. +8 −6 django/db/models/query.py
  24. +6 −0 django/db/models/sql/compiler.py
  25. +1 −3 django/db/models/sql/query.py
  26. +5 −2 django/http/multipartparser.py
  27. +4 −1 django/test/client.py
  28. +2 −1  django/utils/encoding.py
  29. +12 −0 django/utils/http.py
  30. +9 −1 docs/topics/http/file-uploads.txt
  31. +10 −0 tests/regressiontests/file_uploads/tests.py
  32. +39 −1 tests/regressiontests/file_uploads/uploadhandler.py
  33. +9 −8 tests/regressiontests/file_uploads/urls.py
  34. +7 −1 tests/regressiontests/file_uploads/views.py
  35. +12 −12 tests/regressiontests/formwizard/templates/forms/wizard.html
View
11 .hgignore
@@ -1,6 +1,13 @@
-syntax:glob
-
+syntax: glob
+*~
+*.tmp
+*.swp
*.egg-info
*.pot
*.py[co]
docs/_build/
+*.orig
+desktop.ini
+nbproject
+tests/django
+build
View
2  django/contrib/admin/sites.py
@@ -231,7 +231,7 @@ def wrapper(*args, **kwargs):
url(r'^jsi18n/$',
wrap(self.i18n_javascript, cacheable=True),
name='jsi18n'),
- url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
+ url(r'^r/(?P<content_type_id>[a-z\d]+)/(?P<object_id>.+)/$',
wrap(contenttype_views.shortcut)),
url(r'^(?P<app_label>\w+)/$',
wrap(self.app_index),
View
2  django/contrib/admin/templates/registration/password_reset_email.html
@@ -3,7 +3,7 @@
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
-{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
+{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
View
4 django/contrib/auth/forms.py
@@ -5,7 +5,7 @@
from django.template import Context, loader
from django import forms
from django.utils.translation import ugettext_lazy as _
-from django.utils.http import int_to_base36
+from django.utils.http import urlsafe_base64_encode
class UserCreationForm(forms.ModelForm):
"""
@@ -138,7 +138,7 @@ def save(self, domain_override=None, email_template_name='registration/password_
'email': user.email,
'domain': domain,
'site_name': site_name,
- 'uid': int_to_base36(user.id),
+ 'uid': urlsafe_base64_encode(str(user.id)),
'user': user,
'token': token_generator.make_token(user),
'protocol': use_https and 'https' or 'http',
View
11 django/contrib/auth/management/__init__.py
@@ -35,11 +35,12 @@ def create_permissions(app, created_models, verbosity, **kwargs):
# Find all the Permissions that have a context_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
- all_perms = set(auth_app.Permission.objects.filter(
- content_type__in=ctypes,
- ).values_list(
- "content_type", "codename"
- ))
+ all_perms = set()
+ ctypes_pks = set(ct.pk for ct in ctypes)
+ for ctype, codename in auth_app.Permission.objects.all().values_list(
+ 'content_type', 'codename')[:1000000]:
+ if ctype in ctypes_pks:
+ all_perms.add((ctype, codename))
for ctype, (codename, name) in searched_perms:
# If the permissions exists, move on.
View
2  django/contrib/auth/models.py
@@ -83,7 +83,7 @@ class Meta:
verbose_name = _('permission')
verbose_name_plural = _('permissions')
unique_together = (('content_type', 'codename'),)
- ordering = ('content_type__app_label', 'content_type__model', 'codename')
+ ordering = ('codename',)
def __unicode__(self):
return u"%s | %s | %s" % (
View
7 django/contrib/auth/tests/auth_backends.py
@@ -4,7 +4,9 @@
from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
+from django.db import connection
from django.test import TestCase
+from django.utils import unittest
class BackendTest(TestCase):
@@ -100,6 +102,9 @@ def test_get_all_superuser_permissions(self):
user = User.objects.get(username='test2')
self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
+BackendTest = unittest.skipIf(not connection.features.supports_joins,
+ 'Requires JOIN support')(BackendTest)
+
class TestObj(object):
pass
@@ -203,6 +208,8 @@ def test_get_group_permissions(self):
self.user3.groups.add(group)
self.assertEqual(self.user3.get_group_permissions(TestObj()), set(['group_perm']))
+RowlevelBackendTest = unittest.skipIf(not connection.features.supports_joins,
+ 'Requires JOIN support')(RowlevelBackendTest)
class AnonymousUserBackend(SimpleRowlevelBackend):
View
3  django/contrib/auth/tests/forms.py
@@ -1,6 +1,8 @@
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm
+from django.db import connection
from django.test import TestCase
+from django.utils import unittest
class UserCreationFormTest(TestCase):
@@ -191,6 +193,7 @@ class UserChangeFormTest(TestCase):
fixtures = ['authtestdata.json']
+ @unittest.skipIf(not connection.features.supports_joins, 'Requires JOIN support')
def test_username_validity(self):
user = User.objects.get(username='testclient')
data = {'username': 'not valid'}
View
2  django/contrib/auth/tests/templates/registration/password_reset_email.html
@@ -1 +1 @@
-{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/
+{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/
View
4 django/contrib/auth/tests/views.py
@@ -100,13 +100,13 @@ def test_confirm_invalid(self):
def test_confirm_invalid_user(self):
# Ensure that we get a 200 response for a non-existant user, not a 404
- response = self.client.get('/reset/123456-1-1/')
+ response = self.client.get('/reset/123456/1-1/')
self.assertEqual(response.status_code, 200)
self.assertTrue("The password reset link was invalid" in response.content)
def test_confirm_overflow_user(self):
# Ensure that we get a 200 response for a base36 user id that overflows int
- response = self.client.get('/reset/zzzzzzzzzzzzz-1-1/')
+ response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/')
self.assertEqual(response.status_code, 200)
self.assertTrue("The password reset link was invalid" in response.content)
View
2  django/contrib/auth/urls.py
@@ -11,7 +11,7 @@
(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
(r'^password_reset/$', 'django.contrib.auth.views.password_reset'),
(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'),
- (r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 'django.contrib.auth.views.password_reset_confirm'),
+ (r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 'django.contrib.auth.views.password_reset_confirm'),
(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete'),
)
View
12 django/contrib/auth/views.py
@@ -5,7 +5,7 @@
from django.http import HttpResponseRedirect, QueryDict
from django.shortcuts import render_to_response
from django.template import RequestContext
-from django.utils.http import base36_to_int
+from django.utils.http import urlsafe_base64_decode
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
@@ -173,7 +173,7 @@ def password_reset_done(request,
# Doesn't need csrf_protect since no-one can guess the URL
@never_cache
-def password_reset_confirm(request, uidb36=None, token=None,
+def password_reset_confirm(request, uidb64=None, token=None,
template_name='registration/password_reset_confirm.html',
token_generator=default_token_generator,
set_password_form=SetPasswordForm,
@@ -183,13 +183,13 @@ def password_reset_confirm(request, uidb36=None, token=None,
View that checks the hash in a password reset link and presents a
form for entering a new password.
"""
- assert uidb36 is not None and token is not None # checked by URLconf
+ assert uidb64 is not None and token is not None # checked by URLconf
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
try:
- uid_int = base36_to_int(uidb36)
- user = User.objects.get(id=uid_int)
- except (ValueError, User.DoesNotExist):
+ uid = urlsafe_base64_decode(str(uidb64))
+ user = User.objects.get(id=uid)
+ except (TypeError, ValueError, User.DoesNotExist):
user = None
if user is not None and token_generator.check_token(user, token):
View
6 django/core/files/uploadhandler.py
@@ -84,7 +84,8 @@ def handle_raw_input(self, input_data, META, content_length, boundary, encoding=
"""
pass
- def new_file(self, field_name, file_name, content_type, content_length, charset=None):
+ def new_file(self, field_name, file_name, content_type, content_length,
+ charset=None, content_type_extra=None):
"""
Signal that a new file has been started.
@@ -96,6 +97,9 @@ def new_file(self, field_name, file_name, content_type, content_length, charset=
self.content_type = content_type
self.content_length = content_length
self.charset = charset
+ if content_type_extra is None:
+ content_type_extra = {}
+ self.content_type_extra = content_type_extra
def receive_data_chunk(self, raw_data, start):
"""
View
19 django/core/serializers/python.py
@@ -62,10 +62,23 @@ def handle_m2m_field(self, obj, field):
if field.rel.through._meta.auto_created:
if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'):
m2m_value = lambda value: value.natural_key()
+ self._current[field.name] = [m2m_value(related)
+ for related in getattr(obj, field.name).iterator()]
+ elif field.rel.get_related_field().primary_key:
+ m2m_value = lambda value: smart_unicode(
+ getattr(value, related_query.target_field_name + '_id'),
+ strings_only=True)
+ related_query = getattr(obj, field.name)
+ filters = {related_query.source_field_name: obj._get_pk_val()}
+ query = field.rel.through.objects.filter(**filters)
+ self._current[field.name] = sorted((m2m_value(m2m_entity)
+ for m2m_entity in query),
+ reverse=True)
else:
- m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True)
- self._current[field.name] = [m2m_value(related)
- for related in getattr(obj, field.name).iterator()]
+ m2m_value = lambda value: smart_unicode(value._get_pk_val(),
+ strings_only=True)
+ self._current[field.name] = [m2m_value(related)
+ for related in getattr(obj, field.name).iterator()]
def getvalue(self):
return self.objects
View
14 django/db/backends/__init__.py
@@ -280,6 +280,11 @@ class BaseDatabaseFeatures(object):
related_fields_match_type = False
allow_sliced_subqueries = True
+ supports_joins = True
+ distinguishes_insert_from_update = True
+ supports_deleting_related_objects = True
+ supports_select_related = True
+
# Does the default test database allow multiple connections?
# Usually an indication that the test database is in-memory
test_db_allows_multiple_connections = True
@@ -668,6 +673,15 @@ def prep_for_like_query(self, x):
# need not necessarily be implemented using "LIKE" in the backend.
prep_for_iexact_query = prep_for_like_query
+ def value_to_db_auto(self, value):
+ """
+ Transform a value to an object compatible with the auto field required
+ by the backend driver for auto columns.
+ """
+ if value is None:
+ return None
+ return int(value)
+
def value_to_db_date(self, value):
"""
Transform a date value to an object compatible with what is expected
View
14 django/db/backends/creation.py
@@ -2,6 +2,7 @@
import time
from django.conf import settings
+from django.utils.datastructures import DictWrapper
# The prefix to put on the default database name when creating
# the test database.
@@ -26,6 +27,19 @@ def _digest(self, *args):
"""
return '%x' % (abs(hash(args)) % 4294967296L) # 2**32
+ def db_type(self, field):
+ return self._db_type(field, field.get_internal_type())
+
+ def related_db_type(self, field):
+ return self._db_type(field, field.get_related_internal_type())
+
+ def _db_type(self, field, internal_type):
+ data = DictWrapper(field.__dict__, self.connection.ops.quote_name, "qn_")
+ try:
+ return self.connection.creation.data_types[internal_type] % data
+ except KeyError:
+ return None
+
def sql_create_model(self, model, style, known_models=set()):
"""
Returns the SQL required to create a single model, as a tuple of:
View
2  django/db/backends/mysql/base.py
@@ -328,7 +328,7 @@ def _cursor(self):
def _rollback(self):
try:
- BaseDatabaseWrapper._rollback(self)
+ super(DatabaseWrapper, self)._rollback()
except Database.NotSupportedError:
pass
View
2  django/db/backends/sqlite3/base.py
@@ -218,7 +218,7 @@ def close(self):
# database. To prevent accidental data loss, ignore close requests on
# an in-memory db.
if self.settings_dict['NAME'] != ":memory:":
- BaseDatabaseWrapper.close(self)
+ super(DatabaseWrapper, self).close()
FORMAT_QMARK_REGEX = re.compile(r'(?<!%)%s')
View
39 django/db/models/base.py
@@ -273,6 +273,7 @@ class Model(object):
_deferred = False
def __init__(self, *args, **kwargs):
+ self._entity_exists = kwargs.pop('__entity_exists', False)
signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
# Set up the storage for instance state
@@ -362,6 +363,7 @@ def __init__(self, *args, **kwargs):
pass
if kwargs:
raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0])
+ self._original_pk = self.pk if self._meta.pk is not None else None
super(Model, self).__init__()
signals.post_init.send(sender=self.__class__, instance=self)
@@ -470,6 +472,7 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
('raw', 'cls', and 'origin').
"""
using = using or router.db_for_write(self.__class__, instance=self)
+ entity_exists = bool(self._entity_exists and self._original_pk == self.pk)
connection = connections[using]
assert not (force_insert and force_update)
if cls is None:
@@ -516,7 +519,19 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
pk_set = pk_val is not None
record_exists = True
manager = cls._base_manager
- if pk_set:
+ # TODO/NONREL: Some backends could emulate force_insert/_update
+ # with an optimistic transaction, but since it's costly we should
+ # only do it when the user explicitly wants it.
+ # By adding support for an optimistic locking transaction
+ # in Django (SQL: SELECT ... FOR UPDATE) we could even make that
+ # part fully reusable on all backends (the current .exists()
+ # check below isn't really safe if you have lots of concurrent
+ # requests. BTW, and neither is QuerySet.get_or_create).
+ try_update = connection.features.distinguishes_insert_from_update
+ if not try_update:
+ record_exists = False
+
+ if try_update and pk_set:
# Determine whether a record with the primary key already exists.
if (force_update or (not force_insert and
manager.using(using).filter(pk=pk_val).exists())):
@@ -536,13 +551,18 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
self._order = order_value
+ if connection.features.distinguishes_insert_from_update:
+ add = True
+ else:
+ add = not entity_exists
+
if not pk_set:
if force_update:
raise ValueError("Cannot force an update in save() with no primary key.")
- values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
+ values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, add), connection=connection))
for f in meta.local_fields if not isinstance(f, AutoField)]
else:
- values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
+ values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, add), connection=connection))
for f in meta.local_fields]
record_exists = False
@@ -564,11 +584,17 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
# Once saved, this is no longer a to-be-added instance.
self._state.adding = False
+ self._entity_exists = True
+ self._original_pk = self.pk
+
# Signal that the save is complete
if origin and not meta.auto_created:
+ if connection.features.distinguishes_insert_from_update:
+ created = not record_exists
+ else:
+ created = not entity_exists
signals.post_save.send(sender=origin, instance=self,
- created=(not record_exists), raw=raw, using=using)
-
+ created=created, raw=raw, using=using)
save_base.alters_data = True
@@ -580,6 +606,9 @@ def delete(self, using=None):
collector.collect([self])
collector.delete()
+ self._entity_exists = False
+ self._original_pk = None
+
delete.alters_data = True
def _get_FIELD_display(self, field):
View
3  django/db/models/deletion.py
@@ -142,6 +142,9 @@ def collect(self, objs, source=None, nullable=False, collect_related=True,
models, the one case in which the cascade follows the forwards
direction of an FK rather than the reverse direction.)
"""
+ if not connections[self.using].features.supports_deleting_related_objects:
+ collect_related = False
+
new_objs = self.add(objs, source, nullable,
reverse_dependency=reverse_dependency)
if not new_objs:
View
53 django/db/models/fields/__init__.py
@@ -13,7 +13,6 @@
from django.conf import settings
from django import forms
from django.core import exceptions, validators
-from django.utils.datastructures import DictWrapper
from django.utils.functional import curry
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _
@@ -215,11 +214,11 @@ def db_type(self, connection):
# mapped to one of the built-in Django field types. In this case, you
# can implement db_type() instead of get_internal_type() to specify
# exactly which wacky database column type you want to use.
- data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
- try:
- return connection.creation.data_types[self.get_internal_type()] % data
- except KeyError:
- return None
+ return connection.creation.db_type(self)
+
+ def related_db_type(self, connection):
+ # This is the db_type used by a ForeignKey.
+ return connection.creation.related_db_type(self)
def unique(self):
return self._unique or self.primary_key
@@ -252,6 +251,9 @@ def get_cache_name(self):
def get_internal_type(self):
return self.__class__.__name__
+ def get_related_internal_type(self):
+ return self.get_internal_type()
+
def pre_save(self, model_instance, add):
"Returns field's value just before saving."
return getattr(model_instance, self.attname)
@@ -462,21 +464,31 @@ def __init__(self, *args, **kwargs):
def get_internal_type(self):
return "AutoField"
+ def get_related_internal_type(self):
+ return "RelatedAutoField"
+
+ def related_db_type(self, connection):
+ db_type = super(AutoField, self).related_db_type(connection=connection)
+ if db_type is None:
+ return IntegerField().db_type(connection=connection)
+ return db_type
+
def to_python(self, value):
- if value is None:
- return value
- try:
- return int(value)
- except (TypeError, ValueError):
+ if not (value is None or isinstance(value, (basestring, int, long))):
raise exceptions.ValidationError(self.error_messages['invalid'])
+ return value
def validate(self, value, model_instance):
pass
def get_prep_value(self, value):
- if value is None:
- return None
- return int(value)
+ return value
+
+ def get_db_prep_value(self, value, connection, prepared=False):
+ # Casts AutoField into the format expected by the backend
+ if not prepared:
+ value = self.get_prep_value(value)
+ return connection.ops.value_to_db_auto(value)
def contribute_to_class(self, cls, name):
assert not cls._meta.has_auto_field, "A model can't have more than one AutoField."
@@ -979,6 +991,12 @@ def formfield(self, **kwargs):
class PositiveIntegerField(IntegerField):
description = _("Integer")
+ def related_db_type(self, connection):
+ if not connection.features.related_fields_match_type:
+ return IntegerField().related_db_type(connection=connection)
+ return super(PositiveIntegerField, self).related_db_type(
+ connection=connection)
+
def get_internal_type(self):
return "PositiveIntegerField"
@@ -989,6 +1007,13 @@ def formfield(self, **kwargs):
class PositiveSmallIntegerField(IntegerField):
description = _("Integer")
+
+ def related_db_type(self, connection):
+ if not connection.features.related_fields_match_type:
+ return IntegerField().related_db_type(connection=connection)
+ return super(PositiveSmallIntegerField, self).related_db_type(
+ connection=connection)
+
def get_internal_type(self):
return "PositiveSmallIntegerField"
View
11 django/db/models/fields/related.py
@@ -1,5 +1,5 @@
from django.conf import settings
-from django.db import connection, router, transaction
+from django.db import connection, router, transaction, connections
from django.db.backends import util
from django.db.models import signals, get_model
from django.db.models.fields import (AutoField, Field, IntegerField,
@@ -873,7 +873,7 @@ def get_db_prep_save(self, value, connection):
return None
else:
return self.rel.get_related_field().get_db_prep_save(value,
- connection=connection)
+ connection=connections[router.db_for_read(self.rel.to)])
def value_to_string(self, obj):
if not obj:
@@ -924,12 +924,7 @@ def db_type(self, connection):
# If the database needs similar types for key fields however, the only
# thing we can do is making AutoField an IntegerField.
rel_field = self.rel.get_related_field()
- if (isinstance(rel_field, AutoField) or
- (not connection.features.related_fields_match_type and
- isinstance(rel_field, (PositiveIntegerField,
- PositiveSmallIntegerField)))):
- return IntegerField().db_type(connection=connection)
- return rel_field.db_type(connection=connection)
+ return rel_field.related_db_type(connection=connections[router.db_for_read(rel_field.model)])
class OneToOneField(ForeignKey):
"""
View
14 django/db/models/query.py
@@ -216,7 +216,9 @@ def iterator(self):
An iterator over the results from applying this QuerySet to the
database.
"""
- fill_cache = self.query.select_related
+ fill_cache = False
+ if connections[self.db].features.supports_select_related:
+ fill_cache = self.query.select_related
if isinstance(fill_cache, dict):
requested = fill_cache
else:
@@ -280,10 +282,10 @@ def iterator(self):
if skip:
row_data = row[index_start:aggregate_start]
pk_val = row_data[pk_idx]
- obj = model_cls(**dict(zip(init_list, row_data)))
+ obj = model_cls(**dict(zip(init_list, row_data), __entity_exists=True))
else:
# Omit aggregates in object creation.
- obj = model(*row[index_start:aggregate_start])
+ obj = model(*row[index_start:aggregate_start], **{'__entity_exists': True})
# Store the source database of the object
obj._state.db = db
@@ -1195,9 +1197,9 @@ def get_cached_row(klass, row, index_start, using, max_depth=0, cur_depth=0,
obj = None
elif skip:
klass = deferred_class_factory(klass, skip)
- obj = klass(**dict(zip(init_list, fields)))
+ obj = klass(__entity_exists=True, **dict(zip(init_list, fields)))
else:
- obj = klass(*fields)
+ obj = klass(*fields, **{'__entity_exists': True})
else:
# Load all fields on klass
@@ -1213,7 +1215,7 @@ def get_cached_row(klass, row, index_start, using, max_depth=0, cur_depth=0,
if fields == (None,) * field_count:
obj = None
else:
- obj = klass(**dict(zip(field_names, fields)))
+ obj = klass(__entity_exists=True, **dict(zip(field_names, fields)))
# If an object was retrieved, set the database state.
if obj:
View
6 django/db/models/sql/compiler.py
@@ -708,6 +708,12 @@ def results_iter(self):
yield row
+ def has_results(self):
+ # This is always executed on a query clone, so we can modify self.query
+ self.query.add_extra({'a': 1}, None, None, None, None, None)
+ self.query.set_extra_mask(('a',))
+ return bool(self.execute_sql(SINGLE))
+
def execute_sql(self, result_type=MULTI):
"""
Run the query against the database and returns the result(s). The
View
4 django/db/models/sql/query.py
@@ -411,17 +411,15 @@ def get_count(self, using):
def has_results(self, using):
q = self.clone()
- q.add_extra({'a': 1}, None, None, None, None, None)
q.select = []
q.select_fields = []
q.default_cols = False
q.select_related = False
- q.set_extra_mask(('a',))
q.set_aggregate_mask(())
q.clear_ordering(True)
q.set_limits(high=1)
compiler = q.get_compiler(using=using)
- return bool(compiler.execute_sql(SINGLE))
+ return compiler.has_results()
def combine(self, rhs, connector):
"""
View
7 django/http/multipartparser.py
@@ -174,8 +174,11 @@ def parse(self):
file_name = self.IE_sanitize(unescape_entities(file_name))
content_type = meta_data.get('content-type', ('',))[0].strip()
+ content_type_extra = meta_data.get('content-type', (0,{}))[1]
+ if content_type_extra is None:
+ content_type_extra = {}
try:
- charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
+ charset = content_type_extra.get('charset', None)
except:
charset = None
@@ -190,7 +193,7 @@ def parse(self):
try:
handler.new_file(field_name, file_name,
content_type, content_length,
- charset)
+ charset, content_type_extra.copy())
except StopFutureHandlers:
break
View
5 django/test/client.py
@@ -146,7 +146,10 @@ def encode_multipart(boundary, data):
def encode_file(boundary, key, file):
to_str = lambda s: smart_str(s, settings.DEFAULT_CHARSET)
- content_type = mimetypes.guess_type(file.name)[0]
+ if hasattr(file, 'content_type'):
+ content_type = file.content_type
+ else:
+ content_type = mimetypes.guess_type(file.name)[0]
if content_type is None:
content_type = 'application/octet-stream'
return [
View
3  django/utils/encoding.py
@@ -48,7 +48,8 @@ def is_protected_type(obj):
types.NoneType,
int, long,
datetime.datetime, datetime.date, datetime.time,
- float, Decimal)
+ float, Decimal,
+ tuple, list, dict)
)
def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
View
12 django/utils/http.py
@@ -1,9 +1,11 @@
+import base64
import calendar
import datetime
import re
import sys
import urllib
import urlparse
+from binascii import Error as BinasciiError
from email.Utils import formatdate
from django.utils.encoding import smart_str, force_unicode
@@ -168,6 +170,16 @@ def int_to_base36(i):
factor -= 1
return ''.join(base36)
+def urlsafe_base64_encode(s):
+ return base64.urlsafe_b64encode(s).rstrip('\n=')
+
+def urlsafe_base64_decode(s):
+ assert isinstance(s, str)
+ try:
+ return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, '='))
+ except (LookupError, BinasciiError), e:
+ raise ValueError(e)
+
def parse_etags(etag_str):
"""
Parses a string with one or several etags passed in If-None-Match and
View
10 docs/topics/http/file-uploads.txt
@@ -200,6 +200,11 @@ define the following methods/attributes:
For ``text/*`` content-types, the character set (i.e. ``utf8``) supplied
by the browser. Again, "trust but verify" is the best policy here.
+.. attribute:: UploadedFile.content_type_extra
+
+ A dict containing the extra parameters that were passed to the
+ content-type header.
+
.. attribute:: UploadedFile.temporary_file_path()
Only files uploaded onto disk will have this method; it returns the full
@@ -357,7 +362,7 @@ attributes:
The default is 64*2\ :sup:`10` bytes, or 64 KB.
- ``FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset)``
+ ``FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset, content_type_extra)``
Callback signaling that a new file upload is starting. This is called
before any data has been fed to any upload handlers.
@@ -374,6 +379,9 @@ attributes:
``charset`` is the character set (i.e. ``utf8``) given by the browser.
Like ``content_length``, this sometimes won't be provided.
+ ``content_type_extra`` is a dict containing the extra parameters that
+ were passed to the content-type header.
+
This method may raise a ``StopFutureHandlers`` exception to prevent
future handlers from handling this file.
View
10 tests/regressiontests/file_uploads/tests.py
@@ -171,6 +171,16 @@ def test_custom_upload_handler(self):
got = simplejson.loads(response.content)
self.assertTrue('f' not in got)
+ def test_extra_content_type(self):
+ f = tempfile.NamedTemporaryFile()
+ f.write('a' * (2 ** 21))
+ f.seek(0)
+ f.content_type = 'text/plain; blob-key=upload blob key; other=test'
+
+ response = self.client.post("/file_uploads/content_type_extra/", {'f': f})
+ got = simplejson.loads(response.content)
+ self.assertEqual(got['f'], 'upload blob key')
+
def test_broken_custom_upload_handler(self):
f = tempfile.NamedTemporaryFile()
f.write('a' * (2 ** 21))
View
40 tests/regressiontests/file_uploads/uploadhandler.py
@@ -2,7 +2,10 @@
Upload handlers to test the upload API.
"""
-from django.core.files.uploadhandler import FileUploadHandler, StopUpload
+from django.core.files.uploadedfile import InMemoryUploadedFile
+from django.core.files.uploadhandler import (FileUploadHandler, StopUpload,
+ StopFutureHandlers)
+from StringIO import StringIO
class QuotaUploadHandler(FileUploadHandler):
"""
@@ -32,3 +35,38 @@ class ErroringUploadHandler(FileUploadHandler):
"""A handler that raises an exception."""
def receive_data_chunk(self, raw_data, start):
raise CustomUploadError("Oops!")
+
+class ContentTypeExtraUploadHandler(FileUploadHandler):
+ """
+ File upload handler that handles content_type_extra
+ """
+
+ def new_file(self, *args, **kwargs):
+ super(ContentTypeExtraUploadHandler, self).new_file(*args, **kwargs)
+ self.blobkey = self.content_type_extra.get('blob-key', '')
+ self.file = StringIO()
+ self.file.write(self.blobkey)
+ self.active = self.blobkey is not None
+ if self.active:
+ raise StopFutureHandlers()
+
+ def receive_data_chunk(self, raw_data, start):
+ """
+ Add the data to the StringIO file.
+ """
+ if not self.active:
+ return raw_data
+
+ def file_complete(self, file_size):
+ if not self.active:
+ return
+
+ self.file.seek(0)
+ return InMemoryUploadedFile(
+ file = self.file,
+ field_name = self.field_name,
+ name = self.file_name,
+ content_type = self.content_type,
+ size = file_size,
+ charset = self.charset
+ )
View
17 tests/regressiontests/file_uploads/urls.py
@@ -2,12 +2,13 @@
import views
urlpatterns = patterns('',
- (r'^upload/$', views.file_upload_view),
- (r'^verify/$', views.file_upload_view_verify),
- (r'^unicode_name/$', views.file_upload_unicode_name),
- (r'^echo/$', views.file_upload_echo),
- (r'^quota/$', views.file_upload_quota),
- (r'^quota/broken/$', views.file_upload_quota_broken),
- (r'^getlist_count/$', views.file_upload_getlist_count),
- (r'^upload_errors/$', views.file_upload_errors),
+ (r'^upload/$', views.file_upload_view),
+ (r'^verify/$', views.file_upload_view_verify),
+ (r'^unicode_name/$', views.file_upload_unicode_name),
+ (r'^echo/$', views.file_upload_echo),
+ (r'^quota/$', views.file_upload_quota),
+ (r'^quota/broken/$', views.file_upload_quota_broken),
+ (r'^getlist_count/$', views.file_upload_getlist_count),
+ (r'^upload_errors/$', views.file_upload_errors),
+ (r'^content_type_extra/$', views.file_upload_content_type_extra),
)
View
8 tests/regressiontests/file_uploads/views.py
@@ -3,7 +3,8 @@
from django.http import HttpResponse, HttpResponseServerError
from django.utils import simplejson
from models import FileModel, UPLOAD_TO
-from uploadhandler import QuotaUploadHandler, ErroringUploadHandler
+from uploadhandler import (QuotaUploadHandler, ErroringUploadHandler,
+ ContentTypeExtraUploadHandler)
from django.utils.hashcompat import sha_constructor
from tests import UNICODE_FILENAME
@@ -113,3 +114,8 @@ def file_upload_getlist_count(request):
def file_upload_errors(request):
request.upload_handlers.insert(0, ErroringUploadHandler())
return file_upload_echo(request)
+
+def file_upload_content_type_extra(request):
+ request.upload_handlers.insert(0, ContentTypeExtraUploadHandler())
+ r = dict([(k, f.read()) for k, f in request.FILES.items()])
+ return HttpResponse(simplejson.dumps(r))
View
24 tests/regressiontests/formwizard/templates/forms/wizard.html
@@ -1,13 +1,13 @@
-<html>
- <body>
- <p>Step {{ step }} of {{ step_count }}</p>
- <form action="." method="post">
- <table>
- {{ form }}
- </table>
- <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
- {{ previous_fields|safe }}
- <input type="submit">
- </form>
- </body>
+<html>
+ <body>
+ <p>Step {{ step }} of {{ step_count }}</p>
+ <form action="." method="post">
+ <table>
+ {{ form }}
+ </table>
+ <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
+ {{ previous_fields|safe }}
+ <input type="submit">
+ </form>
+ </body>
</html>
Please sign in to comment.
Something went wrong with that request. Please try again.