You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import os
  2. import zlib
  3. import tarfile
  4. import zipfile
  5. import tempfile
  6. import shutil
  7. from PIL import Image
  8. from django.core.files import File
  9. from django.core.exceptions import FieldError
  10. from django.db.models import get_model
  11. from django.db.transaction import commit_on_success
  12. from django.utils.translation import ugettext_lazy as _
  13. from oscar.apps.catalogue.exceptions import (
  14. ImageImportError, IdenticalImageError, InvalidImageArchive)
  15. Category = get_model('catalogue', 'category')
  16. Product = get_model('catalogue', 'product')
  17. ProductImage = get_model('catalogue', 'productimage')
  18. class Importer(object):
  19. allowed_extensions = ['.jpeg', '.jpg', '.gif', '.png']
  20. def __init__(self, logger, field):
  21. self.logger = logger
  22. self._field = field
  23. @commit_on_success
  24. def handle(self, dirname):
  25. stats = {
  26. 'num_processed': 0,
  27. 'num_skipped': 0,
  28. 'num_invalid': 0}
  29. image_dir, filenames = self._get_image_files(dirname)
  30. if image_dir:
  31. for filename in filenames:
  32. try:
  33. lookup_value = self._get_lookup_value_from_filename(filename)
  34. self._process_image(image_dir, filename, lookup_value)
  35. stats['num_processed'] += 1
  36. except Product.MultipleObjectsReturned:
  37. self.logger.warning("Multiple products matching %s='%s', skipping" % (self._field, lookup_value))
  38. stats['num_skipped'] += 1
  39. except Product.DoesNotExist:
  40. self.logger.warning("No item matching %s='%s'" % (self._field, lookup_value))
  41. stats['num_skipped'] += 1
  42. except IdenticalImageError:
  43. self.logger.warning(" - Identical image already exists for %s='%s', skipping" % (self._field, lookup_value))
  44. stats['num_skipped'] += 1
  45. except IOError, e:
  46. raise ImageImportError(_('%(filename)s is not a valid image (%(error)s)') % {
  47. 'filename': filename, 'error': e})
  48. stats['num_invalid'] += 1
  49. except FieldError, e:
  50. raise ImageImportError(e)
  51. self._process_image(image_dir, filename)
  52. if image_dir != dirname:
  53. shutil.rmtree(image_dir)
  54. else:
  55. raise InvalidImageArchive(_('%s is not a valid image archive') % dirname)
  56. self.logger.info("Finished image import: %(num_processed)d imported, %(num_skipped)d skipped" % stats)
  57. def _get_image_files(self, dirname):
  58. filenames = []
  59. image_dir = self._extract_images(dirname)
  60. if image_dir:
  61. for filename in os.listdir(image_dir):
  62. ext = os.path.splitext(filename)[1]
  63. if os.path.isfile(os.path.join(image_dir, filename)) and ext in self.allowed_extensions:
  64. filenames.append(filename)
  65. return image_dir, filenames
  66. def _extract_images(self, dirname):
  67. '''
  68. Returns path to directory containing images in dirname if successful.
  69. Returns empty string if dirname does not exist, or could not be opened.
  70. Assumes that if dirname is a directory, then it contains images.
  71. If dirname is an archive (tar/zip file) then the path returned is to a
  72. temporary directory that should be deleted when no longer required.
  73. '''
  74. if os.path.isdir(dirname):
  75. return dirname
  76. ext = os.path.splitext(dirname)[1]
  77. if ext in ['.gz', '.tar']:
  78. image_dir = tempfile.mkdtemp()
  79. try:
  80. tar_file = tarfile.open(dirname)
  81. tar_file.extractall(image_dir)
  82. tar_file.close()
  83. return image_dir
  84. except (tarfile.TarError, zlib.error):
  85. return ""
  86. elif ext == '.zip':
  87. image_dir = tempfile.mkdtemp()
  88. try:
  89. zip_file = zipfile.ZipFile(dirname)
  90. zip_file.extractall(image_dir)
  91. zip_file.close()
  92. return image_dir
  93. except (zlib.error, zipfile.BadZipfile, zipfile.LargeZipFile):
  94. return ""
  95. # unknown archive - perhaps this should be treated differently
  96. return ""
  97. def _process_image(self, dirname, filename, lookup_value):
  98. file_path = os.path.join(dirname, filename)
  99. trial_image = Image.open(file_path)
  100. trial_image.verify()
  101. kwargs = {self._field: lookup_value}
  102. item = Product._default_manager.get(**kwargs)
  103. new_data = open(file_path, 'rb').read()
  104. next_index = 0
  105. for existing in item.images.all():
  106. next_index = existing.display_order + 1
  107. try:
  108. if new_data == existing.original.read():
  109. raise IdenticalImageError()
  110. except IOError:
  111. # File probably doesn't exist
  112. existing.delete()
  113. new_file = File(open(file_path, 'rb'))
  114. im = ProductImage(product=item, display_order=next_index)
  115. im.original.save(filename, new_file, save=False)
  116. im.save()
  117. self.logger.info(' - Image added to "%s"' % item)
  118. def _fetch_item(self, filename):
  119. kwargs = {self._field: self._get_lookup_value_from_filename(filename)}
  120. return Product._default_manager.get(**kwargs)
  121. def _get_lookup_value_from_filename(self, filename):
  122. return os.path.splitext(filename)[0]