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())
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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'))]
Beispiel #5
0
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
Beispiel #6
0
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()
Beispiel #7
0
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 []
Beispiel #8
0
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)
Beispiel #9
0
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)})
Beispiel #10
0
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&amp;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)
Beispiel #11
0
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
Beispiel #12
0
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
Beispiel #13
0
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)