| import datetime |
| import os |
| |
| from django.conf import settings |
| from django.db.models.fields import Field |
| from django.core.files.base import File, ContentFile |
| from django.core.files.storage import default_storage |
| from django.core.files.images import ImageFile, get_image_dimensions |
| from django.core.files.uploadedfile import UploadedFile |
| from django.utils.functional import curry |
| from django.db.models import signals |
| from django.utils.encoding import force_unicode, smart_str |
| from django.utils.translation import ugettext_lazy, ugettext as _ |
| from django import oldforms |
| from django import forms |
| from django.core import validators |
| from django.db.models.loading import cache |
| |
| class FieldFile(File): |
| def __init__(self, instance, field, name): |
| self.instance = instance |
| self.field = field |
| self.storage = field.storage |
| self._name = name or u'' |
| self._closed = False |
| |
| def __eq__(self, other): |
| # Older code may be expecting FileField values to be simple strings. |
| # By overriding the == operator, it can remain backwards compatibility. |
| if hasattr(other, 'name'): |
| return self.name == other.name |
| return self.name == other |
| |
| # The standard File contains most of the necessary properties, but |
| # FieldFiles can be instantiated without a name, so that needs to |
| # be checked for here. |
| |
| def _require_file(self): |
| if not self: |
| raise ValueError("The '%s' attribute has no file associated with it." % self.field.name) |
| |
| def _get_file(self): |
| self._require_file() |
| if not hasattr(self, '_file'): |
| self._file = self.storage.open(self.name, 'rb') |
| return self._file |
| file = property(_get_file) |
| |
| def _get_path(self): |
| self._require_file() |
| return self.storage.path(self.name) |
| path = property(_get_path) |
| |
| def _get_url(self): |
| self._require_file() |
| return self.storage.url(self.name) |
| url = property(_get_url) |
| |
| def open(self, mode='rb'): |
| self._require_file() |
| return super(FieldFile, self).open(mode) |
| # open() doesn't alter the file's contents, but it does reset the pointer |
| open.alters_data = True |
| |
| # In addition to the standard File API, FieldFiles have extra methods |
| # to further manipulate the underlying file, as well as update the |
| # associated model instance. |
| |
| def save(self, name, content, save=True): |
| name = self.field.generate_filename(self.instance, name) |
| self._name = self.storage.save(name, content) |
| setattr(self.instance, self.field.name, self.name) |
| |
| # Update the filesize cache |
| self._size = len(content) |
| |
| # Save the object because it has changed, unless save is False |
| if save: |
| self.instance.save() |
| save.alters_data = True |
| |
| def delete(self, save=True): |
| self.close() |
| self.storage.delete(self.name) |
| |
| self._name = None |
| setattr(self.instance, self.field.name, self.name) |
| |
| # Delete the filesize cache |
| if hasattr(self, '_size'): |
| del self._size |
| |
| if save: |
| self.instance.save() |
| delete.alters_data = True |
| |
| def __getstate__(self): |
| # FieldFile needs access to its associated model field and an instance |
| # it's attached to in order to work properly, but the only necessary |
| # data to be pickled is the file's name itself. Everything else will |
| # be restored later, by FileDescriptor below. |
| return {'_name': self.name, '_closed': False} |
| |
| class FileDescriptor(object): |
| def __init__(self, field): |
| self.field = field |
| |
| def __get__(self, instance=None, owner=None): |
| if instance is None: |
| raise AttributeError, "%s can only be accessed from %s instances." % (self.field.name(self.owner.__name__)) |
| file = instance.__dict__[self.field.name] |
| if not isinstance(file, FieldFile): |
| # Create a new instance of FieldFile, based on a given file name |
| instance.__dict__[self.field.name] = self.field.attr_class(instance, self.field, file) |
| elif not hasattr(file, 'field'): |
| # The FieldFile was pickled, so some attributes need to be reset. |
| file.instance = instance |
| file.field = self.field |
| file.storage = self.field.storage |
| return instance.__dict__[self.field.name] |
| |
| def __set__(self, instance, value): |
| instance.__dict__[self.field.name] = value |
| |
| class FileField(Field): |
| attr_class = FieldFile |
| |
| def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs): |
| for arg in ('core', 'primary_key', 'unique'): |
| if arg in kwargs: |
| raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__)) |
| |
| self.storage = storage or default_storage |
| self.upload_to = upload_to |
| if callable(upload_to): |
| self.generate_filename = upload_to |
| |
| kwargs['max_length'] = kwargs.get('max_length', 100) |
| super(FileField, self).__init__(verbose_name, name, **kwargs) |
| |
| def get_internal_type(self): |
| return "FileField" |
| |
| def get_db_prep_lookup(self, lookup_type, value): |
| if hasattr(value, 'name'): |
| value = value.name |
| return super(FileField, self).get_db_prep_lookup(lookup_type, value) |
| |
| def get_db_prep_value(self, value): |
| "Returns field's value prepared for saving into a database." |
| # Need to convert File objects provided via a form to unicode for database insertion |
| if value is None: |
| return None |
| return unicode(value) |
| |
| def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): |
| field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) |
| if not self.blank: |
| if rel: |
| # This validator makes sure FileFields work in a related context. |
| class RequiredFileField(object): |
| def __init__(self, other_field_names, other_file_field_name): |
| self.other_field_names = other_field_names |
| self.other_file_field_name = other_file_field_name |
| self.always_test = True |
| def __call__(self, field_data, all_data): |
| if not all_data.get(self.other_file_field_name, False): |
| c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required.")) |
| c(field_data, all_data) |
| # First, get the core fields, if any. |
| core_field_names = [] |
| for f in opts.fields: |
| if f.core and f != self: |
| core_field_names.extend(f.get_manipulator_field_names(name_prefix)) |
| # Now, if there are any, add the validator to this FormField. |
| if core_field_names: |
| field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name)) |
| else: |
| v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required.")) |
| v.always_test = True |
| field_list[0].validator_list.append(v) |
| field_list[0].is_required = field_list[1].is_required = False |
| |
| # If the raw path is passed in, validate it's under the MEDIA_ROOT. |
| def isWithinMediaRoot(field_data, all_data): |
| f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data)) |
| if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))): |
| raise validators.ValidationError(_("Enter a valid filename.")) |
| field_list[1].validator_list.append(isWithinMediaRoot) |
| return field_list |
| |
| def contribute_to_class(self, cls, name): |
| super(FileField, self).contribute_to_class(cls, name) |
| setattr(cls, self.name, FileDescriptor(self)) |
| signals.post_delete.connect(self.delete_file, sender=cls) |
| |
| def delete_file(self, instance, sender, **kwargs): |
| file = getattr(instance, self.attname) |
| # If no other object of this type references the file, |
| # and it's not the default value for future objects, |
| # delete it from the backend. |
| if file and file.name != self.default and \ |
| not sender._default_manager.filter(**{self.name: file.name}): |
| file.delete(save=False) |
| elif file: |
| # Otherwise, just close the file, so it doesn't tie up resources. |
| file.close() |
| |
| def get_manipulator_field_objs(self): |
| return [oldforms.FileUploadField, oldforms.HiddenField] |
| |
| def get_manipulator_field_names(self, name_prefix): |
| return [name_prefix + self.name + '_file', name_prefix + self.name] |
| |
| def save_file(self, new_data, new_object, original_object, change, rel, save=True): |
| upload_field_name = self.get_manipulator_field_names('')[0] |
| if new_data.get(upload_field_name, False): |
| if rel: |
| file = new_data[upload_field_name][0] |
| else: |
| file = new_data[upload_field_name] |
| |
| # Backwards-compatible support for files-as-dictionaries. |
| # We don't need to raise a warning because the storage backend will |
| # do so for us. |
| try: |
| filename = file.name |
| except AttributeError: |
| filename = file['filename'] |
| filename = self.get_filename(filename) |
| |
| getattr(new_object, self.attname).save(filename, file, save) |
| |
| def get_directory_name(self): |
| return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) |
| |
| def get_filename(self, filename): |
| return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename))) |
| |
| def generate_filename(self, instance, filename): |
| return os.path.join(self.get_directory_name(), self.get_filename(filename)) |
| |
| def save_form_data(self, instance, data): |
| if data and isinstance(data, UploadedFile): |
| getattr(instance, self.name).save(data.name, data, save=False) |
| |
| def formfield(self, **kwargs): |
| defaults = {'form_class': forms.FileField} |
| # If a file has been provided previously, then the form doesn't require |
| # that a new file is provided this time. |
| # The code to mark the form field as not required is used by |
| # form_for_instance, but can probably be removed once form_for_instance |
| # is gone. ModelForm uses a different method to check for an existing file. |
| if 'initial' in kwargs: |
| defaults['required'] = False |
| defaults.update(kwargs) |
| return super(FileField, self).formfield(**defaults) |
| |
| class ImageFieldFile(ImageFile, FieldFile): |
| def save(self, name, content, save=True): |
| # Repopulate the image dimension cache. |
| self._dimensions_cache = get_image_dimensions(content) |
| |
| # Update width/height fields, if needed |
| if self.field.width_field: |
| setattr(self.instance, self.field.width_field, self.width) |
| if self.field.height_field: |
| setattr(self.instance, self.field.height_field, self.height) |
| |
| super(ImageFieldFile, self).save(name, content, save) |
| |
| def delete(self, save=True): |
| # Clear the image dimensions cache |
| if hasattr(self, '_dimensions_cache'): |
| del self._dimensions_cache |
| super(ImageFieldFile, self).delete(save) |
| |
| class ImageField(FileField): |
| attr_class = ImageFieldFile |
| |
| def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): |
| self.width_field, self.height_field = width_field, height_field |
| FileField.__init__(self, verbose_name, name, **kwargs) |
| |
| def get_manipulator_field_objs(self): |
| return [oldforms.ImageUploadField, oldforms.HiddenField] |
| |
| def formfield(self, **kwargs): |
| defaults = {'form_class': forms.ImageField} |
| defaults.update(kwargs) |
| return super(ImageField, self).formfield(**defaults) |