November 23, 2017


List items for a Server Order

The script displays the available data centers and items for a package.

This is an older article, the following might provide better instructions.

"""
List items to order device.

The script displays the available data centers and items for a package.
Note: The displayed prices are standard prices.
"""

import SoftLayer


package_id = 200
# Set True to see the hourly prices.
# In case you see empty prices that means the item does not have an hourly price.
hourly = False

# Some packages such as the package 200 requires a preset id.
# Configure the value with "" in case the packages do not requires a preset id.
preset_id = 385

client = SoftLayer.create_client_from_env()
package_service = client['SoftLayer_Product_Package']


def get_all_datacenters_available_for_package(package_id, package_service):
    """Get the available datacenters for a package.
        :param int package_id: The package id to get the available data centers.
        :param SoftLayer_Product_Package package_service: The SoftLayer Product Package service.
        :returns: An array SoftLayer_Location_Region.
    """
    try:
        object_mask = "mask[regions[keyname,description,sortOrder," \
                      "location[location(SoftLayer_Location_Datacenter)[activePresaleEvents," \
                      "priceGroups.id,locationAddress.country,brandCountryRestrictions[locationId, " \
                      "customerCountryCode]]]]]"
        result = package_service.getObject(id=package_id, mask=object_mask)
        regions = []
        for item in result['regions']:
            for detail in item['location']['locationPackageDetails']:
                if detail['isAvailable'] == 1 and detail['locationId'] == item['location']['location']['id'] \
                        and detail['packageId'] == package_id:
                    regions.append(item)
    except SoftLayer.SoftLayerAPIError as e:
        print("Unable to get the data centers. \nfaultCode= %s, \nfaultString= %s" % (e.faultCode, e.faultString))
        exit(0)
    return regions


def get_all_items(package_id, package_service):
    """Get all the items for a package.
        :param int package_id: The package id to get the available data centers.
        :param SoftLayer_Product_Package package_service: The SoftLayer Product Package service.
        :returns: An array of SoftLayer_Product_Item.
    """
    try:
        object_mask = "mask[categories, prices[pricingLocationGroup, presetConfigurations]]"
        items = package_service.getItems(id=package_id, mask=object_mask)
        items = sorted(items, key=lambda item: (item['description']))
    except SoftLayer.SoftLayerAPIError as e:
        print("Unable to get the items. faultCode=%s, faultString=%s" % (e.faultCode, e.faultString))
        exit(0)
    return items


def get_configuration(package_service):
    """Get the configuration for a package.
        :param SoftLayer_Product_Package package_service: The SoftLayer Product Package service.
        :returns: An array of SoftLayer_Product_Package_Order_Configuration.
    """
    try:
        object_mask = "mask[itemCategory]"
        items = package_service.getConfiguration(id=package_id, mask=object_mask)
    except SoftLayer.SoftLayerAPIError as e:
        print("Unable to get the required categories. faultCode=%s, faultString=%s" % (e.faultCode, e.faultString))
        exit(0)
    return items


def is_required_option(list, category_code, configurations):
    """Verifies if a category is required in the configurations of the package
       In case it is not required adds the value none to the list parameter.
        :param An array of SoftLayer_Product_Item list: The list of items.
        :param string category_code: The category code to verify if it is required in the package configuration.
        :param An array of SoftLayer_Product_Package_Order_Configuration configurations:
        The configuration of the package.
        :returns: An array of SoftLayer_Product_Item.
    """
    items = [conf for conf in configurations if conf['itemCategory']['categoryCode'] == category_code
             and conf['isRequired'] == 1]
    if len(items) == 0:
        none = {}
        none['description'] = "None"
        list.insert(0, none)
    return list


def get_filtered_items_category(items, configurations, category_code):
    """Gets the items which belong to a category code.
        :param An array of SoftLayer_Product_Item items: The list of items.
        :param An array of SoftLayer_Product_Package_Order_Configuration configurations:
        The configuration of the package.
        :param string category_code: The category code to filter.
        :returns: An array of SoftLayer_Product_Item.
    """
    filtered = []
    for item in items:
        categories = [category for category in item['categories'] if category['categoryCode'] == category_code]
        if len(categories) > 0:
            filtered.append(item)
    filtered = is_required_option(filtered, category_code, configurations)
    return filtered


def get_standard_item_price(item):
    """Gets the standard price for an item.
        :param An array of SoftLayer_Product_Item item: The item to get the standard price.
        :returns: A SoftLayer_Product_Item_Price.
    """
    prices = [price for price in item['prices'] if price['locationGroupId'] == ""]
    # We always return the last standard price in the list.
    return prices[-1]


def display_datacenters(datacenters):
    """Displays the data centers.
        :param An array SoftLayer_Location_Region datacenters: The list of regions.
    """
    tab_spaces_begin = 4
    id_label = "LocationId"
    print(id_label + " " * tab_spaces_begin + "DATA CENTER")
    for datacenter in datacenters:
        print(str(datacenter['location']['location']['id']) + " " *
              (len(id_label) - len(str(datacenter['location']['location']['id']))) + " " *
              tab_spaces_begin + datacenter['description'])
    print()
    print()
    return


def display_items(items, title, hourly, is_preset, price_preset):
    """Displays the data centers.
        :param An array SoftLayer_Product_Item items: The list of items to display.
        :param string title: The title of the items.
        :param boolean hourly: A boolean value which indicates if the hourly prices must be displayed.
    """
    tab_spaces_begin = 4
    tab_spaces_prices = 4
    tab_spaces_desc = 60
    price_id_label = "PriceId"
    if len(items) > 0:
        if len(items) > 1 or items[0]['description'] != "None":
            if not hourly:
                price_label = "Monthly"
                price_key = "recurringFee"
            else:
                price_label = "Hourly"
                price_key = "hourlyRecurringFee"

            print(price_id_label + " " * tab_spaces_begin + title.upper() + " " * tab_spaces_desc + price_label +
                  " " * tab_spaces_prices + "Setup")
            for item in items:
                if item['description'] == "None":
                    print(" " * len(price_id_label) + " " * tab_spaces_begin + item['description'])
                else:
                    if not is_preset:
                        price = get_standard_item_price(item)
                    else:
                        price = price_preset
                    if price_key in price:
                        print(str(price['id']) + " " * (len(price_id_label) - len(str(price['id']))) + " "
                              * tab_spaces_begin + item['description'].strip() + " "
                              * (tab_spaces_desc + len(title) - len(item['description']))
                              + str(float(price[price_key])) + "$" + " "
                              * (len(price_label) - len(str(float(price[price_key]))))
                              + " " * tab_spaces_prices + str(float(price['setupFee'])) + "$")
            print()
            print()
    return


def is_preset_required(package_id, package_service):
    """Gets the preset configuration required flag.
        :param int package_id: The package id to get the available data centers.
        :param SoftLayer_Product_Package package_service:
        The SoftLayer Product Package service.
        :returns: An boolean which indicates whether preset is required.
    """
    try:
        result = package_service.getPresetConfigurationRequiredFlag(id=package_id)
    except SoftLayer.SoftLayerAPIError as e:
        print("Unable to get the preset configuration required flag. \nfaultCode= %s, \nfaultString= %s"
              % (e.faultCode, e.faultString))
        exit(0)
    return result


def getting_package_preset(package_id, preset_id, package_service):
    """Gets a SoftLayer_Product_Package_Preset object.
        :param int package_id: The package id to get the available data centers.
        :param int preset_id: The preset id of the SoftLayer_Product_Package_Preset to get.
        :param SoftLayer_Product_Package package_service: The SoftLayer Product Package service.
        :returns: A oftLayer_Product_Package_Preset object.
    """
    try:
        object_mask = "mask[packageConfiguration[itemCategory],categories, " \
                      "configuration[price[item[prices[pricingLocationGroup]]],category]]"
        object_filter = {"activePresets": {"id": {"operation": preset_id}}}
        presets = package_service.getActivePresets(id=package_id, mask=object_mask, filter=object_filter)
        if len(presets) == 0:
            print("The configured preset id: '" + str(preset_id) + "' is not valid for the package id: '"
                  + str(package_id) + "'")
            exit(0)
    except SoftLayer.SoftLayerAPIError as e:
        print("Unable to get the active presets. \nfaultCode= %s, \nfaultString= %s" % (e.faultCode, e.faultString))
        exit(0)
    return presets[0]


def get_filtered_item_preset_category(preset_configurations, category_code):
    """Gets the prices for a preset configuration list which contain an arbitrary category code.
        :param SoftLayer_Product_Package_Preset_Configuration preset_configurations:
        The list of configuration in the preset.
        :param string category_code: The category code to filter.
        :returns: An array SoftLayer_Product_Item_Price.
    """
    filtered = []
    for presetConfiguration in preset_configurations:
        if presetConfiguration['category']['categoryCode'] == category_code:
            filtered.append(presetConfiguration['price'])
    return filtered


def get_configurations_not_preset(preset):
    """Gets the configuration list which contains the categories that can be configurable.
        :param SoftLayer_Product_Package_Preset preset: The preset to get the configuration list.
        :returns: An array of SoftLayer_Product_Package_Order_Configuration.
    """
    configurations = []
    # When the package has a preset configuration only a few categories can be edited.
    # Below the list of categories which can be edited.
    allowed_list = ["bandwidth", "port_speed", "sec_ip_addresses", "pri_ipv6_addresses",
                    "static_ipv6_addresses", "evault", "os"]
    for conf in preset['packageConfiguration']:
        for allowed in allowed_list:
            if conf['itemCategory']['categoryCode'] == allowed:
                configurations.append(conf)
                break
    return configurations

# Getting the available data centers.
datacenters = get_all_datacenters_available_for_package(package_id, package_service)
# Displaying the data centers
display_datacenters(datacenters)

if not is_preset_required(package_id, package_service):
    # Getting the items in the package
    items = get_all_items(package_id, package_service)
    # Getting the configuration of the package
    configurations = get_configuration(package_id, package_service)
    configurations = sorted(configurations, key=lambda item: item['isRequired'], reverse=True)
    # Displaying the items
    for conf in configurations:
        listItems = get_filtered_items_category(items, configurations, conf['itemCategory']['categoryCode'])
        display_items(listItems, conf['itemCategory']['name'], hourly, False, "")
else:
    # Getting the preset configuration, these items cannot be changed in the order.
    # Review http://sldn.softlayer.com/reference/datatypes/SoftLayer_Product_Package_Preset for more information.
    preset = getting_package_preset(package_id, preset_id, package_service)
    print("*************************************   PRESET CONFIGURATION   *************************************")
    print("")
    for conf in preset['packageConfiguration']:
        listConfiguredItems = get_filtered_item_preset_category(preset['configuration'],
                                                                conf['itemCategory']['categoryCode'])
        items = []
        if len(listConfiguredItems) > 0:
            items.append(listConfiguredItems[0]['item'])
            display_items(items, conf['itemCategory']['categoryCode'], hourly, True, listConfiguredItems[0])
    # Getting the items which can be configured in the order.
    configurations = get_configurations_not_preset(preset)
    # Getting the items in the package
    items = get_all_items(package_id, package_service)
    print("*************************************   CONFIGURABLE ITEMS   *************************************")
    print("")
    # Displaying the items
    for conf in configurations:
        listItems = get_filtered_items_category(items, configurations, conf['itemCategory']['categoryCode'])
        display_items(listItems, conf['itemCategory']['name'], hourly, False, "")