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 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import os
  2. import csv
  3. import sys
  4. from decimal import Decimal as D
  5. from oscar.apps.catalogue.categories import create_from_breadcrumbs
  6. from oscar.core.loading import get_class, get_classes
  7. ImportError = get_class('partner.exceptions', 'ImportError')
  8. Partner, StockRecord = get_classes('partner.models', ('Partner', 'StockRecord'))
  9. ProductClass, Product, Category, ProductCategory = get_classes(
  10. 'catalogue.models', ('ProductClass', 'Product', 'Category',
  11. 'ProductCategory'))
  12. class StockImporter(object):
  13. def __init__(self, logger, partner, delimiter):
  14. self.logger = logger
  15. self._delimiter = delimiter
  16. try:
  17. self._partner = Partner.objects.get(name=partner)
  18. except Partner.DoesNotExist:
  19. name_list = ", ".join([d['name'] for d in Partner.objects.values('name')])
  20. raise ImportError("Partner named '%s' does not exist (existing partners: %s)" % (partner, name_list))
  21. def handle(self, file_path=None):
  22. u"""Handles the actual import process"""
  23. if not file_path:
  24. raise ImportError("No file path supplied")
  25. Validator().validate(file_path)
  26. self._import(file_path)
  27. def _import(self, file_path):
  28. u"""Imports given file"""
  29. stats = {'updated_items': 0,
  30. 'unchanged_items': 0,
  31. 'unmatched_items': 0}
  32. row_number = 0
  33. for row in csv.reader(open(file_path, 'rb'), delimiter=self._delimiter, quotechar='"', escapechar='\\'):
  34. row_number += 1
  35. self._import_row(row_number, row, stats)
  36. msg = "\tUpdated items: %d\n\tUnchanged items: %d\n\tUnmatched items: %d" % (
  37. stats['updated_items'],
  38. stats['unchanged_items'],
  39. stats['unmatched_items'])
  40. self.logger.info(msg)
  41. def _import_row(self, row_number, row, stats):
  42. if len(row) != 3:
  43. self.logger.error("Row number %d has an invalid number of fields, skipping..." % row_number)
  44. else:
  45. self._update_stockrecord(*row[:3], row_number=row_number, stats=stats)
  46. def _update_stockrecord(self, partner_sku, price_excl_tax, num_in_stock, row_number, stats):
  47. try:
  48. stock = StockRecord.objects.get(partner=self._partner, partner_sku=partner_sku)
  49. except StockRecord.DoesNotExist:
  50. stats['unmatched_items'] += 1
  51. self.logger.error("\t - Row %d: StockRecord for partner '%s' and sku '%s' does not exist, skipping..." % (row_number, self._partner, partner_sku))
  52. return
  53. price_changed = False
  54. if stock.price_excl_tax != D(price_excl_tax):
  55. stock.price_excl_tax = D(price_excl_tax)
  56. price_changed = True
  57. stock_changed = False
  58. if stock.num_in_stock != int(num_in_stock):
  59. stock.num_in_stock = num_in_stock
  60. stock_changed = True
  61. if price_changed or stock_changed:
  62. stock.save()
  63. msg = " SKU %s:" % (partner_sku)
  64. if price_changed:
  65. msg += '\n - Price set to %s' % (price_excl_tax)
  66. if stock_changed:
  67. msg += '\n - Stock set to %s' % num_in_stock
  68. self.logger.info(msg)
  69. stats['updated_items'] += 1
  70. else:
  71. stats['unchanged_items'] += 1
  72. class CatalogueImporter(object):
  73. """
  74. A catalogue importer object
  75. """
  76. _flush = False
  77. def __init__(self, logger, delimiter=",", flush=False):
  78. self.logger = logger
  79. self._delimiter = delimiter
  80. self._flush = flush
  81. def handle(self, file_path=None):
  82. u"""Handles the actual import process"""
  83. if not file_path:
  84. raise ImportError("No file path supplied")
  85. Validator().validate(file_path)
  86. if self._flush is True:
  87. self.logger.info(" - Flushing product data before import")
  88. self._flush_product_data()
  89. self._import(file_path)
  90. def _flush_product_data(self):
  91. u"""Flush out product and stock models"""
  92. ProductClass.objects.all().delete()
  93. Product.objects.all().delete()
  94. Partner.objects.all().delete()
  95. StockRecord.objects.all().delete()
  96. def _import(self, file_path):
  97. u"""Imports given file"""
  98. stats = {'new_items': 0,
  99. 'updated_items': 0}
  100. row_number = 0
  101. for row in csv.reader(open(file_path,'rb'), delimiter=self._delimiter, quotechar='"', escapechar='\\'):
  102. row_number += 1
  103. self._import_row(row_number, row, stats)
  104. msg = "New items: %d, updated items: %d" % (stats['new_items'], stats['updated_items'])
  105. self.logger.info(msg)
  106. def _import_row(self, row_number, row, stats):
  107. if len(row) != 4 and len(row) != 8:
  108. self.logger.error("Row number %d has an invalid number of fields (%d), skipping..." % (row_number, len(row)))
  109. return
  110. item = self._create_item(*row[:4], stats=stats)
  111. if len(row) == 8:
  112. # With stock data
  113. self._create_stockrecord(item, *row[4:8], stats=stats)
  114. def _create_item(self, upc, title, description, product_class, stats):
  115. # Ignore any entries that are NULL
  116. if description == 'NULL':
  117. description = ''
  118. # Create item class and item
  119. product_class,_ = ProductClass.objects.get_or_create(name=product_class)
  120. try:
  121. item = Product.objects.get(upc=upc)
  122. stats['updated_items'] += 1
  123. except Product.DoesNotExist:
  124. item = Product()
  125. stats['new_items'] += 1
  126. item.upc = upc
  127. item.title = title
  128. item.description = description
  129. item.product_class = product_class
  130. item.save()
  131. # Category
  132. cat = create_from_breadcrumbs('Books > Fiction')
  133. ProductCategory.objects.create(product=item, category=cat)
  134. return item
  135. def _create_stockrecord(self, item, partner_name, partner_sku, price_excl_tax, num_in_stock, stats):
  136. # Create partner and stock record
  137. partner, _ = Partner.objects.get_or_create(name=partner_name)
  138. try:
  139. stock = StockRecord.objects.get(partner_sku=partner_sku)
  140. except StockRecord.DoesNotExist:
  141. stock = StockRecord()
  142. stock.product = item
  143. stock.partner = partner
  144. stock.partner_sku = partner_sku
  145. stock.price_excl_tax = D(price_excl_tax)
  146. stock.num_in_stock = num_in_stock
  147. stock.save()
  148. class Validator(object):
  149. def validate(self, file_path):
  150. self._exists(file_path)
  151. self._is_file(file_path)
  152. self._is_readable(file_path)
  153. def _exists(self, file_path):
  154. u"""Check whether a file exists"""
  155. if not os.path.exists(file_path):
  156. raise ImportError("%s does not exist" % (file_path))
  157. def _is_file(self, file_path):
  158. u"""Check whether file is actually a file type"""
  159. if not os.path.isfile(file_path):
  160. raise ImportError("%s is not a file" % (file_path))
  161. def _is_readable(self, file_path):
  162. u"""Check file is readable"""
  163. try:
  164. f = open(file_path, 'r')
  165. f.close()
  166. except:
  167. raise ImportError("%s is not readable" % (file_path))