class SideBarCrossSellingBox(Folder): class_id = 'vertical-item-sidebar-cross-selling-box' class_version = '20090122' class_title = MSG(u'Vertical item cross selling') class_views = ['edit', 'configure'] order_path = 'order-products' order_class = CrossSellingTable __fixed_handlers__ = [order_path] edit_schema = {} edit_widgets = [] item_widgets = [] edit = AutomaticEditView() view = SideBarCrossSellingBox_View() configure = GoToSpecificDocument(title=MSG(u'Configurer'), specific_document=order_path) @staticmethod def _make_resource(cls, folder, name, **kw): Folder._make_resource(cls, folder, name, **kw) order_class = cls.order_class order_class._make_resource(order_class, folder, '%s/%s' % (name, cls.order_path)) @classmethod def get_metadata_schema(cls): return merge_dicts(Folder.get_metadata_schema(), CrossSellingTable.get_metadata_schema())
class CrossSellingBox(Folder): class_id = 'vertical-item-cross-selling-box' class_version = '20090122' class_title = MSG(u'Vertical item cross selling') class_description = MSG(u'Boîte de vente liée') class_views = ['edit'] order_path = 'order-products' order_class = CrossSellingTable __fixed_handlers__ = [order_path] edit = GoToSpecificDocument(specific_document=order_path, specific_view='edit?is_admin_popup=1') view = CrossSellingBox_View() @staticmethod def _make_resource(cls, folder, name, **kw): Folder._make_resource(cls, folder, name, **kw) order_class = cls.order_class order_class._make_resource(order_class, folder, '%s/%s' % (name, cls.order_path)) @classmethod def get_metadata_schema(cls): return merge_dicts(Folder.get_metadata_schema(), show_title=Boolean)
class Customers(Folder): class_id = 'customers' class_views = ['view', 'last_connections'] view = GoToSpecificDocument(title=MSG(u'Customers'), access='is_allowed_to_add', specific_document='/users/') last_connections = GoToSpecificDocument( title=MSG(u'Last connections'), access='is_allowed_to_add', specific_document='./authentification_logs') @staticmethod def _make_resource(cls, folder, name, *args, **kw): Folder._make_resource(cls, folder, name, *args, **kw) AuthentificationLogs._make_resource(AuthentificationLogs, folder, '%s/authentification_logs' % name, *args, **kw)
class CreditAvailable_Table(Table): class_id = 'credit-available-table' class_handler = CreditAvailable_Basetable class_views = ['view', 'back'] back = GoToSpecificDocument(specific_document='..', title=MSG(u'Back')) form = [SelectWidget('user', title=MSG(u'User id')), TextWidget('amount', title=MSG(u'Credit amount')), TextWidget('description', title=MSG(u'Description'))]
class ShopUser_Group(ShopFolder): class_id = 'user-group' class_views = ['edit', 'goto_categories', 'register', 'schema', 'welcome'] class_version = '20100719' class_title = MSG(u'User group') edit = AutomaticEditView() schema = GoToSpecificDocument(specific_document='schema', title=MSG(u'Schema')) welcome = GoToSpecificDocument(specific_document='welcome', title=MSG(u'Edit welcome page')) goto_categories = GoToSpecificDocument( specific_document='../../../categories', title=MSG(u'Revenir aux catégories')) register = Group_Register() edit_schema = {'register_title': Unicode(multilingual=True), 'register_body': XHTMLBody(multilingual=True), 'register_mail_subject': Unicode(multilingual=True), 'register_mail_body': Unicode(multilingual=True), 'validation_mail_subject': Unicode(multilingual=True), 'validation_mail_body': Unicode(multilingual=True), 'invalidation_mail_subject': Unicode(multilingual=True), 'invalidation_mail_body': Unicode(multilingual=True), 'hide_address_on_registration': Boolean, 'user_is_enabled_when_register': Boolean, 'use_default_price': Boolean, 'show_ht_price': Boolean, 'phone_is_mandatory_on_registration': Boolean, 'lastname_is_mandatory_on_registration': Boolean} edit_widgets = [TextWidget('register_title', title=MSG(u'Register view title ?')), RTEWidget('register_body', title=MSG(u'Register body')), TextWidget('register_mail_subject', title=MSG(u'Register mail subject')), MultilineWidget('register_mail_body', title=MSG(u'Register mail body')), TextWidget('validation_mail_subject', title=MSG(u'Validation mail subject')), MultilineWidget('validation_mail_body', title=MSG(u'Validation mail body')), TextWidget('invalidation_mail_subject', title=MSG(u'Invalidation mail subject')), MultilineWidget('invalidation_mail_body', title=MSG(u'Invalidation mail body')), BooleanRadio('hide_address_on_registration', title=MSG(u'Hide address on registration')), BooleanRadio('user_is_enabled_when_register', title=MSG(u'User is enabled ?')), BooleanRadio('use_default_price', title=MSG(u'Use default price ?')), BooleanRadio('show_ht_price', title=MSG(u'Show HT price ?')), BooleanRadio('lastname_is_mandatory_on_registration', title=MSG(u'Lastname is mandatory on registration ?')), BooleanRadio('phone_is_mandatory_on_registration', title=MSG(u'Phone is mandatory on registration ?'))] __fixed_handlers__ = ShopFolder.__fixed_handlers__ + ['welcome'] @staticmethod def _make_resource(cls, folder, name, *args, **kw): # Create group ShopFolder._make_resource(cls, folder, name, *args, **kw) # Group schema cls = CustomerSchema cls._make_resource(cls, folder, '%s/schema' % name) # Welcome Page cls = WebPage cls._make_resource(cls, folder, '%s/welcome' % name, title={'en': u'Welcome'}, state='public') @classmethod def get_metadata_schema(cls): return merge_dicts(ShopFolder.get_metadata_schema(), cls.edit_schema) def get_dynamic_schema(self): schema = self.get_resource('schema') return schema.get_model_schema() def get_dynamic_widgets(self): schema = self.get_resource('schema') return schema.get_model_widgets() def get_register_mail_subject(self): subject = self.get_property('register_mail_subject') if subject: return subject return MSG(u"Inscription confirmation.").gettext() def get_register_mail_body(self): body = self.get_property('register_mail_body') if body: return body return MSG(u"Your inscription has been validated").gettext() def get_prefix(self): # Price prefix # XXX for future we should add group property to price if self.name == 'default': return '' return '%s-' % self.name
class ShippingWay(ShopFolder): class_id = 'shipping' class_title = MSG(u'Shipping') class_description = MSG(u'Allow to define your own shipping way') class_views = ['view', 'configure', 'history', 'prices'] class_version = '20090918' img = '../ui/shop/images/shipping.png' shipping_history_cls = ShippingWayTable @staticmethod def _make_resource(cls, folder, name, *args, **kw): ShopFolder._make_resource(cls, folder, name, *args, **kw) # Image body = lfs.open(get_abspath(cls.img)).read() img = Image._make_resource(Image, folder, '%s/logo.png' % name, body=body, **{'state': 'public'}) # Load zones shop = get_context().resource.parent zones = [] handler = shop.get_resource('countries-zones').handler for record in handler.get_records(): zones.append(handler.get_record_value(record, 'title')) # Create history cls.shipping_history_cls._make_resource(cls.shipping_history_cls, folder, '%s/history' % name) # Import CSV with prices ShippingPrices._make_resource(ShippingPrices, folder, '%s/prices' % name) if getattr(cls, 'csv', None): table = ShippingPricesTable() csv = ro_database.get_handler(get_abspath(cls.csv), ShippingPricesCSV) for row in csv.get_rows(): table.add_record( {'zone': str(zones.index(row.get_value('zone'))), 'max-weight': row.get_value('max-weight'), 'price': row.get_value('price')}) folder.set_handler('%s/prices' % name, table) @classmethod def get_metadata_schema(cls): return merge_dicts(ShopFolder.get_metadata_schema(), delivery_schema) def get_price(self, country, list_weight): list_weight = deepcopy(list_weight) shop = get_shop(self) # Is Free ? if self.get_property('is_free'): return decimal(0) # Transform country to zone countries = shop.get_resource('countries').handler country_record = countries.get_record(int(country)) if countries.get_record_value(country_record, 'enabled') is False: return None zone = countries.get_record_value(country_record, 'zone') # XXX to refactor # Max value mode = self.get_property('mode') if mode == 'weight': list_weight.sort(reverse=True) list_values = list_weight elif mode == 'quantity': list_values = [1] * len(list_weight) # XXX limit by models only_this_models = self.get_property('only_this_models') if only_this_models: cart = ProductCart(get_context()) for product_cart in cart.products: product = shop.get_resource(product_cart['name'], soft=True) if product.get_property('product_model') not in only_this_models: return None # Get corresponding weight/quantity in table of price list_price_ok = {} prices = self.get_resource('prices').handler min_value = 0 for record in prices.search(PhraseQuery('zone', zone), sort_by='max-%s' % mode): max_value = prices.get_record_value(record, 'max-%s' % mode) price = prices.get_record_value(record, 'price') list_price_ok[max_value] = {'min': min_value, 'max': max_value, 'price': price} min_value = max_value # No price ? if len(list_price_ok.keys()) == 0: return None # Check all weigh if < max_weight if mode == 'weight': max_value = max(list_price_ok.keys()) for key in list_weight: if key > max_value: return None # On crée une partition de poids current_value = decimal(0) partition = [] list_values.sort(reverse=True) while list_values: if current_value + list_values[-1] <= max_value: current_value += list_values.pop() else: partition.append(current_value) current_value = decimal(0) if len(list_values) == 0: partition.append(current_value) # Calcul total price total_price = decimal(0) for p in partition: for value in list_price_ok.values(): if p >= value['min'] and p <= value['max']: total_price += value['price'] break # Add insurance if self.get_property('insurance') > decimal(0): context = get_context() cart = ProductCart(context) products_price = cart.get_total_price(shop, with_delivery=False, pretty=False) percent = self.get_property('insurance') / 100 total_price += products_price['with_tax'] * percent return total_price def get_logo(self, context): logo = self.get_property('logo') resource = self.get_resource(logo, soft=True) if resource is None or logo == '.': return return context.get_link(resource) def get_shipping_option(self, context): return None def get_namespace(self, context): language = self.get_content_language(context) return {'name': self.name, 'description': self.get_property('description', language), 'logo': self.get_logo(context), 'title': self.get_title(language)} def get_widget_namespace(self, context, country, list_weight): # Is enabled ? if not self.get_property('enabled'): return None # For good group ? shipping_groups = self.get_property('only_this_groups') if (shipping_groups and context.user.get_property('user_group') not in shipping_groups): return None # Get price of shipping price = self.get_price(country, list_weight) if price is None: return None language = self.get_content_language(context) ns = {'name': self.name, 'img': self.get_logo(context), 'title': self.get_title(language), 'pretty_price': format_price(price), 'price': price} for key in ['description', 'enabled']: ns[key] = self.get_property(key, language) return ns # Views configure = ShippingWay_Configure() history = GoToSpecificDocument(specific_document='history', title=MSG(u'History')) prices = GoToSpecificDocument(specific_document='prices', title=MSG(u'Prices')) # Backoffice order views order_view = ShippingWay_RecordView() order_add_view = ShippingWay_RecordAdd() order_edit_view = ShippingWay_RecordEdit()
class ShopModule_Review(ShopModule): class_id = 'shop_module_review' class_title = MSG(u'Review') class_description = MSG(u"Product Review") class_views = ['list_reviews', 'list_reporting', 'edit', 'cgu'] __fixed_handlers__ = ['cgu', 'images'] item_schema = { 'areview_default_state': States(mandatory=True, default='private'), 'must_be_authenticated_to_post': Boolean, } item_widgets = [ SelectWidget('areview_default_state', has_empty_option=False, title=MSG(u'Review default state')), BooleanRadio('must_be_authenticated_to_post', title=MSG(u'Must be authenticated to post ?')) ] list_reviews = ShopModule_Reviews_List() list_reporting = ShopModule_Reviews_Reporting() add_review = ShopModule_AReview_NewInstance() cgu = GoToSpecificDocument(specific_document='cgu') @staticmethod def _make_resource(cls, folder, name, ctime=None, *args, **kw): ShopModule._make_resource(cls, folder, name, ctime=ctime, *args, **kw) kw = {'title': {'en': 'CGU'}, 'state': 'public'} WebPage._make_resource(WebPage, folder, '%s/cgu' % name, **kw) Folder._make_resource(Folder, folder, '%s/images' % name) # helper def create_new_image(self, context, image): images = self.get_resource('images') query = [ PhraseQuery('parent_path', str(images.get_canonical_path())), PhraseQuery('is_image', True) ] root = context.root results = root.search(AndQuery(*query)) if len(results) == 0: name = '0' else: doc = results.get_documents(sort_by='name', reverse=True)[0] name = str(int(doc.name) + 1) # XXX Temp fix while images.get_resource(name, soft=True) is not None: name = int(name) + 1 name = str(name) # End of temp fix filename, mimetype, body = image _name, type, language = FileName.decode(filename) cls = Image kw = { 'format': mimetype, 'filename': filename, 'extension': type, 'state': 'public' } return self.make_resource(cls, images, name, body, **kw) def render(self, resource, context): if resource.class_id == 'user': return self.render_for_user(resource, context) elif resource.class_id == 'product': return self.render_for_product(resource, context) return u'Invalid review module' def render_for_product(self, resource, context): reviews = resource.get_resource('reviews', soft=True) if reviews is None: return { 'nb_reviews': 0, 'last_review': None, 'note': None, 'link': context.get_link(self), 'here_abspath': str(context.resource.get_abspath()), 'product_abspath': resource.get_abspath(), 'viewboxes': {} } # XXX Should be in catalog for performances abspath = reviews.get_canonical_path() queries = [ PhraseQuery('parent_path', str(abspath)), PhraseQuery('workflow_state', 'public'), PhraseQuery('format', 'shop_module_a_review') ] search = context.root.search(AndQuery(*queries)) brains = list(search.get_documents(sort_by='mtime', reverse=True)) nb_reviews = len(brains) if brains: last_review = brains[0] last_review = reduce_string( brains[0].shop_module_review_description, 200, 200) else: last_review = None note = 0 for brain in brains: note += brain.shop_module_review_note # Get viewboxes viewboxes = [] for brain in brains[:5]: review = context.root.get_resource(brain.abspath) viewbox = Review_Viewbox().GET(review, context) viewboxes.append(viewbox) return { 'nb_reviews': nb_reviews, 'last_review': last_review, 'link': context.get_link(self), 'viewboxes': viewboxes, 'here_abspath': str(context.resource.get_abspath()), 'product_abspath': resource.get_abspath(), 'note': note / nb_reviews if nb_reviews else None } def render_for_user(self, resource, context): # Get review that belong to user query = [ PhraseQuery('shop_module_review_author', resource.name), PhraseQuery('workflow_state', 'public'), PhraseQuery('format', 'shop_module_a_review') ] search = context.root.search(AndQuery(*query)) brains = list(search.get_documents(sort_by='mtime', reverse=True)) nb_reviews = len(brains) # Get viewboxes viewboxes = [] for brain in brains[:5]: review = context.root.get_resource(brain.abspath) viewbox = Review_Viewbox().GET(review, context) viewboxes.append(viewbox) # Return namespace return {'nb_reviews': nb_reviews, 'viewboxes': viewboxes} def get_document_types(self): return []
class CreditPayment(PaymentWay): class_id = 'credit-payment' class_title = MSG(u'Credit payment') class_views = ['view', 'see_voucher', 'configure'] view = CreditPayment_View() configure = PaymentWay_Configure() see_voucher = GoToSpecificDocument(specific_document='users-credit', title=MSG(u'List voucher')) @staticmethod def _make_resource(cls, folder, name, *args, **kw): # Create resource PaymentWay._make_resource(cls, folder, name, *args, **kw) CreditAvailable_Table._make_resource(CreditAvailable_Table, folder, '%s/users-credit' % name) def get_credit_available_for_user(self, user_name): users_credit = self.get_resource('users-credit').handler results = users_credit.search(user=user_name) if len(results) == 0: return decimal('0.0') credit = decimal('0.0') for record in results: credit += users_credit.get_record_value(record, 'amount') return credit def get_credit_namespace_available_for_user(self, user_name): ns = [] users_credit = self.get_resource('users-credit').handler results = users_credit.search(user=user_name) if len(results) == 0: return ns get_value = users_credit.get_record_value for record in results: amount = get_value(record, 'amount') ns.append({'user': get_value(record, 'user'), 'description': get_value(record, 'description'), 'amount': format_price(amount)}) return ns def create_payment(self, context, payment): # Add the payment by credit # XXX We have to check if credit >= amount payments = self.get_resource('payments').handler amount_available = self.get_credit_available_for_user(context.user.name) amount_payed = min(payment['amount'], amount_available) record = payments.add_record( {'ref': payment['ref'], 'amount': amount_payed, 'user': context.user.name, 'state': True, 'resource_validator': payment['resource_validator']}) # The payment is automatically validated self.set_payment_as_ok(record.id, context) return record def is_enabled(self, context): if not self.get_property('enabled'): return False # Only enabled if credit > 0 amount_available = self.get_credit_available_for_user(context.user.name) return amount_available > decimal('0.0') def get_payment_way_description(self, context, total_amount): total_amount = total_amount['with_tax'] if not type(total_amount) is decimal: # XXX We don't need currency total_amount = decimal(total_amount.split(' ')[0]) amount_available = self.get_credit_available_for_user(context.user.name) remaining_amount = amount_available - total_amount if remaining_amount < decimal('0'): remaining_amount = decimal('0') namespace = {'amount_available': format_price(amount_available), 'has_to_complete_payment': amount_available < total_amount, 'amount_to_pay': format_price(total_amount-amount_available), 'remaining_amount': format_price(remaining_amount), 'total_amount': format_price(total_amount)} description_template = self.get_resource( '/ui/backoffice/payments/credit/description.xml') return stl(description_template, namespace=namespace) def _show_payment_form(self, context, payment): amount_available = self.get_credit_available_for_user(context.user.name) remaining_to_pay = payment['amount'] - amount_available # Partial payment if remaining_to_pay > decimal('0'): # Delete credit users_credit = self.get_resource('users-credit') results = users_credit.handler.search(user=context.user.name) if len(results) == 0: raise ValueError, 'Error, credit do not exist' record = results[0] old_amount = users_credit.handler.get_record_value(record, 'amount') new_amount = old_amount - payment['amount'] if new_amount < decimal('0'): users_credit.del_record(record.id) else: kw = {'amount': new_amount} users_credit.update_recod(record.id, **kw) # Encapsulate in pay view payment['mode'] = 'paybox' # XXX (Can have another name ?) payment['amount'] = remaining_to_pay return self.parent.show_payment_form(context, payment) # Complete payment return PaymentWay._show_payment_form(self, context, payment)
class PhotoOrderedTable(ResourcesOrderedTable): class_id = 'gallery-ordered-table' class_title = MSG(u'Photo Ordered Table') class_handler = PhotoOrderedTableFile class_views = ['view', 'back'] orderable_classes = (Image, ) order_root_path = '../images' form = [ImageSelectorWidget('name', title=MSG(u'Name'))] # Views view = PhotoOrderedTable_View() add_image = PhotoAddImage() add_record = PhotoOrderedTable_AddRecord() edit_record = PhotoOrderedTable_EditRecord() back = GoToSpecificDocument(specific_document='..', title=MSG(u'See product')) def get_links(self): # Use the canonical path instead of the abspath base = self.get_canonical_path() handler = self.handler get_value = handler.get_record_value links = [] for record in handler.get_records_in_order(): name = get_value(record, 'name') links.append(str(base.resolve2(name))) return links def update_links(self, source, target): base = self.get_canonical_path() resources_new2old = get_context().database.resources_new2old base = str(base) old_base = resources_new2old.get(base, base) old_base = Path(old_base) new_base = Path(base) handler = self.handler get_value = handler.get_record_value for record in handler.get_records_in_order(): path = get_value(record, 'name') if not path: continue ref = get_reference(path) if ref.scheme: continue # Strip the view path = ref.path name = path.get_name() if name and name[0] == ';': view = '/' + name path = path[:-1] else: view = '' path = str(old_base.resolve2(path)) if path == source: # Hit the old name # Build the new reference with the right path new_ref = deepcopy(ref) new_ref.path = str(new_base.get_pathto(target)) + view handler.update_record(record.id, **{'name': str(new_ref)}) get_context().server.change_resource(self) def update_relative_links(self, source): site_root = self.get_site_root() target = self.get_canonical_path() resources_old2new = get_context().database.resources_old2new handler = self.handler get_value = handler.get_record_value for record in handler.get_records(): path = get_value(record, 'name') if not path: continue ref = get_reference(str(path)) if ref.scheme: continue # Strip the view path = ref.path name = path.get_name() if name and name[0] == ';': view = '/' + name path = path[:-1] else: view = '' path = str(path) # Calcul the old absolute path old_abs_path = source.resolve2(path) # Check if the target path has not been moved new_abs_path = resources_old2new.get(old_abs_path, old_abs_path) # Build the new reference with the right path # Absolute path allow to call get_pathto with the target new_ref = deepcopy(ref) new_ref.path = str(target.get_pathto(new_abs_path)) + view # Update the record handler.update_record(record.id, **{'name': str(new_ref)})
class Product(WorkflowAware, TagsAware, DynamicFolder): class_id = 'product' class_title = MSG(u'Product') class_description = MSG(u'A product') class_version = '20100812' ################## # Configuration ################## viewbox = Product_ViewBox() viewbox_cls = Product_ViewBox cross_selling_viewbox = Product_CrossSellingViewBox() ################## __fixed_handlers__ = DynamicFolder.__fixed_handlers__ + [ 'images', 'order-photos', 'cross-selling' ] ####################### # Views ####################### view = Product_View() login = Shop_Login() register = Shop_Register() tag_view = viewbox edit = Product_Edit() add_link_file = Product_AddLinkFile() change_product_model = Product_ChangeProductModel() declinations = Product_DeclinationsView() new_declination = Declination_NewInstance() order = GoToSpecificDocument(specific_document='order-photos', title=MSG(u'Manage photos'), access='is_allowed_to_edit') print_product = Product_Print() send_to_friend = Product_SendToFriend() edit_cross_selling = GoToSpecificDocument( specific_document='cross-selling', title=MSG(u'Edit cross selling'), access='is_allowed_to_edit') delete_product = Product_Delete() order_preview = viewbox add_image = CurrentFolder_AddImage() @classmethod def get_metadata_schema(cls): return merge_dicts(DynamicFolder.get_metadata_schema(), WorkflowAware.get_metadata_schema(), product_schema, data=XHTMLBody(multilingual=True)) @staticmethod def _make_resource(cls, folder, name, ctime=None, *args, **kw): from shop.cross_selling import CrossSellingTable if ctime is None: ctime = datetime.now() DynamicFolder._make_resource(cls, folder, name, ctime=ctime, *args, **kw) # Images folder ImagesFolder._make_resource(ImagesFolder, folder, '%s/images' % name, body='', title={'en': u'Images'}) # Order images table PhotoOrderedTable._make_resource(PhotoOrderedTable, folder, '%s/order-photos' % name, title={'en': u'Order photos'}) # Cross Selling table CrossSellingTable._make_resource(CrossSellingTable, folder, '%s/cross-selling' % name, title={'en': u'Cross selling'}) def _get_dynamic_catalog_values(self): values = {} dynamic_schema = self.get_dynamic_schema() for key, datatype in get_product_filters().items(): register_key = 'DFT-%s' % key if key in dynamic_schema: value = self.get_dynamic_property(key, dynamic_schema) if value and getattr(datatype, 'is_range', False): value = int(value * 100) if value: values[register_key] = value # Dynamic indexation register_fields = get_register_fields() model = self.get_product_model() if model is None: return {} for key, datatype in self.get_dynamic_schema().items(): # We index dynamic properties that correspond to # an EnumerateTable datatype. # So we are able to know if enumerate value is used or not if issubclass(datatype, EnumerateTable_to_Enumerate) is True: register_key = 'DFT-%s' % datatype.enumerate_name if register_key not in register_fields: register_field(register_key, String(is_indexed=True)) if datatype.multiple is True: values[register_key] = ' '.join(self.get_property(key)) else: values[register_key] = self.get_property(key) return values def _get_preview_content(self, languages): # TagsAware preview not used for products # Saves half the time of reindexing! return None def _get_catalog_values(self): values = merge_dicts(DynamicFolder._get_catalog_values(self), TagsAware._get_catalog_values(self), self._get_dynamic_catalog_values()) # Data data = self.get_property('data') if data is not None: data = xml_to_text(data) values['data'] = data # Reference values['reference'] = self.get_property('reference') # Manufacturer values['manufacturer'] = str(self.get_property('manufacturer')) # Supplier values['supplier'] = str(self.get_property('supplier')) # Stock quantity values['stock_quantity'] = self.get_property('stock-quantity') # Product models values['product_model'] = str(self.get_property('product_model')) # Images order = self.get_resource('order-photos', soft=True) if order: ordered_names = list(order.get_ordered_names()) values['has_images'] = (len(ordered_names) != 0) else: values['has_images'] = False # Price # XXX We can't sort decimal, so transform to int values['stored_price'] = int(self.get_price_with_tax() * 100) values['stored_weight'] = int(self.get_weight() * 100) # Price values['ht_price'] = self.get_price_without_tax() values['ttc_price'] = self.get_price_with_tax() # Creation time values['ctime'] = self.get_property('ctime') # Publication date values['pub_datetime'] = self.get_property('pub_datetime') # Promotion values['has_reduction'] = self.get_property('has_reduction') # not_buyable_by_groups values['not_buyable_by_groups'] = self.get_property( 'not_buyable_by_groups') return values def get_dynamic_schema(self): product_model = self.get_product_model() if not product_model: return {} return product_model.get_model_schema() def get_product_model(self): product_model = self.get_property('product_model') if not product_model: return None return self.get_resource(product_model) def to_text(self): result = {} site_root = self.get_site_root() languages = site_root.get_property('website_languages') product_model = self.get_product_model() schema = {} if product_model: schema = product_model.get_model_schema() purchase_options_schema = self.get_purchase_options_schema() declinations = list(self.search_resources(cls=Declination)) for language in languages: texts = result.setdefault(language, []) for key in ('title', 'description'): value = self.get_property(key, language=language) if value: texts.append(value) # Parent category current_category = self.parent while current_category.class_id == 'category': texts.append(current_category.get_title(language=language)) current_category = current_category.parent # data (html) events = self.get_property('data', language=language) if events: text = [ unicode(value, 'utf-8') for event, value, line in events if event == TEXT ] if text: texts.append(u' '.join(text)) # Dynamic properties for key, datatype in schema.iteritems(): value = self.get_property(key) if value: text = None multiple = datatype.multiple if issubclass(datatype, Unicode): if multiple: text = ' '.join([x for x in value]) else: text = value elif issubclass(datatype, String): if multiple: text = ' '.join([Unicode.decode(x) for x in value]) else: text = Unicode.decode(value) elif issubclass(datatype, Enumerate): values = value if multiple is False: values = [value] # XXX use multilingual label text = ' '.join(values) if text: texts.append(text) # Manufacturer manufacturer = self.get_property('manufacturer') if manufacturer: manufacturer = site_root.get_resource(manufacturer) texts.append(manufacturer.get_title()) # Purchase options for declination in declinations: for key, datatype in purchase_options_schema.iteritems(): name = declination.get_property(key) value = datatype.to_text(name, languages) if value: texts.append(value) # Join for language, texts in result.iteritems(): result[language] = u'\n'.join(texts) return result def get_resource_icon(self, size=16): context = get_context() size = 48 cover = self.get_property('cover') link = '%s/%s' % (context.get_link(self), cover) return '%s/;thumb?width=%s&height=%s' % (link, size, size) ################################################## ## Purchase options ################################################## def get_purchase_options_names(self): """ Return list of enumerates name corresponding to a purchase option (from the enumerates library) """ model = self.get_product_model() if model is None: return [] return model.get_property('declinations_enumerates') def get_purchase_options_schema(self): schema = {} for name in self.get_purchase_options_names(): schema[name] = EnumerateTable_to_Enumerate(enumerate_name=name) return schema def get_javascript_namespace(self, declinations): # XXX # We have to Add price without tax (Before and after reduction) # XXX If handle_stock property is false manage_stock should be false manage_stock = self.get_stock_option() != 'accept' purchase_options_names = self.get_purchase_options_names() # Base product stock_quantity = self.get_property('stock-quantity') products = {} if len(declinations) == 0: products['base_product'] = { 'price_ht': format_price(self.get_price_without_tax()), 'price_ttc': format_price(self.get_price_with_tax()), 'weight': str(self.get_weight()), 'image': [], 'option': {}, 'is_default': True, 'stock': stock_quantity if manage_stock else None } # Other products (declinations) for declination in declinations: dynamic_schema = declination.get_dynamic_schema() stock_quantity = declination.get_quantity_in_stock() price_ht = self.get_price_without_tax( id_declination=declination.name) price_ttc = self.get_price_with_tax( id_declination=declination.name) image = None #declination.get_property('associated-image') products[declination.name] = { 'price_ht': format_price(price_ht), 'price_ttc': format_price(price_ttc), 'weight': str(declination.get_weight()), 'image': get_uri_name(image) if image else None, 'is_default': declination.get_property('is_default'), 'option': {}, 'stock': stock_quantity if manage_stock else None } for name in purchase_options_names: value = declination.get_dynamic_property(name, dynamic_schema) if value: products[declination.name]['option'][name] = value return dumps(products) def get_purchase_options_namespace(self, declinations): namespace = [] shop = get_shop(self) purchase_options_names = self.get_purchase_options_names() values = {} # Has declination ? if not declinations: return namespace # Get uniques purchase option values for declination in declinations: dynamic_schema = declination.get_dynamic_schema() for name in purchase_options_names: value = declination.get_dynamic_property(name, dynamic_schema) if not value: continue if not values.has_key(name): values[name] = set([]) values[name].add(value) # Build datatype / widget enumerates_folder = shop.get_resource('enumerates') for name in purchase_options_names: if values.get(name) is None or len(values[name]) == 0: continue enumerate_table = enumerates_folder.get_resource(name) datatype = Restricted_EnumerateTable_to_Enumerate( enumerate_name=name, values=values[name]) widget_cls = enumerate_table.widget_cls widget = widget_cls(name, has_empty_option=False) namespace.append({ 'title': enumerate_table.get_title(), 'html': widget.to_html(datatype, None) }) return namespace def get_declination(self, kw): """ Return the name of declination corresponding to a dict of purchase options Example: {'color': 'red', 'size': 'M'} ==> Return name of declination if exist ==> Return None if not exist """ purchase_options_schema = self.get_purchase_options_schema() for declination in self.search_resources(cls=Declination): dynamic_schema = declination.get_dynamic_schema() value = [ kw.get(x) == (declination.get_dynamic_property( x, dynamic_schema) or None) for x in purchase_options_schema ] if set(value) == set([True]): return declination.name return None def get_declination_namespace(self, declination_name): namespace = [] shop = get_shop(self) declination = self.get_resource(declination_name) dynamic_schema = declination.get_dynamic_schema() enumerates_folder = shop.get_resource('enumerates') for name in self.get_purchase_options_names(): value = declination.get_dynamic_property(name, dynamic_schema) if not value: continue enumerate_table = enumerates_folder.get_resource(name) datatype = EnumerateTable_to_Enumerate(enumerate_name=name) namespace.append({ 'title': enumerate_table.get_title(), 'value': datatype.get_value(value) }) return namespace # XXX We should be able to activate it or not #def get_available_languages(self, languages): # from itws.utils import is_empty # available_langs = [] # for language in languages: # events = self.get_property('data', language=language) # title = self.get_property('title', language=language) # if events and is_empty(events) is False and len(title.strip()): # available_langs.append(language) # return available_langs ################################################## ## Namespace ################################################## def get_small_namespace(self, context): title = self.get_property('title') # Specific model is ? specific_model_is = SpecificModelIs() product_model = self.get_product_model() if product_model: product_model_name = product_model.name else: product_model_name = None specific_model_is.model_name = product_model_name # Dynamic property dynamic_property = DynamicProperty() dynamic_property.resource = self dynamic_property.context = context dynamic_property.schema = self.get_dynamic_schema() # Category category = { 'name': self.parent.name, 'href': context.get_link(self.parent), 'title': self.parent.get_title() } # Lang (usefull to show contextuel images) ws_languages = context.site_root.get_property('website_languages') accept = context.accept_language lang = accept.select_language(ws_languages) # Shop modules shop_module = ModuleLoader() shop_module.context = context shop_module.here = self # Minititle mini_title = MiniTitle() mini_title.context = context mini_title.here = self # Return namespace return { 'name': self.name, 'lang': lang, 'module': shop_module, 'specific_model_is': specific_model_is, 'dynamic_property': dynamic_property, 'category': category, 'cover': self.get_cover_namespace(context), 'description': self.get_property('description'), 'href': context.get_link(self), 'manufacturer': ManufacturersEnumerate.get_value( self.get_property('manufacturer')), 'mini-title': mini_title, 'price': self.get_price_namespace(), 'reference': self.get_property('reference'), 'title': title } def get_price_namespace(self): has_reduction = self.get_property('has_reduction') ns = { 'with_tax': self.get_price_with_tax(pretty=True), 'without_tax': self.get_price_without_tax(pretty=True), 'has_reduction': has_reduction } if has_reduction: kw = {'pretty': True, 'with_reduction': False} ns['with_tax_before_reduction'] = self.get_price_with_tax(**kw) ns['without_tax_before_reduction'] = self.get_price_with_tax(**kw) return ns def get_cross_selling_namespace(self, context): from shop.categories import Category table = self.get_resource('cross-selling', soft=True) if table is None: # If table do not exist we use default cross selling shop = get_shop(self) table = shop.get_resource('cross-selling') viewbox = self.cross_selling_viewbox cross_selling = [] abspath = self.get_abspath() parent = self.parent if isinstance(parent, Category): current_category = [parent] elif isinstance(self, Category): current_category = [self] else: current_category = [] cross_products = table.get_products(context, self.class_id, current_category, [abspath]) for product in cross_products: cross_selling.append(viewbox.GET(product, context)) return cross_selling def get_namespace(self, context): root = context.root namespace = { 'name': self.name, 'abspath': self.get_abspath(), 'price': self.get_price_namespace() } # Get basic informations namespace['href'] = context.get_link(self) for key in product_schema.keys(): if key == 'data': continue namespace[key] = self.get_property(key) # Category namespace['category'] = { 'name': self.parent.name, 'href': context.get_link(self.parent), 'breadcrumb_title': self.parent.get_property('breadcrumb_title'), 'title': self.parent.get_title() } # Categorie XXX To remove namespace['categorie'] = self.parent.get_title() # Lang (usefull to show contextuel images) ws_languages = context.site_root.get_property('website_languages') accept = context.accept_language lang = accept.select_language(ws_languages) namespace['lang'] = lang # Manufacturer manufacturer = self.get_property('manufacturer') if manufacturer: manufacturer = root.get_resource(manufacturer) link = context.get_link(manufacturer) photo = manufacturer.get_property('photo') namespace['manufacturer'] = { 'name': manufacturer.name, 'link': link, 'photo': resolve_uri2(link, photo), 'title': manufacturer.get_title() } else: namespace['manufacturer'] = None # Data namespace['data'] = self.get_property('data') # Specific product informations model = self.get_product_model() if model: namespace.update(model.get_model_namespace(self)) else: namespace['specific_dict'] = {} namespace['specific_list'] = [] # Images namespace['cover'] = self.get_cover_namespace(context) namespace['images'] = self.get_images_namespace(context) namespace['has_more_than_one_image'] = len(namespace['images']) > 1 # Product is buyable namespace['is_buyable'] = self.is_buyable(context) # Cross selling namespace['cross_selling'] = self.get_cross_selling_namespace(context) # Authentificated ? ac = self.get_access_control() namespace['is_authenticated'] = ac.is_authenticated(context.user, self) # Configuration configuration = ConfigurationProperty() configuration.context = context configuration.resource = self namespace['configuration'] = configuration # Shop modules shop_module = ModuleLoader() shop_module.context = context shop_module.here = self namespace['module'] = shop_module return namespace ##################### # Images ##################### def get_preview_thumbnail(self): container = self cover = self.get_property('cover') # If no cover get default one if not cover: container = self.parent cover = container.get_property('default_product_cover') # If no cover we return None if not cover: return None return container.get_resource(cover, soft=True) def get_cover_namespace(self, context): image = self.get_preview_thumbnail() if not image: return return { 'href': context.get_link(image), 'name': image.name, 'key': image.handler.key, 'title': image.get_property('title') or self.get_title() } def get_images_namespace(self, context): images = [] for image in self.get_ordered_photos(context): images.append({ 'name': image.name, 'href': context.get_link(image), 'title': image.get_property('title') or self.get_title() }) return images def get_ordered_photos(self, context): # Search photos order = self.get_resource('order-photos', soft=True) # Order table can be remove for performances if order is None: return [] ordered_names = list(order.get_ordered_names()) # If no photos, return if not ordered_names: return [] # Get photos images = [] ac = self.get_access_control() user = context.user for name in ordered_names: image = order.get_resource(name, soft=True) if image and ac.is_allowed_to_view(user, image): images.append(image) return images ##################### ## Stock ##################### def get_stock_option(self): # XXX Get option from shop generatl configuration return self.get_property('stock-option') def is_in_stock_or_ignore_stock(self, quantity, id_declination=None): if self.get_stock_option() == 'accept': return True if id_declination: declination = self.get_resource(id_declination) resource = declination else: resource = self return resource.get_property('stock-quantity') >= quantity def send_alert_stock(self): shop = get_shop(self) context = get_context() root = context.root product_uri = context.uri.resolve('/shop/products/%s/' % self.name) kw = {'product_title': self.get_title(), 'product_uri': product_uri} body = mail_stock_body_template.gettext(**kw) for to_addr in shop.get_property('order_notification_mails'): root.send_email(to_addr=to_addr, subject=mail_stock_subject_template.gettext(), text=body) def remove_from_stock(self, quantity, id_declination=None): stock_option = self.get_stock_option() if stock_option == 'dont_handle': return resource = self if id_declination: declination = self.get_resource(id_declination) resource = declination old_quantity = resource.get_quantity_in_stock() new_quantity = old_quantity - quantity if old_quantity > 0 and new_quantity <= 0: self.send_alert_stock() resource.set_property('stock-quantity', new_quantity) # XXX If is declination go private ? if not id_declination and new_quantity <= 0 and stock_option == 'refuse_go_private': self.set_property('state', 'private') def add_on_stock(self, quantity, id_declination=None): stock_option = self.get_stock_option() if stock_option == 'dont_handle': return resource = self if id_declination: declination = self.get_resource(id_declination) resource = declination old_quantity = resource.get_property('stock-quantity') new_quantity = old_quantity + quantity resource.set_property('stock-quantity', new_quantity) def get_quantity_in_stock(self): return self.get_property('stock-quantity') ##################### ## API ##################### def is_buyable(self, context, quantity=1): shop = get_shop(self) group_name = get_group_name(shop, context) return (self.get_price_without_tax() != decimal(0) and group_name not in self.get_property('not_buyable_by_groups') and self.get_statename() == 'public') def get_reference(self, id_declination=None): if id_declination: declination = self.get_resource(id_declination, soft=True) if declination: reference = declination.get_property('reference') if reference: return reference return self.get_property('reference') def get_price_prefix(self): shop = get_shop(self) context = get_context() group_name = get_group_name(shop, context) if get_uri_name(group_name) == 'pro': return 'pro-' return '' def get_tax_value(self, prefix=None): shop = get_shop(self) if prefix is None: prefix = self.get_price_prefix() # Get zone from cookie id_zone = ProductCart(get_context()).id_zone # If not define... get default zone if id_zone is None: id_zone = shop.get_property('shop_default_zone') # Check if zone has tax ? zones = shop.get_resource('countries-zones').handler zone_record = zones.get_record(int(id_zone)) if zones.get_record_value(zone_record, 'has_tax') is True: tax = self.get_property('%stax' % prefix) tax_value = TaxesEnumerate.get_value(tax) or decimal(0) return (tax_value / decimal(100) + 1) return decimal(1) def get_price_without_tax(self, id_declination=None, with_reduction=True, pretty=False, prefix=None): if prefix is None: prefix = self.get_price_prefix() # Base price if with_reduction is True and self.get_property( '%shas_reduction' % prefix): price = self.get_property('%sreduce-pre-tax-price' % prefix) else: price = self.get_property('%spre-tax-price' % prefix) # Declination if id_declination: declination = self.get_resource(id_declination) price = price + declination.get_price_impact(prefix) # Format price if pretty is True: return format_price(price) return price def get_price_with_tax(self, id_declination=None, with_reduction=True, pretty=False, prefix=None): price = self.get_price_without_tax(id_declination, with_reduction=with_reduction, prefix=prefix) price = price * self.get_tax_value(prefix=prefix) # Format price if pretty is True: return format_price(price) return price def get_weight(self, id_declination=None): if id_declination: declination = self.get_resource(id_declination, soft=True) if declination: return declination.get_weight() return self.get_property('weight') def save_barcode(self, reference): shop = get_shop(self) format = shop.get_property('barcode_format') barcode = generate_barcode(format, reference) if not barcode: return self.del_resource('barcode', soft=True) metadata = { 'title': { 'en': u'Barcode' }, 'filename': 'barcode.png', 'format': 'image/png' } Image.make_resource(Image, self, 'barcode', body=barcode, **metadata) ####################### ## Computed fields ####################### computed_schema = { 'frontoffice_uri': String(title=MSG(u'Frontoffice link')), 'cover_uri': String(title=MSG(u'Cover link')), 'price_with_tax': Decimal(title=MSG(u'Price with tax')) } @property def price_with_tax(self): return self.get_price_with_tax(with_reduction=True) @property def frontoffice_uri(self): shop = get_shop(self) site_root = shop.get_site_root() base_uri = shop.get_property('shop_uri') end_uri = site_root.get_pathto(self) return str(get_reference(base_uri).resolve(end_uri)) @property def cover_uri(self): shop = get_shop(self) site_root = shop.get_site_root() cover = self.get_preview_thumbnail() if not cover: return None base_uri = shop.get_property('shop_uri') end_uri = site_root.get_pathto(cover) return str('%s/;download' % get_reference(base_uri).resolve(end_uri)) ####################### ## Class views ####################### default_class_views = [ 'declinations', 'new_declination', 'images', 'order', 'edit_cross_selling', 'delete_product', 'commit_log' ] @property def class_views(self): context = get_context() # Back-Office hostname = context.uri.authority if hostname[:6] == 'admin.': return ['edit'] + self.default_class_views return ['view', 'edit'] + self.default_class_views def get_links(self): return DynamicFolder.get_links(self) def update_links(self, source, target): return DynamicFolder.update_links(self, source, target) def update_relative_links(self, source): return DynamicFolder.update_relative_links(self, source) def update_20110311(self): # Remove old attributes for key in ['is_buyable', 'reduction', 'categories']: self.del_property(key)
class ProductModel(ShopFolder): class_id = 'product-model' class_title = MSG(u'Product Model') class_views = ['configure', 'view'] view = GoToSpecificDocument(specific_document='schema', title=MSG(u'View model details')) configure = ProductModel_Configure() __fixed_handlers__ = ShopFolder.__fixed_handlers__ + ['schema'] @classmethod def get_metadata_schema(cls): return merge_dicts( ShopFolder.get_metadata_schema(), declinations_enumerates=Enumerate_ListEnumerateTable( multiple=True)) def _get_catalog_values(self): proxy = super(ShopFolder, self) values = proxy._get_catalog_values() values['declinations_enumerates'] = self.get_property( 'declinations_enumerates') return values @staticmethod def _make_resource(cls, folder, name, *args, **kw): root = ShopFolder._make_resource(cls, folder, name, **kw) # Base schema ProductModelSchema._make_resource(ProductModelSchema, folder, '%s/schema' % name, title={'en': u'Model schema'}) def get_document_types(self): return [] def get_model_schema(self): schema = {} schema_resource = self.get_resource('schema') schema_handler = schema_resource.handler get_value = schema_handler.get_record_value for record in schema_handler.get_records_in_order(): name = get_value(record, 'name') schema[name] = get_real_datatype(schema_handler, record) return schema def get_model_widgets(self): widgets = [] schema_handler = self.get_resource('schema').handler get_value = schema_handler.get_record_value for record in schema_handler.get_records_in_order(): name = get_value(record, 'name') datatype = get_real_datatype(schema_handler, record) widget = get_default_widget_shop(datatype) title = get_value(record, 'title') widget = widget(name, title=title, has_empty_option=False) widgets.append(widget) return widgets def get_model_namespace(self, resource): context = get_context() specific_model_is = SpecificModelIs() specific_model_is.model_name = self.name namespace = { 'specific_model_is': specific_model_is, 'specific_dict': {}, 'specific_list': [], 'specific_list_complete': [] } dynamic_schema = self.get_model_schema() schema_handler = self.get_resource('schema').handler get_value = schema_handler.get_record_value for record in schema_handler.get_records_in_order(): name = get_value(record, 'name') value = real_value = resource.get_dynamic_property( name, dynamic_schema) # Real value is used to keep the enumerate value # corresponding to the options[{'name': xxx}] datatype = dynamic_schema[name] if value and hasattr(datatype, 'render'): value = datatype.render(value, context) # Build kw kw = {'value': value, 'real_value': real_value} for key in ['name', 'title', 'multiple', 'visible']: kw[key] = get_value(record, key) # Add to namespace namespace['specific_dict'][name] = kw namespace['specific_list_complete'].append(kw) if kw['visible'] and kw['value']: namespace['specific_list'].append(kw) return namespace
class ShopUserFolder(UserFolder): class_id = 'users' class_version = '20100823' backoffice_class_views = ['view', 'addresses_book', 'last_connections'] #'export'] @property def class_views(self): context = get_context() # Back-Office hostname = context.uri.authority if hostname[:6] == 'admin.': return self.backoffice_class_views if hostname[:6] == 'www.aw': # XXX Add a configurator for public profil return ['public_view'] return [] view = Customers_View() public_view = ShopUsers_PublicView() addresses_book = GoToSpecificDocument( title=MSG(u'Addresses book'), access='is_admin', specific_document='../shop/addresses/') last_connections = GoToSpecificDocument( title=MSG(u'Last connections'), access='is_allowed_to_add', specific_document='../shop/customers/authentification_logs') ############################# # Export ############################# export = Export(export_resource=ShopUser, access='is_allowed_to_edit', file_columns=[ 'name', 'is_enabled', 'user_group', 'gender', 'firstname', 'lastname', 'email', 'phone1', 'phone2', 'address', 'nb_orders', 'ctime' ]) ############################# # Api ############################# def set_user(self, email=None, password=None): context = get_context() shop = get_shop(context.resource) # Calculate the user id user_id = self.get_next_user_id() # Add the user cls = shop.user_class user = cls.make_resource(cls, self, user_id) # Set the email and paswword if email is not None: user.set_property('email', email) if password is not None: user.set_password(password) # Set default group root = context.root query = [ PhraseQuery('format', 'user-group'), PhraseQuery('name', 'default') ] search = root.search(AndQuery(*query)) documents = search.get_documents() group = documents[0] group = root.get_resource(group.abspath) user.set_property('user_group', str(group.get_abspath())) user_is_enabled = group.get_property('user_is_enabled_when_register') user.set_property('is_enabled', user_is_enabled) # Return the user return user
class CrossSellingTable(ResourcesOrderedTable): class_id = 'CrossSellingTable' class_title = MSG(u'Cross-Selling Table') class_description = MSG(u'This box allow to configure cross selling') class_handler = ResourcesOrderedTableFile class_version = '20100928' class_views = ['configure', 'back'] form = [ProductSelectorWidget('name', title=MSG(u'Product'))] orderable_classes = Product # Views configure = CrossSelling_Configure() edit = CrossSelling_Edit() view_table = CrossSelling_TableView() add_product = AddProduct_View() # (XXX used by product selector widget) back = GoToSpecificDocument(specific_document='..', title=MSG(u'See product')) @classmethod def get_metadata_schema(cls): return merge_dicts(ResourcesOrderedTable.get_metadata_schema(), cross_selling_schema) def get_order_root(self): return self def get_products(self, context, product_format, categories=[], excluded_products=[]): shop = get_shop(self) table = self if self.get_property('use_shop_configuration'): table = shop.get_resource('cross-selling') if table.get_property('enabled') is False: return root = context.root products_quantity = table.get_property('products_quantity') # Base query query = [ PhraseQuery('format', product_format), PhraseQuery('workflow_state', 'public') ] # Do not show now buyable products group_name = get_group_name(shop, context) q = PhraseQuery('not_buyable_by_groups', group_name) query.append(NotQuery(q)) # Excluded products query if excluded_products: exclude_query = [ PhraseQuery('abspath', str(abspath)) for abspath in excluded_products ] if len(exclude_query) > 1: exclude_query = OrQuery(*exclude_query) else: exclude_query = exclude_query[0] query.append(NotQuery(exclude_query)) # Filter on product title filter_text = table.get_property('filter_text') if filter_text: query.append(PhraseQuery('title', filter_text)) # Categories query mode_categories = table.get_property('categories') if mode_categories == 'current_category': query_categorie = [ PhraseQuery('parent_paths', str(x.get_abspath())) for x in categories ] if len(query_categorie) > 1: query.append(OrQuery(*query_categorie)) elif len(query_categorie) == 1: query.append(query_categorie[0]) elif mode_categories == 'one_category': query.append( PhraseQuery('parent_paths', table.get_property('specific_category'))) # Show reductions ? promotion = table.get_property('show_product_with_promotion') if promotion in ('0', '1'): query.append(PhraseQuery('has_reduction', bool(promotion))) # Product model product_model = table.get_property('product_model') if product_model: query.append(PhraseQuery('product_model', product_model)) # Tags if table.get_property('tags'): query.append( OrQuery(*[ PhraseQuery('tags', x) for x in table.get_property('tags') ])) # Selection in cross selling table handler = table.handler get_value = handler.get_record_value ids = list(handler.get_record_ids_in_order()) names = [] for id in ids[:products_quantity]: record = handler.get_record(id) path = get_value(record, 'name') names.append(path) products_quantity -= 1 resource = self.get_resource(path, soft=True) if resource is None: log_warning('Error cross selling, %s' % path) elif resource.get_property('state') == 'public': yield resource if products_quantity <= 0: return if names: names_query = [PhraseQuery('name', name) for name in names] if len(names_query) > 1: names_query = OrQuery(*names_query) else: names_query = names_query[0] query.append(NotQuery(names_query)) # Complete results sort = table.get_property('sort') if sort == 'random': # Random selection results = root.search(AndQuery(*query)) # XXX It's not relevant to make a random cross selling # with more than 1000 products brains = list(results.get_documents(size=1000)) shuffle(brains) for brain in brains[:products_quantity]: yield root.get_resource(brain.abspath) elif sort == 'last': results = root.search(AndQuery(*query)) brains = list( results.get_documents(sort_by='ctime', reverse=True, size=products_quantity)) for brain in brains: yield root.get_resource(brain.abspath)