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.0KB

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