Django

Code

Ticket #5361: filestorage.3.diff

File filestorage.3.diff, 19.6 kB (added by x0nix, 10 months 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

    old new  
    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

    old new  
     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

    old new  
    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

    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  
    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

    old new  
    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