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.

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