class CategoryStat(Store): """Category searching facet is inconsistent with other facets. This model is there as an attempt to normalize that. """ count = Field(target="count") value = Field(target="id") label = Field(target="name")
class CheckoutProperties(Store): clear_failed_preauth = BooleanField(target='clearFailedAuthorization') clear_preauth = BooleanField(target='clearPreAuthorization') use_preauth = BooleanField(target='usePreAuthorization') recurring_payment = BooleanField(target='requestedRecurringPayment') affiliate_id = Field(target='affiliateID') external_transaction_id = Field(target='externalTransactionID')
class User(Store, RpcMixin): interface = "WSUserMgmt" class Address(Store): country = Field(target='com.bookpac.user.settings.shop.country') firstname = Field(target='com.bookpac.user.settings.shop.firstname') is_valid = Field(target='com.bookpac.user.settings.shop.address.valid') lastname = Field(target='com.bookpac.user.settings.shop.lastname') location = Field(target='com.bookpac.user.settings.shop.location') state = Field(target='com.bookpac.user.settings.shop.state') street = Field(target='com.bookpac.user.settings.shop.address1') zipcode = Field(target='com.bookpac.user.settings.shop.zipcode') id = Field(target='userID') address = EmbeddedStoreField(target='settings', store_class=Address) company = Field(target='company') disabled = BooleanField(target='disabled') email = Field(target='EMail') image_url = Field(target='userImageUrl') level = Field(target='userLevel') locale = Field(target='settings:com.bookpac.user.settings.locale') name = Field(target='userName') nature = Field(target='userNature') private_name = Field(target='userPrivateName') roles = Field(target='roles') verified = BooleanField(target='emailVerified') def has_role(self, role): return role in self.roles @classmethod def get_by_token(cls, token): return cls.signature(method='getUser', args=[token])
class Voucher(Store, RpcMixin): interface = 'WSVoucherMgmt' code = Field(target='code') text = Field(target='text') percentage = IntField(target='percentage') valid_from = DateField(target='validFrom') expiration_date = DateField(target='expirationDate') _initial_amount = EmbeddedStoreField(target='initialAmount', store_class=Price) _amount = EmbeddedStoreField(target='amount', store_class=Price) _java_cls = Field(target='javaClass') @property def initial_amount(self): return Money(amount=self._initial_amount.amount, currency=self._initial_amount.currency) @property def amount(self): return Money(amount=self._amount.amount, currency=self._amount.currency) @property def is_amount(self): return "WSTAmountVoucher" in self._java_cls @property def is_percent(self): return "WSTPercentVoucher" in self._java_cls @classmethod def get_by_token(cls, token): return cls.signature(method='getAssignedVouchers', args=[token])
class Stat(Store): """The reaktor always passes in `name` as the value to use for the search facet. Since it's a value, let's rename it. Some fields also provide a label, which we keep untouched. """ count = Field(target="count") value = Field(target="name") label = Field(target="label")
class DocumentResult(Store): """Search result object wrapping search itemsalongside search info like pagination information. """ class DocumentItem(Store): """Search result item wrapping a document alongside search info like item relevance. """ document = EmbeddedStoreField(target="searchResult", store_class=Document) relevance = FloatField(target="relevance") class Stats(Store): """Represents stats about a search result, e.g. how many books for this language, how many books available as pdf, ... """ category = EmbeddedStoreField(target="category", store_class=CategoryStat, is_array=True) collection_title = EmbeddedStoreField(target="collectionTitle", store_class=Stat, is_array=True) drm = EmbeddedStoreField(target="drmType", store_class=Stat, is_array=True) format = EmbeddedStoreField(target="format", store_class=Stat, is_array=True) language = EmbeddedStoreField(target="language", store_class=Stat, is_array=True) price = EmbeddedStoreField(target="price", store_class=Stat, is_array=True) pub_date = EmbeddedStoreField(target="publication_date", store_class=Stat, is_array=True) rating = EmbeddedStoreField(target="rating", store_class=Stat, is_array=True) source = EmbeddedStoreField(target="source", store_class=Stat, is_array=True) tag = EmbeddedStoreField(target="tag", store_class=Stat, is_array=True) # Without blocking search, other fields don't make sense anymore so there # they are just ignored. count = Field(target="numberOfResults") has_less = Field(target="hasLess") has_more = Field(target="hasMore") items = EmbeddedStoreField(target='results', store_class=DocumentItem, is_array=True) offset = Field(target="offset") stats = EmbeddedStoreField(target='relatedObjects', store_class=Stats) total_count = Field(target="totalNumberOfResults")
class Address(Store): country = Field(target='com.bookpac.user.settings.shop.country') firstname = Field(target='com.bookpac.user.settings.shop.firstname') is_valid = Field(target='com.bookpac.user.settings.shop.address.valid') lastname = Field(target='com.bookpac.user.settings.shop.lastname') location = Field(target='com.bookpac.user.settings.shop.location') state = Field(target='com.bookpac.user.settings.shop.state') street = Field(target='com.bookpac.user.settings.shop.address1') zipcode = Field(target='com.bookpac.user.settings.shop.zipcode')
class PriceNotification(Notification): new_amount = FloatField(target='newPrice:amount') new_currency = Field(target='newPrice:currency') old_amount = FloatField(target='oldPrice:amount') old_currency = Field(target='oldPrice:currency') @property def previous_price(self): return Money(amount=self.old_amount, currency=self.old_currency) @property def current_price(self): return Money(amount=self.new_amount, currency=self.new_currency) @property def price_down(self): return self.type == 'DOCUMENT_LESS_EXPENSIVE' @property def price_up(self): return not self.price_down
class Category(Store, RpcMixin): interface = 'WSContentCategoryMgmt' id = Field(target='ID') name = Field(target='name') children_ids = Field(target='childrenIDs', default=[]) count = IntField(target='count') document_ids = Field(target='documentIDs') filter = Field(target='filter') offset = IntField(target='offset') parent_id = Field(target='parentID', default=False) total_count = IntField(target='subtreeSize') @classmethod def get_by_id(cls, token, cat_id, with_children=True, offset='0', number_of_results=-1, sort=None, direction='asc'): invert = direction == 'desc' return cls.signature(method='getContentCategory', args=[ token, cat_id, with_children, sort, invert, offset, number_of_results ]) @classmethod def get_roots_by_token(cls, token, depth=0, min_number_of_documents=0): return cls.signature(method='getCatalogContentCategoryRootsForUser', args=[token, depth, min_number_of_documents]) @classmethod def get_documents(cls, token, cat_id, include_sub_cats=False, offset=0, number_of_results=-1, sort=None, direction='asc'): invert = direction == 'desc' return cls.signature(interface='WSDocMgmt', method='getDocumentsInContentCategory', args=[ token, cat_id, include_sub_cats, sort, invert, offset, number_of_results ], data_converter=document_converter)
class Basket(Store, RpcMixin): interface = 'WSShopMgmt' # txtr to adyen mapping of payment methods; # see Enum com.bookpac.server.shop.payment.PaymentMethod and adyen's Integration Manual pp 12+13 for the names PAYMENT_METHODS = {"CREDITCARD": ["visa", "mc"], "ELV": ["elv"]} # not sure if this is used # NOTE (Iurii Kudriavtsev): this is not a complete fields definition # class PaymentProperty(Store): # merchant_account = Field(target='merchantAccount') # merchant_ref = Field(target='merchantReference') class PaymentForm(Store): form = Field(target='com.bookpac.server.shop.payment.paymentform') recurring = Field( target='com.bookpac.server.shop.payment.paymentform.recurring') worecurring = Field( target='com.bookpac.server.shop.payment.paymentform.worecurring') id = Field(target='ID') checked_out = BooleanField(target='checkedOut') creation_date = DateField(target='creationTime') modification_date = DateField(target='modificationTime') country = Field(target='country') _total = EmbeddedStoreField(target='total', store_class=Price) _net_total = EmbeddedStoreField(target='netTotal', store_class=Price) _tax_total = EmbeddedStoreField(target='taxTotal', store_class=Price) _undiscounted_total = EmbeddedStoreField(target='undiscountedTotal', store_class=Price) # payment_props = EmbeddedStoreField(target='paymentProperties', store_class=PaymentProperty) payment_forms = EmbeddedStoreField(target='paymentForms', store_class=PaymentForm) items = EmbeddedStoreField(target='positions', store_class=item_factory, is_array=True) authorized_payment_methods = Field(target='authorizedPaymentMethods') vouchers = EmbeddedStoreField(target='voucherApplications', store_class=VoucherItem, is_array=True) @property def total(self): return Money(amount=self._total.amount, currency=self._total.currency) @property def net_total(self): return Money(amount=self._net_total.amount, currency=self._net_total.currency) @property def tax_total(self): return Money(amount=self._tax_total.amount, currency=self._tax_total.currency) @property def undiscounted_total(self): return Money(amount=self._undiscounted_total.amount, currency=self._undiscounted_total.currency) @property def document_items(self): """A property that allows to iterate over the document items. Returns generator. """ for item in self.items: if isinstance(item, DocumentItem): yield item @property def giftcard_items(self): """A property that allows to iterate over the gift card items. Returns generator. """ for item in self.items: if isinstance(item, GiftCardItem): yield item @property def documents(self): """A property that allows to iterate over the documents. Returns generator. """ for item in self.document_items: yield item.document @property def giftcards(self): """A property that allows to iterate over the gift cards. Returns generator. """ for item in self.giftcard_items: yield item.giftcard @property def is_regular(self): """Return `True` if at least one document in the basket is regular (i.e. not a preorder).""" return any(map(lambda d: not d.is_preorder, self.documents)) @property def is_preorder(self): """Return `True` if at least one document in the basket is a preorder.""" return any(map(lambda d: d.is_preorder, self.documents)) def is_authorized_for(self, payment_method): """Check whether the basket is authorized for the given payment_method. """ if payment_method in self.PAYMENT_METHODS.get( "CREDITCARD") and hasattr(self, 'authorized_payment_methods'): return "CREDITCARD" in self.authorized_payment_methods elif payment_method in self.PAYMENT_METHODS.get("ELV") and hasattr( self, 'authorized_payment_methods'): return "ELV" in self.authorized_payment_methods else: return False @classmethod def get_by_id(cls, token, basket_id): return cls.signature(method='getBasket', args=[token, basket_id]) @classmethod def get_by_token(cls, token): return cls.get_by_id(token, None) @classmethod def get_default(cls, token, marker): return cls.signature(method='getDefaultBasket', args=[token, marker]) @classmethod def get_free(cls, token, marker=None): return cls.signature(method='getFreeBasket', args=[token, marker]) @classmethod def get_preview(cls, token, marker=None): return cls.signature(method='getNewPreviewBasket', args=[token, marker]) @classmethod def get_validation(cls, token, marker): return cls.signature(method='getValidationBasket', args=[token, marker]) @classmethod def checkout(cls, token, basket_id, checkout_props): return cls.signature(method='checkoutBasketAsynchronously', data_converter=CheckoutResult, args=[token, basket_id, checkout_props]) @classmethod def create(cls, token, marker=None): return cls.signature(method='getNewBasket', args=[token, marker]) @classmethod def clear(cls, token, basket_id): return cls.signature(method='removeAllBasketPositions', args=[token, basket_id])
class StateNotification(Notification): old_state = Field(target='oldState') new_state = Field(target='newState')
class Auth(Store, RpcMixin): interface = "WSAuth" date = DateField(target="timestamp") result_code = Field(target="resultCode") service_name = Field(target="authenticationServiceName") token = Field(target="token") user = EmbeddedStoreField(target="user", store_class=User) @property def is_local(self): return self.service_name == 'LOCAL' @classmethod def authenticate_with_credentials(cls, name, hashed_pwd, nature, sticky=False): """Regular auth.""" return cls.signature(method='authenticate', args=[name, hashed_pwd, nature, sticky]) @classmethod def authenticate_with_external_credentials(cls, token, service_name, params, sticky=False): """Auth using external credentials (e.g. Facebook).""" return cls.signature(method='authenticateWithExternalCredentials', args=[token, service_name, params, sticky]) @classmethod def authenticate_as_anonymous(cls, nature): return cls.signature(method='authenticateAnonymousUser', args=[nature]) @classmethod def authenticate_anonymous(cls, token, name, hashed_pwd, sticky=False): """Regular auth, for an anonymous user who wants to authenticate himself while retaining his anonymous data (e.g. his basket). """ return cls.signature(method='authenticate', args=[token, name, hashed_pwd, sticky]) @classmethod def deauthenticate(cls, token): return cls.signature(method='deAuthenticate', args=[token]) @classmethod def create_user_from_anonymous(cls, token, name, email, captcha_id, captcha_value, hashed_pwd1, hashed_pwd2): return cls.signature(method='promoteAnonymousUser', args=[token, name, email, captcha_id, captcha_value, hashed_pwd1, hashed_pwd2]) @classmethod def create_user(cls, name, email, hashed_pwd, settings, nature): return cls.signature(method='createUserWithSettings', args=[name, email, hashed_pwd, settings, nature]) @classmethod def request_user_creation(cls, name, email, hashed_pwd, settings, nature): return cls.signature(method='requestUserCreationWithSettings', args=[name, email, hashed_pwd, settings, nature]) @classmethod def request_password_reset(cls, name, nature): return cls.signature(method='requestPasswordResetForUser', args=[name, nature]) @classmethod def reset_password(cls, token, action, secret, hashed_pwd): return cls.signature(interface='WSActionRequestMgmt', method='execute', args=[token, action, secret, {'pw': hashed_pwd}])
class PaymentForm(Store): form = Field(target='com.bookpac.server.shop.payment.paymentform') recurring = Field( target='com.bookpac.server.shop.payment.paymentform.recurring') worecurring = Field( target='com.bookpac.server.shop.payment.paymentform.worecurring')
class Author(Store): first_name = Field(target='firstName') last_name = Field(target='lastName')
class List(Store, RpcMixin): interface = 'WSListMgmt' tracked_fields = ["document_ids"] id = Field(target='ID') count = IntField(target='count') creation_date = DateField(target='creationTime', default='') description = Field(target='description') document_ids = Field(target='documentIDs', default=[]) # global_id = Field(target='globalID') name = Field(target='name') offset = IntField(target='offset') owner = Field(target='owner') total_count = IntField(target='size') @classmethod def delete_by_id(cls, token, list_id, delete_documents=False): return cls.signature(method='deleteList', args=[token, list_id, delete_documents]) @classmethod def _get_by_id(cls, token, list_id, offset, number_of_results): return cls.signature(method='getList', args=[token, list_id, offset, number_of_results]) @classmethod def _get_constrained_by_id(cls, token, list_id, search_string, offset, number_of_results): if not ':' in search_string: search_string = '*%s*' % search_string return cls.signature( method='getListConstrained', args=[token, list_id, search_string, offset, number_of_results]) @classmethod def _change_sorting(cls, token, list_id, sort, direction): # That would be nice, but unfortunately, it's not the case. # sort = cls.fields[sort].target invert = direction == 'desc' return cls.signature(method='changeListSorting', args=[token, list_id, sort, invert]) @classmethod def get_by_ids(cls, token, list_ids): return cls.signature(method='getLists', args=[token, list_ids]) @classmethod def filter(cls, token, list_id, search_string=None, offset=0, number_of_results=-1, sort='creationDate', direction='desc'): cls._change_sorting(token, list_id, sort, direction) if search_string: return cls._get_constrained_by_id(token, list_id, search_string, offset, number_of_results) else: return cls._get_by_id(token, list_id, offset, number_of_results) @classmethod def get_by_doc_ids(cls, token, document_ids): return cls.signature(method='getListsWithDocumentList', args=[token, document_ids], data_converter=lambda d: d) @classmethod def _get_by_type(cls, token, type, offset, number_of_results): return cls.signature(method='getSpecialList', args=[token, type, offset, number_of_results]) @classmethod def get_inbox(cls, token, offset=0, number_of_results=-1): return cls._get_by_type(token, 'INBOX', offset, number_of_results) @classmethod def get_trash(cls, token, offset=0, number_of_results=-1): return cls._get_by_type(token, 'TRASH', offset, number_of_results) @classmethod def get_user_list_ids(cls, token): return cls.signature(method='getListList', args=[token], data_converter=lambda d: d) @classmethod def create(cls, token, name, description=''): def converter(data): return cls({'ID': data, 'name': name, 'description': description}) return cls.signature(method='createList', args=[token, name, description], data_converter=converter) @classmethod def add_documents(cls, token, list_id, document_ids, index=0): return cls.signature(method='addDocumentsToList', args=[token, list_id, document_ids, index]) @classmethod def remove_documents(cls, token, list_id, document_ids): return cls.signature(method='removeDocumentsFromList', args=[token, list_id, document_ids]) @classmethod def empty(cls, token, list_id): # `keepDocumentsInOtherLists` is always True, since reaktor does not support False (cfr. api doc). # Note that since moving a document to trash removes other labels, the expected result # is still reached. return cls.signature(interface='WSDocMgmt', method='removeDocumentsInList', args=[token, list_id, True]) @property def is_inbox(self): return self.name.startswith('INBOX-') @property def is_trash(self): return self.name.startswith('TRASH-') def __len__(self): return self.count def __nonzero__(self): return self.total_count
class Preview(Store): format = Field(target='format')
class Document(Store, RpcMixin): interface = 'WSDocMgmt' class Author(Store): first_name = Field(target='firstName') last_name = Field(target='lastName') class License(Store): key = Field(target='key') user_roles = Field(target='currentUserRoles') class Preview(Store): format = Field(target='format') _categories = EmbeddedStoreField( target='contentCategories', store_class='barrel_reaktor.category.models.Category', is_array=True) _isbn = Field(target='attributes:isbn') audience = Field(target='attributes:audience') author = Field(target='attributes:author', default=u'') author_bio = Field(target='attributes:author_biography', default='') authors = EmbeddedStoreField(target='authors', store_class=Author, is_array=True) catalog_id = Field(target='attributes:catalog_document_id') catalog_state = Field(target='catalogDocumentState') category_ids = Field(target='contentCategoryIDs') content_provider_id = Field( target='attributes:content_provider_specific_id') content_provider_name = Field(target='attributes:content_provider_name') content_source_id = Field(target='attributes:content_source_id') cover_ratio = FloatField(target='attributes:cover_image_aspect_ratio') cover_type = Field(target='attributes:cover_image_type') creation_date = DateField(target='creationTime') cumulative_votes = IntField(target='cumulativeVotes:stars', default=0) currency = Field(target='attributes:currency') delivery_url = Field(target='deliveryUrl') description = Field(target='attributes:description') drm_type = Field(target='drm') editors_comment = Field(target='attributes:editors_comment') extract = Field(target='attributes:extract') file_name = Field(target='fileName', default='') first_publication = Field(target='attributes:date_of_first_publication') format = Field(target='format', default='') fulfillment_id = Field(target='attributes:fulfillment_id') fulfillment_type = Field(target='attributes:fulfillment_type') has_binary = BooleanField(target='hasBinary') has_thumbnail = BooleanField(target='hasThumbnail') hash = Field(target='attributes:binary_hash') id = Field(target='documentID') imprint = Field(target='attributes:imprint', default='') lang_code = Field(target='languageCode') language = Field(target='attributes:language') large_cover_url = Field(target='attributes:cover_image_url_large') licenses = EmbeddedStoreField(target='licenses', store_class=License, is_array=True) medium_cover_url = Field(target='attributes:cover_image_url_medium') modification_date = DateField(target='modificationTime') name = Field(target='displayName') normal_cover_url = Field(target='attributes:cover_image_url_normal') owner = Field(target='owner') pages = IntField(target='attributes:number_of_pages') personal_votes = IntField(target='personalVotes:stars', default=0) previews = EmbeddedStoreField(target='documentPreviews', store_class=Preview, is_array=True) _price = FloatField(target='attributes:price') publication_date = DateField(target='attributes:publication_date') publication_status = DateField(target='attributes:publication_status') publisher = Field(target='attributes:publisher') size = IntField(target='attributes:size') subtitle = Field(target='attributes:subtitle') tax_group = Field(target='attributes:tax_group') title = Field(target='attributes:title', default=u'') title = Field(target='attributes:title', default=u'') type = Field(target='type') _undiscounted_price = FloatField(target='attributes:undiscounted_price') user_state = Field(target='userDocumentState', default='?') user_tags = Field(target='userTags', default=[]) version = IntField(target='version') version_access_type = Field(target='versionAccessType') version_size = IntField(target='versionSize') votes = IntField(target='numberOfVotes') year = IntField(target='attributes:year') @property def isbn(self): return self._isbn.strip() @property def price(self): return Money(amount=self._price, currency=self.currency) @property def undiscounted_price(self): return Money(amount=self._undiscounted_price, currency=self.currency) @property def is_preorder(self): return not self.is_user and self.catalog_state == 'PRE_RELEASE' @property def is_fulfilled(self): return self.user_state == 'FULFILLED' or self.is_upload @property def is_upload(self): return self.user_state == 'UPLOADED_BY_USER' @property def is_catalog(self): return self.type == 'CATALOG' @property def is_user(self): return self.type == 'USER' @property def is_adobe(self): return self.drm_type == 'ADOBE_DRM' @property def is_watermark(self): return self.drm_type == 'WATERMARK' @property def has_drm(self): # drm_type can be (ADOBE_DRM, NONE, UNDEFINED, WATERMARK) return self.is_adobe or self.is_watermark @property def has_custom_cover(self): return self.cover_type == 'USER_UPLOADED' @property def categories(self): """Builds a list of categories in tree order, from oldest ancestor to the leaf. It relies on both `category_ids` and `_categories` attributes. Note that according to reaktor, when viewing a document from a catalog that is not associated to the token nature, the information is not available. """ if hasattr(self, '_category_trail'): return self._category_trail trail = [] if self.category_ids: cats = dict([(c.id, c) for c in self._categories]) current = cats.pop(self.category_ids[0], None) trail = [current] while current.parent_id: current = cats.pop(current.parent_id) trail.insert(0, current) self._category_trail = trail return trail @classmethod def get_by_id(cls, token, doc_id): """Returns `Document` instance for the given id.""" document = cls.signature(method='getDocument', args=[token, doc_id]) # reaktor call may return `None` # raise a proper exception in this case if not document: raise ReaktorArgumentError return document @classmethod def get_by_ids(cls, token, doc_ids): """Returns `Document` instance for the given ids.""" return cls.signature(method='getDocuments', args=[token, doc_ids]) @classmethod def get_user_doc_id(cls, token, doc_id): """Returns user document id for the catalog document id if any.""" return cls.signature(method='getUserDocumentID', data_converter=lambda d: d, args=[token, doc_id]) @classmethod def get_doc_path(cls, token, doc_id, is_user=False): """Returns the path to unzipped epub user document or catalog preview. """ method = 'unzipEpubUserDocument' if is_user else 'unzipEpubPreview' return cls.signature(method=method, data_converter=lambda d: d, args=[token, doc_id]) @classmethod def get_by_isbn(cls, token, isbn): """Returns a document by isbn, using search API endpoint since fetching doc by isbn requires extra rights. """ def converter(data): if 'results' in data: return Document(data['results'][0]['searchResult']) # reaktor call may return `None` # raise a proper exception in this case else: raise ReaktorArgumentError args = [ token, 'isbn:%s' % isbn, None, 0, 1, None, False, None, None, { 'resultType': 'Object' } ] return cls.signature(interface="WSSearchDocument", method='searchDocuments', data_converter=converter, args=args) @classmethod def get_related_by_id(cls, token, doc_id, offset=0, number_of_results=5): """Returns a list of `Documents` that are related to the given id.""" return cls.signature(method='getDocumentsRelatedToDocument', args=[token, doc_id, offset, number_of_results]) @classmethod def change_attributes(cls, token, doc_ids, attributes): return cls.signature(method='changeDocumentAttributes', args=[token, doc_ids, attributes]) @classmethod def remove_cover(cls, token, doc_id): return cls.signature(method='removeCoverImage', args=[token, doc_id])
class License(Store): key = Field(target='key') user_roles = Field(target='currentUserRoles')
class Notification(Store): id = Field(target='ID') isbn = LongIntField(target='isbn') type = Field(target='type') display_name = Field(target='displayName') creation_date = DateField(target='creationTime')
class CheckoutResult(Store): basket = EmbeddedStoreField(target='basket', store_class=Basket) code = Field(target='resultCode') receipt_id = Field(target='receiptIdentifier') transaction_id = Field(target='transactionID')
class Result(Store): """Helper class to abstract `BasketModificationResult` reaktor object.""" code = Field(target='resultCode') basket = EmbeddedStoreField(target='basket', store_class='Basket')
class Price(Store): """Helper class to use with the new reaktor price fields.""" amount = FloatField(target='amount') currency = Field(target='currency')