def test_png(self): self.assertEqual(get_format("path/img.png"), 'PNG')
class LazyThumbRenderer(View): """ Perform requested image render operations and handle fs logic and caching of 404s. Maps a requested action (currently 'thumbnail' and 'resize' are supported) to a matching method named after the action prefixed with 'action_'. Once the argument signatures are relaxed one can implement new image transformations simply by subclassing this view and adding "action_" methods that return raw image data as a string. """ def __init__(self): self.fs = FileSystemStorage() self._allowed_actions = [ a.__name__ for a in (getattr(self, a, None) for a in dir(self)) if type(a) == types.MethodType and getattr(a, 'is_action', False) ] def get(self, request, action, geometry, source_path): """ Perform action routing and handle sanitizing url input. Handles caching the path to a rendered image to django.cache and saves the new image on the filesystem. 404s are cached to save time the next time the missing image is requested. :param request: HttpRequest :param action: some action, eg thumbnail or resize :param geometry: a string of either '\dx\d' or just '\d' :param source_path: the fs path to the image to be manipulated :returns: an HttpResponse with an image/{format} content_type """ # reject naughty paths and actions if source_path.startswith('/'): logger.info("%s: blocked bad path" % source_path) return self.four_oh_four() if re.match('\.\./', source_path): logger.info("%s: blocked bad path" % source_path) return self.four_oh_four() if action not in self._allowed_actions: logger.info("%s: bad action requested: %s" % (source_path, action)) return self.four_oh_four() try: width, height = geometry_parse(action, geometry, ValueError) except ValueError, e: logger.info('corrupted geometry "%s" for action "%s"' % (geometry, action)) return self.four_oh_four() width = int(width) if width is not None else None height = int(height) if height is not None else None rendered_path = request.path[1:] cache_key = self.cache_key(source_path, action, width, height) was_404 = cache.get(cache_key) if was_404 == 1: return self.four_oh_four() img_format = get_format(rendered_path) # TODO this tangled mess of try/except is hideous... but such is # filesystem io? No it can be cleaned up by splitting it out try: # does rendered file already exist? raw_data = self.fs.open(rendered_path).read() except IOError: if was_404 == 0: # then it *was* here last time. if was_404 had been None then # it makes sense for rendered image to not exist yet: we # probably haven't seen it, or it dropped out of cache. logger.info( 'rendered image previously on fs missing. regenerating') try: pil_img = getattr(self, action)(width=width, height=height, img_path=source_path) # this code from sorl-thumbnail buf = StringIO() # TODO we need a better way of choosing options based on size and format params = { 'format': get_format(rendered_path), 'quality': 80, } if params['format'] == "JPEG" and pil_img.mode == 'P': # Cannot save mode 'P' image as JPEG without converting first # (This can happen if we have a GIF file without an extension and don't scale it) pil_img = pil_img.convert() try: pil_img.save(buf, **params) except IOError: logger.exception("pil_img.save(%r)" % params) # TODO reevaluate this except when we make options smarter logger.info( "Failed to create new image %s . Trying without options" % rendered_path) pil_img.save(buf, format=img_format) raw_data = buf.getvalue() buf.close() try: self.fs.save(rendered_path, ContentFile(raw_data)) except OSError, e: if e.errno == errno.EEXIST: pass # race condition, another WSGI worker wrote file or directory first else: logger.exception("saving converted image") raise except (IOError, SuspiciousOperation, ValueError), e: # we've now failed to find a rendered path as well as the # original source path. this is a 404. logger.info('404: %s' % e) cache.set(cache_key, 1, settings.LAZYTHUMBS_404_CACHE_TIMEOUT) return self.four_oh_four()
def test_jpeg(self): self.assertEqual(get_format("path/img.jpeg"), 'JPEG') self.assertEqual(get_format("path/img.jpg"), 'JPEG')
def test_gif(self): self.assertEqual(get_format("path/img.gif"), 'GIF')
def test_notaformat(self): """ get_format will assume JPEG for unknown formats """ self.assertEqual(get_format("path/img"), 'JPEG')
def test_png(self): self.assertEqual(get_format("path/img.png"), "PNG")
def test_gif(self): self.assertEqual(get_format("path/img.gif"), "GIF")
def test_jpeg(self): self.assertEqual(get_format("path/img.jpeg"), "JPEG") self.assertEqual(get_format("path/img.jpg"), "JPEG")
def test_notaformat(self): """ get_format will assume JPEG for unknown formats """ self.assertEqual(get_format("path/img"), "JPEG")