class Preorderlist(Store, RpcMixin): interface='WSShopMgmt' items = EmbeddedStoreField(target='entries', store_class=PreorderlistItem, is_array=True) notifications = EmbeddedStoreField(target='notifications', store_class=notification_factory, is_array=True) def __len__(self): return len(self.items) @classmethod def get_by_token(cls, token): return cls.signature(method='getPreOrderList', args=[token]) def _alerts(self, alert_type, doc): alerts = [] for alert in self.notifications: if isinstance(alert, alert_type) and alert.isbn == doc.isbn: alerts.append(alert) return alerts def price_alerts(self, doc): return self._alerts(PriceNotification, doc) def state_alerts(self, doc): return self._alerts(StateNotification, doc)
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)
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 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 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 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 Wishlist(Store, RpcMixin): interface='WSShopMgmt' items = EmbeddedStoreField(target='entries', store_class=WishlistItem, is_array=True) def __len__(self): return len(self.items) @classmethod def get_by_token(cls, token): return cls.signature(method='getCommercialWishList', args=[token])
class GiftCardItem(Item, RpcMixin): """Abstraction for `BasketPosition` reaktor object, that stores `Voucher`. Named following the naming convention at txtr. """ giftcard = EmbeddedStoreField(target='item', store_class=Voucher) @classmethod def set_basket_quantity(cls, token, basket_id, voucher_code, quantity): """This method should call the similar to `changeDocumentBasketPosition` reaktor method, handling Voucher. """ raise NotImplemented
class VoucherItem(Store, RpcMixin): """Abstraction for `VoucherApplication` reaktor object, that stores `Voucher`.""" interface = 'WSVoucherMgmt' class Result(Store): """Helper class to abstract `BasketModificationResult` reaktor object.""" code = Field(target='resultCode') basket = EmbeddedStoreField(target='basket', store_class='Basket') voucher = EmbeddedStoreField(target='voucher', store_class=Voucher) _discount = EmbeddedStoreField(target='discountAmount', store_class=Price) @property def discount(self): return Money(amount=self._discount.amount, currency=self._discount.currency) @classmethod def apply(cls, token, code, basket_id): """Reaktor `WSVoucherMgmt.addVoucherToBasket` call. Returns `Result` object. """ return cls.signature(method='addVoucherToBasket', args=[token, code, basket_id], data_converter=cls.Result) @classmethod def remove(cls, token, code, basket_id): """Reaktor `WSVoucherMgmt.removeVoucherFromBasket` call. Returns `Result` object. """ return cls.signature(method='removeVoucherFromBasket', args=[token, code, basket_id], data_converter=cls.Result) @classmethod def assign(cls, token, code): return cls.signature(method='assignVoucherToUserAccount', args=[token, code], data_converter=cls.Result)
class Item(Store): """Base item class, to be extended for specific purposes.""" _total = EmbeddedStoreField(target='positionTotal', store_class=Price) _net_total = EmbeddedStoreField(target='positionNetTotal', store_class=Price) _tax_total = EmbeddedStoreField(target='positionTaxTotal', store_class=Price) _undiscounted_total = EmbeddedStoreField( target='undiscountedPositionTotal', store_class=Price) @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) @classmethod def add_to_basket(cls, token, basket_id, item_id): """A convenience shortcut to provide nicer API.""" return cls.set_basket_quantity(token, basket_id, item_id, 1) @classmethod def remove_from_basket(cls, token, basket_id, item_id): """A convenience shortcut to provide nicer API.""" return cls.set_basket_quantity(token, basket_id, item_id, 0)
class ShoppingListItem(Store, RpcMixin): interface = 'WSShopMgmt' document = EmbeddedStoreField(target='document', store_class=Document) creation_date = DateField(target='creationDate') notifications = [] @classmethod def add_to_list(cls): raise NotImplementedError() @classmethod def remove_from_list(cls): raise NotImplementedError()
class DocumentItem(Item, RpcMixin): """Abstraction for `BasketPosition` reaktor object, that stores `Document`.""" interface = 'WSDocMgmt' document = EmbeddedStoreField(target='item', store_class=Document) @classmethod def set_basket_quantity(cls, token, basket_id, doc_id, quantity): """Reaktor `WSDocMgmt.changeDocumentBasketPosition` call. If `quantity` is 0, then removes the document from the basket. If `quantity` is not 0, then adds the document into the basket. Returns None. """ return cls.signature(method='changeDocumentBasketPosition', args=[token, basket_id, doc_id, quantity])
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 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 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 Result(Store): """Helper class to abstract `BasketModificationResult` reaktor object.""" code = Field(target='resultCode') basket = EmbeddedStoreField(target='basket', store_class='Basket')
class CheckoutResult(Store): basket = EmbeddedStoreField(target='basket', store_class=Basket) code = Field(target='resultCode') receipt_id = Field(target='receiptIdentifier') transaction_id = Field(target='transactionID')