Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. mod_overrides = []
  66. for v in config['installed_mods']:
  67. mod_overrides.append(get_class(v))
  68. self.installed_modifications = tuple(mod_overrides)
  69. self._url = url
  70. self._image_root = config['asset_root']
  71. self._process_path()
  72. def _process_path(self):
  73. """
  74. Extracts parameters from the image path
  75. Valid syntax:
  76. - /path/to/image.ext (serve image unchanged)
  77. - /path/to/image.ext.newext (change format of image)
  78. - /path/to/image.ext.options-string.newext (change format and modify
  79. image)
  80. Format of options string is:
  81. key1-value1_key2-value2_key3-value3
  82. """
  83. parts = self._url.split('.')
  84. length = len(parts)
  85. if length == 2:
  86. self.source_filename = self._url
  87. self._params = dict(type=parts[1])
  88. elif length == 3:
  89. self.source_filename = ".".join((parts[0], parts[1]))
  90. self._params = dict(type=parts[2])
  91. elif length == 4:
  92. self.source_filename = ".".join((parts[0], parts[1]))
  93. param_parts = parts[2].split('_')
  94. try:
  95. self._params = dict(
  96. [(x.split("-")[0], x.split("-")[1]) for x in param_parts])
  97. self._params['type'] = parts[3]
  98. except IndexError:
  99. raise ResizerSyntaxException("Invalid filename syntax")
  100. else:
  101. raise ResizerSyntaxException("Invalid filename syntax")
  102. if self._params['type'] not in self.output_formats:
  103. raise ResizerFormatException("Invalid output format")
  104. def source_path(self):
  105. return os.path.join(self._image_root, self.source_filename)
  106. def generate_image(self):
  107. source = Image.open(self.source_path())
  108. if (self._params['type'] == 'png'):
  109. source = source.convert("RGBA")
  110. else:
  111. source = source.convert("RGB")
  112. # Iterate over the installed modifications and apply them to the image
  113. for mod in self.installed_modifications:
  114. source = mod(source, self._params).apply()
  115. output = StringIO.StringIO()
  116. source.save(output, self.get_type()[0], quality=self.quality)
  117. output.seek(0)
  118. data = output.read()
  119. return data
  120. def get_type(self):
  121. return self.output_formats[self._params['type']]
  122. def get_mime_type(self):
  123. return self.get_type()[1]
  124. class BaseImageHandler(object):
  125. """
  126. This can be called by a WSGI script, or via DjangoImageHandler.
  127. Django version is handy for local development, but adds unnecessary
  128. overhead to production
  129. """
  130. modifier = ImageModifier
  131. cache = DiskCache
  132. response_backend = DirectResponse
  133. def build_sendfile_response(self, metadata, modifier, start_response):
  134. pass
  135. def __call__(self, environ, start_response):
  136. path = environ.get('PATH_INFO')
  137. config = environ.get('MEDIA_CONFIG')
  138. # Don't bother processing stuff we know is invalid
  139. if path == '/' or path == '/favicon.ico':
  140. return error404(path, start_response)
  141. path = path[1:]
  142. if config.get('cache_backend'):
  143. self.cache = get_class(config['cache_backend'])
  144. if config.get('response_backend'):
  145. self.response_backend = get_class(config['response_backend'])
  146. try:
  147. c = self.cache(path, config)
  148. m = self.modifier(path, config)
  149. except (ResizerSyntaxException, ResizerFormatException), e:
  150. return error500(path, e, start_response)
  151. try:
  152. if not c.check(m.source_path()):
  153. data = m.generate_image()
  154. c.write(data)
  155. return self.response_backend(m.get_mime_type(),c,start_response).build_response()
  156. except Exception, e:
  157. return error404(path, start_response)
  158. class DjangoImageHandler(BaseImageHandler):
  159. def __call__(self, request):
  160. from django.http import HttpResponse
  161. from django.conf import settings
  162. env = request.META.copy()
  163. config = settings.DYNAMIC_MEDIA_CONFIG
  164. prefix = settings.DYNAMIC_URL_PREFIX
  165. env['MEDIA_CONFIG'] = config
  166. env['PATH_INFO'] = env['PATH_INFO'][len(prefix) + 1:]
  167. django_response = HttpResponse()
  168. def start_response(status, headers):
  169. status = status.split(' ', 1)[0]
  170. django_response.status_code = int(status)
  171. for header, value in headers:
  172. django_response[header] = value
  173. response = super(DjangoImageHandler, self).__call__(env, start_response)
  174. django_response.content = "\n".join(response)
  175. return django_response