Permalink
Browse files

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

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7815 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 595e919 commit 0e8710d5900a75b9a4a1caebb82c939896e99cff @brosner brosner committed Jul 1, 2008
Showing with 2,319 additions and 157 deletions.
  1. +3 −1 AUTHORS
  2. +15 −0 django/conf/global_settings.py
  3. +14 −0 django/contrib/sitemaps/management/commands/ping_google.py
  4. 0 django/core/files/__init__.py
  5. +66 −0 django/core/files/locks.py
  6. +59 −0 django/core/files/move.py
  7. +190 −0 django/core/files/uploadedfile.py
  8. +235 −0 django/core/files/uploadhandler.py
  9. +2 −1 django/core/handlers/modpython.py
  10. +2 −3 django/core/handlers/wsgi.py
  11. +55 −5 django/db/models/base.py
  12. +14 −4 django/db/models/fields/__init__.py
  13. +1 −1 django/db/models/sql/query.py
  14. +30 −36 django/http/__init__.py
  15. +658 −0 django/http/multipartparser.py
  16. +51 −11 django/newforms/fields.py
  17. +14 −5 django/oldforms/__init__.py
  18. +24 −2 django/test/client.py
  19. +41 −9 django/utils/datastructures.py
  20. +24 −0 django/utils/text.py
  21. +2 −2 docs/newforms.txt
  22. +22 −5 docs/request_response.txt
  23. +38 −1 docs/settings.txt
  24. +11 −0 docs/sitemaps.txt
  25. +2 −2 docs/templates.txt
  26. +346 −0 docs/upload_handling.txt
  27. +88 −8 tests/modeltests/model_forms/models.py
  28. +4 −7 tests/regressiontests/bug639/tests.py
  29. +18 −7 tests/regressiontests/datastructures/tests.py
  30. 0 tests/regressiontests/file_uploads/__init__.py
  31. +2 −0 tests/regressiontests/file_uploads/models.py
  32. +158 −0 tests/regressiontests/file_uploads/tests.py
  33. +26 −0 tests/regressiontests/file_uploads/uploadhandler.py
  34. +10 −0 tests/regressiontests/file_uploads/urls.py
  35. +70 −0 tests/regressiontests/file_uploads/views.py
  36. +4 −3 tests/regressiontests/forms/error_messages.py
  37. +10 −9 tests/regressiontests/forms/fields.py
  38. +3 −2 tests/regressiontests/forms/forms.py
  39. +1 −11 tests/regressiontests/test_client_regress/models.py
  40. +0 −1 tests/regressiontests/test_client_regress/urls.py
  41. +3 −21 tests/regressiontests/test_client_regress/views.py
  42. +3 −0 tests/urls.py
View
@@ -59,7 +59,7 @@ answer newbie questions, and generally made Django that much better:
Arthur <avandorp@gmail.com>
av0000@mail.ru
David Avsajanishvili <avsd05@gmail.com>
- axiak@mit.edu
+ Mike Axiak <axiak@mit.edu>
Niran Babalola <niran@niran.org>
Morten Bagai <m@bagai.com>
Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
@@ -142,7 +142,9 @@ answer newbie questions, and generally made Django that much better:
Marc Fargas <telenieko@telenieko.com>
Szilveszter Farkas <szilveszter.farkas@gmail.com>
favo@exoweb.net
+ fdr <drfarina@gmail.com>
Dmitri Fedortchenko <zeraien@gmail.com>
+ Jonathan Feignberg <jdf@pobox.com>
Liang Feng <hutuworm@gmail.com>
Bill Fenner <fenner@gmail.com>
Stefane Fermgier <sf@fermigier.com>
@@ -231,6 +231,21 @@
# Example: "http://media.lawrence.com"
MEDIA_URL = ''
+# List of upload handler classes to be applied in order.
+FILE_UPLOAD_HANDLERS = (
+ 'django.core.files.uploadhandler.MemoryFileUploadHandler',
+ 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
+)
+
+# Maximum size, in bytes, of a request before it will be streamed to the
+# file system instead of into memory.
+FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB
+
+# Directory in which upload streamed files will be temporarily saved. A value of
+# `None` will make Django use the operating system's default temporary directory
+# (i.e. "/tmp" on *nix systems).
+FILE_UPLOAD_TEMP_DIR = None
+
# Default formatting for date objects. See all available format strings here:
# http://www.djangoproject.com/documentation/templates/#now
DATE_FORMAT = 'N j, Y'
@@ -0,0 +1,14 @@
+from django.core.management.base import BaseCommand
+from django.contrib.sitemaps import ping_google
+
+
+class Command(BaseCommand):
+ help = "Ping google with an updated sitemap, pass optional url of sitemap"
+
+ def execute(self, *args, **options):
+ if len(args) == 1:
+ sitemap_url = args[0]
+ else:
+ sitemap_url = None
+ ping_google(sitemap_url=sitemap_url)
+
No changes.
@@ -0,0 +1,66 @@
+"""
+Portable file locking utilities.
+
+Based partially on example by Jonathan Feignberg <jdf@pobox.com> in the Python
+Cookbook, licensed under the Python Software License.
+
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
+
+Example Usage::
+
+ >>> from django.core.files import locks
+ >>> f = open('./file', 'wb')
+ >>> locks.lock(f, locks.LOCK_EX)
+ >>> f.write('Django')
+ >>> f.close()
+"""
+
+__all__ = ('LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock')
+
+system_type = None
+
+try:
+ import win32con
+ import win32file
+ import pywintypes
+ LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
+ LOCK_SH = 0
+ LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
+ __overlapped = pywintypes.OVERLAPPED()
+ system_type = 'nt'
+except (ImportError, AttributeError):
+ pass
+
+try:
+ import fcntl
+ LOCK_EX = fcntl.LOCK_EX
+ LOCK_SH = fcntl.LOCK_SH
+ LOCK_NB = fcntl.LOCK_NB
+ system_type = 'posix'
+except (ImportError, AttributeError):
+ pass
+
+if system_type == 'nt':
+ def lock(file, flags):
+ hfile = win32file._get_osfhandle(file.fileno())
+ win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
+
+ def unlock(file):
+ hfile = win32file._get_osfhandle(file.fileno())
+ win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
+elif system_type == 'posix':
+ def lock(file, flags):
+ fcntl.flock(file.fileno(), flags)
+
+ def unlock(file):
+ fcntl.flock(file.fileno(), fcntl.LOCK_UN)
+else:
+ # File locking is not supported.
+ LOCK_EX = LOCK_SH = LOCK_NB = None
+
+ # Dummy functions that don't do anything.
+ def lock(file, flags):
+ pass
+
+ def unlock(file):
+ pass
@@ -0,0 +1,59 @@
+"""
+Move a file in the safest way possible::
+
+ >>> from django.core.files.move import file_move_save
+ >>> file_move_save("/tmp/old_file", "/tmp/new_file")
+"""
+
+import os
+from django.core.files import locks
+
+__all__ = ['file_move_safe']
+
+try:
+ import shutil
+ file_move = shutil.move
+except ImportError:
+ file_move = os.rename
+
+def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
+ """
+ Moves a file from one location to another in the safest way possible.
+
+ First, try using ``shutils.move``, which is OS-dependent but doesn't break
+ if moving across filesystems. Then, try ``os.rename``, which will break
+ across filesystems. Finally, streams manually from one file to another in
+ pure Python.
+
+ If the destination file exists and ``allow_overwrite`` is ``False``, this
+ function will throw an ``IOError``.
+ """
+
+ # There's no reason to move if we don't have to.
+ if old_file_name == new_file_name:
+ return
+
+ if not allow_overwrite and os.path.exists(new_file_name):
+ raise IOError("Cannot overwrite existing file '%s'." % new_file_name)
+
+ try:
+ file_move(old_file_name, new_file_name)
+ return
+ except OSError:
+ # This will happen with os.rename if moving to another filesystem
+ pass
+
+ # If the built-in didn't work, do it the hard way.
+ new_file = open(new_file_name, 'wb')
+ locks.lock(new_file, locks.LOCK_EX)
+ old_file = open(old_file_name, 'rb')
+ current_chunk = None
+
+ while current_chunk != '':
+ current_chunk = old_file.read(chunk_size)
+ new_file.write(current_chunk)
+
+ new_file.close()
+ old_file.close()
+
+ os.remove(old_file_name)
@@ -0,0 +1,190 @@
+"""
+Classes representing uploaded files.
+"""
+
+import os
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')
+
+class UploadedFile(object):
+ """
+ A abstract uploadded file (``TemporaryUploadedFile`` and
+ ``InMemoryUploadedFile`` are the built-in concrete subclasses).
+
+ An ``UploadedFile`` object behaves somewhat like a file object and
+ represents some file data that the user submitted with a form.
+ """
+ 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
+ self.content_type = content_type
+ self.charset = charset
+
+ def __repr__(self):
+ return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type)
+
+ def _set_file_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):
+ """
+ Read the file and yield chucks of ``chunk_size`` bytes (defaults to
+ ``UploadedFile.DEFAULT_CHUNK_SIZE``).
+ """
+ if not chunk_size:
+ chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
+
+ if hasattr(self, 'seek'):
+ self.seek(0)
+ # Assume the pointer is at zero...
+ counter = self.file_size
+
+ while counter > 0:
+ yield self.read(chunk_size)
+ counter -= chunk_size
+
+ def multiple_chunks(self, chunk_size=None):
+ """
+ Returns ``True`` if you can expect multiple chunks.
+
+ NB: If a particular file representation is in memory, subclasses should
+ always return ``False`` -- there's no good reason to read from memory in
+ chunks.
+ """
+ if not chunk_size:
+ chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
+ return self.file_size < chunk_size
+
+ # Abstract methods; subclasses *must* default read() and probably should
+ # define open/close.
+ def read(self, num_bytes=None):
+ raise NotImplementedError()
+
+ def open(self):
+ pass
+
+ def close(self):
+ pass
+
+ # 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',
+ 'content-type': 'content_type',
+ }
+
+ if key == 'content':
+ return self.read()
+ elif key == 'filename':
+ return self.file_name
+ elif key == 'content-type':
+ return self.content_type
+ else:
+ return getattr(self, key)
+
+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 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)
+
+class InMemoryUploadedFile(UploadedFile):
+ """
+ A file uploaded into memory (i.e. stream-to-memory).
+ """
+ def __init__(self, file, field_name, file_name, content_type, charset, file_size):
+ super(InMemoryUploadedFile, self).__init__(file_name, content_type, charset, file_size)
+ self.file = file
+ self.field_name = field_name
+ self.file.seek(0)
+
+ def seek(self, *args, **kwargs):
+ self.file.seek(*args, **kwargs)
+
+ def open(self):
+ self.seek(0)
+
+ def read(self, *args, **kwargs):
+ return self.file.read(*args, **kwargs)
+
+ def chunk(self, chunk_size=None):
+ self.file.seek(0)
+ yield self.read()
+
+ def multiple_chunks(self, chunk_size=None):
+ # Since it's in memory, we'll never have multiple chunks.
+ return False
+
+class SimpleUploadedFile(InMemoryUploadedFile):
+ """
+ A simple representation of a file, which just has content, size, and a name.
+ """
+ def __init__(self, name, content, content_type='text/plain'):
+ self.file = StringIO(content or '')
+ self.file_name = name
+ self.field_name = None
+ self.file_size = len(content or '')
+ self.content_type = content_type
+ self.charset = None
+ self.file.seek(0)
+
+ def from_dict(cls, file_dict):
+ """
+ Creates a SimpleUploadedFile object from
+ a dictionary object with the following keys:
+ - filename
+ - content-type
+ - content
+ """
+ return cls(file_dict['filename'],
+ file_dict['content'],
+ file_dict.get('content-type', 'text/plain'))
+
+ from_dict = classmethod(from_dict)
Oops, something went wrong. Retry.

0 comments on commit 0e8710d

Please sign in to comment.