Django

Code

Ticket #5361: filestorage.diff

File filestorage.diff, 28.1 kB (added by Marty Alchin <gulopine@gamemusic.org>, 10 months ago)

Moved backend code into django.core.filestorage, added documentation and a few minor tweaks

  • django/core/filestorage/__init__.py

    old new  
     1from StringIO import StringIO 
     2 
     3class Backend(object): 
     4    def get_available_filename(self, filename): 
     5        # If the filename already exists, keep adding an underscore to the name 
     6        # of the file until the filename doesn't exist. 
     7        while self.file_exists(filename): 
     8            try: 
     9                dot_index = filename.rindex('.') 
     10            except ValueError: # filename has no dot 
     11                filename += '_' 
     12            else: 
     13                filename = filename[:dot_index] + '_' + filename[dot_index:] 
     14        return filename 
     15 
     16    def get_filename(self, filename): 
     17        return filename 
     18 
     19class RemoteFile(StringIO): 
     20    """Sends files to a remote backend automatically, when necessary.""" 
     21 
     22    def __init__(self, data, mode, writer): 
     23        self._mode = mode 
     24        self._write_to_backend = writer 
     25        self._is_dirty = False 
     26        StringIO.__init__(self, data) 
     27 
     28    def write(self, data): 
     29        if 'w' not in self._mode: 
     30            raise AttributeError, "File was opened for read-only access." 
     31        StringIO.write(self, data) 
     32        self._is_dirty = True 
     33 
     34    def close(self): 
     35        if self._is_dirty: 
     36            self._write_to_backend(self.getvalue()) 
     37        StringIO.close(self) 
     38 
  • django/core/filestorage/filesystem.py

    old new  
     1import datetime 
     2import os 
     3 
     4from django.conf import settings 
     5from django.utils.encoding import force_unicode, smart_str 
     6 
     7from django.core.filestorage import Backend 
     8 
     9class FileSystemBackend(Backend): 
     10    """Standard filesystem storage""" 
     11 
     12    def __init__(self, location='', media_root=None, media_url=None): 
     13        self.location = location 
     14        if media_root != None and media_url != None: 
     15            # Both were provided, so use them 
     16            pass 
     17        elif media_root is None and media_url is None: 
     18            # Neither were provided, so use global settings 
     19            from django.conf import settings 
     20            try: 
     21                media_root = settings.MEDIA_ROOT 
     22                media_url = settings.MEDIA_URL 
     23            except AttributeError: 
     24                raise ImproperlyConfigured, "Media settings not defined." 
     25        else: 
     26            # One or the other were provided, but not both 
     27            raise ImproperlyConfigured, "Both media_root and media_url must be provided." 
     28        self.media_root = media_root 
     29        self.media_url = media_url 
     30 
     31    def _get_directory_name(self): 
     32        return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.location)))) 
     33 
     34    def _get_absolute_path(self, filename): 
     35        return os.path.normpath(os.path.join(self.media_root, filename)) 
     36 
     37    # The following methods define the Backend API 
     38 
     39    def get_available_filename(self, filename): 
     40        from django.utils.text import get_valid_filename 
     41        f = os.path.join(self._get_directory_name(), get_valid_filename(os.path.basename(filename))) 
     42        return Backend.get_available_filename(self, os.path.normpath(f)) 
     43 
     44    def get_filesize(self, filename): 
     45        return os.path.getsize(self._get_absolute_path(filename)) 
     46 
     47    def get_absolute_url(self, filename): 
     48        import urlparse 
     49        return urlparse.urljoin(self.media_url, filename).replace('\\', '/') 
     50 
     51    def open(self, filename, mode='rb'): 
     52        return open(self._get_absolute_path(filename), mode) 
     53 
     54    def file_exists(self, filename): 
     55        return os.path.exists(self._get_absolute_path(filename)) 
     56 
     57    def save_file(self, filename, raw_contents): 
     58        directory = self._get_directory_name() 
     59        try: # Create the date-based directory if it doesn't exist. 
     60            os.makedirs(os.path.join(self.media_root, directory)) 
     61        except OSError: # Directory probably already exists. 
     62            pass 
     63        filename = self.get_available_filename(filename) 
     64 
     65        # Write the file to disk. 
     66        fp = open(self._get_absolute_path(filename), 'wb') 
     67        fp.write(raw_contents) 
     68        fp.close() 
     69 
     70        return filename 
     71 
     72    def delete_file(self, filename): 
     73        file_name = self._get_absolute_path(filename) 
     74        # If the file exists, delete it from the filesystem. 
     75        if os.path.exists(file_name): 
     76            os.remove(file_name) 
  • django/core/filestorage/__init__.py

    old new  
     1from StringIO import StringIO 
     2 
     3class Backend(object): 
     4    def get_available_filename(self, filename): 
     5        # If the filename already exists, keep adding an underscore to the name 
     6        # of the file until the filename doesn't exist. 
     7        while self.file_exists(filename): 
     8            try: 
     9                dot_index = filename.rindex('.') 
     10            except ValueError: # filename has no dot 
     11                filename += '_' 
     12            else: 
     13                filename = filename[:dot_index] + '_' + filename[dot_index:] 
     14        return filename 
     15 
     16    def get_filename(self, filename): 
     17        return filename 
     18 
     19class RemoteFile(StringIO): 
     20    """Sends files to a remote backend automatically, when necessary.""" 
     21 
     22    def __init__(self, data, mode, writer): 
     23        self._mode = mode 
     24        self._write_to_backend = writer 
     25        self._is_dirty = False 
     26        StringIO.__init__(self, data) 
     27 
     28    def write(self, data): 
     29        if 'w' not in self._mode: 
     30            raise AttributeError, "File was opened for read-only access." 
     31        StringIO.write(self, data) 
     32        self._is_dirty = True 
     33 
     34    def close(self): 
     35        if self._is_dirty: 
     36            self._write_to_backend(self.getvalue()) 
     37        StringIO.close(self) 
     38 
  • django/core/filestorage/filesystem.py

    old new  
     1import datetime 
     2import os 
     3 
     4from django.conf import settings 
     5from django.utils.encoding import force_unicode, smart_str 
     6 
     7from django.core.filestorage import Backend 
     8 
     9class FileSystemBackend(Backend): 
     10    """Standard filesystem storage""" 
     11 
     12    def __init__(self, location='', media_root=None, media_url=None): 
     13        self.location = location 
     14        if media_root != None and media_url != None: 
     15            # Both were provided, so use them 
     16            pass 
     17        elif media_root is None and media_url is None: 
     18            # Neither were provided, so use global settings 
     19            from django.conf import settings 
     20            try: 
     21                media_root = settings.MEDIA_ROOT 
     22                media_url = settings.MEDIA_URL 
     23            except AttributeError: 
     24                raise ImproperlyConfigured, "Media settings not defined." 
     25        else: 
     26            # One or the other were provided, but not both 
     27            raise ImproperlyConfigured, "Both media_root and media_url must be provided." 
     28        self.media_root = media_root 
     29        self.media_url = media_url 
     30 
     31    def _get_directory_name(self): 
     32        return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.location)))) 
     33 
     34    def _get_absolute_path(self, filename): 
     35        return os.path.normpath(os.path.join(self.media_root, filename)) 
     36 
     37    # The following methods define the Backend API 
     38 
     39    def get_available_filename(self, filename): 
     40        from django.utils.text import get_valid_filename 
     41        f = os.path.join(self._get_directory_name(), get_valid_filename(os.path.basename(filename))) 
     42        return Backend.get_available_filename(self, os.path.normpath(f)) 
     43 
     44    def get_filesize(self, filename): 
     45        return os.path.getsize(self._get_absolute_path(filename)) 
     46 
     47    def get_absolute_url(self, filename): 
     48        import urlparse 
     49        return urlparse.urljoin(self.media_url, filename).replace('\\', '/') 
     50 
     51    def open(self, filename, mode='rb'): 
     52        return open(self._get_absolute_path(filename), mode) 
     53 
     54    def file_exists(self, filename): 
     55        return os.path.exists(self._get_absolute_path(filename)) 
     56 
     57    def save_file(self, filename, raw_contents): 
     58        directory = self._get_directory_name() 
     59        try: # Create the date-based directory if it doesn't exist. 
     60            os.makedirs(os.path.join(self.media_root, directory)) 
     61        except OSError: # Directory probably already exists. 
     62            pass 
     63        filename = self.get_available_filename(filename) 
     64 
     65        # Write the file to disk. 
     66        fp = open(self._get_absolute_path(filename), 'wb') 
     67        fp.write(raw_contents) 
     68        fp.close() 
     69 
     70        return filename 
     71 
     72    def delete_file(self, filename): 
     73        file_name = self._get_absolute_path(filename) 
     74        # If the file exists, delete it from the filesystem. 
     75        if os.path.exists(file_name): 
     76            os.remove(file_name) 
  • django/db/models/base.py

    old new  
    1818import types 
    1919import sys 
    2020import os 
     21from warnings import warn 
    2122 
    2223class ModelBase(type): 
    2324    "Metaclass for all models" 
     
    357358        return getattr(self, cachename) 
    358359 
    359360    def _get_FIELD_filename(self, field): 
    360         if getattr(self, field.attname): # value is not blank 
    361             return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname)) 
    362         return '' 
     361        warn("Use instance.%s.open() if you need access to the file." % field.attname, DeprecationWarning) 
     362        return field.backend._get_absolute_path(self.__dict__[field.attname]) 
    363363 
    364364    def _get_FIELD_url(self, field): 
    365         if getattr(self, field.attname): # value is not blank 
    366             import urlparse 
    367             return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/') 
    368         return '' 
     365        warn("Use instance.%s.get_absolute_url()." % field.attname, DeprecationWarning) 
     366        return getattr(self, field.attname).get_absolute_url() 
    369367 
    370368    def _get_FIELD_size(self, field): 
    371         return os.path.getsize(self._get_FIELD_filename(field)) 
     369        warn("Use instance.%s.get_filesize()." % field.attname, DeprecationWarning) 
     370        return getattr(self, field.attname).get_filesize() 
    372371 
    373372    def _save_FIELD_file(self, field, filename, raw_contents, save=True): 
    374         directory = field.get_directory_name() 
    375         try: # Create the date-based directory if it doesn't exist. 
    376             os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 
    377         except OSError: # Directory probably already exists. 
    378             pass 
    379         filename = field.get_filename(filename) 
     373        warn("Use instance.%s.save_file()." % field.attname, DeprecationWarning) 
     374        return getattr(self, field.attname).save_file(filename, raw_contents, save) 
    380375 
    381         # If the filename already exists, keep adding an underscore to the name of 
    382         # the file until the filename doesn't exist. 
    383         while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)): 
    384             try: 
    385                 dot_index = filename.rindex('.') 
    386             except ValueError: # filename has no dot 
    387                 filename += '_' 
    388             else: 
    389                 filename = filename[:dot_index] + '_' + filename[dot_index:] 
    390  
    391         # Write the file to disk. 
    392         setattr(self, field.attname, filename) 
    393  
    394         full_filename = self._get_FIELD_filename(field) 
    395         fp = open(full_filename, 'wb') 
    396         fp.write(raw_contents) 
    397         fp.close() 
    398  
    399         # Save the width and/or height, if applicable. 
    400         if isinstance(field, ImageField) and (field.width_field or field.height_field): 
    401             from django.utils.images import get_image_dimensions 
    402             width, height = get_image_dimensions(full_filename) 
    403             if field.width_field: 
    404                 setattr(self, field.width_field, width) 
    405             if field.height_field: 
    406                 setattr(self, field.height_field, height) 
    407  
    408         # Save the object because it has changed unless save is False 
    409         if save: 
    410             self.save() 
    411  
    412     _save_FIELD_file.alters_data = True 
    413  
    414376    def _get_FIELD_width(self, field): 
    415         return self._get_image_dimensions(field)[0] 
     377        warn("Use instance.%s.get_width()." % field.attname, DeprecationWarning) 
     378        return getattr(self, field.attname).get_width() 
    416379 
    417380    def _get_FIELD_height(self, field): 
    418         return self._get_image_dimensions(field)[1] 
     381        warn("Use instance.%s.get_height()." % field.attname, DeprecationWarning) 
     382        return getattr(self, field.attname).get_height() 
    419383 
    420     def _get_image_dimensions(self, field): 
    421         cachename = "__%s_dimensions_cache" % field.name 
    422         if not hasattr(self, cachename): 
    423             from django.utils.images import get_image_dimensions 
    424             filename = self._get_FIELD_filename(field) 
    425             setattr(self, cachename, get_image_dimensions(filename)) 
    426         return getattr(self, cachename) 
    427  
    428384############################################ 
    429385# HELPER FUNCTIONS (CURRIED MODEL METHODS) # 
    430386############################################ 
  • django/db/models/fields/__init__.py

    old new  
    694694        defaults.update(kwargs) 
    695695        return super(EmailField, self).formfield(**defaults) 
    696696 
     697class File(object): 
     698    def __init__(self, obj, field, filename): 
     699        self.obj = obj 
     700        self.field = field 
     701        self.backend = field.backend 
     702        self.filename = filename 
     703 
     704    def __str__(self): 
     705        return self.backend.get_filename(self.filename) 
     706 
     707    def get_absolute_url(self): 
     708        return self.backend.get_absolute_url(self.filename) 
     709 
     710    def get_filesize(self): 
     711        if not hasattr(self, '_filesize'): 
     712            self._filesize = self.backend.get_filesize(self.filename)) 
     713        return self._filesize 
     714 
     715    def open(self, mode='rb'): 
     716        return self.backend.open(self.filename, mode) 
     717 
     718    def save_file(self, filename, raw_contents, save=True): 
     719        self.filename = self.backend.save_file(filename, raw_contents) 
     720 
     721        # Save the object because it has changed, unless save is False 
     722        if save: 
     723            self.obj.save() 
     724 
     725class FileProxy(object): 
     726    def __init__(self, field): 
     727        self.field = field 
     728        self.cache_name = self.field.get_cache_name() 
     729 
     730    def __get__(self, instance=None, owner=None): 
     731        if instance is None: 
     732            raise AttributeError, "%s can only be accessed from %s instances." % (self.field.attname, self.owner.__name__) 
     733        return getattr(instance, self.cache_name) 
     734 
     735    def __set__(self, instance, value): 
     736        if hasattr(instance, self.cache_name): 
     737            raise AttributeError, "%s can not be set in this manner." % self.field.attname 
     738        instance.__dict__[self.field.attname] = value 
     739        attr = self.field.attr_class(instance, self.field, value) 
     740        setattr(instance, self.cache_name, attr) 
     741 
    697742class FileField(Field): 
    698     def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): 
    699         self.upload_to = upload_to 
     743    attr_class = File 
     744 
     745    def __init__(self, verbose_name=None, name=None, upload_to='', backend=None, **kwargs): 
     746        if backend is None: 
     747            from django.db.models.fields.backends.filesystem import FileSystemBackend 
     748            backend = FileSystemBackend(location=upload_to) 
     749        self.backend = self.upload_to = backend 
    700750        Field.__init__(self, verbose_name, name, **kwargs) 
    701751 
    702752    def get_db_prep_save(self, value): 
     
    704754        # Need to convert UploadedFile objects provided via a form to unicode for database insertion 
    705755        if value is None: 
    706756            return None 
    707         return unicode(value
     757        return unicode(value.filename
    708758 
    709759    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): 
    710760        field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) 
     
    744794 
    745795    def contribute_to_class(self, cls, name): 
    746796        super(FileField, self).contribute_to_class(cls, name) 
     797        setattr(cls, self.attname, FileProxy(self)) 
    747798        setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 
    748799        setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 
    749800        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 
    750801        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save)) 
    751802        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 
    752803 
    753     def delete_file(self, instance): 
    754         if getattr(instance, self.attname): 
    755             file_name = getattr(instance, 'get_%s_filename' % self.name)() 
    756             # If the file exists and no other object of this type references it, 
    757             # delete it from the filesystem. 
    758             if os.path.exists(file_name) and \ 
    759                 not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}): 
    760                 os.remove(file_name) 
     804    def delete_file(self, instance, sender): 
     805        filename = getattr(instance, self.attname).filename 
     806        # If no other object of this type references the file, 
     807        # delete it from the backend. 
     808        if not sender._default_manager.filter(**{self.name: filename}): 
     809            self.backend.delete_file(filename) 
    761810 
    762811    def get_manipulator_field_objs(self): 
    763812        return [oldforms.FileUploadField, oldforms.HiddenField] 
     
    768817    def save_file(self, new_data, new_object, original_object, change, rel, save=True): 
    769818        upload_field_name = self.get_manipulator_field_names('')[0] 
    770819        if new_data.get(upload_field_name, False): 
    771             func = getattr(new_object, 'save_%s_file' % self.name) 
    772820            if rel: 
    773                 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save) 
     821                field = new_data[upload_field_name][0] 
    774822            else: 
    775                 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save) 
     823                field = new_data[upload_field_name] 
     824            getattr(new_object, self.attname).save_file(field["filename"], field["content"], save) 
    776825 
    777     def get_directory_name(self): 
    778         return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) 
    779  
    780     def get_filename(self, filename): 
    781         from django.utils.text import get_valid_filename 
    782         f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename))) 
    783         return os.path.normpath(f) 
    784  
    785826    def save_form_data(self, instance, data): 
    786827        if data: 
    787             getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False) 
    788          
     828            getattr(instance, self.attnamename).save_file(data.filename, data.content, save=False) 
     829 
    789830    def formfield(self, **kwargs): 
    790831        defaults = {'form_class': forms.FileField} 
    791832        # If a file has been provided previously, then the form doesn't require  
     
    814855        defaults.update(kwargs) 
    815856        return super(FloatField, self).formfield(**defaults) 
    816857 
     858class ImageFile(File): 
     859    def get_width(self): 
     860        return self._get_image_dimensions()[0] 
     861 
     862    def get_height(self): 
     863        return self._get_image_dimensions()[1] 
     864 
     865    def _get_image_dimensions(self): 
     866        if not hasattr(self, '_dimensions_cache'): 
     867            from django.utils.images import get_image_dimensions 
     868            self._dimensions_cache = get_image_dimensions(self.open()) 
     869        return self._dimensions_cache 
     870 
    817871class ImageField(FileField): 
     872    attr_class = ImageFile 
     873 
    818874    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): 
    819875        self.width_field, self.height_field = width_field, height_field 
    820876        FileField.__init__(self, verbose_name, name, **kwargs) 
     
    832888            setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self)) 
    833889 
    834890    def save_file(self, new_data, new_object, original_object, change, rel, save=True): 
    835         FileField.save_file(self, new_data, new_object, original_object, change, rel, save) 
    836891        # If the image has height and/or width field(s) and they haven't 
    837892        # changed, set the width and/or height field(s) back to their original 
    838893        # values. 
    839         if change and (self.width_field or self.height_field) and save: 
    840             if self.width_field: 
    841                 setattr(new_object, self.width_field, getattr(original_object, self.width_field)) 
    842             if self.height_field: 
    843                 setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 
    844             new_object.save() 
     894        if self.width_field or self.height_field: 
     895            if original_object and not change: 
     896                if self.width_field: 
     897                    setattr(new_object, self.width_field, getattr(original_object, self.width_field)) 
     898                if self.height_field: 
     899                    setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 
     900            else: 
     901                from cStringIO import StringIO 
     902                from django.utils.images import get_image_dimensions 
    845903 
     904                upload_field_name = self.get_manipulator_field_names('')[0] 
     905                if rel: 
     906                    field = new_data[upload_field_name][0] 
     907                else: 
     908                    field = new_data[upload_field_name] 
     909 
     910                # Get the width and height from the raw content to avoid extra 
     911                # unnecessary trips to the file backend. 
     912                width, height = get_image_dimensions(StringIO(field["content"])) 
     913 
     914                if self.width_field: 
     915                    setattr(new_object, self.width_field, width) 
     916                if self.height_field: 
     917                    setattr(new_object, self.height_field, height) 
     918        FileField.save_file(self, new_data, new_object, original_object, change, rel, save) 
     919 
    846920    def formfield(self, **kwargs): 
    847921        defaults = {'form_class': forms.ImageField} 
    848922        return super(ImageField, self).formfield(**defaults) 
  • django/utils/images.py

    old new  
    66 
    77import ImageFile 
    88 
    9 def get_image_dimensions(path): 
    10     """Returns the (width, height) of an image at a given path.""" 
     9def get_image_dimensions(file_or_path): 
     10    """Returns the (width, height) of an image, given an open file or a path.""" 
    1111    p = ImageFile.Parser() 
    12     fp = open(path, 'rb') 
     12    if hasattr(file_or_path, 'read'): 
     13        fp = file_or_path 
     14    else: 
     15        fp = open(file_or_path, 'rb') 
    1316    while 1: 
    1417        data = fp.read(1024) 
    1518        if not data: 
     
    1922            return p.image.size 
    2023            break 
    2124    fp.close() 
    22     return None 
     25    return None 
  • docs/db-api.txt

    old new  
    18481848get_FOO_filename() 
    18491849------------------ 
    18501850 
     1851**Deprecated in Django development version. See `managing files` for the new, 
     1852preferred method for dealing with files.** 
     1853 
    18511854For every ``FileField``, the object will have a ``get_FOO_filename()`` method, 
    18521855where ``FOO`` is the name of the field. This returns the full filesystem path 
    18531856to the file, according to your ``MEDIA_ROOT`` setting. 
     
    18581861get_FOO_url() 
    18591862------------- 
    18601863 
     1864**Deprecated in Django development version. See `managing files` for the new, 
     1865preferred method for dealing with files.** 
     1866 
    18611867For every ``FileField``, the object will have a ``get_FOO_url()`` method, 
    18621868where ``FOO`` is the name of the field. This returns the full URL to the file, 
    18631869according to your ``MEDIA_URL`` setting. If the value is blank, this method 
     
    18661872get_FOO_size() 
    18671873-------------- 
    18681874 
     1875**Deprecated in Django development version. See `managing files` for the new, 
     1876preferred method for dealing with files.** 
     1877 
    18691878For every ``FileField``, the object will have a ``get_FOO_size()`` method, 
    18701879where ``FOO`` is the name of the field. This returns the size of the file, in 
    18711880bytes. (Behind the scenes, it uses ``os.path.getsize``.) 
     
    18731882save_FOO_file(filename, raw_contents) 
    18741883------------------------------------- 
    18751884 
     1885**Deprecated in Django development version. See `managing files` for the new, 
     1886preferred method for dealing with files.** 
     1887 
    18761888For every ``FileField``, the object will have a ``save_FOO_file()`` method, 
    18771889where ``FOO`` is the name of the field. This saves the given file to the 
    18781890filesystem, using the given filename. If a file with the given filename already 
     
    18821894get_FOO_height() and get_FOO_width() 
    18831895------------------------------------ 
    18841896 
     1897**Deprecated in Django development version. See `managing files` for the new, 
     1898preferred method for dealing with files.** 
     1899 
    18851900For every ``ImageField``, the object will have ``get_FOO_height()`` and 
    18861901``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This 
    18871902returns the height (or width) of the image, as an integer, in pixels. 
  • docs/model-api.txt

    old new  
    227227``FileField`` 
    228228~~~~~~~~~~~~~ 
    229229 
    230 A file-upload field. Has one **required** argument
     230A file-upload field. **Requires** exactly one of the following two arguments
    231231 
    232232    ======================  =================================================== 
    233233    Argument                Description 
    234234    ======================  =================================================== 
    235235    ``upload_to``           A local filesystem path that will be appended to 
    236236                            your ``MEDIA_ROOT`` setting to determine the 
    237                             output of the ``get_<fieldname>_url()`` helper 
    238                             function. 
     237                            final storage destination. If this argument is 
     238                            supplied, the storage backend will default to 
     239                            ``FileSystemBackend``. 
     240    ``backend``             **New in Django development version** 
     241 
     242                            A storage backend object, which handles the storage 
     243                            and retrieval of your files. See `managing files`_ 
     244                            for details on how to provide this object. 
    239245    ======================  =================================================== 
    240246 
    241 This path may contain `strftime formatting`_, which will be replaced by the 
    242 date/time of the file upload (so that uploaded files don't fill up the given 
    243 directory). 
     247.. _managing files: ../files/ 
    244248 
     249The ``upload_to`` path may contain `strftime formatting`_, which will be 
     250replaced by the date/time of the file upload (so that uploaded files don't fill 
     251up the given directory). 
     252 
    245253The admin represents this field as an ``<input type="file">`` (a file-upload 
    246254widget). 
    247255 
    248 Using a ``FileField`` or an ``ImageField`` (see below) in a model takes a few 
    249 steps: 
     256Using a ``FileField`` or an ``ImageField`` (see below) in a model without a 
     257specified backend takes a few steps: 
    250258 
    251259    1. In your settings file, you'll need to define ``MEDIA_ROOT`` as the 
    252260       full path to a directory where you'd like Django to store uploaded