import os from decimal import Decimal as D from django.db.transaction import commit_on_success from django.utils.translation import ugettext_lazy as _ from oscar.apps.catalogue.categories import create_from_breadcrumbs from oscar.apps.dashboard.reports.csv_utils import CsvUnicodeReader from oscar.core.loading import get_class, get_classes ImportError = get_class('partner.exceptions', 'ImportError') Partner, StockRecord = get_classes('partner.models', ('Partner', 'StockRecord')) ProductClass, Product, Category, ProductCategory = get_classes( 'catalogue.models', ('ProductClass', 'Product', 'Category', 'ProductCategory')) class StockImporter(object): def __init__(self, logger, partner, delimiter): self.logger = logger self._delimiter = delimiter try: self._partner = Partner.objects.get(name=partner) except Partner.DoesNotExist: name_list = ", ".join([d['name'] for d in Partner.objects.values('name')]) raise ImportError(_("Partner named '%(partner)s' does not exist (existing partners: %(list)s)") % { 'partner': partner, 'list': name_list}) def handle(self, file_path=None): u"""Handles the actual import process""" if not file_path: raise ImportError(_("No file path supplied")) Validator().validate(file_path) self._import(file_path) def _import(self, file_path): u"""Imports given file""" stats = {'updated_items': 0, 'unchanged_items': 0, 'unmatched_items': 0} row_number = 0 for row in CsvUnicodeReader(open(file_path, 'rb'), delimiter=self._delimiter, quotechar='"', escapechar='\\'): row_number += 1 self._import_row(row_number, row, stats) msg = "\tUpdated items: %d\n\tUnchanged items: %d\n\tUnmatched items: %d" % ( stats['updated_items'], stats['unchanged_items'], stats['unmatched_items']) self.logger.info(msg) def _import_row(self, row_number, row, stats): if len(row) != 3: self.logger.error("Row number %d has an invalid number of fields, skipping..." % row_number) else: self._update_stockrecord(*row[:3], row_number=row_number, stats=stats) def _update_stockrecord(self, partner_sku, price_excl_tax, num_in_stock, row_number, stats): try: stock = StockRecord.objects.get(partner=self._partner, partner_sku=partner_sku) except StockRecord.DoesNotExist: stats['unmatched_items'] += 1 self.logger.error("\t - Row %d: StockRecord for partner '%s' and sku '%s' does not exist, skipping..." % (row_number, self._partner, partner_sku)) return price_changed = False if stock.price_excl_tax != D(price_excl_tax): stock.price_excl_tax = D(price_excl_tax) price_changed = True stock_changed = False if stock.num_in_stock != int(num_in_stock): stock.num_in_stock = num_in_stock stock_changed = True if price_changed or stock_changed: stock.save() msg = " SKU %s:" % (partner_sku) if price_changed: msg += '\n - Price set to %s' % (price_excl_tax) if stock_changed: msg += '\n - Stock set to %s' % num_in_stock self.logger.info(msg) stats['updated_items'] += 1 else: stats['unchanged_items'] += 1 # Deprecated class CatalogueImporter(object): """ A catalogue importer object """ _flush = False def __init__(self, logger, delimiter=",", flush=False): self.logger = logger self._delimiter = delimiter self._flush = flush def handle(self, file_path=None): u"""Handles the actual import process""" if not file_path: raise ImportError(_("No file path supplied")) Validator().validate(file_path) if self._flush is True: self.logger.info(" - Flushing product data before import") self._flush_product_data() self._import(file_path) def _flush_product_data(self): u"""Flush out product and stock models""" ProductClass.objects.all().delete() Product.objects.all().delete() Partner.objects.all().delete() StockRecord.objects.all().delete() @commit_on_success def _import(self, file_path): u"""Imports given file""" stats = {'new_items': 0, 'updated_items': 0} row_number = 0 for row in CsvUnicodeReader(open(file_path,'rb'), delimiter=self._delimiter, quotechar='"', escapechar='\\'): row_number += 1 self._import_row(row_number, row, stats) msg = "New items: %d, updated items: %d" % (stats['new_items'], stats['updated_items']) self.logger.info(msg) def _import_row(self, row_number, row, stats): if len(row) != 5 and len(row) != 9: self.logger.error("Row number %d has an invalid number of fields (%d), skipping..." % (row_number, len(row))) return item = self._create_item(*row[:5], stats=stats) if len(row) == 9: # With stock data self._create_stockrecord(item, *row[5:9], stats=stats) def _create_item(self, product_class, category_str, upc, title, description, stats): # Ignore any entries that are NULL if description == 'NULL': description = '' # Create item class and item product_class, __ = ProductClass.objects.get_or_create(name=product_class) try: item = Product.objects.get(upc=upc) stats['updated_items'] += 1 except Product.DoesNotExist: item = Product() stats['new_items'] += 1 item.upc = upc item.title = title item.description = description item.product_class = product_class item.save() # Category cat = create_from_breadcrumbs(category_str) ProductCategory.objects.create(product=item, category=cat) return item def _create_stockrecord(self, item, partner_name, partner_sku, price_excl_tax, num_in_stock, stats): # Create partner and stock record partner, _ = Partner.objects.get_or_create( name=partner_name) try: stock = StockRecord.objects.get(partner_sku=partner_sku) except StockRecord.DoesNotExist: stock = StockRecord() stock.product = item stock.partner = partner stock.partner_sku = partner_sku stock.price_excl_tax = D(price_excl_tax) stock.num_in_stock = num_in_stock stock.save() class Validator(object): def validate(self, file_path): self._exists(file_path) self._is_file(file_path) self._is_readable(file_path) def _exists(self, file_path): u"""Check whether a file exists""" if not os.path.exists(file_path): raise ImportError(_("%s does not exist") % (file_path)) def _is_file(self, file_path): u"""Check whether file is actually a file type""" if not os.path.isfile(file_path): raise ImportError(_("%s is not a file") % (file_path)) def _is_readable(self, file_path): u"""Check file is readable""" try: f = open(file_path, 'r') f.close() except: raise ImportError(_("%s is not readable") % (file_path))