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.

utils.py 6.1KB

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