# coding=utf-8
"""
Model fields of ThumbnailField
"""
__author__ = 'Alisue <lambdalisue@hashnote.net>'
from django.db.models.fields.files import ImageField
from django.db.models.fields.files import ImageFieldFile
from django.db.models.fields.files import ImageFileDescriptor
from django.utils.text import ugettext_lazy as _
from thumbnailfield.conf import settings
from thumbnailfield.utils import save_to_storage
from thumbnailfield.utils import get_content_file
from thumbnailfield.utils import get_thumbnail_filename
from thumbnailfield.utils import get_fileformat_from_filename
from thumbnailfield.utils import get_processed_image
from thumbnailfield.compatibility import Image
[docs]class ThumbnailFileDescriptor(ImageFileDescriptor):
"""
Enhanced ImageFileDescriptor
Just like the ImageFileDescriptor, but for ThumbnailField. The only
difference is removing previous Image and Thumbnails from storage when the
value has changed.
"""
def __set__(self, instance, value):
if settings.THUMBNAILFIELD_REMOVE_PREVIOUS:
previous_file = instance.__dict__.get(self.field.attname, None)
if previous_file and isinstance(previous_file, ThumbnailFieldFile):
previous_files = [previous_file.path]
# Add thumbnail files
iterator = previous_file.iter_thumbnail_filenames()
for thumbnail_filename in iterator:
previous_files.append(thumbnail_filename)
super(ThumbnailFileDescriptor, self).__set__(instance, value)
# remove previous files if the vaue has changed
if previous_file and isinstance(previous_file, ThumbnailFieldFile):
current_file = getattr(instance, self.field.attname)
if previous_file != current_file:
for f in previous_files:
self.field.storage.delete(f)
else:
super(ThumbnailFileDescriptor, self).__set__(instance, value)
[docs]class ThumbnailFieldFile(ImageFieldFile):
"""Enhanced ImageFieldFile
This FieldFile contains thumbnail ImageFieldFile instances
and these thumbnails are automatically generate when accessed
Attributes:
_get_thumbnail_filename -- get thumbnail filename
_get_image -- get PIL image instance
_get_thumbnail -- get PIL image instance of thumbnail
_get_thumbnail_file -- get ImageFieldFile instance of thumbnail
_create_thumbnail -- create PIL image instance of thumbnail
_create_thumbnail_file -- create ImageFieldFile instance of thumbnail
_update_thumbnail_file -- update thumbnail file and return
ImageFieldFile instance
_remove_thumbnail_file -- remove thumbanil file from storage
iter_pattern_name -- return iterator of pattern name
get_pattern_name -- return list of pattern name
iter_thumbnail_filenames -- return iterator of thumbnail filename
get_thumbnail_filenames -- return list of thumbnail filename
iter_thumbnail_files -- return iterator of thumbnail file
get_thumbnail_files -- return list of thumbnail file
update_thumbnail_files -- update thumbnail files in storage
remove_thumbnail_files -- remove thumbnail files from storage
"""
def __init__(self, *args, **kwargs):
"""Constructor"""
super(ThumbnailFieldFile, self).__init__(*args, **kwargs)
self.patterns = self.field.patterns
self.pil_save_options = self.field.pil_save_options
# create access properties
for name in self.patterns.iterkeys():
if name is None:
# None is for original file thus continue
continue
fget = lambda self, name = name: self._get_thumbnail_file(name)
setattr(ThumbnailFieldFile, name, property(fget=fget))
fget = lambda self, name = name: self._get_thumbnail_file(
name).file
setattr(ThumbnailFieldFile, '%s_file' % name, property(fget=fget))
fget = lambda self, name = name: self._get_thumbnail_file(
name).path
setattr(ThumbnailFieldFile, '%s_path' % name, property(fget=fget))
fget = lambda self, name = name: self._get_thumbnail_file(name).url
setattr(ThumbnailFieldFile, '%s_url' % name, property(fget=fget))
fget = lambda self, name = name: self._get_thumbnail_file(
name).size
setattr(ThumbnailFieldFile, '%s_size' % name, property(fget=fget))
def _get_thumbnail_filename(self, name):
"""get thumbnail filename with name
thumbnail filename is generated with utils.get_thumbnail_filename
method. original path is path of this field file
"""
return get_thumbnail_filename(self.name, name)
def _get_image(self):
"""get PIL image of this field file
PIL Image instance is cached in '_image_cache' attribute of this
instance
"""
attr_name = '_image_cache'
if not getattr(self, attr_name, None):
self.seek(0)
setattr(self, attr_name, Image.open(self.file))
return getattr(self, attr_name)
def _get_thumbnail(self, name, force=False):
"""get PIL thumbnail of this field file
PIL Image instance of thumbnail is cached in '_image_<name>_cache'
attribute of this instance. The instance is created by
'_create_thumbnail' method with
given named patterns
Attribute:
name -- A name of thumbnail patterns
"""
attr_name = '_image_%s_cache' % name
if force or not getattr(self, attr_name, None):
patterns = self.patterns[name]
setattr(self, attr_name, self._create_thumbnail(patterns))
return getattr(self, attr_name)
def _create_thumbnail(self, patterns):
"""create PIL thumbnail of this field file
Attribute:
patterns -- A process patterns to generate thumbnail
"""
img = self._get_image()
if img:
thumb = get_processed_image(self, img, patterns)
return thumb
return None
def _get_thumbnail_file(self, name, force=False):
"""get ImageFieldFile of thumbnail
ImageFieldFile instance is cached in '_thumbnail_file_<name>_cache'
attribute of this instance. The instance is created by
'_create_thumbnail_file' method with given named patterns
Attribute:
name -- A name of thumbnail patterns
"""
attr_name = '_thumbnail_file_%s_cache' % name
if force or not getattr(self, attr_name, None):
thumbs_file = self._get_or_create_thumbnail_file(
name, force, self.pil_save_options)
if not thumbs_file:
return None
setattr(self, attr_name, thumbs_file)
return getattr(self, attr_name)
def _get_or_create_thumbnail_file(self, name, force=False,
pil_save_options=None):
"""get or create thumbnail file and return ImageFieldFile
Attribute:
name -- A name of thumbnail patterns
"""
thumbs_filename = self._get_thumbnail_filename(name)
if force or not self.storage.exists(thumbs_filename):
thumbs = self._get_thumbnail(name, force)
if not thumbs:
return None
thumbs_filename = self._get_thumbnail_filename(name)
save_to_storage(thumbs, self.storage,
thumbs_filename, **(pil_save_options or {}))
thumbs_file = ImageFieldFile(self.instance,
self.field,
thumbs_filename)
return thumbs_file
def _update_thumbnail_file(self, name):
"""update thumbnail file of storage
Attribute:
name -- A name of thumbnail patterns
"""
return self._get_thumbnail_file(name, force=True)
def _remove_thumbnail_file(self, name, save=True):
"""remove thumbnail file from storage
Attribute:
name -- A name of thumbnail patterns
save -- If true, the model instance of this field will be saved
"""
attr_name = '_thumbnail_file_%s_cache' % name
thumbs_file = getattr(self, attr_name, None)
if thumbs_file:
thumbs_file.delete(save)
delattr(self, attr_name)
[docs] def iter_pattern_names(self):
"""return iterator of thumbnail pattern names"""
return self.patterns.iterkeys()
[docs] def get_pattern_names(self):
"""return list of thumbnail pattern names"""
pattern_names = [n for n in self.iter_pattern_names()]
return pattern_names
[docs] def iter_thumbnail_filenames(self):
"""return iterator of thumbnail filenames"""
for name in self.iter_pattern_names():
yield self._get_thumbnail_filename(name)
[docs] def get_thumbnail_filenames(self):
"""return list of thumbnail filenames"""
thumbnail_filenames = [f for f in self.iter_thumbnail_filenames()]
return thumbnail_filenames
[docs] def iter_thumbnail_files(self):
"""return iterator of thumbnail files"""
for name in self.iter_pattern_names():
yield self._get_thumbnail_file(name)
[docs] def get_thumbnail_files(self):
"""return list of thumbnail files"""
thumbnail_files = [f for f in self.iter_thumbnail_files()]
return thumbnail_files
[docs] def update_thumbnail_files(self):
"""update thumbanil files of storage"""
for name in self.iter_pattern_names():
self._update_thumbnail_file(name)
[docs] def remove_thumbnail_files(self, save=True):
"""remove thumbnail files from storage
Attribute:
save -- If true, the model instance of this field will be saved.
"""
for name in self.iter_pattern_names():
self._remove_thumbnail_file(name, save=False)
if save:
self.instance.save()
[docs] def save(self, name, content, save=True):
if self and not self._committed and None in self.patterns:
# Apply original image process
processed = self._get_thumbnail(None)
file_fmt = get_fileformat_from_filename(name)
content = get_content_file(processed, file_fmt)
super(ThumbnailFieldFile, self).save(name, content, save=save)
[docs] def delete(self, save=True):
self.remove_thumbnail_files(save=False)
super(ThumbnailFieldFile, self).delete(save=save)
[docs]class ThumbnailField(ImageField):
"""Enhanced ImageField
ThumbnailField has the follwoing features
- Automatically remove previous file
- Automatically generate thumbnail files
- Automatically remove generated previous thumbnail files
"""
attr_class = ThumbnailFieldFile
descriptor_class = ThumbnailFileDescriptor
description = _("Thumbnail")
def __init__(self, verbose_name=None, name=None, width_field=None,
height_field=None, patterns=None,
pil_save_options=None, **kwargs):
"""Constructor
Patterns:
patterns attribute is used to generate thumbnail files (or apply
processes to original file). The format of this attribute is::
patterns = {
<Name>: (
(<square_size>), # with default process_method
# with square size
(<width>, <height>), # with default process_method
# with width/height
(<width>, <height>, <method_name>),
(<width>, <height>, <method_name>, <method_options>),
),
<Name>: (
(<width>, <height>, <method_name>, <method_options>),
(<width>, <height>, <method_name>, <method_options>),
(<width>, <height>, <method_name>, <method_options>),
),
}
``Name`` is a name of thumbnail. ``None`` or '' for original image.
``width`` and ``height`` is used in process method. Some process
method
have to be set this value ``None``
``method_name`` is a name of method or process_method. You can use
a string name registered in
settings.THUMBNAILFIELD_PROCESS_METHOD_TABLE
``method_options`` is a dictionary instance used in particular
method. for example, ``crop`` method required ``left`` and
``upper`` options to process.
pil_save_options:
a dictionary which will be passed as a keyword argument list to
PIL image save method.
"""
patterns = patterns or {}
if '' in patterns:
patterns[None] = patterns['']
del patterns['']
patterns[None] = patterns.get(None, None)
self.patterns = patterns
self.pil_save_options = pil_save_options or settings.THUMBNAILFIELD_DEFAULT_PIL_SAVE_OPTIONS
super(ThumbnailField, self).__init__(
verbose_name, name, width_field, height_field, **kwargs)
try:
from south.modelsinspector import add_introspection_rules
rules = [
(
(ThumbnailField,),
[],
{
'patterns': ['patterns', {}],
},
)
]
add_introspection_rules(rules, [r"^thumbnailfield.fields.ThumbnailField"])
except ImportError:
pass