Пример #1
0
    def add_product_data(self, data_list, images):
        """
        :param data_list: list of product data in the following form:
        [{'ean13':EAN13, 'price':PRICE, 'categories':[MAIN_CATEGORY, OTHER,...],
          'langs': ['id':LANG_ID, 'name':PRODUCT_NAME, 'link_rewrite': link_rewrite]},...]
        see how these fields are generated in AddProductsShopForm.clean() method.
        :param images: list of images for every product
        :return: update_ok string if everything is fine, error string that matches
                 HTML ID in template otherwise
        """

        # Check default lang, add link rewrite if it is different
        default_lang = self.default_lang
        for prod in data_list:
            langs = set([lang['id'] for lang in prod['langs']])
            if default_lang not in langs:
                new_value = prod['langs'][0].copy()
                new_value['id'] = default_lang
                prod['langs'].append(new_value)

        domain = self.shop.domain
        headers = {'Content-Type': 'application/x-www-form-urlencoded',
                   'Authorization': self.shop.authheader}

        context = {'prestashop_url': domain, 'products': data_list,
                   'date_add': datetime.now()}

        products_xml = render_to_string('xml_templates/add_products15.xml', context).encode('utf-8')

        try:
            result = get_xml_data("http://%s/api/products" % domain,
                                  headers, urllib.urlencode({'xml': products_xml}))
            # Extract product ids
            product_ids = [e.text for e in result.findall('.//product/id')]

            # TODO: Prestashop 1.5 does not allow to set quantities simalteniously,
            # set them separately
            quantities = [x['quantity'] for x in data_list]
            update_data_list = [(p_id, '0', {'quantity': str(q)}) for p_id, q in zip(product_ids, quantities)]
            self.update_presta_data(update_data_list)
            # IMAGES ADD CODE
            self.add_images.delay(self, product_ids, images)

        except urllib2.HTTPError, e:
            result = e.fp.read()
            send_email.delay('mail/error', {'domain': domain, 'message': str(result),
                                            'data_list': str(data_list),
                                            'full_data': ''}, "Add product client Error")
            return '14_update_permission_or_csv'
Пример #2
0
 def default_lang(self):
     if hasattr(self, '_default_lang'):
         return self._default_lang
     domain = self.shop.domain
     headers = {"Authorization": self.shop.authheader}
     # get default language
     try:
         data = get_xml_data("http://%s/api/%s/1" % (domain, 'configurations'), headers)
         id_lang = data.findtext('.//value')
         if not id_lang:
             id_lang = '1'
     except urllib2.URLError:
         id_lang = '1'
     self._default_lang = id_lang
     return id_lang
Пример #3
0
 def get_categories(self):
     domain = self.shop.domain
     categories = []
     headers = {"Authorization": self.shop.authheader}
     try:
         data = get_xml_data("http://%s/api/%s?display=[id,name]" % (domain, 'categories'), headers)
     except urllib2.URLError:
         # return non-empty list to prevent IndexErrors
         return [('1', _('NO CATEGORIES FOUND'))]
     for c in data.findall('.//category'):
         id = c.findtext('.//id')
         name = c.findtext(".//language[@id='%s']" % self.default_lang)
         categories.append((id, name))
     if not categories:
         # return non-empty list to prevent IndexErrors
         categories = [('1', _('NO CATEGORIES FOUND'))]
     return categories
Пример #4
0
    def get_presta_data(self, page_num, page_limit):
        domain = self.shop.domain
        headers = {"Authorization": self.shop.authheader}
        sync_type = self.shop.sync_type.lower()
        result_dict = {}

        try:
            # get products
            initial_index = page_num * page_limit
            data = get_xml_data("http://%s/api/%s?display=[id,%s,%s,name]&sort=[id_ASC]&limit=%d,%d&price[base_price][use_reduction]=0&price[base_price][use_tax]=0"
                                % (domain, 'products', sync_type, JOINED_ATTRS, initial_index, page_limit), headers)

            self._get_presta_data(headers, data, result_dict, domain, sync_type, page_limit, page_num)
        except urllib2.HTTPError as e:
            error = PrestaError.get_error(PrestaError.DOMAIN, 'Incorrect value', self)
            if e.code == 500:
                # This should never happen after we implemented proper pagination
                message = e.fp.read()
                send_email.delay('mail/error', {'domain': domain, 'data_list': str(e.message),
                                                'full_data': str(page_num), 'message': str(message)},
                                 "Client Error")
                error = {PrestaError.DOMAIN: _('You have some problem with API access configuration,<br>'
                                               'try if API works by manually examining url:<br>') +
                                             '<a href="http://%s/api/products">http://%s/api/products</a>' % (domain, domain)}
            elif e.code in (401, 403):
                # send email about this
                send_email.delay('mail/error', {'domain': domain, 'data_list': '',
                                                'full_data': '', 'message': ''}, "Incorrect key")
                error = PrestaError.get_error(PrestaError.KEY, 'Incorrect value', self)
                if 'attribute_lang' in result_dict:
                    error = PrestaError.get_error(PrestaError.KEY, PrestaError.PROD_COMBINATIONS, self)
                elif 'product' in result_dict:
                    error = PrestaError.get_error(PrestaError.KEY, PrestaError.PROD_OPTIONS_VALUES, self)
            elif e.code == 404 or e.code == 503:
                error = PrestaError.get_error(PrestaError.DOMAIN, PrestaError.NOT_ACTIVATED, self)
            return result_dict, error
        except (urllib2.URLError, socket.timeout):
            # either timeout or dns error
            error = PrestaError.get_error(PrestaError.DOMAIN, PrestaError.NOT_REACHABLE, self)
            return result_dict, error
        except PrestaError, e:
            # no products found in the shop
            error = PrestaError.get_error(PrestaError.DOMAIN, e.message, self)
            return result_dict, error
Пример #5
0
 def getAPI(cls, shop):
     """
     :type shop: Shop
     :rtype: PrestashopAPI
     """
     try:
         PrestashopAPI.urlopen(u'http://{0}/api'.format(shop.domain).encode('utf-8'), timeout=5)
     except urllib2.HTTPError, e:
         status = e.code
         if status in (401,403):
             headers = {"Authorization": shop.authheader}
             try:
                 data = get_xml_data(u'http://{0}/api/'.format(shop.domain).encode('utf-8'), headers)
                 if data.findtext('.//stock_availables/description'):
                     return PrestashopAPI15(shop)
             except urllib2.HTTPError:
                 pass
             except urllib2.URLError:
                 shop.set_alarm('URL is not valid')
             return PrestashopAPI14(shop)
Пример #6
0
    def make_first_image_default(self, product_ids, headers):
        if self.__version__ == PrestashopAPI15.__version__:
            return
        del headers['Content-Type']
        domain = self.shop.domain
        data_list = []
        context = {'prestashop_url': domain, 'products': data_list}

        for product_id in product_ids:
            prod_dict = {'id': product_id}
            data = get_xml_data("http://%s/api/products/%s" % (domain, product_id), headers)
            prod_dict['out_of_stock'] = data.findtext('.//out_of_stock')
            prod_dict['price'] = data.findtext('.//price')
            prod_dict['quantity'] = data.findtext('.//quantity')
            prod_dict['image_id'] = data.findtext('.//images/image/id')
            data_list.append(prod_dict)

        headers['Content-Type'] = 'application/x-www-form-urlencoded'
        xml = render_to_string('xml_templates/update_products.xml', context)
        req = urllib2.Request(smart_str("http://%s/api/products" % (domain,)),
                              headers=headers)
        req.get_method = lambda: 'PUT'
        urllib2.urlopen(req, data=xml.encode('utf-8'))
Пример #7
0
 def _get_xml_data(self, url):
     return get_xml_data(url, self.headers)
Пример #8
0
 def _get_xml_data(self, url):
     return get_xml_data(url, self.headers)
Пример #9
0
    def update_presta_data(self, data_list):
        """data_list = """
        domain = self.shop.domain
        authheader = self.shop.authheader
        headers = {"Authorization": self.shop.authheader}
        total_results = {'errors': []}

        # count how many products were updated
        total_price = 0
        total_count = 0
        # set to true if we find products that depend on stock
        depends_on_stock = False

        for row in data_list:
            id = row[0]
            id_attribute = row[1]
            is_combination = bool(id_attribute != '0')

            # PRICE update goes first -----------------
            if is_combination:
                resource = 'combinations/' + str(id_attribute)
            else:
                resource = 'products/' + str(id)

            if 'base_price' in row[2]:
                try:
                    # TODO: we're always replacing the price here, so we don't actually need it, but keep it for consistency with 1.4
                    req = urllib2.Request("http://%s/api/%s?price[base_price][use_reduction]=0&price[base_price][use_tax]=0" % (domain, resource),
                                          headers={'Content-Type': 'text/xml',
                                                   'Authorization': authheader})
                    xml = urllib2.urlopen(req).read()
                    xml = clean_chunked_data(xml)
                    xml = xml.decode('utf-8')

                    # replace base_price with price for combinations
                    if is_combination:
                        price = re.findall(ur'<price>(.*)</price>', xml)[0]
                        xml = re.sub(ur'(?<=<base_price>)(.*)(?=</base_price>)', price, xml)

                    for attr_name, attr_value in row[2].items():
                        xml = re.sub(ur'<{0}>(.*)</{0}>'.format(attr_name),
                                     u'<{0}>{1}</{0}>'.format(attr_name, '<![CDATA[{0}]]>'.format(attr_value)), xml)
                    # remove non-settable fields
                    xml = re.sub(r'<id_default_image(.+)</id_default_image>', '', xml)
                    xml = re.sub(r'<position_in_category(.+)</position_in_category>', '', xml)
                    xml = re.sub(r'<manufacturer_name(.+)</manufacturer_name>', '', xml)
                    xml = re.sub(r'<associations>(.+)</associations>(?s)', '', xml)
                    # replace price with base_price
                    base_price = re.findall(ur'<base_price>(.*)</base_price>', xml)[0]
                    xml = re.sub(ur'(?<=<price>)(.*)(?=</price>)', base_price, xml)
                    # replace quantity. it's not writable anymore
                    xml = re.sub(r'<quantity(.*)</quantity>', '', xml)

                    req = urllib2.Request(str("http://%s/api/%s" % (domain, resource)),
                                          headers={'Content-Type': 'application/x-www-form-urlencoded',
                                                   'Authorization': authheader})
                    req.get_method = lambda: 'PUT'
                    urllib2.urlopen(req, data=xml.encode('utf-8'))
                    total_price += 1
                except urllib2.HTTPError as e:
                    err_msg = e.fp.read()
                    total_results['errors'].append({'message': str(e), 'details': err_msg,
                                                    'product': id,
                                                    'combination': int(id_attribute)})
                except HTTPException as e:
                    total_results['errors'].append({'message': str(e), 'details': e.message,
                                                    'product': id,
                                                    'combination': int(id_attribute)})
            # END PRICE ---------------------------------

            # Quantity is not present for main product with combinations
            if 'quantity' in row[2]:
                # find right stock id
                stocks = get_xml_data("http://%s/api/%s?filter[id_product]=[%s]&filter[id_product_attribute]=[%s]"
                                      % (domain, 'stock_availables', id, id_attribute), headers)
                stock_id = stocks.findall('.//stock_available')
                if stock_id:
                    stock_id = stock_id[0].attrib['id']
                    req = urllib2.Request("http://%s/api/%s/%s" % (domain, 'stock_availables', stock_id),
                                          headers={'Content-Type': 'text/xml',
                                                   'Authorization': authheader})
                    xml = urllib2.urlopen(req).read()
                    xml = clean_chunked_data(xml)
                    xml = xml.decode('utf-8')
                    for attr_name, attr_value in row[2].items():
                        xml = re.sub(r'<{0}>(.*)</{0}>'.format(attr_name),
                                     '<{0}>{1}</{0}>'.format(attr_name, attr_value), xml)

                    # TODO: write test for it, otherwise prestashop returns error
                    if id_attribute == '0':
                        xml = xml.replace('<id_product_attribute></id_product_attribute>',
                                          '<id_product_attribute>0</id_product_attribute>')

                    # TODO: write test for depends on stock
                    if '<depends_on_stock><![CDATA[1]]></depends_on_stock>' in xml:
                        depends_on_stock = True
                        continue

                    try:
                        req = urllib2.Request(str("http://%s/api/%s/%s" % (domain, 'stock_availables', stock_id)),
                                              headers={'Content-Type': 'application/x-www-form-urlencoded',
                                                       'Authorization': authheader})
                        req.get_method = lambda: 'PUT'
                        urllib2.urlopen(req, data=xml.encode('utf-8'))
                        total_count += 1
                    except urllib2.HTTPError as e:
                        err_msg = e.fp.read()
                        total_results['errors'].append({'message': str(e), 'details': err_msg,
                                                        'product': id, 'combination': int(id_attribute)})
                    except HTTPException as e:
                        total_results['errors'].append({'message': str(e), 'details': e.message,
                                                        'product': id, 'combination': int(id_attribute)})
                else:
                    # stock not available, report it
                    total_results['errors'].append({'message': _('No stock available'), 'details': _('No stock available'),
                                                    'product': id, 'combination': int(id_attribute)})
        if depends_on_stock:
            total_results['result'] = '15_depends_on_stock'
        if total_count or total_price:
            total_results['result'] = 'update_ok'
        else:
            total_results['result'] = '14_update_permission'
        total_results['count'] = total_count
        return total_results
Пример #10
0
    def _get_presta_data(self, headers, data, result_dict, domain, sync_type, page_limit, page_num):

        id_lang = self.default_lang

        presta_version = get_xml_data("http://%s/api/%s?display=[name,value]"
                                      % (domain, 'configurations'), headers)
        presta_db_version = ''
        sort_attr = '&sort=[id_ASC]'
        for attr in presta_version.findall('.//configuration'):
            attr_name = attr.findtext('.//name')
            if attr_name == 'PS_VERSION_DB':
                presta_db_version = attr.findtext('.//value')
                break

        if presta_db_version == '1.5.5.0':
            sort_attr = ''

        # get products
        initial_index = page_num * page_limit
        data = get_xml_data("http://%s/api/%s?display=[id,%s,%s,name]%s&limit=%d,%d&price[base_price][use_reduction]=0&price[base_price][use_tax]=0"
                            % (domain, 'products', sync_type, JOINED_ATTRS, sort_attr, initial_index, page_limit), headers)

        result_dict['product'] = {}
        result_dict['product_lang'] = {}
        for elem in data.findall('.//product'):
            product_id = elem.findtext('.//id')
            values_dict = {}
            for attr, formatter in UpdateProductValidator.ATTRS:
                attr_value = elem.findtext('.//%s' % attr)
                # TODO: report it
                if not attr_value:
                    attr_value = '0'
                values_dict[attr] = formatter(attr_value)
            result_dict['product'][product_id] = {'sync_type': elem.findtext('.//%s' % sync_type),
                                                  'values': values_dict}
            product_name = elem.findtext(".//language[@id='%s']" % id_lang)
            if not product_name:
                try:
                    product_name = elem.findall('.//language')[0].text
                except KeyError:
                    product_name = _('DUMMY')
            result_dict['product_lang'][product_id] = product_name

        # product ids are later used to select combinations
        product_ids = sorted([int(k) for k in result_dict['product_lang'].keys()])

        # if there are no products - ha, didn't expect someone doing this
        if not product_ids:
            raise PrestaError(PrestaError.NO_PRODUCTS)

        max_prod_id = max(product_ids)
        min_prod_id = min(product_ids)
        product_ids = str(min_prod_id) + ',' + str(max_prod_id)
        # Finished is set to True so we can exit the loop
        if len(result_dict['product_lang'].keys()) < page_limit:
            self.finished = True
        else:
            self.finished = False

        # get attributes
        data = get_xml_data("http://%s/api/%s?display=[id,name]" % (domain, 'product_option_values'), headers)
        result_dict['attribute_lang'] = {}
        for elem in data.findall('.//product_option_value'):
            id = elem.findtext('.//id')
            result_dict['attribute_lang'][id] = elem.findtext(".//language[@id='%s']" % id_lang)
            if not result_dict['attribute_lang'].get(id):
                try:
                    result_dict['attribute_lang'][id] = elem.findall('.//language')[0].text
                except KeyError:
                    result_dict['attribute_lang'][id] = _('DUMMY')

        # get combinations
        data = get_xml_data("http://%s/api/%s?display=full&sort=[id_product_ASC]&filter[id_product]=[%s]"
                            % (domain, 'combinations', product_ids), headers)
        result_dict['product_attribute'] = defaultdict(dict)
        result_dict['product_attribute_combination'] = {}
        for elem in data.findall('.//combination'):
            comb_id = elem.findtext('.//id')
            prod_id = elem.findtext('.//id_product')
            # TODO: skip prod_ids that are not present in the 'product' dict
            # TODO: happens because of prestashop sorting BUG
            if not prod_id in result_dict['product']:
                continue
            values_dict = {}
            for attr, formatter in UpdateProductValidator.ATTRS:
                # TODO: hack because we don't have base_price
                if attr == 'base_price':
                    values_dict[attr] = formatter(elem.findtext('.//%s' % 'price'))
                else:
                    values_dict[attr] = formatter(elem.findtext('.//%s' % attr))
            result_dict['product_attribute'][prod_id][comb_id] =\
            {'sync_type': elem.findtext('.//%s' % sync_type), 'values': values_dict}
            result_dict['product_attribute_combination'][comb_id] = [elem.findtext('.//id') for elem in elem.findall('.//product_option_value')]

        # get stock_data
        data = get_xml_data("http://%s/api/%s?display=[id_product,id_product_attribute,quantity]&sort=[id_product_ASC]&filter[id_product]=[%s]"
                            % (domain, 'stock_availables', product_ids), headers)
        for elem in data.findall('.//stock_available'):
            prod_id = elem.findtext('.//id_product')
            comb_id = elem.findtext('.//id_product_attribute')
            quantity = elem.findtext('.//quantity')
            # TODO: log?
            # sometimes prod_id is empty - this is weird but we need to handle it
            if prod_id:
                # TODO: check why prod_id does not exist, may be convert to dict from defaultdict
                if comb_id != '0' and prod_id in result_dict['product_attribute']:
                    # Weird situation again we should check whether comb_id present,
                    # sometimes it's not. TODO: log?
                    try:
                        result_dict['product_attribute'][prod_id][comb_id]['values']['quantity'] = quantity
                    except KeyError:
                        pass
                else:
                    # TODO: skip prod_ids that are not present in the 'product' dict
                    # TODO: happens because of sorting bug
                    if prod_id in result_dict['product']:
                        result_dict['product'][prod_id]['values']['quantity'] = quantity

        # change values format back:
        products = []
        for prod_id, values in result_dict['product'].iteritems():
            products.append((prod_id, values['sync_type'], values['values']))
        result_dict['product'] = products

        product_attributes = defaultdict(list)
        for prod_id, values in result_dict['product_attribute'].iteritems():
            for comb_id, values in values.items():
                product_attributes[prod_id].append((comb_id, values['sync_type'], values['values']))
            products.append((prod_id, values['sync_type'], values['values']))
        result_dict['product_attribute'] = product_attributes
Пример #11
0
    def _get_presta_data(self, headers, data, result_dict, domain, sync_type, page_limit, page_num):

        id_lang = self.default_lang

        result_dict['product'] = []
        result_dict['product_lang'] = {}
        for elem in data.findall('.//product'):
            values_dict = {}
            for attr, formatter in UpdateProductValidator.ATTRS:
                values_dict[attr] = formatter(elem.findtext('.//%s' % attr))
            result_dict['product'].append((elem.findtext('.//id'),
                                           elem.findtext('.//%s' % sync_type), values_dict))

        for elem in data.findall('.//product'):
            id = elem.findtext('.//id')
            result_dict['product_lang'][id] = elem.findtext(".//language[@id='%s']" % id_lang)
            if not result_dict['product_lang'].get(id):
                try:
                    result_dict['product_lang'][id] = elem.findall('.//language')[0].text
                except KeyError:
                    result_dict['product_lang'][id] = _('DUMMY')

        # product ids are later used to select combinations
        product_ids = '|'.join(result_dict['product_lang'].keys())
        # Finished is set to True so we can exit the loop
        if len(result_dict['product_lang'].keys()) < page_limit:
            self.finished = True
        else:
            self.finished = False

        # get attributes
        data = get_xml_data("http://%s/api/%s?display=[id,name]" % (domain, 'product_option_values'), headers)
        result_dict['attribute_lang'] = {}
        for elem in data.findall('.//product_option_value'):
            id = elem.findtext('.//id')
            result_dict['attribute_lang'][id] = elem.findtext(".//language[@id='%s']" % id_lang)
            if not result_dict['attribute_lang'].get(id):
                try:
                    result_dict['attribute_lang'][id] = elem.findall('.//language')[0].text
                except KeyError:
                    result_dict['attribute_lang'][id] = _('DUMMY')

        # get combinations
        data = get_xml_data("http://%s/api/%s?display=full&sort=[id_ASC]&filter[id_product]=[%s]"
                            % (domain, 'combinations', product_ids), headers)
        result_dict['product_attribute'] = defaultdict(list)
        result_dict['product_attribute_combination'] = {}
        for elem in data.findall('.//combination'):
            comb_id = elem.findtext('.//id')
            values_dict = {}
            for attr, formatter in UpdateProductValidator.ATTRS:
                values_dict[attr] = formatter(elem.findtext('.//%s' % attr))
            result_dict['product_attribute'][elem.findtext('.//id_product')].append(
                (comb_id, elem.findtext('.//%s' % sync_type), values_dict))

            result_dict['product_attribute_combination'][comb_id] = []
            for elem in elem.findall('.//product_option_value'):
                # Check if id is indeed in attributes, it can happen if DB is corrupted.
                attr_id = elem.findtext('.//id')
                if attr_id in result_dict['attribute_lang']:
                    result_dict['product_attribute_combination'][comb_id].append(attr_id)