"""Logic for Physical Product Tracks. Perform CRUD operations against the physical product schema. """ from oto import response from ows_product_physical import config from ows_product_physical.constant import error from ows_product_physical.constant import field from ows_product_physical.logic import ownership from ows_product_physical.logic import product_physical from ows_product_physical.models import ows_contracts from ows_product_physical.models import ows_product from ows_product_physical.models import ows_royalties from ows_product_physical.models import persister from ows_product_physical.models import track as track_model from ows_product_physical.models.ows_account import get_vendor from ows_product_physical.utils import track_utils from ows_product_physical.validation import validation from ows_product_physical.validation.validation import json_validator DELETE_PRODUCT_TRACK_BODY_VALIDATOR = json_validator( config.DELETE_TRACK_SCHEMA) POST_PRODUCT_TRACK_BODY_VALIDATOR = json_validator(config.POST_TRACK_SCHEMA) PUT_PRODUCT_TRACK_BODY_VALIDATOR = json_validator(config.PUT_TRACK_SCHEMA) def update_tracks(product_id, tracks, account_type=None, account_id=None): """Update a physical product's tracks. Args: product_id (int): id of product to update tracks for tracks (list): containing the put request json payload as a list account_type (str|None): a grass header parameter (subaccount|vendor) account_id (str|None): a grass header parameter Return: Response: the response of the create/update/delete combo operation, or the per-track level errors from any failed track operation validations. """ product_response = product_physical.fetch_by_id( product_id, account_type, account_id) if not product_response: return product_response validation_responses = [] has_validation_errors = False product_tracks = get_tracks(product_id, account_type, account_id) for track in tracks: track_id = track.get('track_id', None) is_deleted = track.get('is_deleted', None) == 'Y' track_ownership = ownership.check_track_ownership( track_id, product_tracks) if track_id and is_deleted: # delete if not track_ownership: return track_ownership validation_response = validation.validate( track, DELETE_PRODUCT_TRACK_BODY_VALIDATOR) elif track_id: if not track_ownership: return track_ownership # update validation_response = validation.validate( track, PUT_PRODUCT_TRACK_BODY_VALIDATOR) else: # create validation_response = validation.validate( track, POST_PRODUCT_TRACK_BODY_VALIDATOR) if not validation_response: has_validation_errors = True validation_responses.append( validation_response.errors.get('message')) else: validation_responses.append(None) if has_validation_errors: return response.create_error_response( code=error.VALIDATION_ERROR, message={ 'tracks': validation_responses, 'global': None}) upc = product_response.message['upc'] persister_response = track_model.process_tracks( product_id, upc, tracks) if persister_response.status != 201: global_error_message = persister_response.errors.get('message') return response.create_error_response( code=error.VALIDATION_ERROR, message={ 'tracks': validation_responses, 'global': global_error_message}) return persister_response def _validate_products_for_copy(*products): """Check product ownership and types for copying. Args: products: products to validate Returns: response.Response: success if valid, error if not. """ product_ownerships = set( (product['vendor_id'], product['subaccount_id']) for product in products) if len(product_ownerships) > 1: return response.create_error_response( code=error.OWNERSHIP_ERROR, message='product ownership inconsistent.' ) if any(product for product in products if product['context_type'] != 'physical'): return response.create_error_response( code=error.INTERNAL_ERROR, message='copy only allowed on physical products.' ) return response.Response() def copy_product_tracks(product_id, new_product_id): """Copy product tracks. Args: product_id (int): product_id to copy from new_product_id (int): product_id to copy to Return: Response: the response of the copy operation """ product_response = ows_product.get_product(product_id) if not product_response: return product_response product = product_response.message new_product_response = ows_product.get_product(new_product_id) if not new_product_response: return new_product_response new_product = new_product_response.message validation_response = _validate_products_for_copy(product, new_product) if not validation_response: return validation_response upc = new_product['upc'] tracks_response = track_model.get_tracks(product_id) if not tracks_response: return tracks_response fields = track_utils.map_tracks_response_to_fields_for_copy( tracks_response) process_response = track_model.process_tracks( new_product_id, upc, fields) if process_response.status != 201: return process_response return response.Response(status=201) def get_tracks(product_id, account_type=None, account_id=None): """Retrieve a physical product's tracks. Args: product_id (int): id of product to update tracks for account_type (str|None): a grass header parameter (subaccount|vendor) account_id (str|None): a grass header parameter Return: Response: the response of the get operation """ product_response = product_physical.fetch_by_id( product_id, account_type, account_id) if not product_response: return product_response return track_model.get_tracks(product_id) def get_publishing_obligations_for_product( product_id, account_type, account_id): """Get publishing obligation data for specified tracks. Args: product_id (int): product id. account_type (str): the user account type in the header. account_id (str): the user account id from the header. Returns: response.Response: a Response object with JSON data """ if account_type and account_id: ownership_check = ows_product.check_product_ownership( product_id, account_type, account_id) if not ownership_check.message: return response.create_error_response( message=error.OWNERSHIP_ERROR_MESSAGE.format(account_type), status=error.FORBIDDEN_CODE, code=error.OWNERSHIP_ERROR) release = persister.get_product_by_id(product_id) if not release: return release product_tracks = track_model.get_tracks(product_id) if not product_tracks.message['items']: return response.create_error_response( code=error.PUBLISHING_OBLIGATION_NOT_FOUND_NO_TRACK_CODE, message=( error.PUBLISHING_OBLIGATION_NOT_FOUND_NO_TRACK_MESSAGE.format( product_id)), status=error.NOT_FOUND) return track_model.get_publishing_obligations(product_id) def update_publishing_obligations( product_id, data, account_type, account_id): """Update publishing obligation data for specified tracks. Args: product_id (int): product id. data(list): publishing obligation data for different tracks. account_type (str): the user account type in the header. account_id (str): the user account id from the header. Returns: response.Response: a Response object with JSON data """ product_response = ows_product.get_product(product_id) if not product_response: return product_response upc = product_response.message['upc'] product_tracks = track_model.get_tracks(product_id) if not product_tracks.message['items']: error_message = ( error.PUBLISHING_OBLIGATION_NOT_FOUND_NO_TRACK_MESSAGE.format( product_id)) return response.create_error_response( code=error.PUBLISHING_OBLIGATION_NOT_FOUND_NO_TRACK_CODE, message=error_message, status=error.NOT_FOUND) is_mech_admin_response = _is_mech_admin(account_type, account_id) if not is_mech_admin_response: return is_mech_admin_response vendor_is_mech_admin = is_mech_admin_response.message['mechadmin_physical'] # Validate data validation_response = _validate_track_publishing_obligations( data, vendor_is_mech_admin) if not validation_response: return validation_response # Set third_party_publisher field to default database value if not set for track_data in data: track_data[field.THIRD_PARTY_PUBLISHER] = ( track_data.get(field.THIRD_PARTY_PUBLISHER, 'N')) update_publishing_obligations_response = ( track_model.bulk_update_publishing_obligations( product_id, upc, data)) if not update_publishing_obligations_response: return update_publishing_obligations_response updated_publishing_obligations = track_model.get_publishing_obligations( product_id) return updated_publishing_obligations def _is_valid_publishing_obligation_answers(data, vendor_is_mech_admin): """Validate publishing obligation answers. Args: data (dict): Set of answers for tuid vendor_is_mech_admin (bool): Specifies vendor mech admin status Returns: bool: True if data is valid """ us_publishing_obligation = data[field.US_PUBLISHING_OBLIGATION] third_party_publisher = data.get(field.THIRD_PARTY_PUBLISHER) publishers = data.get(field.PUBLISHER_NAMES, []) has_publishers = len(publishers) > 0 # Check if US Publishing Obligation can have 3rd-party publishers if us_publishing_obligation is None or \ us_publishing_obligation in field.NO_3RD_PARTY_ENUM: # Third party publisher can't be set to 'Y' and publishers cannot # be specified return third_party_publisher in (None, 'N',) # Make sure valid value for publishing obligation is specified if us_publishing_obligation not in field.OBLIGATION_ENUM: return False # US Publishing Obligation is set to Composition at this point if third_party_publisher == 'Y': # Vendor must have mech admin access to answer this question and must # also specify the publishers return vendor_is_mech_admin and has_publishers if third_party_publisher == 'N': # Filling out publishers is optional return True # Vendors with mech admin access must specify an answer # for third_party_publisher. Otherwise blank answer is allowed return not vendor_is_mech_admin def _validate_track_publishing_obligations(data, vendor_is_mech_admin): """Validate data for publishing obligations update. Args: data (list): data for update publishing obligations for multiple track_id. vendor_is_mech_admin (bool): vendor mech admin status retrieved from ows-contracts. Returns: response.Response: success if valid, error with message if not. """ for track_data in data: if not _is_valid_publishing_obligation_answers( track_data, vendor_is_mech_admin): return response.create_error_response( message='Invalid response for track_id {}'.format( track_data['track_id']), code=error.VALIDATION_ERROR) return response.Response() def _is_mech_admin(account_type, account_id): """Check if an account is mech admin for physical products. Args: account_type (str): The account type. account_id (str): The account id. Returns: Response: Response containing whether or not the account is mech admin. """ vendor_response = get_vendor(account_type, account_id) if not vendor_response: return vendor_response is_migrated_to_abacus = vendor_response.message.get('migrated_to_abacus') vendor_id = vendor_response.message.get('vendor_id') if not is_migrated_to_abacus: return ows_contracts.is_mech_admin(account_type, account_id) return ows_royalties.is_abacus_account_mech_admin(vendor_id)