import re from six.moves import urllib import os, os.path from datetime import datetime from django.conf import settings import six from django.utils.safestring import mark_safe from .utils import curry from django.core.files.images import ImageFile from django.core.files.storage import get_storage_class from django.contrib.staticfiles import finders from sorl.thumbnail import get_thumbnail from .conf import * class FilePath(six.text_type): def __new__(cls, str, instance=None, field=None, settings={}): self = super(FilePath, cls).__new__(cls, str.strip()) self._instance = instance self._field = field self._exists = None self._size = None self._accessed_time = None self._created_time = None self._modified_time = None self._thumbnails = {} self.settings = { 'img_attrs': {}, 'thumbnail_size': None, 'thumbnail_attrs': {}, } self.settings.update(settings) return self def _html_attrs(self, **kwargs): attrs = {} attrs.update(kwargs) if 'css_class' in attrs: attrs['class'] = attrs['css_class'] del attrs['css_class'] return attrs @property def unescaped(self): return urllib.parse.unquote(self) @property def escaped(self): return urllib.parse.quote(self.unescaped) @property def url(self): if not self.startswith('/') and self.find('//') == -1: return os.path.join(MEDIA_URL, self.escaped) return self.escaped @property def local_path(self): if not self.startswith('/') and self.find('//') == -1: return os.path.join(MEDIA_ROOT, urllib.parse.unquote(self)) return self def _get_local_path_or_file(self): # if file is in static instead of media directory, sorl raises # a suspicious operation error. So we open it safely without errors. if self.startswith('/'): if self.startswith('/static/'): path = self.replace('/static/', '') elif self.startswith(settings.STATIC_URL): path = self.replace(settings.STATIC_URL, '') else: return self.local_path else: return self.local_path path = finders.find(urllib.parse.unquote(path)) image = ImageFile(open(path, 'r')) return image @property def filename(self): return urllib.parse.unquote(re.sub(r'^.+\/', '', self)) @property def display_name(self): without_extension = re.sub(r'\.[\w\d]+$', '', self.filename) with_spaces = re.sub(r'_', ' ', without_extension) return with_spaces @property def ext(self): return re.sub(r'^.+\.', '', self.filename) def exists(self): if self._exists == None: self._exists = os.path.exists(self.local_path) return self._exists def get_size(self): if self._size == None: self._size = os.path.getsize(self.local_path) return self._size def get_accessed_time(self): if self._accessed_time == None: self._accessed_time = datetime.fromtimestamp(os.path.getatime(self.local_path)) return self._accessed_time def get_created_time(self): if self._created_time == None: self._created_time = datetime.fromtimestamp(os.path.getctime(self.local_path)) return self._created_time def get_modified_time(self): if self._modified_time == None: self._modified_time = datetime.fromtimestamp(os.path.getmtime(self.local_path)) return self._modified_time class ImagePath(FilePath): def img_tag(self, **kwargs): attrs = {} attrs.update(self.settings['img_attrs']) attrs.update(kwargs) attrs = self._html_attrs(**attrs) attrs_str = ''.join([ u'%s="%s" ' % (key, value) for key, value in attrs.items() ]) return mark_safe(u'' % (self.url, attrs_str)) def _thumbnail_file_format(self): if self.ext.lower() in ['gif', 'png']: return 'PNG' return 'JPEG' def thumbnail(self, size=None, **kwargs): size = size or self.settings['thumbnail_size'] if not size: raise Exception('No thumbnail size supplied') attrs = { 'format': self._thumbnail_file_format(), 'upscale': False, } attrs.update(self.settings['thumbnail_attrs']) attrs.update(kwargs) all_attrs = { 'size': size } all_attrs.update(attrs) key = hash(frozenset(all_attrs)) if not key in self._thumbnails: #self._thumbnails[key] = get_thumbnail(self._get_local_path_or_file(), size, **attrs) self._thumbnails[key] = get_thumbnail(self.local_path, size, **attrs) return self._thumbnails[key] def thumbnail_tag(self, size, opts={}, **kwargs): try: thumbnail = self.thumbnail(size, **opts) except EnvironmentError as e: if settings.THUMBNAIL_DEBUG: raise e return '' src = ImagePath(thumbnail.url, self._instance, self._field) attrs = { 'width': thumbnail.width, 'height': thumbnail.height } attrs.update(self.settings['img_attrs']) attrs.update(kwargs) return src.img_tag(**attrs) def __getattr__(self, attr): thumbnail_mxn = re.match(r'^thumbnail_(tag_)?(\d*x?\d+)$', attr) if thumbnail_mxn: tag = thumbnail_mxn.group(1) == 'tag_' size = thumbnail_mxn.group(2) if tag: return curry(self.thumbnail_tag, size) else: return curry(self.thumbnail, size) raise AttributeError class FilePaths(six.text_type): item_class = FilePath def __new__(cls, str, instance=None, field=None, settings={}): self = super(FilePaths, cls).__new__(cls, str) self._instance = instance self._field = field self._all = None self._length = None self._current = 0 self.settings = { 'img_attrs': {}, 'thumbnail_size': None, 'thumbnail_attrs': {}, } self.settings.update(settings) return self def all(self): if self._all == None: self._all = [] for f in self.splitlines(): self._all.append(self._field.attr_class.item_class(f, self._instance, self._field, self.settings)) self._length = len(self._all) return self._all def count(self): self.all() return self._length def first(self): return self.all() and self.all()[0] or None def last(self): return self.all() and self.all()[-1] or None def next(self): f = self.all()[self._current] self._current += 1 return f def next_n(self, n): files = self.all()[self._current:self._current+n] self._current += n return files def next_all(self): files = self.all()[self._current:] self._current = self._length - 1 return files def has_next(self): self.all() return max(0, self._length - self._current - 1) def reset(self): self._current = 0 def __getattr__(self, attr): next_n = re.match(r'^next_(\d+)$', attr) if next_n: n = int(next_n.group(1)) return curry(self.next_n, n) raise AttributeError class ImagePaths(FilePaths): item_class = ImagePath def as_gallery(self): raise NotImplementedError def as_carousel(self): raise NotImplementedError class FilesDescriptor(object): """ Used django.db.models.fields.files.FileDescriptor as an example. This descriptor returns an unicode object, with special methods for formatting like filename(), absolute(), relative() and img_tag(). """ def __init__(self, field): self.field = field def __get__(self, instance=None, owner=None): if instance is None: return self files = instance.__dict__[self.field.name] if isinstance(files, six.string_types) and not isinstance(files, (FilePath, FilePaths)): attr = self.field.attr_class(files, instance, self.field) instance.__dict__[self.field.name] = attr return instance.__dict__[self.field.name] def __set__(self, instance, value): instance.__dict__[self.field.name] = value