Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

__init__.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from oscar.apps.image.dynamic.cache import DiskCache
  2. from oscar.apps.image.dynamic.exceptions import ResizerConfigurationException, \
  3. ResizerSyntaxException, ResizerFormatException
  4. from oscar.apps.image.dynamic.mods import AutotrimMod, CropMod, ResizeMod
  5. from oscar.apps.image.dynamic.response_backends import DirectResponse
  6. from wsgiref.util import request_uri, application_uri
  7. import Image
  8. import cStringIO
  9. import datetime
  10. import math
  11. import os
  12. import sys
  13. try:
  14. import cStringIO as StringIO
  15. except:
  16. import StringIO
  17. def get_class(kls):
  18. try:
  19. parts = kls.split('.')
  20. module = ".".join(parts[:-1])
  21. m = __import__(module)
  22. for comp in parts[1:]:
  23. m = getattr(m, comp)
  24. return m
  25. except (ImportError, AttributeError), e:
  26. raise ResizerConfigurationException('Error importing class "%s"' % kls)
  27. def error404(path, start_response):
  28. """ Returns an error 404 with text giving the requested URL. """
  29. status = '404 NOT FOUND'
  30. output = '404: File Not Found: ' + path + '\n'
  31. response_headers = [('Content-type', 'text/plain')]
  32. start_response(status, response_headers)
  33. return [output]
  34. def error500(path, e, start_response):
  35. """ Returns an error 500 with text giving the requested URL. """
  36. status = '500 Exception'
  37. output = '500: ' + str(e) + '\n'
  38. response_headers = [('Content-type', 'text/plain')]
  39. start_response(status, response_headers)
  40. return [output]
  41. class ImageModifier(object):
  42. """
  43. Modifies an image and saves to a cache location
  44. Output Formats:
  45. extension => ('format', 'mime-type')
  46. the key is the extension appended to the URL,
  47. the value tuple holds PIL's name for the format and the mime-type to serve
  48. with
  49. """
  50. output_formats = {
  51. 'jpeg': ('JPEG', 'image/jpeg'),
  52. 'jpg': ('JPEG', 'image/jpeg'),
  53. 'gif': ('GIF', 'image/gif'),
  54. 'png': ('PNG', 'image/png'),
  55. }
  56. # When we process an image, these modifications are applied in order
  57. installed_modifications = (
  58. AutotrimMod,
  59. CropMod,
  60. ResizeMod,
  61. )
  62. quality = 80
  63. def __init__(self, url, config):
  64. if config.get('installed_mods'):
  65. self.installed_modifications = config['installed_mods']
  66. self._url = url
  67. self._image_root = config['asset_root']
  68. self._process_path()
  69. def _process_path(self):
  70. """
  71. Extracts parameters from the image path
  72. Valid syntax:
  73. - /path/to/image.ext (serve image unchanged)
  74. - /path/to/image.ext.newext (change format of image)
  75. - /path/to/image.ext.options-string.newext (change format and modify
  76. image)
  77. Format of options string is:
  78. key1-value1_key2-value2_key3-value3
  79. """
  80. parts = self._url.split('.')
  81. length = len(parts)
  82. if length == 2:
  83. self.source_filename = self._url
  84. self._params = dict(type=parts[1])
  85. elif length == 3:
  86. self.source_filename = ".".join((parts[0], parts[1]))
  87. self._params = dict(type=parts[2])
  88. elif length == 4:
  89. self.source_filename = ".".join((parts[0], parts[1]))
  90. param_parts = parts[2].split('_')
  91. try:
  92. self._params = dict(
  93. [(x.split("-")[0], x.split("-")[1]) for x in param_parts])
  94. self._params['type'] = parts[3]
  95. except IndexError:
  96. raise ResizerSyntaxException("Invalid filename syntax")
  97. else:
  98. raise ResizerSyntaxException("Invalid filename syntax")
  99. if self._params['type'] not in self.output_formats:
  100. raise ResizerFormatException("Invalid output format")
  101. def source_path(self):
  102. return os.path.join(self._image_root, self.source_filename)
  103. def generate_image(self):
  104. source = Image.open(self.source_path())
  105. if (self._params['type'] == 'png'):
  106. source = source.convert("RGBA")
  107. else:
  108. source = source.convert("RGB")
  109. # Iterate over the installed modifications and apply them to the image
  110. for mod in self.installed_modifications:
  111. source = mod(source, self._params).apply()
  112. output = StringIO.StringIO()
  113. source.save(output, self.get_type()[0], quality=self.quality)
  114. output.seek(0)
  115. data = output.read()
  116. return data
  117. def get_type(self):
  118. return self.output_formats[self._params['type']]
  119. def get_mime_type(self):
  120. return self.get_type()[1]
  121. class BaseImageHandler(object):
  122. """
  123. This can be called by a WSGI script, or via DjangoImageHandler.
  124. Django version is handy for local development, but adds unnecessary
  125. overhead to production
  126. """
  127. modifier = ImageModifier
  128. cache = DiskCache
  129. response_backend = DirectResponse
  130. def build_sendfile_response(self, metadata, modifier, start_response):
  131. pass
  132. def __call__(self, environ, start_response):
  133. path = environ.get('PATH_INFO')
  134. config = environ.get('MEDIA_CONFIG')
  135. # Don't bother processing stuff we know is invalid
  136. if path == '/' or path == '/favicon.ico':
  137. return error404(path, start_response)
  138. path = path[1:]
  139. if config.get('cache_backend'):
  140. self.cache = get_class(config['cache_backend'])
  141. if config.get('response_backend'):
  142. self.response_backend = get_class(config['response_backend'])
  143. if config.get('installed_mods'):
  144. mod_overrides = []
  145. for v in config['installed_mods']:
  146. mod_overrides.append(get_class(v))
  147. config.installed_mods = tuple(mod_overrides)
  148. try:
  149. c = self.cache(path, config)
  150. m = self.modifier(path, config)
  151. except (ResizerSyntaxException, ResizerFormatException), e:
  152. return error500(path, e, start_response)
  153. try:
  154. if not c.check(m.source_path()):
  155. data = m.generate_image()
  156. c.write(data)
  157. return self.response_backend(config, m.get_mime_type(),c,start_response).build_response()
  158. except Exception, e:
  159. return error404(path, start_response)
  160. class DjangoImageHandler(BaseImageHandler):
  161. def __call__(self, request):
  162. from django.http import HttpResponse
  163. from django.conf import settings
  164. env = request.META.copy()
  165. config = settings.DYNAMIC_MEDIA_CONFIG
  166. prefix = settings.DYNAMIC_URL_PREFIX
  167. env['MEDIA_CONFIG'] = config
  168. env['PATH_INFO'] = env['PATH_INFO'][len(prefix) + 1:]
  169. django_response = HttpResponse()
  170. def start_response(status, headers):
  171. status = status.split(' ', 1)[0]
  172. django_response.status_code = int(status)
  173. for header, value in headers:
  174. django_response[header] = value
  175. response = super(DjangoImageHandler, self).__call__(env, start_response)
  176. django_response.content = "\n".join(response)
  177. return django_response