Example #1
0
 def test_png(self):
     self.assertEqual(get_format("path/img.png"), 'PNG')
Example #2
0
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()
Example #3
0
 def test_jpeg(self):
     self.assertEqual(get_format("path/img.jpeg"), 'JPEG')
     self.assertEqual(get_format("path/img.jpg"), 'JPEG')
Example #4
0
 def test_gif(self):
     self.assertEqual(get_format("path/img.gif"), 'GIF')
Example #5
0
 def test_notaformat(self):
     """ get_format will assume JPEG for unknown formats """
     self.assertEqual(get_format("path/img"), 'JPEG')
Example #6
0
 def test_png(self):
     self.assertEqual(get_format("path/img.png"), "PNG")
Example #7
0
 def test_gif(self):
     self.assertEqual(get_format("path/img.gif"), "GIF")
Example #8
0
 def test_jpeg(self):
     self.assertEqual(get_format("path/img.jpeg"), "JPEG")
     self.assertEqual(get_format("path/img.jpg"), "JPEG")
Example #9
0
 def test_notaformat(self):
     """ get_format will assume JPEG for unknown formats """
     self.assertEqual(get_format("path/img"), "JPEG")