Skip to content
Browse files

newforms-admin: Merged from trunk up to [7877].

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7881 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 9cbd07b commit 60c060c4762f55418da5628bbe6525e7820e9c61 @brosner brosner committed
Showing with 1,341 additions and 306 deletions.
  1. +1 −0 AUTHORS
  2. +2 −3 django/contrib/admin/views/main.py
  3. +114 −43 django/core/files/uploadedfile.py
  4. +6 −37 django/core/files/uploadhandler.py
  5. +6 −2 django/core/handlers/base.py
  6. +2 −0 django/core/mail.py
  7. +17 −15 django/core/paginator.py
  8. +35 −34 django/db/models/base.py
  9. +8 −5 django/db/models/fields/__init__.py
  10. +70 −51 django/db/models/query.py
  11. +1 −0 django/http/multipartparser.py
  12. +51 −0 django/http/utils.py
  13. +6 −21 django/newforms/fields.py
  14. +1 −1 django/oldforms/__init__.py
  15. +32 −19 django/test/client.py
  16. +9 −0 django/utils/translation/trans_real.py
  17. +2 −2 django/views/debug.py
  18. +2 −2 django/views/generic/list_detail.py
  19. +4 −5 docs/generic_views.txt
  20. +2 −13 docs/newforms.txt
  21. +20 −7 docs/pagination.txt
  22. +1 −1 docs/settings.txt
  23. +27 −23 docs/upload_handling.txt
  24. +4 −4 tests/modeltests/model_forms/models.py
  25. +8 −8 tests/modeltests/pagination/models.py
  26. 0 tests/regressiontests/admin_scripts/__init__.py
  27. 0 tests/regressiontests/admin_scripts/management/__init__.py
  28. 0 tests/regressiontests/admin_scripts/management/commands/__init__.py
  29. +10 −0 tests/regressiontests/admin_scripts/management/commands/app_command.py
  30. +9 −0 tests/regressiontests/admin_scripts/management/commands/base_command.py
  31. +9 −0 tests/regressiontests/admin_scripts/management/commands/label_command.py
  32. +9 −0 tests/regressiontests/admin_scripts/management/commands/noargs_command.py
  33. +12 −0 tests/regressiontests/admin_scripts/models.py
  34. +817 −0 tests/regressiontests/admin_scripts/tests.py
  35. +27 −4 tests/regressiontests/file_uploads/tests.py
  36. +1 −0 tests/regressiontests/file_uploads/urls.py
  37. +14 −4 tests/regressiontests/file_uploads/views.py
  38. +2 −2 tests/regressiontests/forms/fields.py
View
1 AUTHORS
@@ -287,6 +287,7 @@ answer newbie questions, and generally made Django that much better:
Neal Norwitz <nnorwitz@google.com>
Todd O'Bryan <toddobryan@mac.com>
oggie rob <oz.robharvey@gmail.com>
+ oggy <ognjen.maric@gmail.com>
Jay Parlar <parlar@gmail.com>
Carlos Eduardo de Paula <carlosedp@gmail.com>
pavithran s <pavithran.s@gmail.com>
View
5 django/contrib/admin/views/main.py
@@ -1,6 +1,6 @@
from django.contrib.admin.filterspecs import FilterSpec
from django.contrib.admin.options import IncorrectLookupParameters
-from django.core.paginator import QuerySetPaginator, InvalidPage
+from django.core.paginator import Paginator, InvalidPage
from django.db import models
from django.db.models.query import QuerySet
from django.utils.encoding import force_unicode, smart_str
@@ -109,8 +109,7 @@ def get_query_string(self, new_params=None, remove=None):
return '?%s' % urlencode(p)
def get_results(self, request):
- paginator = QuerySetPaginator(self.query_set, self.list_per_page)
-
+ paginator = Paginator(self.query_set, self.list_per_page)
# Get the number of objects, with admin filters applied.
try:
result_count = paginator.count
View
157 django/core/files/uploadedfile.py
@@ -3,12 +3,40 @@
"""
import os
+import tempfile
+import warnings
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
-__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')
+from django.conf import settings
+
+__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile', 'SimpleUploadedFile')
+
+# Because we fooled around with it a bunch, UploadedFile has a bunch
+# of deprecated properties. This little shortcut helps define 'em
+# without too much code duplication.
+def deprecated_property(old, new, readonly=False):
+ def issue_warning():
+ warnings.warn(
+ message = "UploadedFile.%s is deprecated; use UploadedFile.%s instead." % (old, new),
+ category = DeprecationWarning,
+ stacklevel = 3
+ )
+
+ def getter(self):
+ issue_warning()
+ return getattr(self, new)
+
+ def setter(self, value):
+ issue_warning()
+ setattr(self, new, value)
+
+ if readonly:
+ return property(getter)
+ else:
+ return property(getter, setter)
class UploadedFile(object):
"""
@@ -20,34 +48,34 @@ class UploadedFile(object):
"""
DEFAULT_CHUNK_SIZE = 64 * 2**10
- def __init__(self, file_name=None, content_type=None, file_size=None, charset=None):
- self.file_name = file_name
- self.file_size = file_size
+ def __init__(self, name=None, content_type=None, size=None, charset=None):
+ self.name = name
+ self.size = size
self.content_type = content_type
self.charset = charset
def __repr__(self):
- return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type)
+ return "<%s: %s (%s)>" % (self.__class__.__name__, self.name, self.content_type)
+
+ def _get_name(self):
+ return self._name
- def _set_file_name(self, name):
+ def _set_name(self, name):
# Sanitize the file name so that it can't be dangerous.
if name is not None:
# Just use the basename of the file -- anything else is dangerous.
name = os.path.basename(name)
-
+
# File names longer than 255 characters can cause problems on older OSes.
if len(name) > 255:
name, ext = os.path.splitext(name)
name = name[:255 - len(ext)] + ext
-
- self._file_name = name
-
- def _get_file_name(self):
- return self._file_name
-
- file_name = property(_get_file_name, _set_file_name)
- def chunk(self, chunk_size=None):
+ self._name = name
+
+ name = property(_get_name, _set_name)
+
+ def chunks(self, chunk_size=None):
"""
Read the file and yield chucks of ``chunk_size`` bytes (defaults to
``UploadedFile.DEFAULT_CHUNK_SIZE``).
@@ -58,12 +86,27 @@ def chunk(self, chunk_size=None):
if hasattr(self, 'seek'):
self.seek(0)
# Assume the pointer is at zero...
- counter = self.file_size
+ counter = self.size
while counter > 0:
yield self.read(chunk_size)
counter -= chunk_size
+ # Deprecated properties
+ filename = deprecated_property(old="filename", new="name")
+ file_name = deprecated_property(old="file_name", new="name")
+ file_size = deprecated_property(old="file_size", new="size")
+ chunk = deprecated_property(old="chunk", new="chunks", readonly=True)
+
+ def _get_data(self):
+ warnings.warn(
+ message = "UploadedFile.data is deprecated; use UploadedFile.read() instead.",
+ category = DeprecationWarning,
+ stacklevel = 2
+ )
+ return self.read()
+ data = property(_get_data)
+
def multiple_chunks(self, chunk_size=None):
"""
Returns ``True`` if you can expect multiple chunks.
@@ -74,9 +117,9 @@ def multiple_chunks(self, chunk_size=None):
"""
if not chunk_size:
chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
- return self.file_size < chunk_size
+ return self.size > chunk_size
- # Abstract methods; subclasses *must* default read() and probably should
+ # Abstract methods; subclasses *must* define read() and probably should
# define open/close.
def read(self, num_bytes=None):
raise NotImplementedError()
@@ -87,23 +130,49 @@ def open(self):
def close(self):
pass
+ def xreadlines(self):
+ return self
+
+ def readlines(self):
+ return list(self.xreadlines())
+
+ def __iter__(self):
+ # Iterate over this file-like object by newlines
+ buffer_ = None
+ for chunk in self.chunks():
+ chunk_buffer = StringIO(chunk)
+
+ for line in chunk_buffer:
+ if buffer_:
+ line = buffer_ + line
+ buffer_ = None
+
+ # If this is the end of a line, yield
+ # otherwise, wait for the next round
+ if line[-1] in ('\n', '\r'):
+ yield line
+ else:
+ buffer_ = line
+
+ if buffer_ is not None:
+ yield buffer_
+
# Backwards-compatible support for uploaded-files-as-dictionaries.
def __getitem__(self, key):
- import warnings
warnings.warn(
message = "The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.",
category = DeprecationWarning,
stacklevel = 2
)
backwards_translate = {
- 'filename': 'file_name',
+ 'filename': 'name',
'content-type': 'content_type',
- }
+ }
if key == 'content':
return self.read()
elif key == 'filename':
- return self.file_name
+ return self.name
elif key == 'content-type':
return self.content_type
else:
@@ -113,34 +182,36 @@ class TemporaryUploadedFile(UploadedFile):
"""
A file uploaded to a temporary location (i.e. stream-to-disk).
"""
-
- def __init__(self, file, file_name, content_type, file_size, charset):
- super(TemporaryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
- self.file = file
- self.path = file.name
- self.file.seek(0)
+ def __init__(self, name, content_type, size, charset):
+ super(TemporaryUploadedFile, self).__init__(name, content_type, size, charset)
+ if settings.FILE_UPLOAD_TEMP_DIR:
+ self._file = tempfile.NamedTemporaryFile(suffix='.upload', dir=settings.FILE_UPLOAD_TEMP_DIR)
+ else:
+ self._file = tempfile.NamedTemporaryFile(suffix='.upload')
def temporary_file_path(self):
"""
Returns the full path of this file.
"""
- return self.path
-
- def read(self, *args, **kwargs):
- return self.file.read(*args, **kwargs)
-
- def open(self):
- self.seek(0)
-
- def seek(self, *args, **kwargs):
- self.file.seek(*args, **kwargs)
+ return self.name
+
+ # Most methods on this object get proxied to NamedTemporaryFile.
+ # We can't directly subclass because NamedTemporaryFile is actually a
+ # factory function
+ def read(self, *args): return self._file.read(*args)
+ def seek(self, offset): return self._file.seek(offset)
+ def write(self, s): return self._file.write(s)
+ def close(self): return self._file.close()
+ def __iter__(self): return iter(self._file)
+ def readlines(self, size=None): return self._file.readlines(size)
+ def xreadlines(self): return self._file.xreadlines()
class InMemoryUploadedFile(UploadedFile):
"""
A file uploaded into memory (i.e. stream-to-memory).
"""
- def __init__(self, file, field_name, file_name, content_type, file_size, charset):
- super(InMemoryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
+ def __init__(self, file, field_name, name, content_type, size, charset):
+ super(InMemoryUploadedFile, self).__init__(name, content_type, size, charset)
self.file = file
self.field_name = field_name
self.file.seek(0)
@@ -154,7 +225,7 @@ def open(self):
def read(self, *args, **kwargs):
return self.file.read(*args, **kwargs)
- def chunk(self, chunk_size=None):
+ def chunks(self, chunk_size=None):
self.file.seek(0)
yield self.read()
@@ -168,9 +239,9 @@ class SimpleUploadedFile(InMemoryUploadedFile):
"""
def __init__(self, name, content, content_type='text/plain'):
self.file = StringIO(content or '')
- self.file_name = name
+ self.name = name
self.field_name = None
- self.file_size = len(content or '')
+ self.size = len(content or '')
self.content_type = content_type
self.charset = None
self.file.seek(0)
View
43 django/core/files/uploadhandler.py
@@ -132,21 +132,15 @@ def new_file(self, file_name, *args, **kwargs):
Create the file object to append to as data is coming in.
"""
super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs)
- self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR)
- self.write = self.file.write
+ self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset)
def receive_data_chunk(self, raw_data, start):
- self.write(raw_data)
+ self.file.write(raw_data)
def file_complete(self, file_size):
self.file.seek(0)
- return TemporaryUploadedFile(
- file = self.file,
- file_name = self.file_name,
- content_type = self.content_type,
- file_size = file_size,
- charset = self.charset
- )
+ self.file.size = file_size
+ return self.file
class MemoryFileUploadHandler(FileUploadHandler):
"""
@@ -189,37 +183,12 @@ def file_complete(self, file_size):
return InMemoryUploadedFile(
file = self.file,
field_name = self.field_name,
- file_name = self.file_name,
+ name = self.file_name,
content_type = self.content_type,
- file_size = file_size,
+ size = file_size,
charset = self.charset
)
-class TemporaryFile(object):
- """
- A temporary file that tries to delete itself when garbage collected.
- """
- def __init__(self, dir):
- if not dir:
- dir = tempfile.gettempdir()
- try:
- (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir)
- self.file = os.fdopen(fd, 'w+b')
- except (OSError, IOError):
- raise OSError("Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?")
- self.name = name
-
- def __getattr__(self, name):
- a = getattr(self.__dict__['file'], name)
- if type(a) != type(0):
- setattr(self, name, a)
- return a
-
- def __del__(self):
- try:
- os.unlink(self.name)
- except OSError:
- pass
def load_handler(path, *args, **kwargs):
"""
View
8 django/core/handlers/base.py
@@ -6,8 +6,12 @@
class BaseHandler(object):
# Changes that are always applied to a response (in this order).
- response_fixes = [http.fix_location_header,
- http.conditional_content_removal]
+ response_fixes = [
+ http.fix_location_header,
+ http.conditional_content_removal,
+ http.fix_IE_for_attach,
+ http.fix_IE_for_vary,
+ ]
def __init__(self):
self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
View
2 django/core/mail.py
@@ -205,10 +205,12 @@ def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
conversions.
"""
if to:
+ assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
self.to = list(to)
else:
self.to = []
if bcc:
+ assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
self.bcc = list(bcc)
else:
self.bcc = []
View
32 django/core/paginator.py
@@ -1,6 +1,12 @@
class InvalidPage(Exception):
pass
+class PageNotAnInteger(InvalidPage):
+ pass
+
+class EmptyPage(InvalidPage):
+ pass
+
class Paginator(object):
def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):
self.object_list = object_list
@@ -14,14 +20,14 @@ def validate_number(self, number):
try:
number = int(number)
except ValueError:
- raise InvalidPage('That page number is not an integer')
+ raise PageNotAnInteger('That page number is not an integer')
if number < 1:
- raise InvalidPage('That page number is less than 1')
+ raise EmptyPage('That page number is less than 1')
if number > self.num_pages:
if number == 1 and self.allow_empty_first_page:
pass
else:
- raise InvalidPage('That page contains no results')
+ raise EmptyPage('That page contains no results')
return number
def page(self, number):
@@ -36,7 +42,11 @@ def page(self, number):
def _get_count(self):
"Returns the total number of objects, across all pages."
if self._count is None:
- self._count = len(self.object_list)
+ from django.db.models.query import QuerySet
+ if isinstance(self.object_list, QuerySet):
+ self._count = self.object_list.count()
+ else:
+ self._count = len(self.object_list)
return self._count
count = property(_get_count)
@@ -61,15 +71,7 @@ def _get_page_range(self):
return range(1, self.num_pages + 1)
page_range = property(_get_page_range)
-class QuerySetPaginator(Paginator):
- """
- Like Paginator, but works on QuerySets.
- """
- def _get_count(self):
- if self._count is None:
- self._count = self.object_list.count()
- return self._count
- count = property(_get_count)
+QuerySetPaginator = Paginator # For backwards-compatibility.
class Page(object):
def __init__(self, object_list, number, paginator):
@@ -133,14 +135,14 @@ def validate_page_number(self, page_number):
try:
page_number = int(page_number) + 1
except ValueError:
- raise InvalidPage
+ raise PageNotAnInteger
return self.validate_number(page_number)
def get_page(self, page_number):
try:
page_number = int(page_number) + 1
except ValueError:
- raise InvalidPage
+ raise PageNotAnInteger
return self.page(page_number).object_list
def has_next_page(self, page_number):
View
69 django/db/models/base.py
@@ -3,6 +3,10 @@
import sys
import os
from itertools import izip
+try:
+ set
+except NameError:
+ from sets import Set as set # Python 2.3 fallback.
import django.db.models.manipulators # Imported to register signal handler.
import django.db.models.manager # Ditto.
@@ -23,13 +27,11 @@
from django.core.files import locks
from django.conf import settings
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
class ModelBase(type):
- "Metaclass for all models"
+ """
+ Metaclass for all models.
+ """
def __new__(cls, name, bases, attrs):
super_new = super(ModelBase, cls).__new__
parents = [b for b in bases if isinstance(b, ModelBase)]
@@ -141,7 +143,9 @@ def add_to_class(cls, name, value):
setattr(cls, name, value)
def _prepare(cls):
- # Creates some methods once self._meta has been populated.
+ """
+ Creates some methods once self._meta has been populated.
+ """
opts = cls._meta
opts._prepare(cls)
@@ -160,6 +164,7 @@ def _prepare(cls):
dispatcher.send(signal=signals.class_prepared, sender=cls)
+
class Model(object):
__metaclass__ = ModelBase
@@ -264,7 +269,7 @@ def _set_pk_val(self, value):
def save(self):
"""
- Save the current instance. Override this in a subclass if you want to
+ Saves the current instance. Override this in a subclass if you want to
control the saving process.
"""
self.save_base()
@@ -290,7 +295,7 @@ def save_base(self, raw=False, cls=None):
# If we are in a raw save, save the object exactly as presented.
# That means that we don't try to be smart about saving attributes
- # that might have come from the parent class - we just save the
+ # that might have come from the parent class - we just save the
# attributes we have been given to the class we have been given.
if not raw:
for parent, field in meta.parents.items():
@@ -298,7 +303,7 @@ def save_base(self, raw=False, cls=None):
setattr(self, field.attname, self._get_pk_val(parent._meta))
non_pks = [f for f in meta.local_fields if not f.primary_key]
-
+
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
pk_val = self._get_pk_val(meta)
# Note: the comparison with '' is required for compatibility with
@@ -368,10 +373,12 @@ def validate(self):
def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
"""
- Recursively populates seen_objs with all objects related to this object.
+ Recursively populates seen_objs with all objects related to this
+ object.
+
When done, seen_objs.items() will be in the format:
[(model_class, {pk_val: obj, pk_val: obj, ...}),
- (model_class, {pk_val: obj, pk_val: obj, ...}),...]
+ (model_class, {pk_val: obj, pk_val: obj, ...}), ...]
"""
pk_val = self._get_pk_val()
if seen_objs.add(self.__class__, pk_val, self, parent, nullable):
@@ -408,11 +415,11 @@ def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
def delete(self):
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
- # Find all the objects than need to be deleted
+ # Find all the objects than need to be deleted.
seen_objs = CollectedObjects()
self._collect_sub_objects(seen_objs)
- # Actually delete the objects
+ # Actually delete the objects.
delete_objects(seen_objs)
delete.alters_data = True
@@ -451,12 +458,12 @@ def _get_next_or_previous_in_order(self, is_next):
return getattr(self, cachename)
def _get_FIELD_filename(self, field):
- if getattr(self, field.attname): # value is not blank
+ if getattr(self, field.attname): # Value is not blank.
return os.path.normpath(os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname)))
return ''
def _get_FIELD_url(self, field):
- if getattr(self, field.attname): # value is not blank
+ if getattr(self, field.attname): # Value is not blank.
import urlparse
return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
return ''
@@ -471,16 +478,14 @@ def _save_FIELD_file(self, field, filename, raw_field, save=True):
except OSError: # Directory probably already exists.
pass
- #
# Check for old-style usage (files-as-dictionaries). Warn here first
# since there are multiple locations where we need to support both new
# and old usage.
- #
if isinstance(raw_field, dict):
import warnings
warnings.warn(
message = "Representing uploaded files as dictionaries is"\
- " deprected. Use django.core.files.SimpleUploadedFile"\
+ " deprecated. Use django.core.files.SimpleUploadedFile"\
" instead.",
category = DeprecationWarning,
stacklevel = 2
@@ -505,41 +510,35 @@ def _save_FIELD_file(self, field, filename, raw_field, save=True):
filename = field.get_filename(filename)
- #
- # If the filename already exists, keep adding an underscore to the name of
- # the file until the filename doesn't exist.
- #
+ # If the filename already exists, keep adding an underscore to the name
+ # of the file until the filename doesn't exist.
while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
try:
dot_index = filename.rindex('.')
- except ValueError: # filename has no dot
+ except ValueError: # filename has no dot.
filename += '_'
else:
filename = filename[:dot_index] + '_' + filename[dot_index:]
- #
- # Save the file name on the object and write the file to disk
- #
+ # Save the file name on the object and write the file to disk.
setattr(self, field.attname, filename)
-
full_filename = self._get_FIELD_filename(field)
-
if hasattr(raw_field, 'temporary_file_path'):
# This file has a file path that we can move.
raw_field.close()
file_move_safe(raw_field.temporary_file_path(), full_filename)
-
else:
# This is a normal uploadedfile that we can stream.
fp = open(full_filename, 'wb')
locks.lock(fp, locks.LOCK_EX)
- for chunk in raw_field.chunk():
+ for chunk in raw_field.chunks():
fp.write(chunk)
locks.unlock(fp)
fp.close()
# Save the width and/or height, if applicable.
- if isinstance(field, ImageField) and (field.width_field or field.height_field):
+ if isinstance(field, ImageField) and \
+ (field.width_field or field.height_field):
from django.utils.images import get_image_dimensions
width, height = get_image_dimensions(full_filename)
if field.width_field:
@@ -547,7 +546,7 @@ def _save_FIELD_file(self, field, filename, raw_field, save=True):
if field.height_field:
setattr(self, field.height_field, height)
- # Save the object because it has changed unless save is False
+ # Save the object because it has changed, unless save is False.
if save:
self.save()
@@ -567,6 +566,7 @@ def _get_image_dimensions(self, field):
setattr(self, cachename, get_image_dimensions(filename))
return getattr(self, cachename)
+
############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
############################################
@@ -582,6 +582,7 @@ def method_set_order(ordered_obj, self, id_list):
ordered_obj.objects.filter(**{'pk': j, order_name: rel_val}).update(_order=i)
transaction.commit_unless_managed()
+
def method_get_order(ordered_obj, self):
rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
order_name = ordered_obj._meta.order_with_respect_to.name
@@ -589,6 +590,7 @@ def method_get_order(ordered_obj, self):
return [r[pk_name] for r in
ordered_obj.objects.filter(**{order_name: rel_val}).values(pk_name)]
+
##############################################
# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
##############################################
@@ -596,6 +598,7 @@ def method_get_order(ordered_obj, self):
def get_absolute_url(opts, func, self, *args, **kwargs):
return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self, *args, **kwargs)
+
########
# MISC #
########
@@ -607,8 +610,6 @@ class Empty(object):
# Prior to Python 2.5, Exception was an old-style class
def subclass_exception(name, parent, unused):
return types.ClassType(name, (parent,), {})
-
else:
def subclass_exception(name, parent, module):
return type(name, (parent,), {'__module__': module})
-
View
13 django/db/models/fields/__init__.py
@@ -751,9 +751,12 @@ def get_internal_type(self):
def get_db_prep_save(self, value):
"Returns field's value prepared for saving into a database."
# Need to convert UploadedFile objects provided via a form to unicode for database insertion
- if value is None:
+ if hasattr(value, 'name'):
+ return value.name
+ elif value is None:
return None
- return unicode(value)
+ else:
+ return unicode(value)
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
@@ -827,7 +830,7 @@ def save_file(self, new_data, new_object, original_object, change, rel, save=Tru
# We don't need to raise a warning because Model._save_FIELD_file will
# do so for us.
try:
- file_name = file.file_name
+ file_name = file.name
except AttributeError:
file_name = file['filename']
@@ -842,9 +845,9 @@ def get_filename(self, filename):
return os.path.normpath(f)
def save_form_data(self, instance, data):
- from django.newforms.fields import UploadedFile
+ from django.core.files.uploadedfile import UploadedFile
if data and isinstance(data, UploadedFile):
- getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
+ getattr(instance, "save_%s_file" % self.name)(data.name, data, save=False)
def formfield(self, **kwargs):
defaults = {'form_class': forms.FileField}
View
121 django/db/models/query.py
@@ -13,19 +13,20 @@
CHUNK_SIZE = 100
ITER_CHUNK_SIZE = CHUNK_SIZE
-# Pull into this namespace for backwards compatibility
+# Pull into this namespace for backwards compatibility.
EmptyResultSet = sql.EmptyResultSet
class CyclicDependency(Exception):
pass
+
class CollectedObjects(object):
"""
- A container that stores keys and lists of values along with
- remembering the parent objects for all the keys.
+ A container that stores keys and lists of values along with remembering the
+ parent objects for all the keys.
- This is used for the database object deletion routines so that we
- can calculate the 'leaf' objects which should be deleted first.
+ This is used for the database object deletion routines so that we can
+ calculate the 'leaf' objects which should be deleted first.
"""
def __init__(self):
@@ -34,26 +35,27 @@ def __init__(self):
def add(self, model, pk, obj, parent_model, nullable=False):
"""
- Adds an item.
- model is the class of the object being added,
- pk is the primary key, obj is the object itself,
- parent_model is the model of the parent object
- that this object was reached through, nullable should
- be True if this relation is nullable.
+ Adds an item to the container.
+
+ Arguments:
+ * model - the class of the object being added.
+ * pk - the primary key.
+ * obj - the object itself.
+ * parent_model - the model of the parent object that this object was
+ reached through.
+ * nullable - should be True if this relation is nullable.
- If the item already existed in the structure,
- returns true, otherwise false.
+ Returns True if the item already existed in the structure and
+ False otherwise.
"""
d = self.data.setdefault(model, SortedDict())
retval = pk in d
d[pk] = obj
- # Nullable relationships can be ignored -- they
- # are nulled out before deleting, and therefore
- # do not affect the order in which objects have
- # to be deleted.
+ # Nullable relationships can be ignored -- they are nulled out before
+ # deleting, and therefore do not affect the order in which objects
+ # have to be deleted.
if parent_model is not None and not nullable:
self.children.setdefault(parent_model, []).append(model)
-
return retval
def __contains__(self, key):
@@ -77,8 +79,8 @@ def keys(self):
def ordered_keys(self):
"""
- Returns the models in the order that they should be
- dealth with i.e. models with no dependencies first.
+ Returns the models in the order that they should be dealt with (i.e.
+ models with no dependencies first).
"""
dealt_with = SortedDict()
# Start with items that have no children
@@ -91,19 +93,22 @@ def ordered_keys(self):
dealt_with[model] = None
found = True
if not found:
- raise CyclicDependency("There is a cyclic dependency of items to be processed.")
+ raise CyclicDependency(
+ "There is a cyclic dependency of items to be processed.")
return dealt_with.keys()
def unordered_keys(self):
"""
- Fallback for the case where is a cyclic dependency but we
- don't care.
+ Fallback for the case where is a cyclic dependency but we don't care.
"""
return self.data.keys()
+
class QuerySet(object):
- "Represents a lazy database lookup for a set of objects"
+ """
+ Represents a lazy database lookup for a set of objects.
+ """
def __init__(self, model=None, query=None):
self.model = model
self.query = query or sql.Query(self.model, connection)
@@ -116,7 +121,7 @@ def __init__(self, model=None, query=None):
def __getstate__(self):
"""
- Allows the Queryset to be pickled.
+ Allows the QuerySet to be pickled.
"""
# Force the cache to be fully populated.
len(self)
@@ -131,7 +136,7 @@ def __repr__(self):
def __len__(self):
# Since __len__ is called quite frequently (for example, as part of
# list(qs), we make some effort here to be as efficient as possible
- # whilst not messing up any existing iterators against the queryset.
+ # whilst not messing up any existing iterators against the QuerySet.
if self._result_cache is None:
if self._iter:
self._result_cache = list(self._iter)
@@ -173,7 +178,9 @@ def __nonzero__(self):
return True
def __getitem__(self, k):
- "Retrieve an item or slice from the set of results."
+ """
+ Retrieves an item or slice from the set of results.
+ """
if not isinstance(k, (slice, int, long)):
raise TypeError
assert ((not isinstance(k, slice) and (k >= 0))
@@ -264,7 +271,7 @@ def count(self):
Performs a SELECT COUNT() and returns the number of records as an
integer.
- If the queryset is already cached (i.e. self._result_cache is set) this
+ If the QuerySet is already cached (i.e. self._result_cache is set) this
simply returns the length of the cached results set to avoid multiple
SELECT COUNT(*) calls.
"""
@@ -290,7 +297,7 @@ def get(self, *args, **kwargs):
def create(self, **kwargs):
"""
- Create a new object with the given kwargs, saving it to the database
+ Creates a new object with the given kwargs, saving it to the database
and returning the created object.
"""
obj = self.model(**kwargs)
@@ -425,8 +432,8 @@ def values_list(self, *fields, **kwargs):
def dates(self, field_name, kind, order='ASC'):
"""
- Returns a list of datetime objects representing all available dates
- for the given field_name, scoped to 'kind'.
+ Returns a list of datetime objects representing all available dates for
+ the given field_name, scoped to 'kind'.
"""
assert kind in ("month", "year", "day"), \
"'kind' must be one of 'year', 'month' or 'day'."
@@ -441,7 +448,7 @@ def dates(self, field_name, kind, order='ASC'):
def none(self):
"""
- Returns an empty queryset.
+ Returns an empty QuerySet.
"""
return self._clone(klass=EmptyQuerySet)
@@ -485,6 +492,7 @@ def _filter_or_exclude(self, negate, *args, **kwargs):
def complex_filter(self, filter_obj):
"""
Returns a new QuerySet instance with filter_obj added to the filters.
+
filter_obj can be a Q object (or anything with an add_to_query()
method) or a dictionary of keyword lookup arguments.
@@ -500,8 +508,9 @@ def complex_filter(self, filter_obj):
def select_related(self, *fields, **kwargs):
"""
- Returns a new QuerySet instance that will select related objects. If
- fields are specified, they must be ForeignKey fields and only those
+ Returns a new QuerySet instance that will select related objects.
+
+ If fields are specified, they must be ForeignKey fields and only those
related objects are included in the selection.
"""
depth = kwargs.pop('depth', 0)
@@ -521,13 +530,15 @@ def select_related(self, *fields, **kwargs):
def dup_select_related(self, other):
"""
- Copies the related selection status from the queryset 'other' to the
- current queryset.
+ Copies the related selection status from the QuerySet 'other' to the
+ current QuerySet.
"""
self.query.select_related = other.query.select_related
def order_by(self, *field_names):
- """Returns a new QuerySet instance with the ordering changed."""
+ """
+ Returns a new QuerySet instance with the ordering changed.
+ """
assert self.query.can_filter(), \
"Cannot reorder a query once a slice has been taken."
obj = self._clone()
@@ -544,9 +555,9 @@ def distinct(self, true_or_false=True):
return obj
def extra(self, select=None, where=None, params=None, tables=None,
- order_by=None, select_params=None):
+ order_by=None, select_params=None):
"""
- Add extra SQL fragments to the query.
+ Adds extra SQL fragments to the query.
"""
assert self.query.can_filter(), \
"Cannot change a query once a slice has been taken"
@@ -556,7 +567,7 @@ def extra(self, select=None, where=None, params=None, tables=None,
def reverse(self):
"""
- Reverses the ordering of the queryset.
+ Reverses the ordering of the QuerySet.
"""
clone = self._clone()
clone.query.standard_ordering = not clone.query.standard_ordering
@@ -589,12 +600,13 @@ def _fill_cache(self, num=None):
def _merge_sanity_check(self, other):
"""
- Checks that we are merging two comparable queryset classes. By default
+ Checks that we are merging two comparable QuerySet classes. By default
this does nothing, but see the ValuesQuerySet for an example of where
it's useful.
"""
pass
+
class ValuesQuerySet(QuerySet):
def __init__(self, *args, **kwargs):
super(ValuesQuerySet, self).__init__(*args, **kwargs)
@@ -617,7 +629,7 @@ def _setup_query(self):
Constructs the field_names list that the values query will be
retrieving.
- Called by the _clone() method after initialising the rest of the
+ Called by the _clone() method after initializing the rest of the
instance.
"""
self.extra_names = []
@@ -658,6 +670,7 @@ def _merge_sanity_check(self, other):
raise TypeError("Merging '%s' classes must involve the same values in each case."
% self.__class__.__name__)
+
class ValuesListQuerySet(ValuesQuerySet):
def iterator(self):
self.query.trim_extra_select(self.extra_names)
@@ -681,6 +694,7 @@ def _clone(self, *args, **kwargs):
clone.flat = self.flat
return clone
+
class DateQuerySet(QuerySet):
def iterator(self):
return self.query.results_iter()
@@ -689,7 +703,7 @@ def _setup_query(self):
"""
Sets up any special features of the query attribute.
- Called by the _clone() method after initialising the rest of the
+ Called by the _clone() method after initializing the rest of the
instance.
"""
self.query = self.query.clone(klass=sql.DateQuery, setup=True)
@@ -706,6 +720,7 @@ def _clone(self, klass=None, setup=False, **kwargs):
c._setup_query()
return c
+
class EmptyQuerySet(QuerySet):
def __init__(self, model=None, query=None):
super(EmptyQuerySet, self).__init__(model, query)
@@ -733,6 +748,7 @@ def iterator(self):
# (it raises StopIteration immediately).
yield iter([]).next()
+
# QOperator, QNot, QAnd and QOr are temporarily retained for backwards
# compatibility. All the old functionality is now part of the 'Q' class.
class QOperator(Q):
@@ -743,12 +759,14 @@ def __init__(self, *args, **kwargs):
QOr = QAnd = QOperator
+
def QNot(q):
warnings.warn('Use ~q instead of QNot(q)', DeprecationWarning, stacklevel=2)
return ~q
+
def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
- requested=None):
+ requested=None):
"""
Helper function that recursively returns an object with the specified
related attributes already populated.
@@ -774,6 +792,7 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
setattr(obj, f.get_cache_name(), rel_obj)
return obj, index_end
+
def delete_objects(seen_objs):
"""
Iterate through a list of seen classes, and remove any instances that are
@@ -782,10 +801,10 @@ def delete_objects(seen_objs):
try:
ordered_classes = seen_objs.keys()
except CyclicDependency:
- # if there is a cyclic dependency, we cannot in general delete
- # the objects. However, if an appropriate transaction is set
- # up, or if the database is lax enough, it will succeed.
- # So for now, we go ahead and try anway.
+ # If there is a cyclic dependency, we cannot in general delete the
+ # objects. However, if an appropriate transaction is set up, or if the
+ # database is lax enough, it will succeed. So for now, we go ahead and
+ # try anyway.
ordered_classes = seen_objs.unordered_keys()
obj_pairs = {}
@@ -794,7 +813,7 @@ def delete_objects(seen_objs):
items.sort()
obj_pairs[cls] = items
- # Pre notify all instances to be deleted
+ # Pre-notify all instances to be deleted.
for pk_val, instance in items:
dispatcher.send(signal=signals.pre_delete, sender=cls,
instance=instance)
@@ -808,7 +827,7 @@ def delete_objects(seen_objs):
if field.rel and field.null and field.rel.to in seen_objs:
update_query.clear_related(field, pk_list)
- # Now delete the actual data
+ # Now delete the actual data.
for cls in ordered_classes:
items = obj_pairs[cls]
items.reverse()
@@ -831,6 +850,7 @@ def delete_objects(seen_objs):
transaction.commit_unless_managed()
+
def insert_query(model, values, return_id=False, raw_values=False):
"""
Inserts a new record for the given model. This provides an interface to
@@ -840,4 +860,3 @@ def insert_query(model, values, return_id=False, raw_values=False):
query = sql.InsertQuery(model, connection)
query.insert_values(values, raw_values)
return query.execute_sql(return_id)
-
View
1 django/http/multipartparser.py
@@ -136,6 +136,7 @@ def parse(self):
# since we cannot be sure a file is complete until
# we hit the next boundary/part of the multipart content.
self.handle_file_complete(old_field_name, counters)
+ old_field_name = None
try:
disposition = meta_data['content-disposition'][1]
View
51 django/http/utils.py
@@ -31,3 +31,54 @@ def conditional_content_removal(request, response):
if request.method == 'HEAD':
response.content = ''
return response
+
+def fix_IE_for_attach(request, response):
+ """
+ This function will prevent Django from serving a Content-Disposition header
+ while expecting the browser to cache it (only when the browser is IE). This
+ leads to IE not allowing the client to download.
+ """
+ if 'MSIE' not in request.META.get('HTTP_USER_AGENT', '').upper():
+ return response
+
+ offending_headers = ('no-cache', 'no-store')
+ if response.has_header('Content-Disposition'):
+ try:
+ del response['Pragma']
+ except KeyError:
+ pass
+ if response.has_header('Cache-Control'):
+ cache_control_values = [value.strip() for value in
+ response['Cache-Control'].split(',')
+ if value.strip().lower() not in offending_headers]
+
+ if not len(cache_control_values):
+ del response['Cache-Control']
+ else:
+ response['Cache-Control'] = ', '.join(cache_control_values)
+
+ return response
+
+def fix_IE_for_vary(request, response):
+ """
+ This function will fix the bug reported at
+ http://support.microsoft.com/kb/824847/en-us?spid=8722&sid=global
+ by clearing the Vary header whenever the mime-type is not safe
+ enough for Internet Explorer to handle. Poor thing.
+ """
+ if 'MSIE' not in request.META.get('HTTP_USER_AGENT', '').upper():
+ return response
+
+ # These mime-types that are decreed "Vary-safe" for IE:
+ safe_mime_types = ('text/html', 'text/plain', 'text/sgml')
+
+ # The first part of the Content-Type field will be the MIME type,
+ # everything after ';', such as character-set, can be ignored.
+ if response['Content-Type'].split(';')[0] not in safe_mime_types:
+ try:
+ del response['Vary']
+ except KeyError:
+ pass
+
+ return response
+
View
27 django/newforms/fields.py
@@ -27,7 +27,7 @@
from util import ErrorList, ValidationError
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
-
+from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
__all__ = (
'Field', 'CharField', 'IntegerField',
@@ -419,18 +419,6 @@ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
# It's OK if Django settings aren't configured.
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
-class UploadedFile(StrAndUnicode):
- "A wrapper for files uploaded in a FileField"
- def __init__(self, filename, data):
- self.filename = filename
- self.data = data
-
- def __unicode__(self):
- """
- The unicode representation is the filename, so that the pre-database-insertion
- logic can use UploadedFile objects
- """
- return self.filename
class FileField(Field):
widget = FileInput
@@ -460,23 +448,20 @@ def clean(self, data, initial=None):
category = DeprecationWarning,
stacklevel = 2
)
+ data = UploadedFile(data['filename'], data['content'])
try:
- file_name = data.file_name
- file_size = data.file_size
+ file_name = data.name
+ file_size = data.size
except AttributeError:
- try:
- file_name = data.get('filename')
- file_size = bool(data['content'])
- except (AttributeError, KeyError):
- raise ValidationError(self.error_messages['invalid'])
+ raise ValidationError(self.error_messages['invalid'])
if not file_name:
raise ValidationError(self.error_messages['invalid'])
if not file_size:
raise ValidationError(self.error_messages['empty'])
- return UploadedFile(file_name, data)
+ return data
class ImageField(FileField):
default_error_messages = {
View
2 django/oldforms/__init__.py
@@ -686,7 +686,7 @@ def isNonEmptyFile(self, new_data, all_data):
if upload_errors:
raise validators.CriticalValidationError, upload_errors
try:
- file_size = new_data.file_size
+ file_size = new_data.size
except AttributeError:
file_size = len(new_data['content'])
if not file_size:
View
51 django/test/client.py
@@ -90,32 +90,34 @@ def encode_multipart(boundary, data):
"""
lines = []
to_str = lambda s: smart_str(s, settings.DEFAULT_CHARSET)
+
+ # Not by any means perfect, but good enough for our purposes.
+ is_file = lambda thing: hasattr(thing, "read") and callable(thing.read)
+
+ # Each bit of the multipart form data could be either a form value or a
+ # file, or a *list* of form values and/or files. Remember that HTTP field
+ # names can be duplicated!
for (key, value) in data.items():
- if isinstance(value, file):
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"; filename="%s"' \
- % (to_str(key), to_str(os.path.basename(value.name))),
- 'Content-Type: application/octet-stream',
- '',
- value.read()
- ])
- else:
- if not isinstance(value, basestring) and is_iterable(value):
- for item in value:
+ if is_file(value):
+ lines.extend(encode_file(boundary, key, value))
+ elif not isinstance(value, basestring) and is_iterable(value):
+ for item in value:
+ if is_file(item):
+ lines.extend(encode_file(boundary, key, item))
+ else:
lines.extend([
'--' + boundary,
'Content-Disposition: form-data; name="%s"' % to_str(key),
'',
to_str(item)
])
- else:
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % to_str(key),
- '',
- to_str(value)
- ])
+ else:
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % to_str(key),
+ '',
+ to_str(value)
+ ])
lines.extend([
'--' + boundary + '--',
@@ -123,6 +125,17 @@ def encode_multipart(boundary, data):
])
return '\r\n'.join(lines)
+def encode_file(boundary, key, file):
+ to_str = lambda s: smart_str(s, settings.DEFAULT_CHARSET)
+ return [
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"; filename="%s"' \
+ % (to_str(key), to_str(os.path.basename(file.name))),
+ 'Content-Type: application/octet-stream',
+ '',
+ file.read()
+ ]
+
class Client:
"""
A class that can act as a client for testing purposes.
View
9 django/utils/translation/trans_real.py
@@ -161,6 +161,15 @@ def _translation(path):
res = _translation(globalpath)
+ # We want to ensure that, for example, "en-gb" and "en-us" don't share
+ # the same translation object (thus, merging en-us with a local update
+ # doesn't affect en-gb), even though they will both use the core "en"
+ # translation. So we have to subvert Python's internal gettext caching.
+ base_lang = lambda x: x.split('-', 1)[0]
+ if base_lang(lang) in [base_lang(trans) for trans in _translations]:
+ res._info = res._info.copy()
+ res._catalog = res._catalog.copy()
+
def _merge(path):
t = _translation(path)
if t is not None:
View
4 django/views/debug.py
@@ -450,11 +450,11 @@ def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_na
{% if frame.context_line %}
<div class="context" id="c{{ frame.id }}">
{% if frame.pre_context %}
- <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}{% if line %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endif %}{% endfor %}</ol>
+ <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
{% endif %}
<ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line|escape }} <span>...</span></li></ol>
{% if frame.post_context %}
- <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}{% if line %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endif %}{% endfor %}</ol>
+ <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
{% endif %}
</div>
{% endif %}
View
4 django/views/generic/list_detail.py
@@ -1,7 +1,7 @@
from django.template import loader, RequestContext
from django.http import Http404, HttpResponse
from django.core.xheaders import populate_xheaders
-from django.core.paginator import QuerySetPaginator, InvalidPage
+from django.core.paginator import Paginator, InvalidPage
from django.core.exceptions import ObjectDoesNotExist
def object_list(request, queryset, paginate_by=None, page=None,
@@ -45,7 +45,7 @@ def object_list(request, queryset, paginate_by=None, page=None,
if extra_context is None: extra_context = {}
queryset = queryset._clone()
if paginate_by:
- paginator = QuerySetPaginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
+ paginator = Paginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
if not page:
page = request.GET.get('page', 1)
try:
View
9 docs/generic_views.txt
@@ -816,15 +816,14 @@ specify the page number in the URL in one of two ways:
These values and lists are 1-based, not 0-based, so the first page would be
represented as page ``1``.
-An example of the use of pagination can be found in the `object pagination`_
-example model.
+For more on pagination, read the `pagination documentation`_.
-.. _`object pagination`: ../models/pagination/
+.. _`pagination documentation`: ../pagination/
**New in Django development version:**
-As a special case, you are also permitted to use
-``last`` as a value for ``page``::
+As a special case, you are also permitted to use ``last`` as a value for
+``page``::
/objects/?page=last
View
15 docs/newforms.txt
@@ -1334,23 +1334,12 @@ given length.
* Validates that non-empty file data has been bound to the form.
* Error message keys: ``required``, ``invalid``, ``missing``, ``empty``
-An ``UploadedFile`` object has two attributes:
-
- ====================== ====================================================
- Attribute Description
- ====================== ====================================================
- ``filename`` The name of the file, provided by the uploading
- client.
-
- ``content`` The array of bytes comprising the file content.
- ====================== ====================================================
-
-The string representation of an ``UploadedFile`` is the same as the filename
-attribute.
+To learn more about the ``UploadedFile`` object, see the `file uploads documentation`_.
When you use a ``FileField`` in a form, you must also remember to
`bind the file data to the form`_.
+.. _file uploads documentation: ../upload_handling/
.. _`bind the file data to the form`: `Binding uploaded files to a form`_
``FilePathField``
View
27 docs/pagination.txt
@@ -59,6 +59,11 @@ page::
...
InvalidPage
+Note that you can give ``Paginator`` a list/tuple or a Django ``QuerySet``. The
+only difference is in implementation; if you pass a ``QuerySet``, the
+``Paginator`` will call its ``count()`` method instead of using ``len()``,
+because the former is more efficient.
+
``Paginator`` objects
=====================
@@ -77,6 +82,21 @@ Attributes
``page_range`` -- A 1-based range of page numbers, e.g., ``[1, 2, 3, 4]``.
+``InvalidPage`` exceptions
+==========================
+
+The ``page()`` method raises ``InvalidPage`` if the requested page is invalid
+(i.e., not an integer) or contains no objects. Generally, it's enough to trap
+the ``InvalidPage`` exception, but if you'd like more granularity, you can trap
+either of the following exceptions:
+
+``PageNotAnInteger`` -- Raised when ``page()`` is given a value that isn't an integer.
+
+``EmptyPage`` -- Raised when ``page()`` is given a valid value but no objects exist on that page.
+
+Both of the exceptions are subclasses of ``InvalidPage``, so you can handle
+them both with a simple ``except InvalidPage``.
+
``Page`` objects
================
@@ -116,13 +136,6 @@ Attributes
``paginator`` -- The associated ``Paginator`` object.
-``QuerySetPaginator`` objects
-=============================
-
-Use ``QuerySetPaginator`` instead of ``Paginator`` if you're paginating across
-a ``QuerySet`` from Django's database API. This is slightly more efficient, and
-there are no API differences between the two classes.
-
The legacy ``ObjectPaginator`` class
====================================
View
2 docs/settings.txt
@@ -279,7 +279,7 @@ Default: ``''`` (Empty string)
The database backend to use. The build-in database backends are
``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
-``'sqlite3'``, ``'oracle'``, and ``'oracle'``.
+``'sqlite3'``, and ``'oracle'``.
In the Django development version, you can use a database backend that doesn't
ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
View
50 docs/upload_handling.txt
@@ -22,7 +22,7 @@ Consider a simple form containing a ``FileField``::
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
file = forms.FileField()
-
+
A view handling this form will receive the file data in ``request.FILES``, which
is a dictionary containing a key for each ``FileField`` (or ``ImageField``, or
other ``FileField`` subclass) in the form. So the data from the above form would
@@ -64,34 +64,34 @@ methods to access the uploaded content:
``UploadedFile.read()``
Read the entire uploaded data from the file. Be careful with this
method: if the uploaded file is huge it can overwhelm your system if you
- try to read it into memory. You'll probably want to use ``chunk()``
+ try to read it into memory. You'll probably want to use ``chunks()``
instead; see below.
-
+
``UploadedFile.multiple_chunks()``
Returns ``True`` if the uploaded file is big enough to require
reading in multiple chunks. By default this will be any file
larger than 2.5 megabytes, but that's configurable; see below.
-
+
``UploadedFile.chunk()``
A generator returning chunks of the file. If ``multiple_chunks()`` is
``True``, you should use this method in a loop instead of ``read()``.
-
+
In practice, it's often easiest simply to use ``chunks()`` all the time;
see the example below.
-
+
``UploadedFile.file_name``
The name of the uploaded file (e.g. ``my_file.txt``).
-
+
``UploadedFile.file_size``
The size, in bytes, of the uploaded file.
-
+
There are a few other methods and attributes available on ``UploadedFile``
objects; see `UploadedFile objects`_ for a complete reference.
Putting it all together, here's a common way you might handle an uploaded file::
-
+
def handle_uploaded_file(f):
- destination = open('some/file/name.txt', 'wb')
+ destination = open('some/file/name.txt', 'wb+')
for chunk in f.chunks():
destination.write(chunk)
@@ -126,27 +126,27 @@ Three `settings`_ control Django's file upload behavior:
The maximum size, in bytes, for files that will be uploaded
into memory. Files larger than ``FILE_UPLOAD_MAX_MEMORY_SIZE``
will be streamed to disk.
-
+
Defaults to 2.5 megabytes.
-
+
``FILE_UPLOAD_TEMP_DIR``
The directory where uploaded files larger than ``FILE_UPLOAD_TEMP_DIR``
will be stored.
-
+
Defaults to your system's standard temporary directory (i.e. ``/tmp`` on
most Unix-like systems).
-
+
``FILE_UPLOAD_HANDLERS``
The actual handlers for uploaded files. Changing this setting
allows complete customization -- even replacement -- of
Django's upload process. See `upload handlers`_, below,
for details.
-
+
Defaults to::
-
+
("django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",)
-
+
Which means "try to upload to memory first, then fall back to temporary
files."
@@ -161,35 +161,39 @@ All ``UploadedFile`` objects define the following methods/attributes:
Returns a byte string of length ``num_bytes``, or the complete file if
``num_bytes`` is ``None``.
- ``UploadedFile.chunk(self, chunk_size=None)``
+ ``UploadedFile.chunks(self, chunk_size=None)``
A generator yielding small chunks from the file. If ``chunk_size`` isn't
- given, chunks will be 64 kb.
+ given, chunks will be 64 KB.
``UploadedFile.multiple_chunks(self, chunk_size=None)``
Returns ``True`` if you can expect more than one chunk when calling
- ``UploadedFile.chunk(self, chunk_size)``.
+ ``UploadedFile.chunks(self, chunk_size)``.
``UploadedFile.file_size``
The size, in bytes, of the uploaded file.
-
+
``UploadedFile.file_name``
The name of the uploaded file as provided by the user.
-
+
``UploadedFile.content_type``
The content-type header uploaded with the file (e.g. ``text/plain`` or
``application/pdf``). Like any data supplied by the user, you shouldn't
trust that the uploaded file is actually this type. You'll still need to
validate that the file contains the content that the content-type header
claims -- "trust but verify."
-
+
``UploadedFile.charset``
For ``text/*`` content-types, the character set (i.e. ``utf8``) supplied
by the browser. Again, "trust but verify" is the best policy here.
+ ``UploadedFile.__iter__()``
+ Iterates over the lines in the file.
+
``UploadedFile.temporary_file_path()``
Only files uploaded onto disk will have this method; it returns the full
path to the temporary uploaded file.
+
Upload Handlers
===============
View
8 tests/modeltests/model_forms/models.py
@@ -803,7 +803,7 @@ def __unicode__(self):
>>> f.is_valid()
True
>>> type(f.cleaned_data['file'])
-<class 'django.newforms.fields.UploadedFile'>
+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
>>> instance = f.save()
>>> instance.file
u'...test1.txt'
@@ -814,7 +814,7 @@ def __unicode__(self):
>>> f.is_valid()
True
>>> type(f.cleaned_data['file'])
-<class 'django.newforms.fields.UploadedFile'>
+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
>>> instance = f.save()
>>> instance.file
u'...test1.txt'
@@ -906,7 +906,7 @@ def __unicode__(self):
>>> f.is_valid()
True
>>> type(f.cleaned_data['image'])
-<class 'django.newforms.fields.UploadedFile'>
+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
>>> instance = f.save()
>>> instance.image
u'...test.png'
@@ -918,7 +918,7 @@ def __unicode__(self):
>>> f.is_valid()
True
>>> type(f.cleaned_data['image'])
-<class 'django.newforms.fields.UploadedFile'>
+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
>>> instance = f.save()
>>> instance.image
u'...test.png'
View
16 tests/modeltests/pagination/models.py
@@ -31,7 +31,7 @@ def __unicode__(self):
# New/current API (Paginator/Page) #
####################################
->>> from django.core.paginator import Paginator, InvalidPage
+>>> from django.core.paginator import Paginator
>>> paginator = Paginator(Article.objects.all(), 5)
>>> paginator.count
9
@@ -82,15 +82,15 @@ def __unicode__(self):
>>> p.end_index()
9
-# Invalid pages raise InvalidPage.
+# Empty pages raise EmptyPage.
>>> paginator.page(0)
Traceback (most recent call last):
...
-InvalidPage: ...
+EmptyPage: ...
>>> paginator.page(3)
Traceback (most recent call last):
...
-InvalidPage: ...
+EmptyPage: ...
# Empty paginators with allow_empty_first_page=True.
>>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True)
@@ -148,7 +148,7 @@ def __unicode__(self):
>>> from warnings import filterwarnings
>>> filterwarnings("ignore")
->>> from django.core.paginator import ObjectPaginator, InvalidPage
+>>> from django.core.paginator import ObjectPaginator, EmptyPage
>>> paginator = ObjectPaginator(Article.objects.all(), 5)
>>> paginator.hits
9
@@ -181,15 +181,15 @@ def __unicode__(self):
>>> paginator.last_on_page(1)
9
-# Invalid pages raise InvalidPage.
+# Invalid pages raise EmptyPage.
>>> paginator.get_page(-1)
Traceback (most recent call last):
...
-InvalidPage: ...
+EmptyPage: ...
>>> paginator.get_page(2)
Traceback (most recent call last):
...
-InvalidPage: ...
+EmptyPage: ...
# Empty paginators with allow_empty_first_page=True.
>>> paginator = ObjectPaginator(Article.objects.filter(id=0), 5)
View
0 tests/regressiontests/admin_scripts/__init__.py
No changes.
View
0 tests/regressiontests/admin_scripts/management/__init__.py
No changes.
View
0 tests/regressiontests/admin_scripts/management/commands/__init__.py
No changes.
View
10 tests/regressiontests/admin_scripts/management/commands/app_command.py
@@ -0,0 +1,10 @@
+from django.core.management.base import AppCommand
+
+class Command(AppCommand):
+ help = 'Test Application-based commands'
+ requires_model_validation = False
+ args = '[appname ...]'
+
+ def handle_app(self, app, **options):
+ print 'EXECUTE:AppCommand app=%s, options=%s' % (app, sorted(options.items()))
+
View
9 tests/regressiontests/admin_scripts/management/commands/base_command.py
@@ -0,0 +1,9 @@
+from django.core.management.base import BaseCommand
+
+class Command(BaseCommand):
+ help = 'Test basic commands'
+ requires_model_validation = False
+ args = '[labels ...]'
+
+ def handle(self, *labels, **options):
+ print 'EXECUTE:BaseCommand labels=%s, options=%s' % (labels, sorted(options.items()))
View
9 tests/regressiontests/admin_scripts/management/commands/label_command.py
@@ -0,0 +1,9 @@
+from django.core.management.base import LabelCommand
+
+class Command(LabelCommand):
+ help = "Test Label-based commands"
+ requires_model_validation = False
+ args = '<label>'
+
+ def handle_label(self, label, **options):
+ print 'EXECUTE:LabelCommand label=%s, options=%s' % (label, sorted(options.items()))
View
9 tests/regressiontests/admin_scripts/management/commands/noargs_command.py
@@ -0,0 +1,9 @@
+from django.core.management.base import NoArgsCommand
+
+class Command(NoArgsCommand):
+ help = "Test No-args commands"
+ requires_model_validation = False
+
+
+ def handle_noargs(self, **options):
+ print 'EXECUTE:NoArgsCommand options=%s' % sorted(options.items())
View
12 tests/regressiontests/admin_scripts/models.py
@@ -0,0 +1,12 @@
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100, default='Default headline')
+ pub_date = models.DateTimeField()
+
+ def __unicode__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('-pub_date', 'headline')
+
View
817 tests/regressiontests/admin_scripts/tests.py
@@ -0,0 +1,817 @@
+"""
+A series of tests to establish that the command-line managment tools work as
+advertised - especially with regards to the handling of the DJANGO_SETTINGS_MODULE
+and default settings.py files.
+"""
+import os
+import unittest
+import shutil
+
+from django import conf, bin
+from django.conf import settings
+
+class AdminScriptTestCase(unittest.TestCase):
+ def write_settings(self, filename, apps=None):
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ settings_file = open(os.path.join(test_dir,filename), 'w')
+ settings_file.write('# Settings file automatically generated by regressiontests.admin_scripts test case\n')
+ exports = [
+ 'DATABASE_ENGINE',
+ 'DATABASE_NAME',
+ 'DATABASE_USER',
+ 'DATABASE_PASSWORD',
+ 'DATABASE_HOST',
+ 'DATABASE_PORT',
+ 'ROOT_URLCONF'
+ ]
+ for s in exports:
+ if hasattr(settings,s):
+ settings_file.write("%s = '%s'\n" % (s, str(getattr(settings,s))))
+
+ if apps is None:
+ apps = ['django.contrib.auth', 'django.contrib.contenttypes', 'regressiontests.admin_scripts']
+
+ if apps:
+ settings_file.write("INSTALLED_APPS = %s\n" % apps)
+
+ settings_file.close()
+
+ def remove_settings(self, filename):
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ os.remove(os.path.join(test_dir, filename))
+ # Also try to remove the pyc file; if it exists, it could
+ # mess up later tests that depend upon the .py file not existing
+ try:
+ os.remove(os.path.join(test_dir, filename + 'c'))
+ except OSError:
+ pass
+
+ def run_test(self, script, args, settings_file=None, apps=None):
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ project_dir = os.path.dirname(test_dir)
+ base_dir = os.path.dirname(project_dir)
+
+ # Build the command line
+ cmd = 'python "%s"' % script
+ cmd += ''.join(' %s' % arg for arg in args)
+
+ # Remember the old environment
+ old_django_settings_module = os.environ.get('DJANGO_SETTINGS_MODULE', None)
+ old_python_path = os.environ.get('PYTHONPATH', None)
+ old_cwd = os.getcwd()
+
+ # Set the test environment
+ if settings_file:
+ os.environ['DJANGO_SETTINGS_MODULE'] = settings_file
+ elif 'DJANGO_SETTINGS_MODULE' in os.environ:
+ del os.environ['DJANGO_SETTINGS_MODULE']
+
+ os.environ['PYTHONPATH'] = os.pathsep.join([project_dir,base_dir])
+
+ # Move to the test directory and run
+ os.chdir(test_dir)
+ stdin, stdout, stderr = os.popen3(cmd)
+ out, err = stdout.read(), stderr.read()
+
+ # Restore the old environment
+ if old_django_settings_module:
+ os.environ['DJANGO_SETTINGS_MODULE'] = old_django_settings_module
+ if old_python_path:
+ os.environ['PYTHONPATH'] = old_python_path
+
+ # Move back to the old working directory
+ os.chdir(old_cwd)
+
+ return out, err
+
+ def run_django_admin(self, args, settings_file=None):
+ bin_dir = os.path.dirname(bin.__file__)
+ return self.run_test(os.path.join(bin_dir,'django-admin.py'), args, settings_file)
+
+ def run_manage(self, args, settings_file=None):
+ conf_dir = os.path.dirname(conf.__file__)
+ template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py')
+
+ test_dir = os.path.dirname(os.path.dirname(__file__))