Created
November 21, 2012 18:48
-
-
Save hanleybrand/4126830 to your computer and use it in GitHub Desktop.
Here's how a thumbnail is requested
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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',))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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), | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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