
Property changes on: django\core\filestorage
___________________________________________________________________
Name: svn:ignore
   + *.pyc


Index: django/core/filestorage/base.py
===================================================================
--- django/core/filestorage/base.py	(revision 0)
+++ django/core/filestorage/base.py	(revision 0)
@@ -0,0 +1,39 @@
+from StringIO import StringIO
+
+from django.utils.text import get_valid_filename
+
+class Backend(object):
+    def get_valid_filename(self, filename):
+        return get_valid_filename(filename)
+
+    def get_available_filename(self, filename):
+        # If the filename already exists, keep adding an underscore to the name
+        # of the file until the filename doesn't exist.
+        while self.file_exists(filename):
+            try:
+                dot_index = filename.rindex('.')
+            except ValueError: # filename has no dot
+                filename += '_'
+            else:
+                filename = filename[:dot_index] + '_' + filename[dot_index:]
+        return filename
+
+class RemoteFile(StringIO):
+    """Sends files to a remote backend automatically, when necessary."""
+
+    def __init__(self, data, mode, writer):
+        self._mode = mode
+        self._write_to_backend = writer
+        self._is_dirty = False
+        StringIO.__init__(self, data)
+
+    def write(self, data):
+        if 'w' not in self._mode:
+            raise AttributeError, "File was opened for read-only access."
+        StringIO.write(self, data)
+        self._is_dirty = True
+
+    def close(self):
+        if self._is_dirty:
+            self._write_to_backend(self.getvalue())
+        StringIO.close(self)
Index: django/core/filestorage/__init__.py
===================================================================
Index: django/core/filestorage/filesystem.py
===================================================================
--- django/core/filestorage/filesystem.py	(revision 0)
+++ django/core/filestorage/filesystem.py	(revision 0)
@@ -0,0 +1,50 @@
+import os
+import urlparse
+
+from django.conf import settings
+from django.utils.encoding import force_unicode, smart_str
+from django.core.filestorage.base import Backend
+from django.utils.text import force_unicode
+
+class FileSystemBackend(Backend):
+    """Standard filesystem storage"""
+
+    def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL):
+        self.location = os.path.abspath(location)
+        self.base_url = base_url
+
+    def get_absolute_path(self, filename):
+        return os.path.normpath(os.path.join(self.location, filename))
+
+    def get_filesize(self, filename):
+        return os.path.getsize(self.get_absolute_path(filename))
+
+    def get_absolute_url(self, filename):
+        return urlparse.urljoin(self.base_url, filename).replace('\\', '/')
+
+    def open_file(self, filename, mode='rb'):
+        return open(self.get_absolute_path(filename), mode)
+
+    def file_exists(self, filename):
+        return os.path.exists(self.get_absolute_path(filename))
+
+    def save_file(self, filename, raw_contents):
+        try: # Create the destination directory if it doesn't exist.
+            os.makedirs(os.path.join(self.location, os.path.dirname(filename)))
+        except OSError: # Directory probably already exists.
+            pass
+        filename = self.get_available_filename(filename)
+
+        # Write the file to disk.
+        fp = self.open_file(filename, 'wb')
+        fp.write(raw_contents)
+        fp.close()
+
+        # Store filenames with forward slashes, even on Windows
+        return force_unicode(filename.replace('\\', '/'))
+
+    def delete_file(self, filename):
+        file_name = self.get_absolute_path(filename)
+        # If the file exists, delete it from the filesystem.
+        if os.path.exists(file_name):
+            os.remove(file_name)
Index: django/core/filestorage/__init__.py
===================================================================
Index: django/core/filestorage/base.py
===================================================================
--- django/core/filestorage/base.py	(revision 0)
+++ django/core/filestorage/base.py	(revision 0)
@@ -0,0 +1,39 @@
+from StringIO import StringIO
+
+from django.utils.text import get_valid_filename
+
+class Backend(object):
+    def get_valid_filename(self, filename):
+        return get_valid_filename(filename)
+
+    def get_available_filename(self, filename):
+        # If the filename already exists, keep adding an underscore to the name
+        # of the file until the filename doesn't exist.
+        while self.file_exists(filename):
+            try:
+                dot_index = filename.rindex('.')
+            except ValueError: # filename has no dot
+                filename += '_'
+            else:
+                filename = filename[:dot_index] + '_' + filename[dot_index:]
+        return filename
+
+class RemoteFile(StringIO):
+    """Sends files to a remote backend automatically, when necessary."""
+
+    def __init__(self, data, mode, writer):
+        self._mode = mode
+        self._write_to_backend = writer
+        self._is_dirty = False
+        StringIO.__init__(self, data)
+
+    def write(self, data):
+        if 'w' not in self._mode:
+            raise AttributeError, "File was opened for read-only access."
+        StringIO.write(self, data)
+        self._is_dirty = True
+
+    def close(self):
+        if self._is_dirty:
+            self._write_to_backend(self.getvalue())
+        StringIO.close(self)
Index: django/core/filestorage/filesystem.py
===================================================================
--- django/core/filestorage/filesystem.py	(revision 0)
+++ django/core/filestorage/filesystem.py	(revision 0)
@@ -0,0 +1,50 @@
+import os
+import urlparse
+
+from django.conf import settings
+from django.utils.encoding import force_unicode, smart_str
+from django.core.filestorage.base import Backend
+from django.utils.text import force_unicode
+
+class FileSystemBackend(Backend):
+    """Standard filesystem storage"""
+
+    def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL):
+        self.location = os.path.abspath(location)
+        self.base_url = base_url
+
+    def get_absolute_path(self, filename):
+        return os.path.normpath(os.path.join(self.location, filename))
+
+    def get_filesize(self, filename):
+        return os.path.getsize(self.get_absolute_path(filename))
+
+    def get_absolute_url(self, filename):
+        return urlparse.urljoin(self.base_url, filename).replace('\\', '/')
+
+    def open_file(self, filename, mode='rb'):
+        return open(self.get_absolute_path(filename), mode)
+
+    def file_exists(self, filename):
+        return os.path.exists(self.get_absolute_path(filename))
+
+    def save_file(self, filename, raw_contents):
+        try: # Create the destination directory if it doesn't exist.
+            os.makedirs(os.path.join(self.location, os.path.dirname(filename)))
+        except OSError: # Directory probably already exists.
+            pass
+        filename = self.get_available_filename(filename)
+
+        # Write the file to disk.
+        fp = self.open_file(filename, 'wb')
+        fp.write(raw_contents)
+        fp.close()
+
+        # Store filenames with forward slashes, even on Windows
+        return force_unicode(filename.replace('\\', '/'))
+
+    def delete_file(self, filename):
+        file_name = self.get_absolute_path(filename)
+        # If the file exists, delete it from the filesystem.
+        if os.path.exists(file_name):
+            os.remove(file_name)
Index: django/core/serializers/base.py
===================================================================
--- django/core/serializers/base.py	(revision 7091)
+++ django/core/serializers/base.py	(working copy)
@@ -61,7 +61,10 @@
         if isinstance(field, models.DateTimeField):
             value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S")
         elif isinstance(field, models.FileField):
-            value = getattr(obj, "get_%s_url" % field.name, lambda: None)()
+            try:
+                value = getattr(obj, field.name).get_absolute_url()
+            except ValueError:
+                value = ""
         else:
             value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
         return smart_unicode(value)
Index: django/db/models/__init__.py
===================================================================
--- django/db/models/__init__.py	(revision 7091)
+++ django/db/models/__init__.py	(working copy)
@@ -8,6 +8,7 @@
 from django.db.models.base import Model, AdminOptions
 from django.db.models.fields import *
 from django.db.models.fields.subclassing import SubfieldBase
+from django.db.models.fields.files import FileField, ImageField
 from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
 from django.db.models import signals
 from django.utils.functional import curry
Index: django/db/models/base.py
===================================================================
--- django/db/models/base.py	(revision 7091)
+++ django/db/models/base.py	(working copy)
@@ -2,7 +2,7 @@
 import django.db.models.manager
 from django.core import validators
 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
-from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
+from django.db.models.fields import AutoField, FieldDoesNotExist
 from django.db.models.fields.related import OneToOneRel, ManyToOneRel
 from django.db.models.query import delete_objects
 from django.db.models.options import Options, AdminOptions
@@ -18,6 +18,7 @@
 import types
 import sys
 import os
+from warnings import warn
 
 class ModelBase(type):
     "Metaclass for all models"
@@ -369,74 +370,37 @@
         return getattr(self, cachename)
 
     def _get_FIELD_filename(self, field):
-        if getattr(self, field.attname): # value is not blank
-            return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
-        return ''
+        warn("Use instance.%s.get_absolute_path()." % field.attname, DeprecationWarning)
+        try:
+            return getattr(self, field.attname).get_absolute_path()
+        except ValueError:
+            # For backward compatibility
+            return settings.MEDIA_ROOT
 
     def _get_FIELD_url(self, field):
-        if getattr(self, field.attname): # value is not blank
-            import urlparse
-            return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
-        return ''
+        warn("Use instance.%s.get_absolute_url()." % field.attname, DeprecationWarning)
+        try:
+            return getattr(self, field.attname).get_absolute_url()
+        except ValueError:
+            # For backward compatibility
+            return settings.MEDIA_URL
 
     def _get_FIELD_size(self, field):
-        return os.path.getsize(self._get_FIELD_filename(field))
+        warn("Use instance.%s.get_filesize()." % field.attname, DeprecationWarning)
+        return getattr(self, field.attname).get_filesize()
 
     def _save_FIELD_file(self, field, filename, raw_contents, save=True):
-        directory = field.get_directory_name()
-        try: # Create the date-based directory if it doesn't exist.
-            os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
-        except OSError: # Directory probably already exists.
-            pass
-        filename = field.get_filename(filename)
+        warn("Use instance.%s.save_file()." % field.attname, DeprecationWarning)
+        return getattr(self, field.attname).save_file(filename, raw_contents, save)
 
-        # 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
-                filename += '_'
-            else:
-                filename = filename[:dot_index] + '_' + filename[dot_index:]
-
-        # Write the file to disk.
-        setattr(self, field.attname, filename)
-
-        full_filename = self._get_FIELD_filename(field)
-        fp = open(full_filename, 'wb')
-        fp.write(raw_contents)
-        fp.close()
-
-        # Save the width and/or height, if applicable.
-        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:
-                setattr(self, field.width_field, width)
-            if field.height_field:
-                setattr(self, field.height_field, height)
-
-        # Save the object because it has changed unless save is False
-        if save:
-            self.save()
-
-    _save_FIELD_file.alters_data = True
-
     def _get_FIELD_width(self, field):
-        return self._get_image_dimensions(field)[0]
+        warn("Use instance.%s.get_width()." % field.attname, DeprecationWarning)
+        return getattr(self, field.attname).get_width()
 
     def _get_FIELD_height(self, field):
-        return self._get_image_dimensions(field)[1]
+        warn("Use instance.%s.get_height()." % field.attname, DeprecationWarning)
+        return getattr(self, field.attname).get_height()
 
-    def _get_image_dimensions(self, field):
-        cachename = "__%s_dimensions_cache" % field.name
-        if not hasattr(self, cachename):
-            from django.utils.images import get_image_dimensions
-            filename = self._get_FIELD_filename(field)
-            setattr(self, cachename, get_image_dimensions(filename))
-        return getattr(self, cachename)
-
 ############################################
 # HELPER FUNCTIONS (CURRIED MODEL METHODS) #
 ############################################
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 7091)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -1,5 +1,4 @@
 import datetime
-import os
 import time
 try:
     import decimal
@@ -276,6 +275,8 @@
         name_prefix is a prefix to prepend to the "field_name" argument.
         rel is a boolean specifying whether this field is in a related context.
         """
+        from django.db.models.fields import files
+
         field_objs, params = self.prepare_field_objs_and_params(manipulator, name_prefix)
 
         # Add the "unique" validator(s).
@@ -307,7 +308,7 @@
         # If this field is in a related context, check whether any other fields
         # in the related object have core=True. If so, add a validator --
         # RequiredIfOtherFieldsGiven -- to this FormField.
-        if rel and not self.blank and not isinstance(self, AutoField) and not isinstance(self, FileField):
+        if rel and not self.blank and not isinstance(self, AutoField) and not isinstance(self, files.FileField):
             # First, get the core fields, if any.
             core_field_names = []
             for f in opts.fields:
@@ -707,112 +708,6 @@
         defaults.update(kwargs)
         return super(EmailField, self).formfield(**defaults)
 
-class FileField(Field):
-    def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
-        self.upload_to = upload_to
-        kwargs['max_length'] = kwargs.get('max_length', 100)
-        Field.__init__(self, verbose_name, name, **kwargs)
-
-    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:
-            return None
-        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)
-        if not self.blank:
-            if rel:
-                # This validator makes sure FileFields work in a related context.
-                class RequiredFileField(object):
-                    def __init__(self, other_field_names, other_file_field_name):
-                        self.other_field_names = other_field_names
-                        self.other_file_field_name = other_file_field_name
-                        self.always_test = True
-                    def __call__(self, field_data, all_data):
-                        if not all_data.get(self.other_file_field_name, False):
-                            c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
-                            c(field_data, all_data)
-                # First, get the core fields, if any.
-                core_field_names = []
-                for f in opts.fields:
-                    if f.core and f != self:
-                        core_field_names.extend(f.get_manipulator_field_names(name_prefix))
-                # Now, if there are any, add the validator to this FormField.
-                if core_field_names:
-                    field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
-            else:
-                v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
-                v.always_test = True
-                field_list[0].validator_list.append(v)
-                field_list[0].is_required = field_list[1].is_required = False
-
-        # If the raw path is passed in, validate it's under the MEDIA_ROOT.
-        def isWithinMediaRoot(field_data, all_data):
-            f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data))
-            if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))):
-                raise validators.ValidationError, _("Enter a valid filename.")
-        field_list[1].validator_list.append(isWithinMediaRoot)
-        return field_list
-
-    def contribute_to_class(self, cls, name):
-        super(FileField, self).contribute_to_class(cls, name)
-        setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
-        setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
-        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
-        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
-        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
-
-    def delete_file(self, instance):
-        if getattr(instance, self.attname):
-            file_name = getattr(instance, 'get_%s_filename' % self.name)()
-            # If the file exists and no other object of this type references it,
-            # delete it from the filesystem.
-            if os.path.exists(file_name) and \
-                not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}):
-                os.remove(file_name)
-
-    def get_manipulator_field_objs(self):
-        return [oldforms.FileUploadField, oldforms.HiddenField]
-
-    def get_manipulator_field_names(self, name_prefix):
-        return [name_prefix + self.name + '_file', name_prefix + self.name]
-
-    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
-        upload_field_name = self.get_manipulator_field_names('')[0]
-        if new_data.get(upload_field_name, False):
-            func = getattr(new_object, 'save_%s_file' % self.name)
-            if rel:
-                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
-            else:
-                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
-
-    def get_directory_name(self):
-        return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
-
-    def get_filename(self, filename):
-        from django.utils.text import get_valid_filename
-        f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
-        return os.path.normpath(f)
-
-    def save_form_data(self, instance, data):
-        from django.newforms.fields import UploadedFile
-        if data and isinstance(data, UploadedFile):
-            getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
-
-    def formfield(self, **kwargs):
-        defaults = {'form_class': forms.FileField}
-        # If a file has been provided previously, then the form doesn't require
-        # that a new file is provided this time.
-        # The code to mark the form field as not required is used by
-        # form_for_instance, but can probably be removed once form_for_instance
-        # is gone. ModelForm uses a different method to check for an existing file.
-        if 'initial' in kwargs:
-            defaults['required'] = False
-        defaults.update(kwargs)
-        return super(FileField, self).formfield(**defaults)
-
 class FilePathField(Field):
     def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
         self.path, self.match, self.recursive = path, match, recursive
@@ -833,40 +728,6 @@
         defaults.update(kwargs)
         return super(FloatField, self).formfield(**defaults)
 
-class ImageField(FileField):
-    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
-        self.width_field, self.height_field = width_field, height_field
-        FileField.__init__(self, verbose_name, name, **kwargs)
-
-    def get_manipulator_field_objs(self):
-        return [oldforms.ImageUploadField, oldforms.HiddenField]
-
-    def contribute_to_class(self, cls, name):
-        super(ImageField, self).contribute_to_class(cls, name)
-        # Add get_BLAH_width and get_BLAH_height methods, but only if the
-        # image field doesn't have width and height cache fields.
-        if not self.width_field:
-            setattr(cls, 'get_%s_width' % self.name, curry(cls._get_FIELD_width, field=self))
-        if not self.height_field:
-            setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self))
-
-    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
-        FileField.save_file(self, new_data, new_object, original_object, change, rel, save)
-        # If the image has height and/or width field(s) and they haven't
-        # changed, set the width and/or height field(s) back to their original
-        # values.
-        if change and (self.width_field or self.height_field) and save:
-            if self.width_field:
-                setattr(new_object, self.width_field, getattr(original_object, self.width_field))
-            if self.height_field:
-                setattr(new_object, self.height_field, getattr(original_object, self.height_field))
-            new_object.save()
-
-    def formfield(self, **kwargs):
-        defaults = {'form_class': forms.ImageField}
-        defaults.update(kwargs)
-        return super(ImageField, self).formfield(**defaults)
-
 class IntegerField(Field):
     empty_strings_allowed = False
     def get_manipulator_field_objs(self):
Index: django/db/models/fields/files.py
===================================================================
--- django/db/models/fields/files.py	(revision 0)
+++ django/db/models/fields/files.py	(revision 0)
@@ -0,0 +1,299 @@
+import datetime
+import os
+
+from django.db.models.fields import Field
+from django.core.filestorage.filesystem import FileSystemBackend
+from django.utils.functional import curry
+from django.dispatch import dispatcher
+from django.db.models import signals
+from django.utils.encoding import force_unicode, smart_str
+from django.utils.translation import ugettext_lazy, ugettext as _
+from django import oldforms
+from django import newforms as forms
+from django.core import validators
+
+class File(object):
+    def __init__(self, obj, field, filename):
+        self.obj = obj
+        self.field = field
+        self.backend = field.backend
+        self.filename = filename or u''
+
+    def __unicode__(self):
+        return self.filename or u''
+
+    def __repr__(self):
+        return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
+
+    def __nonzero__(self):
+        return not not self.filename
+
+    def __eq__(self, other):
+        if not self.filename and not other:
+            # An empty filename should equate to None
+            return True
+        return self.filename == other
+
+    def get_absolute_path(self):
+        if not self:
+            raise ValueError, "The '%s' attribute has no file associated with it." % self.field.name
+        return self.backend.get_absolute_path(self.filename)
+
+    def get_absolute_url(self):
+        if not self:
+            raise ValueError, "The '%s' attribute has no file associated with it." % self.field.name
+        return self.backend.get_absolute_url(self.filename)
+
+    def get_filesize(self):
+        if not self:
+            raise ValueError, "The '%s' attribute has no file associated with it." % self.field.name
+        if not hasattr(self, '_filesize'):
+            self._filesize = self.backend.get_filesize(self.filename)
+        return self._filesize
+
+    def open_file(self, mode='rb'):
+        if not self:
+            raise ValueError, "The '%s' attribute has no file associated with it." % self.field.name
+        return self.backend.open_file(self.filename, mode)
+
+    def save_file(self, filename, raw_contents, save=True):
+        filename = self.field.generate_filename(self.obj, filename)
+        self.filename = self.backend.save_file(filename, raw_contents)
+        self._has_file = True
+
+        # Update the filesize cache
+        self._filesize = len(raw_contents)
+
+        # Save the object because it has changed, unless save is False
+        if save:
+            self.obj.save()
+
+    def delete_file(self, save=True):
+        if not self:
+            raise ValueError, "The '%s' attribute has no file associated with it." % self.field.name
+        self.backend.delete_file(self.filename)
+
+        self.filename = None
+
+        # Delete the filesize cache
+        if hasattr(self, '_filesize'):
+            del self._filesize
+
+        if save:
+            self.obj.save()
+
+class FileProxy(object):
+    def __init__(self, field):
+        self.field = field
+
+    def __get__(self, instance=None, owner=None):
+        if instance is None:
+            raise AttributeError, "%s can only be accessed from %s instances." % (self.field.name, self.owner.__name__)
+        return instance.__dict__[self.field.name]
+
+    def __set__(self, instance, value):
+        attr = self.field.attr_class(instance, self.field, value)
+        instance.__dict__[self.field.name] = attr
+
+class FileField(Field):
+    attr_class = File
+
+    def __init__(self, verbose_name=None, name=None, upload_to='', backend=None, **kwargs):
+        for arg in ('core', 'primary_key', 'unique'):
+            if arg in kwargs:
+                raise TypeError, "__init__() got an unexpected keyword argument '%s'" % arg
+        self.backend = backend or FileSystemBackend()
+
+        self.upload_to = upload_to
+        if callable(upload_to):
+            self.generate_filename = upload_to
+
+        kwargs['max_length'] = kwargs.get('max_length', 100)
+        super(FileField, self).__init__(verbose_name, name, **kwargs)
+
+    def get_db_prep_lookup(self, lookup_type, value):
+        if hasattr(value, 'filename'):
+            value = value.filename
+        return super(FileField, self).get_db_prep_lookup(lookup_type, value)
+
+    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:
+            return None
+        return unicode(value.filename)
+
+    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)
+        if not self.blank:
+            if rel:
+                # This validator makes sure FileFields work in a related context.
+                class RequiredFileField(object):
+                    def __init__(self, other_field_names, other_file_field_name):
+                        self.other_field_names = other_field_names
+                        self.other_file_field_name = other_file_field_name
+                        self.always_test = True
+                    def __call__(self, field_data, all_data):
+                        if not all_data.get(self.other_file_field_name, False):
+                            c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
+                            c(field_data, all_data)
+                # First, get the core fields, if any.
+                core_field_names = []
+                for f in opts.fields:
+                    if f.core and f != self:
+                        core_field_names.extend(f.get_manipulator_field_names(name_prefix))
+                # Now, if there are any, add the validator to this FormField.
+                if core_field_names:
+                    field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
+            else:
+                v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
+                v.always_test = True
+                field_list[0].validator_list.append(v)
+                field_list[0].is_required = field_list[1].is_required = False
+
+        # If the raw path is passed in, validate it's under the MEDIA_ROOT.
+        def isWithinMediaRoot(field_data, all_data):
+            f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data))
+            if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))):
+                raise validators.ValidationError, _("Enter a valid filename.")
+        field_list[1].validator_list.append(isWithinMediaRoot)
+        return field_list
+
+    def contribute_to_class(self, cls, name):
+        super(FileField, self).contribute_to_class(cls, name)
+        setattr(cls, self.name, FileProxy(self))
+        setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
+        setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
+        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
+        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
+        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
+
+    def delete_file(self, instance, sender):
+        filename = getattr(instance, self.attname).filename
+        # If no other object of this type references the file,
+        # delete it from the backend.
+        if not sender._default_manager.filter(**{self.name: filename}):
+            self.backend.delete_file(filename)
+
+    def get_manipulator_field_objs(self):
+        return [oldforms.FileUploadField, oldforms.HiddenField]
+
+    def get_manipulator_field_names(self, name_prefix):
+        return [name_prefix + self.name + '_file', name_prefix + self.name]
+
+    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
+        upload_field_name = self.get_manipulator_field_names('')[0]
+        if new_data.get(upload_field_name, False):
+            if rel:
+                field = new_data[upload_field_name][0]
+            else:
+                field = new_data[upload_field_name]
+            filename = self.get_filename(field["filename"])
+            getattr(new_object, self.attname).save_file(filename, field["content"], save)
+
+    def get_directory_name(self):
+        return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
+
+    def get_filename(self, filename):
+        return os.path.normpath(self.backend.get_valid_filename(os.path.basename(filename)))
+
+    def generate_filename(self, obj, filename):
+        return os.path.join(self.get_directory_name(), self.get_filename(filename))
+
+    def save_form_data(self, instance, data):
+        from django.newforms.fields import UploadedFile 
+        if data and isinstance(data, UploadedFile): 
+            getattr(instance, self.attname).save_file(data.filename, data.content, save=False)
+
+    def formfield(self, **kwargs):
+        defaults = {'form_class': forms.FileField}
+        # If a file has been provided previously, then the form doesn't require
+        # that a new file is provided this time.
+        # The code to mark the form field as not required is used by 
+        # form_for_instance, but can probably be removed once form_for_instance 
+        # is gone. ModelForm uses a different method to check for an existing file.
+        if 'initial' in kwargs:
+            defaults['required'] = False
+        defaults.update(kwargs)
+        return super(FileField, self).formfield(**defaults)
+
+class ImageFile(File):
+    def get_width(self):
+        return self._get_image_dimensions()[0]
+
+    def get_height(self):
+        return self._get_image_dimensions()[1]
+
+    def _get_image_dimensions(self):
+        if not hasattr(self, '_dimensions_cache'):
+            from django.utils.images import get_image_dimensions
+            self._dimensions_cache = get_image_dimensions(self.open_file())
+        return self._dimensions_cache
+
+    def save_file(self, filename, raw_contents, save=True):
+        super(ImageFile, self).save_file(filename, raw_contents, save)
+        
+        # Update the cache for image dimensions
+        from django.utils.images import get_image_dimensions
+        from cStringIO import StringIO
+        self._dimensions_cache = get_image_dimensions(StringIO(raw_contents))
+
+    def delete_file(self, save=True):
+        # Clear the image dimensions cache
+        del self._dimensions_cache
+
+        super(ImageFile, self).delete_file(save)
+
+class ImageField(FileField):
+    attr_class = ImageFile
+
+    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
+        self.width_field, self.height_field = width_field, height_field
+        FileField.__init__(self, verbose_name, name, **kwargs)
+
+    def get_manipulator_field_objs(self):
+        return [oldforms.ImageUploadField, oldforms.HiddenField]
+
+    def contribute_to_class(self, cls, name):
+        super(ImageField, self).contribute_to_class(cls, name)
+        # Add get_BLAH_width and get_BLAH_height methods, but only if the
+        # image field doesn't have width and height cache fields.
+        if not self.width_field:
+            setattr(cls, 'get_%s_width' % self.name, curry(cls._get_FIELD_width, field=self))
+        if not self.height_field:
+            setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self))
+
+    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
+        # If the image has height and/or width field(s) and they haven't
+        # changed, set the width and/or height field(s) back to their original
+        # values.
+        if self.width_field or self.height_field:
+            if original_object and not change:
+                if self.width_field:
+                    setattr(new_object, self.width_field, getattr(original_object, self.width_field))
+                if self.height_field:
+                    setattr(new_object, self.height_field, getattr(original_object, self.height_field))
+            else:
+                from cStringIO import StringIO
+                from django.utils.images import get_image_dimensions
+
+                upload_field_name = self.get_manipulator_field_names('')[0]
+                if rel:
+                    field = new_data[upload_field_name][0]
+                else:
+                    field = new_data[upload_field_name]
+
+                # Get the width and height from the raw content to avoid extra
+                # unnecessary trips to the file backend.
+                width, height = get_image_dimensions(StringIO(field["content"]))
+
+                if self.width_field:
+                    setattr(new_object, self.width_field, width)
+                if self.height_field:
+                    setattr(new_object, self.height_field, height)
+        FileField.save_file(self, new_data, new_object, original_object, change, rel, save)
+
+    def formfield(self, **kwargs):
+        defaults = {'form_class': forms.ImageField}
+        defaults.update(kwargs)
+        return super(ImageField, self).formfield(**defaults)
Index: django/db/models/manipulators.py
===================================================================
--- django/db/models/manipulators.py	(revision 7091)
+++ django/db/models/manipulators.py	(working copy)
@@ -1,7 +1,8 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django import oldforms
 from django.core import validators
-from django.db.models.fields import FileField, AutoField
+from django.db.models.fields import AutoField
+from django.db.models.fields.files import FileField
 from django.dispatch import dispatcher
 from django.db.models import signals
 from django.utils.functional import curry
Index: django/utils/images.py
===================================================================
--- django/utils/images.py	(revision 7091)
+++ django/utils/images.py	(working copy)
@@ -6,10 +6,13 @@
 
 import ImageFile
 
-def get_image_dimensions(path):
-    """Returns the (width, height) of an image at a given path."""
+def get_image_dimensions(file_or_path):
+    """Returns the (width, height) of an image, given an open file or a path."""
     p = ImageFile.Parser()
-    fp = open(path, 'rb')
+    if hasattr(file_or_path, 'read'):
+        fp = file_or_path
+    else:
+        fp = open(file_or_path, 'rb')
     while 1:
         data = fp.read(1024)
         if not data:
Index: docs/db-api.txt
===================================================================
--- docs/db-api.txt	(revision 7091)
+++ docs/db-api.txt	(working copy)
@@ -1871,6 +1871,9 @@
 get_FOO_filename()
 ------------------
 
+**Deprecated in Django development version. See `managing files`_ for the new,
+preferred method for dealing with files.**
+
 For every ``FileField``, the object will have a ``get_FOO_filename()`` method,
 where ``FOO`` is the name of the field. This returns the full filesystem path
 to the file, according to your ``MEDIA_ROOT`` setting.
@@ -1881,6 +1884,9 @@
 get_FOO_url()
 -------------
 
+**Deprecated in Django development version. See `managing files`_ for the new,
+preferred method for dealing with files.**
+
 For every ``FileField``, the object will have a ``get_FOO_url()`` method,
 where ``FOO`` is the name of the field. This returns the full URL to the file,
 according to your ``MEDIA_URL`` setting. If the value is blank, this method
@@ -1889,6 +1895,9 @@
 get_FOO_size()
 --------------
 
+**Deprecated in Django development version. See `managing files`_ for the new,
+preferred method for dealing with files.**
+
 For every ``FileField``, the object will have a ``get_FOO_size()`` method,
 where ``FOO`` is the name of the field. This returns the size of the file, in
 bytes. (Behind the scenes, it uses ``os.path.getsize``.)
@@ -1896,6 +1905,9 @@
 save_FOO_file(filename, raw_contents)
 -------------------------------------
 
+**Deprecated in Django development version. See `managing files`_ for the new,
+preferred method for dealing with files.**
+
 For every ``FileField``, the object will have a ``save_FOO_file()`` method,
 where ``FOO`` is the name of the field. This saves the given file to the
 filesystem, using the given filename. If a file with the given filename already
@@ -1905,10 +1917,15 @@
 get_FOO_height() and get_FOO_width()
 ------------------------------------
 
+**Deprecated in Django development version. See `managing files`_ for the new,
+preferred method for dealing with files.**
+
 For every ``ImageField``, the object will have ``get_FOO_height()`` and
 ``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This
 returns the height (or width) of the image, as an integer, in pixels.
 
+.. _`managing files`: ../files/
+
 Shortcuts
 =========
 
Index: docs/files.txt
===================================================================
--- docs/files.txt	(revision 0)
+++ docs/files.txt	(revision 0)
@@ -0,0 +1,244 @@
+==============
+Managing files
+==============
+
+When dealing with files, Django provides a number of features to make this task
+easier and more portable. A backend protocol is available to allow files to be
+stored in a variety of locations, and a special object is provided to allow
+models to make use of this protocol, without having to worry about which storage
+system is being used.
+
+Using files in models
+=====================
+
+When accessing a ``FileField`` attached to a model, a special object provides
+access to the file and information about it.
+
+get_absolute_path()
+-------------------
+
+Returns the absolute path to the file's location on a local filesystem. For
+backends which do not store files locally, this will return `None`.
+
+get_absolute_url()
+------------------
+
+Provides a URL where the content of the file can be retrieved. Therefore,
+returned from this method is suitable for use as the destination of a link to
+the file.
+
+get_filesize()
+--------------
+
+Returns the size of the file, as an integer.
+
+open_file(mode='rb')
+--------------------
+
+Returns an open file object, providing read or write access to the file's
+contents. The ``mode`` argument allows the same values as Python's standard
+``open()`` function.
+
+save_file(filename, raw_contents, save=True)
+--------------------------------------------
+
+Saves a new file with the filename and contents provided. This will not replace
+the existing file, but will create a new file and update the object to point to
+it. The optional ``save`` argument dictates whether the model instance will be
+saved to the database immediately.
+
+get_width() and get_height()
+----------------------------
+
+When using an ``ImageField``, these two methods will be available, providing
+easy access to the dimensions of the image.
+
+Example
+-------
+
+Consider the following model, using an ``ImageField`` to store a product photo::
+
+    class Product(models.Model):
+        name = models.CharField(maxlength=255)
+        price = models.DecimalField(max_digits=5, decimal_places=2)
+        photo = models.ImageField(upload_to='product_photos')
+
+Your views can then use the ``photo`` attribute with the functions described
+above, as follows::
+
+    >>> car = Product.object.get(name="'57 Chevy")
+    >>> car.photo.get_absolute_url()
+    '/products/photo/123.jpg'
+    >>> car.photo.get_width(), car.photo.get_height()
+    (800, 600)
+
+Using a storage backend with FileField
+======================================
+
+When using a storage backend, supply whatever options are appropriate for
+that backend when creating a new object. Details on the requirements for the
+included backends can be found below. Then pass that object as the ``backend``
+argument to a ``FileField``.
+
+If using the ``FileSystemBackend``, it is not necessary to create a backend
+object explicitly. Simply supplying the ``upload_to`` argument will create the
+backend object automatically.
+
+See the ```FileField`` documentation`_ for more information on using the field.
+
+.. _FileField documentation: ../model-api/#filefield
+
+For example, the following code will explicitly use the ``FileSystemBackend``::
+
+    from django.db import models
+    from django.core.filestorage.filesystem import FileSystemBackend
+    
+    fs = FileSystemBackend(location='product_photos')
+    
+    class Product(models.Model):
+        name = models.CharField(maxlength=255)
+        price = models.DecimalField(max_digits=5, decimal_places=2)
+        photo = models.ImageField(backend=fs)
+
+Using a storage backend on its own
+==================================
+
+Storage backends may also be used directly, without being attached to a model.
+Simply use the following API on any instantiated backend to access files without
+having to worry about the underlying storage mechanism.
+        
+file_exists(filename)
+---------------------
+
+Returns ``True`` or ``False, indicating whether there is already a file present
+at the location referenced by``filename``.
+
+open_file(filename, mode='rb')
+------------------------------
+
+Returns an open file, or file-like, object to provide access to the contents of
+the file referenced by ``filename``. The ``mode`` argument allows the same
+values as Python's standard ``open()`` function.
+
+get_filesize(filename)
+----------------------
+
+Returns the total size of the file referenced by ``filename``, as an integer.
+
+get_absolute_url(filename)
+--------------------------
+
+Returns the URL where the contents of the file referenced by ``filename`` can
+be accessed.
+
+save_file(filename, raw_contents)
+---------------------------------
+
+Saves a new file using the backend-specific storage mechanism, preferably using
+the name specified. If there already exists a file at the location referenced by
+``filename``, this may modify the filename as necessary to locate one that is
+available. Once the file is saved, this method will return the filename where
+the file was actually stored.
+
+delete_file(filename)
+---------------------
+
+Deletes the file referenced by ``filename``. If the file does not already exist,
+this method will simply return without raising an exception.
+
+Available backends
+==================
+
+Only one storage backend is supplied in the official Django distribution, but
+more may be available elsewhere. If you'd like to use a different backend than
+the one listed below, see the documentation included with the backend.
+
+django.core.filestorage.filesystem.FileSystemBackend
+----------------------------------------------------
+
+This backend stores files on the system's standard filesystem.
+
+    ======================  ===================================================
+    Argument                Description
+    ======================  ===================================================
+    ``location``            Required. A local filesystem path that will be
+                            appended to the ``MEDIA_ROOT`` setting to determine
+                            the output of the ``get_<fieldname>_url()`` helper
+                            function.
+    ``media_root``          Required. Absolute path to the directory that holds
+                            the files for this backend. If omitted, it will be
+                            set to the value of your ``MEDIA_ROOT`` setting.
+    ``media_url``           Optional. URL that serves the files stored in this
+                            backend. If omitted, it will default to the value
+                            of your ``MEDIA_URL`` setting.
+    ======================  ===================================================
+
+Writing a storage backend
+=========================
+
+While the default filesystem storage is suitable for most needs, there are many
+other storage mechanisms that may be used, and situations that will require
+special processing. In order to use Django in these environments, it's fairly
+simple to write a new storage backend, creating a wrapper around whatever
+libraries are used to access your files, or simply customizing method calls on
+an existing backend.
+
+A backend must implement the methods described above, but the built-in backends
+also define two other methods to assist in the process. When writing a custom
+backend from scratch, these methods are available on the provided ``Backend``
+class, living at ``django.core.filestorage.base``, so subclassing it will allow
+these methods to be available on the custom backend as well. When extending an
+existing backend, overriding these methods allow a great deal of customization.
+
+get_valid_filename(filename)
+----------------------------
+
+Returns a filename suitable for use with the underlying storage system. The
+``filename`` argument passed to this method is the original filename sent to the
+server, after having any path information removed. Override this to customize
+how non-standard characters are converted to safe filenames.
+
+The code provided on ``Backend`` retains only alpha-numeric characters, periods
+and underscores from the original filename, removing everything else.
+
+get_available_filename(filename)
+--------------------------------
+
+Returns a filename that is available in the storage mechanism, possibly taking
+the provided filename into account. The ``filename`` argument passed to this
+method will have already cleaned to a filename valid for the storage system,
+according to the ``get_valid_filename()`` method described above.
+
+The code provided on ``Backend`` simply appends underscores to the filename
+until it finds one that's available in the destination directory.
+
+Opening remote files
+--------------------
+
+When accessing a file stored at a remote location, the object returned by
+``open_file()`` should function like a standard `file object`_, but to keep
+network traffic to a minimum, writes to the remote storage system should only
+occur if actually necessary. To make this task easier, Django provides a class
+to automate this process.
+
+Living at ``django.core.filestorage.base``, the ``RemoteFile`` class simulates
+a standard Python `file object`_, but can write changes to a remote storage
+system when application using a function provided by the storage backend.
+Creating an instance of this object requires three arguments, which are
+desribed below.
+
+    ======================  ===================================================
+    Argument                Description
+    ======================  ===================================================
+    ``data``                The raw content of the file.
+    ``mode``                The access mode that was passed to the
+                            ``open_file()`` method.
+    ``writer``              A function that will be used to write the contents
+                            to the backend-specific storage mechanism. The
+                            function provided here will need to take a single
+                            argument, which will be the raw content to be
+                            written to the file.
+    ======================  ===================================================
+
+.. _file object: http://docs.python.org/lib/bltin-file-objects.html
+
Index: docs/model-api.txt
===================================================================
--- docs/model-api.txt	(revision 7091)
+++ docs/model-api.txt	(working copy)
@@ -230,26 +230,34 @@
 ``FileField``
 ~~~~~~~~~~~~~
 
-A file-upload field. Has one **required** argument:
+A file-upload field. **Requires** exactly one of the following two arguments:
 
     ======================  ===================================================
     Argument                Description
     ======================  ===================================================
     ``upload_to``           A local filesystem path that will be appended to
                             your ``MEDIA_ROOT`` setting to determine the
-                            output of the ``get_<fieldname>_url()`` helper
-                            function.
+                            final storage destination. If this argument is
+                            supplied, the storage backend will default to
+                            ``FileSystemBackend``.
+    ``backend``             **New in Django development version**
+
+                            A storage backend object, which handles the storage
+                            and retrieval of your files. See `managing files`_
+                            for details on how to provide this object.
     ======================  ===================================================
 
-This path may contain `strftime formatting`_, which will be replaced by the
-date/time of the file upload (so that uploaded files don't fill up the given
-directory).
+.. _managing files: ../files/
 
+The ``upload_to`` path may contain `strftime formatting`_, which will be
+replaced by the date/time of the file upload (so that uploaded files don't fill
+up the given directory).
+
 The admin represents this field as an ``<input type="file">`` (a file-upload
 widget).
 
-Using a ``FileField`` or an ``ImageField`` (see below) in a model takes a few
-steps:
+Using a ``FileField`` or an ``ImageField`` (see below) in a model without a
+specified backend takes a few steps:
 
     1. In your settings file, you'll need to define ``MEDIA_ROOT`` as the
        full path to a directory where you'd like Django to store uploaded

Property changes on: tests\modeltests\files
___________________________________________________________________
Name: svn:ignore
   + *.pyc


Index: tests/modeltests/files/__init__.py
===================================================================
Index: tests/modeltests/files/models.py
===================================================================
--- tests/modeltests/files/models.py	(revision 0)
+++ tests/modeltests/files/models.py	(revision 0)
@@ -0,0 +1,81 @@
+"""
+42. Storing files according to a custom backend
+
+FileField and its variations can take a "backend" argument to specify how and
+where files should be stored. 
+"""
+
+import tempfile
+
+from django.db import models
+from django.core.filestorage.filesystem import FileSystemBackend
+
+temp_dir = tempfile.gettempdir()
+
+class CustomBackend(FileSystemBackend):
+    def get_available_filename(self, filename):
+        # Append numbers to duplicate files rather than underscores, like Trac
+
+        parts = filename.split('.')
+        basename, ext = parts[0], parts[1:]
+        number = 2
+
+        while self.file_exists(filename):
+            filename = '.'.join([basename, str(number)] + ext)
+            number += 1
+
+        return filename
+
+standard_backend = FileSystemBackend(location=temp_dir)
+custom_backend = CustomBackend(location=temp_dir)
+
+class Storage(models.Model):
+    normal = models.FileField(backend=standard_backend, upload_to='tests')
+    custom = models.FileField(backend=custom_backend, upload_to='tests')
+
+__test__ = {'API_TESTS':"""
+# An object without a file has limited functionality
+
+>>> obj1 = Storage()
+>>> obj1.normal
+<File: >
+>>> obj1.normal.get_filesize()
+Traceback (most recent call last):
+...
+ValueError: The 'normal' attribute has no file associated with it.
+
+# Saving a file enables full functionality
+
+>>> obj1.normal.save_file('django_test.txt', 'content')
+>>> obj1.normal
+<File: tests/django_test.txt>
+>>> obj1.normal.get_filesize()
+7
+>>> obj1.normal.open_file().read()
+'content'
+
+# Save another file with the same name
+
+>>> obj2 = Storage()
+>>> obj2.normal.save_file('django_test.txt', 'more content')
+>>> obj2.normal
+<File: tests/django_test_.txt>
+>>> obj2.normal.get_filesize()
+12
+
+# Custom backends can behave differently
+
+>>> obj1.custom.save_file('django_test.txt', 'trac-style filenames')
+>>> obj1.custom
+<File: tests/django_test.2.txt>
+>>> obj2.custom.save_file('django_test.txt', 'another file')
+>>> obj2.custom
+<File: tests/django_test.3.txt>
+
+# Clean up the temporary files
+
+>>> obj1.normal.delete_file()
+>>> obj1.custom.delete_file()
+>>> obj2.normal.delete_file()
+>>> obj2.custom.delete_file()
+"""}
Index: tests/modeltests/files/__init__.py
===================================================================
Index: tests/modeltests/files/models.py
===================================================================
--- tests/modeltests/files/models.py	(revision 0)
+++ tests/modeltests/files/models.py	(revision 0)
@@ -0,0 +1,81 @@
+"""
+42. Storing files according to a custom backend
+
+FileField and its variations can take a "backend" argument to specify how and
+where files should be stored. 
+"""
+
+import tempfile
+
+from django.db import models
+from django.core.filestorage.filesystem import FileSystemBackend
+
+temp_dir = tempfile.gettempdir()
+
+class CustomBackend(FileSystemBackend):
+    def get_available_filename(self, filename):
+        # Append numbers to duplicate files rather than underscores, like Trac
+
+        parts = filename.split('.')
+        basename, ext = parts[0], parts[1:]
+        number = 2
+
+        while self.file_exists(filename):
+            filename = '.'.join([basename, str(number)] + ext)
+            number += 1
+
+        return filename
+
+standard_backend = FileSystemBackend(location=temp_dir)
+custom_backend = CustomBackend(location=temp_dir)
+
+class Storage(models.Model):
+    normal = models.FileField(backend=standard_backend, upload_to='tests')
+    custom = models.FileField(backend=custom_backend, upload_to='tests')
+
+__test__ = {'API_TESTS':"""
+# An object without a file has limited functionality
+
+>>> obj1 = Storage()
+>>> obj1.normal
+<File: >
+>>> obj1.normal.get_filesize()
+Traceback (most recent call last):
+...
+ValueError: The 'normal' attribute has no file associated with it.
+
+# Saving a file enables full functionality
+
+>>> obj1.normal.save_file('django_test.txt', 'content')
+>>> obj1.normal
+<File: tests/django_test.txt>
+>>> obj1.normal.get_filesize()
+7
+>>> obj1.normal.open_file().read()
+'content'
+
+# Save another file with the same name
+
+>>> obj2 = Storage()
+>>> obj2.normal.save_file('django_test.txt', 'more content')
+>>> obj2.normal
+<File: tests/django_test_.txt>
+>>> obj2.normal.get_filesize()
+12
+
+# Custom backends can behave differently
+
+>>> obj1.custom.save_file('django_test.txt', 'trac-style filenames')
+>>> obj1.custom
+<File: tests/django_test.2.txt>
+>>> obj2.custom.save_file('django_test.txt', 'another file')
+>>> obj2.custom
+<File: tests/django_test.3.txt>
+
+# Clean up the temporary files
+
+>>> obj1.normal.delete_file()
+>>> obj1.custom.delete_file()
+>>> obj2.normal.delete_file()
+>>> obj2.custom.delete_file()
+"""}
Index: tests/modeltests/model_forms/models.py
===================================================================
--- tests/modeltests/model_forms/models.py	(revision 7091)
+++ tests/modeltests/model_forms/models.py	(working copy)
@@ -743,7 +743,7 @@
 <class 'django.newforms.fields.UploadedFile'>
 >>> instance = f.save()
 >>> instance.file
-u'.../test1.txt'
+<File: .../test1.txt>
 
 # Edit an instance that already has the file defined in the model. This will not
 # save the file again, but leave it exactly as it is.
@@ -752,14 +752,14 @@
 >>> f.is_valid()
 True
 >>> f.cleaned_data['file']
-u'.../test1.txt'
+<File: .../test1.txt>
 >>> instance = f.save()
 >>> instance.file
-u'.../test1.txt'
+<File: .../test1.txt>
 
 # Delete the current file since this is not done by Django.
 
->>> os.unlink(instance.get_file_filename())
+>>> instance.file.delete_file()
 
 # Override the file by uploading a new one.
 
@@ -768,7 +768,7 @@
 True
 >>> instance = f.save()
 >>> instance.file
-u'.../test2.txt'
+<File: .../test2.txt>
 
 >>> instance.delete()
 
@@ -780,14 +780,14 @@
 True
 >>> instance = f.save()
 >>> instance.file
-''
+<File: >
 
 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
 >>> f.is_valid()
 True
 >>> instance = f.save()
 >>> instance.file
-u'.../test3.txt'
+<File: .../test3.txt>
 >>> instance.delete()
 
 # ImageField ###################################################################
@@ -809,7 +809,7 @@
 <class 'django.newforms.fields.UploadedFile'>
 >>> instance = f.save()
 >>> instance.image
-u'.../test.png'
+<File: .../test.png>
 
 # Edit an instance that already has the image defined in the model. This will not
 # save the image again, but leave it exactly as it is.
@@ -818,14 +818,14 @@
 >>> f.is_valid()
 True
 >>> f.cleaned_data['image']
-u'.../test.png'
+<File: .../test.png>
 >>> instance = f.save()
 >>> instance.image
-u'.../test.png'
+<File: .../test.png>
 
 # Delete the current image since this is not done by Django.
 
->>> os.unlink(instance.get_image_filename())
+>>> instance.image.delete_file()
 
 # Override the file by uploading a new one.
 
@@ -834,7 +834,7 @@
 True
 >>> instance = f.save()
 >>> instance.image
-u'.../test2.png'
+<File: .../test2.png>
 
 >>> instance.delete()
 
@@ -846,14 +846,14 @@
 True
 >>> instance = f.save()
 >>> instance.image
-''
+<File: >
 
 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
 >>> f.is_valid()
 True
 >>> instance = f.save()
 >>> instance.image
-u'.../test3.png'
+<File: .../test3.png>
 >>> instance.delete()
 
 """}
Index: tests/regressiontests/bug639/tests.py
===================================================================
--- tests/regressiontests/bug639/tests.py	(revision 7091)
+++ tests/regressiontests/bug639/tests.py	(working copy)
@@ -39,4 +39,4 @@
         Make sure to delete the "uploaded" file to avoid clogging /tmp.
         """
         p = Photo.objects.get()
-        os.unlink(p.get_image_filename())
\ No newline at end of file
+        os.unlink(p.image.get_absolute_path())
\ No newline at end of file
Index: tests/regressiontests/serializers_regress/models.py
===================================================================
--- tests/regressiontests/serializers_regress/models.py	(revision 7091)
+++ tests/regressiontests/serializers_regress/models.py	(working copy)
@@ -158,8 +158,8 @@
 class EmailPKData(models.Model):
     data = models.EmailField(primary_key=True)
 
-class FilePKData(models.Model):
-    data = models.FileField(primary_key=True, upload_to='/foo/bar')
+# class FilePKData(models.Model):
+#    data = models.FileField(primary_key=True, upload_to='/foo/bar')
 
 class FilePathPKData(models.Model):
     data = models.FilePathField(primary_key=True)
Index: tests/regressiontests/serializers_regress/tests.py
===================================================================
--- tests/regressiontests/serializers_regress/tests.py	(revision 7091)
+++ tests/regressiontests/serializers_regress/tests.py	(working copy)
@@ -223,7 +223,7 @@
 #     (pk_obj, 620, DatePKData, datetime.date(2006,6,16)),
 #     (pk_obj, 630, DateTimePKData, datetime.datetime(2006,6,16,10,42,37)),
     (pk_obj, 640, EmailPKData, "hovercraft@example.com"),
-    (pk_obj, 650, FilePKData, 'file:///foo/bar/whiz.txt'),
+#     (pk_obj, 650, FilePKData, 'file:///foo/bar/whiz.txt'),
     (pk_obj, 660, FilePathPKData, "/foo/bar/whiz.txt"),
     (pk_obj, 670, DecimalPKData, decimal.Decimal('12.345')),
     (pk_obj, 671, DecimalPKData, decimal.Decimal('-12.345')),
