| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- from oscar.apps.image.dynamic.cache import DiskCache
- from oscar.apps.image.dynamic.exceptions import ResizerConfigurationException, \
- ResizerSyntaxException, ResizerFormatException
- from oscar.apps.image.dynamic.mods import AutotrimMod, CropMod, ResizeMod
- from oscar.apps.image.dynamic.response_backends import DirectResponse
- from wsgiref.util import request_uri, application_uri
- import Image
- import cStringIO
- import datetime
- import math
- import os
- import sys
-
- try:
- import cStringIO as StringIO
- except:
- import StringIO
-
- def get_class(kls):
- try:
- parts = kls.split('.')
- module = ".".join(parts[:-1])
- m = __import__(module)
- for comp in parts[1:]:
- m = getattr(m, comp)
- return m
- except (ImportError, AttributeError), e:
- raise ResizerConfigurationException('Error importing class "%s"' % kls)
-
-
- def error404(path, start_response):
- """ Returns an error 404 with text giving the requested URL. """
- status = '404 NOT FOUND'
- output = '404: File Not Found: ' + path + '\n'
-
- response_headers = [('Content-type', 'text/plain')]
- start_response(status, response_headers)
-
- return [output]
-
-
- def error500(path, e, start_response):
- """ Returns an error 500 with text giving the requested URL. """
- status = '500 Exception'
- output = '500: ' + str(e) + '\n'
-
- response_headers = [('Content-type', 'text/plain')]
- start_response(status, response_headers)
-
- return [output]
-
-
- class ImageModifier(object):
- """
- Modifies an image and saves to a cache location
-
- Output Formats:
-
- extension => ('format', 'mime-type')
-
- the key is the extension appended to the URL,
- the value tuple holds PIL's name for the format and the mime-type to serve
- with
- """
- output_formats = {
- 'jpeg': ('JPEG', 'image/jpeg'),
- 'jpg': ('JPEG', 'image/jpeg'),
- 'gif': ('GIF', 'image/gif'),
- 'png': ('PNG', 'image/png'),
- }
-
- # When we process an image, these modifications are applied in order
- installed_modifications = (
- AutotrimMod,
- CropMod,
- ResizeMod,
- )
-
- quality = 80
-
- def __init__(self, url, config):
- if config.get('installed_mods'):
- self.installed_modifications = config['installed_mods']
-
- self._url = url
- self._image_root = config['asset_root']
- self._process_path()
-
- def _process_path(self):
- """
- Extracts parameters from the image path
-
- Valid syntax:
- - /path/to/image.ext (serve image unchanged)
- - /path/to/image.ext.newext (change format of image)
- - /path/to/image.ext.options-string.newext (change format and modify
- image)
-
- Format of options string is:
- key1-value1_key2-value2_key3-value3
- """
- parts = self._url.split('.')
-
- length = len(parts)
-
- if length == 2:
- self.source_filename = self._url
- self._params = dict(type=parts[1])
- elif length == 3:
- self.source_filename = ".".join((parts[0], parts[1]))
- self._params = dict(type=parts[2])
- elif length == 4:
- self.source_filename = ".".join((parts[0], parts[1]))
-
- param_parts = parts[2].split('_')
-
- try:
- self._params = dict(
- [(x.split("-")[0], x.split("-")[1]) for x in param_parts])
- self._params['type'] = parts[3]
- except IndexError:
- raise ResizerSyntaxException("Invalid filename syntax")
- else:
- raise ResizerSyntaxException("Invalid filename syntax")
-
- if self._params['type'] not in self.output_formats:
- raise ResizerFormatException("Invalid output format")
-
- def source_path(self):
- return os.path.join(self._image_root, self.source_filename)
-
- def generate_image(self):
- source = Image.open(self.source_path())
-
- if (self._params['type'] == 'png'):
- source = source.convert("RGBA")
- else:
- source = source.convert("RGB")
-
- # Iterate over the installed modifications and apply them to the image
- for mod in self.installed_modifications:
- source = mod(source, self._params).apply()
-
- output = StringIO.StringIO()
-
- source.save(output, self.get_type()[0], quality=self.quality)
-
- output.seek(0)
- data = output.read()
-
- return data
-
- def get_type(self):
- return self.output_formats[self._params['type']]
-
- def get_mime_type(self):
- return self.get_type()[1]
-
-
- class BaseImageHandler(object):
- """
- This can be called by a WSGI script, or via DjangoImageHandler.
- Django version is handy for local development, but adds unnecessary
- overhead to production
- """
- modifier = ImageModifier
- cache = DiskCache
- response_backend = DirectResponse
-
- def build_sendfile_response(self, metadata, modifier, start_response):
- pass
-
- def __call__(self, environ, start_response):
- path = environ.get('PATH_INFO')
- config = environ.get('MEDIA_CONFIG')
-
- # Don't bother processing stuff we know is invalid
- if path == '/' or path == '/favicon.ico':
- return error404(path, start_response)
- path = path[1:]
-
- if config.get('cache_backend'):
- self.cache = get_class(config['cache_backend'])
- if config.get('response_backend'):
- self.response_backend = get_class(config['response_backend'])
- if config.get('installed_mods'):
- mod_overrides = []
- for v in config['installed_mods']:
- mod_overrides.append(get_class(v))
- config.installed_mods = tuple(mod_overrides)
-
- try:
- c = self.cache(path, config)
- m = self.modifier(path, config)
- except (ResizerSyntaxException, ResizerFormatException), e:
- return error500(path, e, start_response)
- try:
- if not c.check(m.source_path()):
- data = m.generate_image()
- c.write(data)
- return self.response_backend(config, m.get_mime_type(),c,start_response).build_response()
- except Exception, e:
- return error404(path, start_response)
-
-
- class DjangoImageHandler(BaseImageHandler):
-
- def __call__(self, request):
- from django.http import HttpResponse
- from django.conf import settings
-
- env = request.META.copy()
-
- config = settings.DYNAMIC_MEDIA_CONFIG
- prefix = settings.DYNAMIC_URL_PREFIX
-
- env['MEDIA_CONFIG'] = config
- env['PATH_INFO'] = env['PATH_INFO'][len(prefix) + 1:]
-
- django_response = HttpResponse()
-
- def start_response(status, headers):
- status = status.split(' ', 1)[0]
- django_response.status_code = int(status)
- for header, value in headers:
- django_response[header] = value
-
- response = super(DjangoImageHandler, self).__call__(env, start_response)
- django_response.content = "\n".join(response)
-
- return django_response
|