Skip to content

Instantly share code, notes, and snippets.

@hanleybrand
Created November 21, 2012 18:48
Show Gist options
  • Save hanleybrand/4126830 to your computer and use it in GitHub Desktop.
Save hanleybrand/4126830 to your computer and use it in GitHub Desktop.
Here's how a thumbnail is requested
# rooibos/storage/views.py
###
# a request for /media/thumb/{media.pk}/{record.id}/
# handled by url(r'^thumb/(?P<id>\d+)/(?P<name>[-\w]+)/$', record_thumbnail, name='storage-thumbnail'),
# record_thumbnail and its decorator below
# it calls get_thumbnail_for_record from rooibos/storage/__init__.py
def add_content_length(func):
def _add_header(request, *args, **kwargs):
response = func(request, *args, **kwargs)
if type(response) == HttpResponse:
response['Content-Length'] = len(response.content)
return response
return _add_header
# cache_control from django/views/decorators/cache.py
@add_content_length
@cache_control(private=True, max_age=3600)
def record_thumbnail(request, id, name):
filename = get_thumbnail_for_record(id, request.user, crop_to_square=request.GET.has_key('square'))
if filename:
Activity.objects.create(event='media-thumbnail',
request=request,
content_type=ContentType.objects.get_for_model(Record),
object_id=id,
#content_object=record,
data=dict(square=int(request.GET.has_key('square'))))
try:
return HttpResponse(content=open(filename, 'rb').read(), mimetype='image/jpeg')
except IOError:
logging.error("IOError: %s" % filename)
return HttpResponseRedirect(reverse('static', args=('images/thumbnail_unavailable.png',)))
# rooibos/storage/__init__.py
def get_thumbnail_for_record(record, user=None, crop_to_square=False):
return get_image_for_record(record, user, width=100, height=100, crop_to_square=crop_to_square)
def get_image_for_record(record, user=None, width=100000, height=100000, passwords={}, crop_to_square=False):
media = get_media_for_record(record, user, passwords)
q = Q(mimetype__startswith='image/')
if settings.FFMPEG_EXECUTABLE:
# also support video and audio
q = q | Q(mimetype__startswith='video/') | Q(mimetype__startswith='audio/')
if PDF_SUPPORT:
q = q | Q(mimetype='application/pdf')
media = media.select_related('storage').filter(q)
if not media:
return None
map(lambda m: m.identify(lazy=True), media)
media = sorted(media, _imgsizecmp, reverse=True)
# find matching media
last = None
for m in media:
if m.width > width or m.height > height:
# Image still larger than given dimensions
last = m
elif (m.width == width and m.height <= height) or (m.width <= width and m.height == height):
# exact match
break
else:
# Now we have a smaller image
m = last or m
break
# m is now equal or larger to requested size, or smaller but closest to the requested size
# check what user size restrictions are
restrictions = get_effective_permissions_and_restrictions(user, m.storage)[3]
if restrictions:
width = min(width, restrictions.get('width', width))
height = min(height, restrictions.get('height', height))
# see if image needs resizing
if m.width > width or m.height > height or m.mimetype != 'image/jpeg' or not m.is_local():
def derivative_image(master, width, height):
if not master.file_exists():
logging.error('Image derivative failed for media %d, cannot find file "%s"' % (master.id, master.get_absolute_file_path()))
return None, (None, None)
import ImageFile
ImageFile.MAXBLOCK = 16 * 1024 * 1024
from multimedia import get_image
try:
file = get_image(master)
image = Image.open(file)
if crop_to_square:
w, h = image.size
if w > h:
image = image.crop(((w - h) / 2, 0, (w - h) / 2 + h, h))
elif w < h:
image = image.crop((0, (h - w) / 2, w, (h - w) / 2 + w))
image.thumbnail((width, height), Image.ANTIALIAS)
output = StringIO.StringIO()
if image.mode != "RGB":
image = image.convert("RGB")
image.save(output, 'JPEG', quality=85, optimize=True)
return output.getvalue(), image.size
except Exception, e:
logging.error('Image derivative failed for media %d (%s)' % (master.id, e))
return None, (None, None)
# See if a derivative already exists
name = '%s-%sx%s%s.jpg' % (m.id, width, height, 'sq' if crop_to_square else '')
sp = m.storage.get_derivative_storage_path()
if sp:
if not os.path.exists(sp):
try:
os.makedirs(sp)
except:
# check if directory exists now, if so another process may have created it
if not os.path.exists(sp):
# still does not exist, raise error
raise
path = os.path.join(sp, name)
if not os.path.exists(path) or os.path.getsize(path) == 0:
output, (w, h) = derivative_image(m, width, height)
if output:
with file(path, 'wb') as f:
f.write(output)
else:
return None
return path
else:
return None
else:
return m.get_absolute_file_path()
def get_media_for_record(record, user=None, passwords={}):
"""
Returns all media accessible to the user either directly through collections
or indirectly through presentations.
A user always must have access to the storage where the media is stored.
"""
from rooibos.presentation.models import Presentation
record_id = getattr(record, 'id', record)
#### see Step 3.py for below method
record = Record.filter_one_by_access(user, record_id)
if not record:
# Try to get to record through an accessible presentation -
# own presentations don't count, since it's already established that owner
# doesn't have access to the record.
pw_q = Q(
# Presentation must not have password
Q(password=None) | Q(password='') |
# or must know password
Q(id__in=Presentation.check_passwords(passwords))
)
access_q = Q(
# Must have access to presentation
id__in=filter_by_access(user, Presentation),
# and presentation must not be archived
hidden=False
)
accessible_presentations = Presentation.objects.filter(
pw_q, access_q, items__record__id=record_id)
# Now get all the presentation owners so we can check if any of them have access
# to the record
owners = User.objects.filter(id__in=accessible_presentations.values('owner'))
if not any(Record.filter_one_by_access(owner, record_id) for owner in owners):
return Media.objects.none()
return Media.objects.filter(
record__id=record_id,
storage__id__in=filter_by_access(user, Storage),
)
# rooibos/data/models.py
### these methods would be called like
### get_media_for_record({record.id}, {user.id})
### from url like url(r'^thumb/(?P<id>\d+)/(?P<name>[-\w]+) and using current user
@staticmethod
def filter_one_by_access(user, id):
try:
return Record.filter_by_access(user, id).get()
except ObjectDoesNotExist:
return None
@staticmethod
def filter_by_access(user, *ids):
records = Record.objects.distinct()
ids = map(int, ids)
if user:
if user.is_superuser:
return records.filter(id__in=ids)
accessible_records = cache_get_many(
['record-access-%d-%d' % (user.id or 0, id) for id in ids],
model_dependencies=[Record, Collection, AccessControl]
)
accessible_record_ids = map(lambda (k, v): (int(k.rsplit('-', 1)[1]), v), accessible_records.iteritems())
allowed_ids = [k for k, v in accessible_record_ids if v == 't']
denied_ids = [k for k, v in accessible_record_ids if v == 'f']
to_check = [id for id in ids if not id in allowed_ids and not id in denied_ids]
if not to_check:
return records.filter(id__in=allowed_ids)
else:
allowed_ids = []
to_check = ids
# check which records have individual ACLs set
individual = _records_with_individual_acl_by_ids(to_check)
if individual:
allowed_ids.extend(filter_by_access(user, Record.objects.filter(id__in=individual)).values_list('id', flat=True))
to_check = [id for id in to_check if not id in individual]
# check records without individual ACLs
if to_check:
cq = Q(collectionitem__collection__in=filter_by_access(user, Collection),
collectionitem__hidden=False)
mq = Q(collectionitem__collection__in=filter_by_access(user, Collection, write=True),
owner=None)
oq = Q(owner=user) if user and not user.is_anonymous() else Q()
records = records.filter(cq | mq | oq)
checked = records.filter(id__in=to_check).values_list('id', flat=True)
allowed_ids.extend(checked)
if user:
cache_update = dict(
('record-access-%d-%d' % (user.id or 0, id), 't' if id in checked else 'f')
for id in to_check
)
cache_set_many(cache_update, model_dependencies=[Record, Collection, AccessControl])
### Returns a QuerySet instance
return records.filter(id__in=allowed_ids)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment