diff options
| author | Jacob Kaplan-Moss <jacob@jacobian.org> | 2008-08-08 20:59:02 +0000 |
|---|---|---|
| committer | Jacob Kaplan-Moss <jacob@jacobian.org> | 2008-08-08 20:59:02 +0000 |
| commit | 7899568e01fc9c68afe995fa71de915dd9fcdd76 (patch) | |
| tree | 35f1e999a9a48fe24790f00c2335e558a53fc718 /django/db | |
| parent | c49eac7d4f64c374d19aa81f2c813a4b20e4cad7 (diff) | |
File storage refactoring, adding far more flexibility to Django's file handling. The new files.txt document has details of the new features.
This is a backwards-incompatible change; consult BackwardsIncompatibleChanges for details.
Fixes #3567, #3621, #4345, #5361, #5655, #7415.
Many thanks to Marty Alchin who did the vast majority of this work.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@8244 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/db')
| -rw-r--r-- | django/db/models/__init__.py | 1 | ||||
| -rw-r--r-- | django/db/models/base.py | 121 | ||||
| -rw-r--r-- | django/db/models/fields/__init__.py | 160 | ||||
| -rw-r--r-- | django/db/models/fields/files.py | 315 | ||||
| -rw-r--r-- | django/db/models/manipulators.py | 3 |
5 files changed, 346 insertions, 254 deletions
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 18c47e86f3..cbd685547e 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -8,6 +8,7 @@ from django.db.models.manager import Manager from django.db.models.base import Model from django.db.models.fields import * from django.db.models.fields.subclassing import SubfieldBase +from django.db.models.fields.files import FileField, ImageField from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED from django.db.models import signals diff --git a/django/db/models/base.py b/django/db/models/base.py index 3d7eac9284..59a503ff82 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -3,6 +3,7 @@ import types import sys import os from itertools import izip +from warnings import warn try: set except NameError: @@ -12,7 +13,7 @@ import django.db.models.manipulators # Imported to register signal handler. import django.db.models.manager # Ditto. from django.core import validators from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError -from django.db.models.fields import AutoField, ImageField +from django.db.models.fields import AutoField from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField from django.db.models.query import delete_objects, Q, CollectedObjects from django.db.models.options import Options @@ -463,110 +464,42 @@ class Model(object): return getattr(self, cachename) def _get_FIELD_filename(self, field): - if getattr(self, field.attname): # Value is not blank. - return os.path.normpath(os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))) - return '' + warn("instance.get_%s_filename() is deprecated. Use instance.%s.path instead." % \ + (field.attname, field.attname), DeprecationWarning, stacklevel=3) + try: + return getattr(self, field.attname).path + except ValueError: + return '' def _get_FIELD_url(self, field): - if getattr(self, field.attname): # Value is not blank. - import urlparse - return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/') - return '' + warn("instance.get_%s_url() is deprecated. Use instance.%s.url instead." % \ + (field.attname, field.attname), DeprecationWarning, stacklevel=3) + try: + return getattr(self, field.attname).url + except ValueError: + return '' def _get_FIELD_size(self, field): - return os.path.getsize(self._get_FIELD_filename(field)) - - def _save_FIELD_file(self, field, filename, raw_field, save=True): - # Create the upload directory if it doesn't already exist - directory = os.path.join(settings.MEDIA_ROOT, field.get_directory_name()) - if not os.path.exists(directory): - os.makedirs(directory) - elif not os.path.isdir(directory): - raise IOError('%s exists and is not a directory' % directory) - - # Check for old-style usage (files-as-dictionaries). Warn here first - # since there are multiple locations where we need to support both new - # and old usage. - if isinstance(raw_field, dict): - import warnings - warnings.warn( - message = "Representing uploaded files as dictionaries is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.", - category = DeprecationWarning, - stacklevel = 2 - ) - from django.core.files.uploadedfile import SimpleUploadedFile - raw_field = SimpleUploadedFile.from_dict(raw_field) - - elif isinstance(raw_field, basestring): - import warnings - warnings.warn( - message = "Representing uploaded files as strings is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.", - category = DeprecationWarning, - stacklevel = 2 - ) - from django.core.files.uploadedfile import SimpleUploadedFile - raw_field = SimpleUploadedFile(filename, raw_field) - - if filename is None: - filename = raw_field.file_name - - filename = field.get_filename(filename) - - # If the filename already exists, keep adding an underscore to the name - # of the file until the filename doesn't exist. - while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)): - try: - dot_index = filename.rindex('.') - except ValueError: # filename has no dot. - filename += '_' - else: - filename = filename[:dot_index] + '_' + filename[dot_index:] - - # Save the file name on the object and write the file to disk. - setattr(self, field.attname, filename) - full_filename = self._get_FIELD_filename(field) - if hasattr(raw_field, 'temporary_file_path'): - # This file has a file path that we can move. - file_move_safe(raw_field.temporary_file_path(), full_filename) - raw_field.close() - else: - # This is a normal uploadedfile that we can stream. - fp = open(full_filename, 'wb') - locks.lock(fp, locks.LOCK_EX) - for chunk in raw_field.chunks(): - fp.write(chunk) - locks.unlock(fp) - fp.close() + warn("instance.get_%s_size() is deprecated. Use instance.%s.size instead." % \ + (field.attname, field.attname), DeprecationWarning, stacklevel=3) + return getattr(self, field.attname).size - # Save the width and/or height, if applicable. - if isinstance(field, ImageField) and \ - (field.width_field or field.height_field): - from django.utils.images import get_image_dimensions - width, height = get_image_dimensions(full_filename) - if field.width_field: - setattr(self, field.width_field, width) - if field.height_field: - setattr(self, field.height_field, height) - - # Save the object because it has changed, unless save is False. - if save: - self.save() + def _save_FIELD_file(self, field, filename, content, save=True): + warn("instance.save_%s_file() is deprecated. Use instance.%s.save() instead." % \ + (field.attname, field.attname), DeprecationWarning, stacklevel=3) + return getattr(self, field.attname).save(filename, content, save) _save_FIELD_file.alters_data = True def _get_FIELD_width(self, field): - return self._get_image_dimensions(field)[0] + warn("instance.get_%s_width() is deprecated. Use instance.%s.width instead." % \ + (field.attname, field.attname), DeprecationWarning, stacklevel=3) + return getattr(self, field.attname).width() def _get_FIELD_height(self, field): - return self._get_image_dimensions(field)[1] - - def _get_image_dimensions(self, field): - cachename = "__%s_dimensions_cache" % field.name - if not hasattr(self, cachename): - from django.utils.images import get_image_dimensions - filename = self._get_FIELD_filename(field) - setattr(self, cachename, get_image_dimensions(filename)) - return getattr(self, cachename) + warn("instance.get_%s_height() is deprecated. Use instance.%s.height instead." % \ + (field.attname, field.attname), DeprecationWarning, stacklevel=3) + return getattr(self, field.attname).height() ############################################ diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index dbb3e520c0..f19fb258a3 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -10,6 +10,7 @@ except ImportError: from django.db import connection, get_creation_module from django.db.models import signals from django.db.models.query_utils import QueryWrapper +from django.dispatch import dispatcher from django.conf import settings from django.core import validators from django import oldforms @@ -757,131 +758,6 @@ class EmailField(CharField): defaults.update(kwargs) return super(EmailField, self).formfield(**defaults) -class FileField(Field): - def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): - self.upload_to = upload_to - kwargs['max_length'] = kwargs.get('max_length', 100) - Field.__init__(self, verbose_name, name, **kwargs) - - def get_internal_type(self): - return "FileField" - - def get_db_prep_value(self, value): - "Returns field's value prepared for saving into a database." - # Need to convert UploadedFile objects provided via a form to unicode for database insertion - if hasattr(value, 'name'): - return value.name - elif value is None: - return None - else: - 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, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) - setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) - setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) - setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save)) - signals.post_delete.connect(self.delete_file, sender=cls) - - def delete_file(self, instance, **kwargs): - if getattr(instance, self.attname): - file_name = getattr(instance, 'get_%s_filename' % self.name)() - # If the file exists and no other object of this type references it, - # delete it from the filesystem. - if os.path.exists(file_name) and \ - not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}): - os.remove(file_name) - - 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] - - if not file: - return - - # Backwards-compatible support for files-as-dictionaries. - # We don't need to raise a warning because Model._save_FIELD_file will - # do so for us. - try: - file_name = file.name - except AttributeError: - file_name = file['filename'] - - func = getattr(new_object, 'save_%s_file' % self.name) - func(file_name, 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): - from django.utils.text import get_valid_filename - f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename))) - return os.path.normpath(f) - - def save_form_data(self, instance, data): - from django.core.files.uploadedfile import UploadedFile - if data and isinstance(data, UploadedFile): - getattr(instance, "save_%s_file" % self.name)(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 FilePathField(Field): def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): self.path, self.match, self.recursive = path, match, recursive @@ -923,40 +799,6 @@ class FloatField(Field): defaults.update(kwargs) return super(FloatField, self).formfield(**defaults) -class ImageField(FileField): - 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 contribute_to_class(self, cls, name): - super(ImageField, self).contribute_to_class(cls, name) - # Add get_BLAH_width and get_BLAH_height methods, but only if the - # image field doesn't have width and height cache fields. - if not self.width_field: - setattr(cls, 'get_%s_width' % self.name, curry(cls._get_FIELD_width, field=self)) - if not self.height_field: - setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self)) - - def save_file(self, new_data, new_object, original_object, change, rel, save=True): - FileField.save_file(self, new_data, new_object, original_object, change, rel, save) - # If the image has height and/or width field(s) and they haven't - # changed, set the width and/or height field(s) back to their original - # values. - if change and (self.width_field or self.height_field) and save: - if self.width_field: - setattr(new_object, self.width_field, getattr(original_object, self.width_field)) - if self.height_field: - setattr(new_object, self.height_field, getattr(original_object, self.height_field)) - new_object.save() - - def formfield(self, **kwargs): - defaults = {'form_class': forms.ImageField} - defaults.update(kwargs) - return super(ImageField, self).formfield(**defaults) - class IntegerField(Field): empty_strings_allowed = False def get_db_prep_value(self, value): diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py new file mode 100644 index 0000000000..76639596b5 --- /dev/null +++ b/django/db/models/fields/files.py @@ -0,0 +1,315 @@ +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)) + setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) + setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) + setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) + setattr(cls, 'save_%s_file' % self.name, lambda instance, name, content, save=True: instance._save_FIELD_file(self, name, content, save)) + 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): + + if not hasattr(content, 'read'): + import warnings + warnings.warn( + message = "Representing files as strings is deprecated." \ + "Use django.core.files.base.ContentFile instead.", + category = DeprecationWarning, + stacklevel = 2 + ) + content = ContentFile(content) + + # 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 contribute_to_class(self, cls, name): + super(ImageField, self).contribute_to_class(cls, name) + # Add get_BLAH_width and get_BLAH_height methods, but only if the + # image field doesn't have width and height cache fields. + if not self.width_field: + setattr(cls, 'get_%s_width' % self.name, curry(cls._get_FIELD_width, field=self)) + if not self.height_field: + setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self)) + + def formfield(self, **kwargs): + defaults = {'form_class': forms.ImageField} + defaults.update(kwargs) + return super(ImageField, self).formfield(**defaults) diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index a3c917a486..c657d0158b 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -1,7 +1,8 @@ from django.core.exceptions import ObjectDoesNotExist from django import oldforms from django.core import validators -from django.db.models.fields import FileField, AutoField +from django.db.models.fields import AutoField +from django.db.models.fields.files import FileField from django.db.models import signals from django.utils.functional import curry from django.utils.datastructures import DotExpandedDict |
