Ticket #5361: filestorage.diff

File filestorage.diff, 28.1 KB (added by Marty Alchin <gulopine@…>, 12 years ago)

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

  • django/core/filestorage/__init__.py

     
     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

     
     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

     
     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

     
     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

     
    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

     
    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

     
    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
     26 No newline at end of file
  • docs/db-api.txt

     
    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

     
    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
Back to Top