293 lines
8.5 KiB
Python
293 lines
8.5 KiB
Python
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'<img src="%s" %s/>' % (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
|