Ticket #5361: filestorage.3.diff

File filestorage.3.diff, 19.6 KB (added by x0nix, 12 years ago)

Fixed few details, so it works for me with current trunk (hope didn't break it for you :-) ). Also default backend is loaded from settings.

  • django/db/models/base.py

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

     
     1
    12import datetime
    23import os
    34import time
     
    704705        defaults.update(kwargs)
    705706        return super(EmailField, self).formfield(**defaults)
    706707
     708class File(object):
     709    def __init__(self, obj, field, filename):
     710        self.obj = obj
     711        self.field = field
     712        self.backend = field.backend
     713        self.filename = filename
     714
     715    def __str__(self):
     716        return self.backend.get_filename(self.filename)
     717
     718    def get_absolute_url(self):
     719        return self.backend.get_absolute_url(self.filename)
     720
     721    def get_filesize(self):
     722        if not hasattr(self, '_filesize'):
     723            self._filesize = self.backend.get_filesize(self.filename)
     724        return self._filesize
     725
     726    def open(self, mode='rb'):
     727        return self.backend.open(self.filename, mode)
     728
     729    def save_file(self, filename, raw_contents, save=True):
     730        self.filename = self.backend.save_file(filename, raw_contents)
     731
     732        # Update the filesize cache
     733        self._filesize = len(raw_contents)
     734
     735        # Save the object because it has changed, unless save is False
     736        if save:
     737            self.obj.save()
     738
     739class FileProxy(object):
     740    def __init__(self, field):
     741        self.field = field
     742        self.cache_name = self.field.get_cache_name()
     743
     744    def __get__(self, instance=None, owner=None):
     745        if instance is None:
     746            raise AttributeError, "%s can only be accessed from %s instances." % (self.field.attname, self.owner.__name__)
     747        return getattr(instance, self.cache_name)
     748
     749    def __set__(self, instance, value):
     750        if hasattr(instance, self.cache_name):
     751            raise AttributeError, "%s can not be set in this manner." % self.field.attname
     752        instance.__dict__[self.field.attname] = value
     753        attr = self.field.attr_class(instance, self.field, value)
     754        setattr(instance, self.cache_name, attr)
     755
    707756class FileField(Field):
    708     def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
    709         self.upload_to = upload_to
     757    attr_class = File
     758
     759    def __init__(self, verbose_name=None, name=None, upload_to='', backend=None, **kwargs):
     760        if backend is None:
     761            from django.core.urlresolvers import get_callable
     762            backend = get_callable(settings.DEFAULT_FILESTORAGE_BACKEND)(location=upload_to)
     763        self.backend = self.upload_to = backend
    710764        Field.__init__(self, verbose_name, name, **kwargs)
    711765
    712766    def get_db_prep_save(self, value):
     
    714768        # Need to convert UploadedFile objects provided via a form to unicode for database insertion
    715769        if value is None:
    716770            return None
    717         return unicode(value)
     771        return unicode(value.filename)
    718772
    719773    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
    720774        field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
     
    754808
    755809    def contribute_to_class(self, cls, name):
    756810        super(FileField, self).contribute_to_class(cls, name)
     811        setattr(cls, self.attname, FileProxy(self))
    757812        setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
    758813        setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
    759814        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
    760815        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
    761816        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
    762817
    763     def delete_file(self, instance):
    764         if getattr(instance, self.attname):
    765             file_name = getattr(instance, 'get_%s_filename' % self.name)()
    766             # If the file exists and no other object of this type references it,
    767             # delete it from the filesystem.
    768             if os.path.exists(file_name) and \
    769                 not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}):
    770                 os.remove(file_name)
     818    def delete_file(self, instance, sender):
     819        filename = getattr(instance, self.attname).filename
     820        # If no other object of this type references the file,
     821        # delete it from the backend.
     822        if not sender._default_manager.filter(**{self.name: filename}):
     823            self.backend.delete_file(filename)
    771824
    772825    def get_manipulator_field_objs(self):
    773826        return [oldforms.FileUploadField, oldforms.HiddenField]
     
    778831    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
    779832        upload_field_name = self.get_manipulator_field_names('')[0]
    780833        if new_data.get(upload_field_name, False):
    781             func = getattr(new_object, 'save_%s_file' % self.name)
    782834            if rel:
    783                 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
     835                field = new_data[upload_field_name][0]
    784836            else:
    785                 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
     837                field = new_data[upload_field_name]
     838            getattr(new_object, self.attname).save_file(field["filename"], field["content"], save)
    786839
    787     def get_directory_name(self):
    788         return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
    789 
    790     def get_filename(self, filename):
    791         from django.utils.text import get_valid_filename
    792         f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
    793         return os.path.normpath(f)
    794 
    795840    def save_form_data(self, instance, data):
    796841        if data:
    797             getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
     842            getattr(instance, self.attname).save_file(data.filename, data.content)
    798843
    799844    def formfield(self, **kwargs):
    800845        defaults = {'form_class': forms.FileField}
     
    824869        defaults.update(kwargs)
    825870        return super(FloatField, self).formfield(**defaults)
    826871
     872class ImageFile(File):
     873    def get_width(self):
     874        return self._get_image_dimensions()[0]
     875
     876    def get_height(self):
     877        return self._get_image_dimensions()[1]
     878
     879    def _get_image_dimensions(self):
     880        if not hasattr(self, '_dimensions_cache'):
     881            from django.utils.images import get_image_dimensions
     882            self._dimensions_cache = get_image_dimensions(self.open())
     883        return self._dimensions_cache
     884
     885    def save_file(self, filename, raw_contents):
     886        super(ImageFile, self).save_file(filename, raw_contents)
     887       
     888        # Update the cache for image dimensions
     889        from django.utils.images import get_image_dimensions
     890        from cStringIO import StringIO
     891        self._dimensions_cache = get_image_dimensions(StringIO(raw_contents))
     892
    827893class ImageField(FileField):
     894    attr_class = ImageFile
     895
    828896    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
    829897        self.width_field, self.height_field = width_field, height_field
    830898        FileField.__init__(self, verbose_name, name, **kwargs)
     
    842910            setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self))
    843911
    844912    def save_file(self, new_data, new_object, original_object, change, rel, save=True):
    845         FileField.save_file(self, new_data, new_object, original_object, change, rel, save)
    846913        # If the image has height and/or width field(s) and they haven't
    847914        # changed, set the width and/or height field(s) back to their original
    848915        # values.
    849         if change and (self.width_field or self.height_field) and save:
    850             if self.width_field:
    851                 setattr(new_object, self.width_field, getattr(original_object, self.width_field))
    852             if self.height_field:
    853                 setattr(new_object, self.height_field, getattr(original_object, self.height_field))
    854             new_object.save()
     916        if self.width_field or self.height_field:
     917            if original_object and not change:
     918                if self.width_field:
     919                    setattr(new_object, self.width_field, getattr(original_object, self.width_field))
     920                if self.height_field:
     921                    setattr(new_object, self.height_field, getattr(original_object, self.height_field))
     922            else:
     923                from cStringIO import StringIO
     924                from django.utils.images import get_image_dimensions
    855925
     926                upload_field_name = self.get_manipulator_field_names('')[0]
     927                if rel:
     928                    field = new_data[upload_field_name][0]
     929                else:
     930                    field = new_data[upload_field_name]
     931
     932                # Get the width and height from the raw content to avoid extra
     933                # unnecessary trips to the file backend.
     934                width, height = get_image_dimensions(StringIO(field["content"]))
     935
     936                if self.width_field:
     937                    setattr(new_object, self.width_field, width)
     938                if self.height_field:
     939                    setattr(new_object, self.height_field, height)
     940        FileField.save_file(self, new_data, new_object, original_object, change, rel, save)
     941
    856942    def formfield(self, **kwargs):
    857943        defaults = {'form_class': forms.ImageField}
    858944        defaults.update(kwargs)
  • django/conf/global_settings.py

     
    250250from django import get_version
    251251URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_version()
    252252
     253# Default backend to use for saving FileFields
     254DEFAULT_FILESTORAGE_BACKEND = 'django.core.filestorage.filesystem.FileSystemBackend'
     255
     256
    253257##############
    254258# MIDDLEWARE #
    255259##############
  • 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

     
    18511851get_FOO_filename()
    18521852------------------
    18531853
     1854**Deprecated in Django development version. See `managing files` for the new,
     1855preferred method for dealing with files.**
     1856
    18541857For every ``FileField``, the object will have a ``get_FOO_filename()`` method,
    18551858where ``FOO`` is the name of the field. This returns the full filesystem path
    18561859to the file, according to your ``MEDIA_ROOT`` setting.
     
    18611864get_FOO_url()
    18621865-------------
    18631866
     1867**Deprecated in Django development version. See `managing files` for the new,
     1868preferred method for dealing with files.**
     1869
    18641870For every ``FileField``, the object will have a ``get_FOO_url()`` method,
    18651871where ``FOO`` is the name of the field. This returns the full URL to the file,
    18661872according to your ``MEDIA_URL`` setting. If the value is blank, this method
     
    18691875get_FOO_size()
    18701876--------------
    18711877
     1878**Deprecated in Django development version. See `managing files` for the new,
     1879preferred method for dealing with files.**
     1880
    18721881For every ``FileField``, the object will have a ``get_FOO_size()`` method,
    18731882where ``FOO`` is the name of the field. This returns the size of the file, in
    18741883bytes. (Behind the scenes, it uses ``os.path.getsize``.)
     
    18761885save_FOO_file(filename, raw_contents)
    18771886-------------------------------------
    18781887
     1888**Deprecated in Django development version. See `managing files` for the new,
     1889preferred method for dealing with files.**
     1890
    18791891For every ``FileField``, the object will have a ``save_FOO_file()`` method,
    18801892where ``FOO`` is the name of the field. This saves the given file to the
    18811893filesystem, using the given filename. If a file with the given filename already
     
    18851897get_FOO_height() and get_FOO_width()
    18861898------------------------------------
    18871899
     1900**Deprecated in Django development version. See `managing files` for the new,
     1901preferred method for dealing with files.**
     1902
    18881903For every ``ImageField``, the object will have ``get_FOO_height()`` and
    18891904``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This
    18901905returns the height (or width) of the image, as an integer, in pixels.
  • docs/model-api.txt

     
    229229``FileField``
    230230~~~~~~~~~~~~~
    231231
    232 A file-upload field. Has one **required** argument:
     232A file-upload field. **Requires** exactly one of the following two arguments:
    233233
    234234    ======================  ===================================================
    235235    Argument                Description
    236236    ======================  ===================================================
    237237    ``upload_to``           A local filesystem path that will be appended to
    238238                            your ``MEDIA_ROOT`` setting to determine the
    239                             output of the ``get_<fieldname>_url()`` helper
    240                             function.
     239                            final storage destination. If this argument is
     240                            supplied, the storage backend will default to
     241                            ``FileSystemBackend``.
     242    ``backend``             **New in Django development version**
     243
     244                            A storage backend object, which handles the storage
     245                            and retrieval of your files. See `managing files`_
     246                            for details on how to provide this object.
    241247    ======================  ===================================================
    242248
    243 This path may contain `strftime formatting`_, which will be replaced by the
    244 date/time of the file upload (so that uploaded files don't fill up the given
    245 directory).
     249.. _managing files: ../files/
    246250
     251The ``upload_to`` path may contain `strftime formatting`_, which will be
     252replaced by the date/time of the file upload (so that uploaded files don't fill
     253up the given directory).
     254
    247255The admin represents this field as an ``<input type="file">`` (a file-upload
    248256widget).
    249257
    250 Using a ``FileField`` or an ``ImageField`` (see below) in a model takes a few
    251 steps:
     258Using a ``FileField`` or an ``ImageField`` (see below) in a model without a
     259specified backend takes a few steps:
    252260
    253261    1. In your settings file, you'll need to define ``MEDIA_ROOT`` as the
    254262       full path to a directory where you'd like Django to store uploaded
Back to Top