app/django/db/models/fields/files.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     1 import datetime
       
     2 import os
       
     3 
       
     4 from django.conf import settings
       
     5 from django.db.models.fields import Field
       
     6 from django.core.files.base import File, ContentFile
       
     7 from django.core.files.storage import default_storage
       
     8 from django.core.files.images import ImageFile, get_image_dimensions
       
     9 from django.core.files.uploadedfile import UploadedFile
       
    10 from django.utils.functional import curry
       
    11 from django.db.models import signals
       
    12 from django.utils.encoding import force_unicode, smart_str
       
    13 from django.utils.translation import ugettext_lazy, ugettext as _
       
    14 from django import forms
       
    15 from django.db.models.loading import cache
       
    16 
       
    17 class FieldFile(File):
       
    18     def __init__(self, instance, field, name):
       
    19         self.instance = instance
       
    20         self.field = field
       
    21         self.storage = field.storage
       
    22         self._name = name or u''
       
    23         self._closed = False
       
    24 
       
    25     def __eq__(self, other):
       
    26         # Older code may be expecting FileField values to be simple strings.
       
    27         # By overriding the == operator, it can remain backwards compatibility.
       
    28         if hasattr(other, 'name'):
       
    29             return self.name == other.name
       
    30         return self.name == other
       
    31 
       
    32     # The standard File contains most of the necessary properties, but
       
    33     # FieldFiles can be instantiated without a name, so that needs to
       
    34     # be checked for here.
       
    35 
       
    36     def _require_file(self):
       
    37         if not self:
       
    38             raise ValueError("The '%s' attribute has no file associated with it." % self.field.name)
       
    39 
       
    40     def _get_file(self):
       
    41         self._require_file()
       
    42         if not hasattr(self, '_file'):
       
    43             self._file = self.storage.open(self.name, 'rb')
       
    44         return self._file
       
    45     file = property(_get_file)
       
    46 
       
    47     def _get_path(self):
       
    48         self._require_file()
       
    49         return self.storage.path(self.name)
       
    50     path = property(_get_path)
       
    51 
       
    52     def _get_url(self):
       
    53         self._require_file()
       
    54         return self.storage.url(self.name)
       
    55     url = property(_get_url)
       
    56 
       
    57     def _get_size(self):
       
    58         self._require_file()
       
    59         return self.storage.size(self.name)
       
    60     size = property(_get_size)
       
    61 
       
    62     def open(self, mode='rb'):
       
    63         self._require_file()
       
    64         return super(FieldFile, self).open(mode)
       
    65     # open() doesn't alter the file's contents, but it does reset the pointer
       
    66     open.alters_data = True
       
    67 
       
    68     # In addition to the standard File API, FieldFiles have extra methods
       
    69     # to further manipulate the underlying file, as well as update the
       
    70     # associated model instance.
       
    71 
       
    72     def save(self, name, content, save=True):
       
    73         name = self.field.generate_filename(self.instance, name)
       
    74         self._name = self.storage.save(name, content)
       
    75         setattr(self.instance, self.field.name, self.name)
       
    76 
       
    77         # Update the filesize cache
       
    78         self._size = len(content)
       
    79 
       
    80         # Save the object because it has changed, unless save is False
       
    81         if save:
       
    82             self.instance.save()
       
    83     save.alters_data = True
       
    84 
       
    85     def delete(self, save=True):
       
    86         # Only close the file if it's already open, which we know by the
       
    87         # presence of self._file
       
    88         if hasattr(self, '_file'):
       
    89             self.close()
       
    90             del self._file
       
    91             
       
    92         self.storage.delete(self.name)
       
    93 
       
    94         self._name = None
       
    95         setattr(self.instance, self.field.name, self.name)
       
    96 
       
    97         # Delete the filesize cache
       
    98         if hasattr(self, '_size'):
       
    99             del self._size
       
   100 
       
   101         if save:
       
   102             self.instance.save()
       
   103     delete.alters_data = True
       
   104 
       
   105     def __getstate__(self):
       
   106         # FieldFile needs access to its associated model field and an instance
       
   107         # it's attached to in order to work properly, but the only necessary
       
   108         # data to be pickled is the file's name itself. Everything else will
       
   109         # be restored later, by FileDescriptor below.
       
   110         return {'_name': self.name, '_closed': False}
       
   111 
       
   112 class FileDescriptor(object):
       
   113     def __init__(self, field):
       
   114         self.field = field
       
   115 
       
   116     def __get__(self, instance=None, owner=None):
       
   117         if instance is None:
       
   118             raise AttributeError, "%s can only be accessed from %s instances." % (self.field.name(self.owner.__name__))
       
   119         file = instance.__dict__[self.field.name]
       
   120         if not isinstance(file, FieldFile):
       
   121             # Create a new instance of FieldFile, based on a given file name
       
   122             instance.__dict__[self.field.name] = self.field.attr_class(instance, self.field, file)
       
   123         elif not hasattr(file, 'field'):
       
   124             # The FieldFile was pickled, so some attributes need to be reset.
       
   125             file.instance = instance
       
   126             file.field = self.field
       
   127             file.storage = self.field.storage
       
   128         return instance.__dict__[self.field.name]
       
   129 
       
   130     def __set__(self, instance, value):
       
   131         instance.__dict__[self.field.name] = value
       
   132 
       
   133 class FileField(Field):
       
   134     attr_class = FieldFile
       
   135 
       
   136     def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
       
   137         for arg in ('primary_key', 'unique'):
       
   138             if arg in kwargs:
       
   139                 raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__))
       
   140 
       
   141         self.storage = storage or default_storage
       
   142         self.upload_to = upload_to
       
   143         if callable(upload_to):
       
   144             self.generate_filename = upload_to
       
   145 
       
   146         kwargs['max_length'] = kwargs.get('max_length', 100)
       
   147         super(FileField, self).__init__(verbose_name, name, **kwargs)
       
   148 
       
   149     def get_internal_type(self):
       
   150         return "FileField"
       
   151 
       
   152     def get_db_prep_lookup(self, lookup_type, value):
       
   153         if hasattr(value, 'name'):
       
   154             value = value.name
       
   155         return super(FileField, self).get_db_prep_lookup(lookup_type, value)
       
   156 
       
   157     def get_db_prep_value(self, value):
       
   158         "Returns field's value prepared for saving into a database."
       
   159         # Need to convert File objects provided via a form to unicode for database insertion
       
   160         if value is None:
       
   161             return None
       
   162         return unicode(value)
       
   163 
       
   164     def contribute_to_class(self, cls, name):
       
   165         super(FileField, self).contribute_to_class(cls, name)
       
   166         setattr(cls, self.name, FileDescriptor(self))
       
   167         signals.post_delete.connect(self.delete_file, sender=cls)
       
   168 
       
   169     def delete_file(self, instance, sender, **kwargs):
       
   170         file = getattr(instance, self.attname)
       
   171         # If no other object of this type references the file,
       
   172         # and it's not the default value for future objects,
       
   173         # delete it from the backend.
       
   174         if file and file.name != self.default and \
       
   175             not sender._default_manager.filter(**{self.name: file.name}):
       
   176                 file.delete(save=False)
       
   177         elif file:
       
   178             # Otherwise, just close the file, so it doesn't tie up resources.
       
   179             file.close()
       
   180 
       
   181     def get_directory_name(self):
       
   182         return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
       
   183 
       
   184     def get_filename(self, filename):
       
   185         return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))
       
   186 
       
   187     def generate_filename(self, instance, filename):
       
   188         return os.path.join(self.get_directory_name(), self.get_filename(filename))
       
   189 
       
   190     def save_form_data(self, instance, data):
       
   191         if data and isinstance(data, UploadedFile):
       
   192             getattr(instance, self.name).save(data.name, data, save=False)
       
   193 
       
   194     def formfield(self, **kwargs):
       
   195         defaults = {'form_class': forms.FileField}
       
   196         # If a file has been provided previously, then the form doesn't require
       
   197         # that a new file is provided this time.
       
   198         # The code to mark the form field as not required is used by
       
   199         # form_for_instance, but can probably be removed once form_for_instance
       
   200         # is gone. ModelForm uses a different method to check for an existing file.
       
   201         if 'initial' in kwargs:
       
   202             defaults['required'] = False
       
   203         defaults.update(kwargs)
       
   204         return super(FileField, self).formfield(**defaults)
       
   205 
       
   206 class ImageFieldFile(ImageFile, FieldFile):
       
   207     def save(self, name, content, save=True):
       
   208         # Repopulate the image dimension cache.
       
   209         self._dimensions_cache = get_image_dimensions(content)
       
   210 
       
   211         # Update width/height fields, if needed
       
   212         if self.field.width_field:
       
   213             setattr(self.instance, self.field.width_field, self.width)
       
   214         if self.field.height_field:
       
   215             setattr(self.instance, self.field.height_field, self.height)
       
   216 
       
   217         super(ImageFieldFile, self).save(name, content, save)
       
   218 
       
   219     def delete(self, save=True):
       
   220         # Clear the image dimensions cache
       
   221         if hasattr(self, '_dimensions_cache'):
       
   222             del self._dimensions_cache
       
   223         super(ImageFieldFile, self).delete(save)
       
   224 
       
   225 class ImageField(FileField):
       
   226     attr_class = ImageFieldFile
       
   227 
       
   228     def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
       
   229         self.width_field, self.height_field = width_field, height_field
       
   230         FileField.__init__(self, verbose_name, name, **kwargs)
       
   231 
       
   232     def formfield(self, **kwargs):
       
   233         defaults = {'form_class': forms.ImageField}
       
   234         defaults.update(kwargs)
       
   235         return super(ImageField, self).formfield(**defaults)