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

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