Ejemplo n.º 1
0
class InventoryUpgrader(Thread):
    def __init__(self, asset):
        Thread.__init__(self)
        self._asset = asset
        self._client = InventoryClient()

        # Get product name
        try:
            prod_url = '{}/api/catalogManagement/v2/productSpecification/{}?fields=name'\
                .format(settings.CATALOG, self._asset.product_id)

            resp = requests.get(prod_url)
            resp.raise_for_status()

            self._product_name = resp.json()['name']
        except HTTPError:
            self._product_name = None

    def _save_failed(self, pending_off, pending_products):
        # The failed upgrades list may be upgraded by other threads or other server instances
        # In this case context must be accessed as a shared resource
        context_id = Context.objects.all()[0].pk

        lock = DocumentLock('wstore_context', context_id, 'ctx')
        lock.wait_document()

        # At this point only the current thread can modify the list of pending upgrades
        context = Context.objects.all()[0]
        context.failed_upgrades.append({
            'asset_id': self._asset.pk,
            'pending_offerings': pending_off,
            'pending_products': pending_products
        })
        context.save()

        lock.unlock_document()

    def _notify_user(self, patched_product):
        if self._product_name is not None:
            try:
                not_handler = NotificationsHandler()
                order = Order.objects.get(
                    order_id=patched_product['name'].split('=')[-1])

                not_handler.send_product_upgraded_notification(
                    order,
                    order.get_product_contract(unicode(patched_product['id'])),
                    self._product_name)

            except:
                # A failure in the email notification is not relevant
                pass

    def upgrade_products(self, product_ids, id_filter):
        def is_digital_char(characteristic):
            # Return whether a characteristics is defining asset info for the given one
            def is_product(id_):
                sp = id_.split(':')
                return len(sp) == 2 and sp[0] == 'product' and sp[
                    1] == self._asset.product_id

            def is_offering(id_):
                sp = id_.split(':')
                offerings = []

                if len(sp) == 2 and sp[0] == 'offering':
                    offerings = Offering.objects.filter(off_id=sp[1])

                return len(offerings) == 1 and \
                        (offerings[0].asset == self._asset or self._asset.pk in offerings[0].asset.bundled_assets)

            dig_char = False
            id_str = ''

            name = characteristic['name'].lower()

            if name.endswith('asset type') or name.endswith(
                    'media type') or name.endswith('location'):
                # There are several formats for asset characteristics within the inventory products depending on
                # the number and the structure of the involved bundles
                # name: Asset Type  , For single offering with single product
                # name: offering:123 Asset Type   , For bundle offering with single product
                # name: product:123 Asset Type    , For single offering with bundle product
                # name: offering:123 product:345 Asset Type  , For bundle offering with bundle product

                id_str = name.replace('asset type',
                                      '').replace('media type',
                                                  '').replace('location', '')
                bundle_ids = id_str.split(' ')

                dig_char = len(bundle_ids) == 1 or \
                           (len(bundle_ids) == 2 and (is_product(bundle_ids[0]) or is_offering(bundle_ids[0]))) or \
                           (len(bundle_ids) == 3 and is_offering(bundle_ids[0]) and is_product(bundle_ids[1]))

            return dig_char, id_str

        n_pages = int(math.ceil(len(product_ids) / PAGE_LEN))

        missing_upgrades = []
        for page in range(0, n_pages):
            # Get the ids related to the current product page
            offset = page * int(PAGE_LEN)

            page_ids = [
                unicode(id_filter(p_id))
                for p_id in product_ids[offset:offset + int(PAGE_LEN)]
            ]
            ids = ','.join(page_ids)

            # Get product characteristics field
            try:
                products = self._client.get_products(
                    query={
                        'id': ids,
                        'fields': 'id,productCharacteristic'
                    })
            except HTTPError:
                missing_upgrades.extend(page_ids)
                continue

            # Patch product to include new asset information
            for product in products:
                pre_ids = ''
                product_id = unicode(product['id'])

                new_characteristics = []
                for char in product['productCharacteristic']:
                    is_dig, ids_str = is_digital_char(char)
                    if not is_dig:
                        new_characteristics.append(char)
                    else:
                        pre_ids = ids_str

                new_characteristics.append({
                    'name':
                    '{}Media Type'.format(pre_ids),
                    'value':
                    self._asset.content_type
                })

                new_characteristics.append({
                    'name':
                    '{}Asset Type'.format(pre_ids),
                    'value':
                    self._asset.resource_type
                })

                new_characteristics.append({
                    'name': '{}Location'.format(pre_ids),
                    'value': self._asset.download_link
                })

                try:
                    # The inventory API returns the product after patching
                    patched_product = self._client.patch_product(
                        product_id,
                        {'productCharacteristic': new_characteristics})
                except HTTPError:
                    missing_upgrades.append(product_id)
                    continue

                self._notify_user(patched_product)

        return missing_upgrades

    def upgrade_asset_products(self, offering_ids):
        # Get all the product ids related to the given product offering
        missing_off = []
        missing_products = []
        for off_id in offering_ids:
            try:
                product_ids = self._client.get_products(query={
                    'productOffering.id': off_id,
                    'fields': 'id'
                })
            except HTTPError:
                # Failure reading the available product ids, all upgrades pending
                missing_off.append(off_id)
                continue

            missing_products.extend(
                self.upgrade_products(product_ids, lambda p_id: p_id['id']))

        return missing_off, missing_products

    def _get_providing_offerings(self):
        # Get product bundles that contain the included asset
        assets = [self._asset]
        assets.extend(Resource.objects.filter(bundled_assets=self._asset.pk))

        # Get all the offerings that include the asset or one of the bundles
        offerings = []
        for asset in assets:
            offerings.extend(Offering.objects.filter(asset=asset))

        # Get all the offering bundles which include the previous offerings
        bundles = []
        for off in offerings:
            bundles.extend(Offering.objects.filter(bundled_offerings=off.pk))

        offerings.extend(bundles)
        return offerings

    def run(self):
        # Get all the offerings that give access to the provided digital asset
        offerings = self._get_providing_offerings()

        # Upgrade all the products related to the provided asset
        missing_off, missing_products = self.upgrade_asset_products(
            [offering.off_id for offering in offerings])

        if len(missing_off) > 0 or len(missing_products) > 0:
            self._save_failed(missing_off, missing_products)