Пример #1
0
    def reset(self):
        self.properties = {}
        self.components = {}

        # A Catalog "in memory"
        fields = {
            '__uid__': String(is_key_field=True,
                              is_stored=True,
                              is_indexed=True),
            'type': String(is_indexed=True),
            'dtstart': DateTime(is_stored=True, is_indexed=True),
            'dtend': DateTime(is_stored=True, is_indexed=True)
        }
        self.catalog = make_catalog(None, fields)
Пример #2
0
class End_Field(Datetime_Field):

    datatype = DateTime(time_is_required=False)
    stored = True
    title = MSG(u'End')
    required = True
    widget = Datetime_Field.widget(value_time_default=time(10, 0))
Пример #3
0
class ShopModule_Edit(DBResource_Edit):

    title = MSG(u'Edit')
    access = 'is_allowed_to_edit'

    base_schema = {'title': Unicode, 'timestamp': DateTime(readonly=True)}

    base_widgets = [timestamp_widget, title_widget]

    def get_schema(self, resource, context):
        return merge_dicts(self.base_schema, resource.item_schema)

    def get_widgets(self, resource, context):
        return self.base_widgets + resource.item_widgets

    def action(self, resource, context, form):
        # Check edit conflict
        self.check_edit_conflict(resource, context, form)
        if context.edit_conflict:
            return

        # Save changes
        title = form['title']
        language = resource.get_content_language(context)
        resource.set_property('title', title, language=language)
        for key, datatype in resource.item_schema.items():
            if getattr(datatype, 'multilingual', False) is True:
                resource.set_property(key, form[key], language)
            else:
                resource.set_property(key, form[key])

        # Ok
        context.message = messages.MSG_CHANGES_SAVED
Пример #4
0
    def save_state(self):
        if self.incremental_save is False:
            File.save_state(self)
            self.incremental_save = True
            return

        # Incremental Save
        file = self.safe_open(self.key, 'a')
        try:
            # Added properties records
            for seq in self.added_properties:
                version = self.properties[seq]
                version = self._version_to_str(-1, seq, version)
                file.write(version)
            self.added_properties = []
            # Added records
            for id, seq in self.added_records:
                version = self.records[id][seq]
                version = self._version_to_str(id, seq, version)
                file.write(version)
            self.added_records = []
            # Removed records
            for id, ts in self.removed_records:
                file.write('id:%s/DELETED\n' % id)
                file.write('ts:%s\n' % DateTime.encode(ts))
                file.write('\n')
            self.removed_records = []
        finally:
            file.close()

        # Update the timestamp
        self.timestamp = self.database.fs.get_mtime(self.key)
        self.dirty = None
Пример #5
0
 def get_datatype(self, name):
     # Table schema
     if name == 'ts':
         return DateTime(multiple=False)
     if name in self.schema:
         return self.schema[name]
     return String(multiple=True)
Пример #6
0
    def save_state(self):
        if self.incremental_save is False:
            File.save_state(self)
            self.incremental_save = True
            return

        # Incremental Save
        file = self.safe_open(self.key, 'a')
        try:
            # Added properties records
            for seq in self.added_properties:
                version = self.properties[seq]
                version = self._version_to_str(-1, seq, version)
                file.write(version)
            self.added_properties = []
            # Added records
            for id, seq in self.added_records:
                version = self.records[id][seq]
                version = self._version_to_str(id, seq, version)
                file.write(version)
            self.added_records = []
            # Removed records
            for id, ts in self.removed_records:
                file.write('id:%s/DELETED\n' % id)
                file.write('ts:%s\n' % DateTime.encode(ts))
                file.write('\n')
            self.removed_records = []
        finally:
            file.close()

        # Update the timestamp
        self.timestamp = self.database.fs.get_mtime(self.key)
        self.dirty = None
Пример #7
0
 def get_record_datatype(self, name):
     # Record schema
     if name == 'ts':
         return DateTime(multiple=False)
     if name in self.record_properties:
         return self.record_properties[name]
     # FIXME Probably we should raise an exception here
     return String(multiple=True)
Пример #8
0
    def _get_schema(self, resource, context):
        schema = {'timestamp': DateTime(readonly=True), 'referrer': URI}

        # Add schema from the resource
        for name in self.get_fields():
            datatype = self._get_datatype(resource, context, name)

            # Special case: datetime
            if issubclass(datatype, DateTime):
                schema['%s_time' % name] = Time
            # Special case: birthdate
            elif issubclass(datatype, BirthDate):
                schema['%s_day' % name] = Days
                schema['%s_month' % name] = Months
                schema['%s_year' % name] = Years

            # Standard case
            schema[name] = datatype

        return schema
Пример #9
0
class Password_Field(Metadata_Field):

    datatype = Password_Datatype()
    parameters_schema = {
        'algo': String(),
        'salt': String(),
        'date': DateTime()
    }
    widget = PasswordWidget

    def access(self, mode, resource):
        return mode == 'write'

    def set_value(self, resource, name, value, language=None, **kw):
        if value is not None:
            algo = 'sha256'
            value, salt = get_secure_hash(value, algo)
            kw['algo'] = algo
            kw['salt'] = salt
            kw['date'] = get_context().timestamp

        # super
        proxy = super(Password_Field, self)
        return proxy.set_value(resource, name, value, language, **kw)
Пример #10
0
    class_id = 'orders'
    class_title = MSG(u'Orders')
    class_views = ['view']  # 'export']
    class_version = '20091127'

    # Views
    view = OrdersView()

    def get_document_types(self):
        return [Order]

    #############################
    # Export
    #############################
    export = Export(
        export_resource=Order,
        access='is_allowed_to_edit',
        file_columns=['name', 'state', 'total_price', 'creation_datetime'])


# Register catalog fields
register_field('customer_id', String(is_indexed=True))
register_field('is_payed', Boolean(is_stored=True))
register_field('creation_datetime', DateTime(is_stored=True, is_indexed=True))

# Register resources
register_resource_class(Order)
register_resource_class(Orders)
register_resource_class(OrdersProducts)
Пример #11
0
def render_namespace(items, times, with_new_url, current_date):
    nitems, iitems = len(items), 0
    # blocks = [(nrows, ncols), ..]
    blocks, table, state = [], [], []
    nrows = 0
    for itime, time in enumerate(times[:-1]):
        cells = []

        tt_end = times[itime + 1]
        # add the busy cells
        for rowspan in state:
            if rowspan > 0:
                cells.append(Cell(Cell.busy, colspan=1))
            else:
                cells.append(Cell(Cell.free, start=time, end=tt_end))

        # add new cells
        icell = 0
        while iitems < nitems:
            start, end, item, cal = items[iitems]
            if start != time:
                break
            # look for a free cell
            while icell < len(cells):
                if cells[icell].type == Cell.free:
                    break
                icell = icell + 1
            # add cell
            rowspan = max(times.index(end) - times.index(start), 1)
            cell = Cell(Cell.new, item, start, end, rowspan, 1, cal)
            if icell >= len(cells):
                state.append(rowspan)
                cells.append(cell)
            else:
                state[icell] = rowspan
                cells[icell] = cell
            # next item
            iitems = iitems + 1

        ncols = len(cells)
        # empty row?
        if ncols == 0:
            cells.append(Cell(Cell.free, start=time, end=tt_end))

        # next row, reduce the current rowspans
        nrows = nrows + 1
        for i in range(ncols):
            rowspan = state[i]
            if rowspan > 0:
                state[i] = rowspan - 1

        # a new block?
        state_0_count = state.count(0)
        if state_0_count == ncols:
            state = []
            blocks.append((nrows, ncols))
            nrows = 0

        # Add current row cells to table
        table.append(cells)

    # calculate the number of columns
    total_ncols = mcm(map(lambda x: x[1], blocks))

    # add colspan to each row and fill the incomplete rows with free cells
    base = 0
    for nrows, ncols in blocks:
        if ncols != 0:
            b_colspan = total_ncols/ncols
        else:
            b_colspan = total_ncols

        for irow in range(nrows):
            cells = table[base + irow]
            for cell in cells:
                cell.colspan = b_colspan
            for icol in range(len(cells), ncols):
                cells.append(Cell(Cell.free, start=times[base+irow],
                                  colspan=b_colspan))
            table[base + irow] = cells
        base = base + nrows

    ####################################################################
    # FOR EACH ROW
    for index, row_cells in enumerate(table):
        new_cells, free_cells = [], []
        for cell in row_cells:
            if cell.type == Cell.new:
                new_cells.append(cell)
            elif cell.type == Cell.free:
                free_cells.append(cell)

        if free_cells == row_cells:
            continue

        # Try to extend colspan of new cells
        #   (checking the cells below in rowspan)
        for cell in new_cells:
            colspan = cell.colspan
            if colspan <= 1:
                continue

            # FOR EACH LINE BELOW, USED FOR CELL TO EXTEND
            new_extended = []
            for n in range(cell.rowspan):
                if colspan <= 1:
                    break
                # GET CURRENT TESTED ROW
                row_index = index + n
                icells = table[row_index]
                # REDUCE max colspan if necessary
                ilen = len(icells)
                if ilen < colspan:
                    colspan = ilen
                # TRY TO EXTEND
                i = row_cells.index(cell)
                k = 0
                while k < colspan and (i+k) < ilen:
                    if icells[i+k].type != Cell.free:
                        colspan = k
                        break
                    k = k + 1
                new_extended.append((row_index, i))

            # Update cells below
            if colspan > 1:
                for row_index, i in new_extended:
                    c_colspan = colspan
                    c_row = table[row_index]
                    l_c_row = len(c_row)
                    for col in range(i+1, i+colspan):
                        if col < l_c_row:
                            current_cell = c_row[col]
                            free_cells.remove(current_cell)
                            current_cell.type = Cell.busy
                            c_colspan = c_colspan + current_cell.colspan
                    current_cell[i].colspan = c_colspan

        # Collapse free cells
        l_row_cells = len(row_cells)
        for icell, cell in enumerate(row_cells):
            if cell.type == Cell.free:
                jcelltest = icell + 1
                start = cell.start
                while jcelltest < l_row_cells:
                    if jcelltest != icell:
                        celltest = row_cells[jcelltest]
                        if celltest.type != Cell.free:
                            break
                        if celltest.start == start:
                            celltest.type = Cell.busy
                            cell.colspan = cell.colspan + celltest.colspan
                    jcelltest = jcelltest + 1


    ######################################################################
    # render_namespace
    ######################################################################

    url = ';new_event'
    ns_rows = []
    for cells in table:
        ns_cells = []
        for cell in cells:
            # Don't add busy cells as they don't appear in template
            if cell.type == Cell.busy:
                continue
            ns_cell = cell.to_dict()
            if with_new_url is True:
                # Add start time to url used to add events
                query = []
                if cell.start is not None:
                    x = datetime.combine(current_date, cell.start)
                    query.append('dtstart=%s' % DateTime.encode(x))
                if cell.end is not None:
                    x = datetime.combine(current_date, cell.end)
                    query.append('dtend=%s' % DateTime.encode(x))
                ns_cell['newurl'] = '%s?%s' % (url, '&'.join(query))
            ns_cells.append(ns_cell)
        ns_rows.append({'cells': ns_cells})

    return ns_rows, total_ncols
Пример #12
0
 def get_date(self):
     try:
         return DateTime.decode(self.value)
     except Exception:
         return datetime.today()
Пример #13
0
class Datetime_Field(Metadata_Field):
    datatype = DateTime()
    widget = DatetimeWidget
    rest_type = 'datetime'
Пример #14
0
class ShopUser(User, DynamicFolder):

    class_version = '20100720'
    class_id = 'user'

    # Views
    manage = ShopUser_Manage()
    profile = ShopUser_Profile()
    public_profile = ShopUser_PublicProfile()
    edit_account = ShopUser_EditAccount()
    edit_group = ShopUser_EditGroup()
    viewbox = ShopUser_Viewbox()

    # Confirm registration
    confirm_registration = Shop_UserConfirmRegistration()
    send_confirmation_view = Shop_UserSendConfirmation()

    # Orders views
    orders_view = ShopUser_OrdersView()
    order_view = ShopUser_OrderView()

    # Addresses views
    addresses_book = Addresses_Book(access='is_allowed_to_edit')
    edit_address = ShopUser_EditAddress()
    add_address = ShopUser_AddAddress()

    add_image = CurrentFolder_AddImage()

    # Base schema / widgets
    base_schema = merge_dicts(
        User.get_metadata_schema(),
        lastname=Unicode(title=MSG(u'Lastname')),
        firstname=Unicode(title=MSG(u'Firstname')),
        email=Email(title=MSG(u'Email')),
        ctime=DateTime(title=MSG(u'Register date')),
        last_time=DateTime(title=MSG(u'Last connection')),
        gender=Civilite(title=MSG(u"Civility")),
        phone1=String(mandatory=True, title=MSG(u'Phone1')),
        phone2=String(title=MSG(u'Phone2')))

    base_widgets = [
        TextWidget('email', title=MSG(u"Email")),
        SelectRadio('gender', title=MSG(u"Civility"), has_empty_option=False),
        TextWidget('lastname', title=MSG(u"Lastname")),
        TextWidget('firstname', title=MSG(u"Firstname")),
        TextWidget('phone1', title=MSG(u"Phone number")),
        TextWidget('phone2', title=MSG(u"Mobile"))
    ]

    base_items = [{
        'name': 'account',
        'title': MSG(u"Edit my account"),
        'href': ';edit_account',
        'img': '/ui/icons/48x48/card.png'
    }, {
        'name': 'preferences',
        'title': MSG(u'Edit my preferences'),
        'href': ';edit_preferences',
        'img': '/ui/icons/48x48/preferences.png'
    }, {
        'name': 'password',
        'title': MSG(u'Edit my password'),
        'href': ';edit_password',
        'img': '/ui/icons/48x48/lock.png'
    }, {
        'name': 'addresses',
        'title': MSG(u'My addresses book'),
        'href': ';addresses_book',
        'img': '/ui/icons/48x48/tasks.png'
    }, {
        'name': 'orders',
        'title': MSG(u'Orders history'),
        'href': ';orders_view',
        'img': '/ui/shop/images/bag_green.png'
    }]

    @staticmethod
    def _make_resource(cls, folder, name, *args, **kw):
        ctime = datetime.now()
        User._make_resource(cls, folder, name, ctime=ctime, *args, **kw)

    base_class_views = [
        'profile', 'addresses_book', 'edit_account', 'orders_view',
        'edit_preferences', 'edit_password'
    ]

    @property
    def class_views(self):
        context = get_context()
        # Back-Office
        hostname = context.uri.authority
        if hostname[:6] == 'admin.':
            return ['manage'] + self.base_class_views
        if hostname[:6] == 'www.aw':
            # XXX Add a configurator for public profil
            return ['public_profile'] + self.base_class_views
        return self.base_class_views

    @classmethod
    def get_metadata_schema(cls):
        return merge_dicts(DynamicFolder.get_metadata_schema(),
                           cls.base_schema,
                           is_enabled=Boolean(title=MSG(u'Enabled')),
                           user_group=UserGroup_Enumerate(title=MSG(u'Group')))

    @classmethod
    def get_dynamic_schema(cls):
        context = get_context()
        self = context.resource
        if (hasattr(context, 'view')
                and issubclass(context.view.__class__, RegisterForm)):
            group = context.view.get_group(context)
            return group.get_dynamic_schema()
        if not isinstance(self, User):
            self = context.user
        if self is None:
            group = context.site_root.get_resource('shop/groups/default')
        else:
            group = self.get_group(context)
        # By default we use default group
        if group is None:
            group = context.site_root.get_resource('shop/groups/default')
        return group.get_dynamic_schema()

    @classmethod
    def get_dynamic_widgets(cls):
        context = get_context()
        self = context.resource
        if issubclass(context.view.__class__, RegisterForm):
            group = context.view.get_group(context)
            return group.get_dynamic_widgets()
        if not isinstance(self, User):
            self = context.user
        if self is None:
            return []
        group = self.get_group(context)
        return group.get_dynamic_widgets()

    def _get_catalog_values(self):
        values = User._get_catalog_values(self)
        values['ctime'] = self.get_property('ctime')
        values['last_time'] = self.get_property('last_time')
        values['user_group'] = str(self.get_property('user_group'))
        values['is_enabled'] = self.get_property('is_enabled')
        return values

    def get_public_title(self):
        try:
            return self.get_dynamic_property('pseudo')
        except:
            # XXX Fix that bug
            return MSG(u'Unknow')

    def get_document_types(self):
        return []

    def save_form(self, schema, form):
        dynamic_schema = self.get_dynamic_schema()
        metadata_schema = self.get_metadata_schema()
        for key in schema:
            if key in ['password', 'user_must_confirm']:
                continue
            elif (key not in metadata_schema and key not in dynamic_schema):
                continue
            value = form[key]
            if value is None:
                self.del_property(value)
                continue
            datatype = schema[key]
            if issubclass(datatype, (String, Unicode)):
                value = value.strip()
            self.set_property(key, value)

    confirmation_txt = MSG(
        u"To finalize your inscription you have to confirm your identity "
        u"by clicking this link:"
        u"\n"
        u"\n {uri}\n"
        u"\n"
        u"(Your identity confirmation key is {key})")

    def send_register_confirmation(self, context, need_email_validation=False):
        # Get group
        group = self.get_group(context)
        # Get mail subject and body
        subject = group.get_register_mail_subject()
        text = group.get_register_mail_body()
        # Registration need validation ?
        if need_email_validation:
            key = generate_password(30)
            self.set_property('user_must_confirm', key)
            # Build the confirmation link
            confirm_url = deepcopy(context.uri)
            path = '/users/%s/;confirm_registration' % self.name
            confirm_url.path = Path(path)
            confirm_url.query = {'key': key, 'username': self.get_login_name()}
            confirm_url = str(confirm_url)
            text += '\n\n'
            text += self.confirmation_txt.gettext(uri=confirm_url, key=key)

        # Send mail
        context.root.send_email(to_addr=self.get_property('email'),
                                subject=subject,
                                text=text)

    def get_group(self, context):
        user_group = self.get_property('user_group')
        return context.root.get_resource(user_group)

    def to_text(self):
        texts = []
        for key, datatype in (self.get_dynamic_schema().items() +
                              self.get_metadata_schema().items()):
            if key == 'password':
                continue
            value = self.get_property(key)
            if value:
                value = datatype.encode(value)
                value = unicode(value, 'utf-8')
                texts.append(value)
        return u'\n'.join(texts)

    def get_namespace(self, context):
        root = context.root
        # Get dynamic user values
        dynamic_user_value = ResourceDynamicProperty()
        dynamic_user_value.schema = self.get_dynamic_schema()
        dynamic_user_value.resource = self
        # Module
        shop_module = ModuleLoader()
        shop_module.context = context
        shop_module.here = self
        # Get modules items
        modules_items = []
        search = context.root.search(is_shop_user_module=True)
        for brain in search.get_documents():
            shop_user_module = root.get_resource(brain.abspath)
            modules_items.append({
                'name': shop_user_module.element_name,
                'title': shop_user_module.element_title,
                'href': shop_user_module.element_name,
                'img': shop_user_module.class_icon48
            })
        # Ctime
        ctime = self.get_property('ctime')
        accept = context.accept_language
        # ACLS
        ac = self.get_access_control()
        is_authenticated = ac.is_authenticated(context.user, self)
        is_owner = context.user is not None and context.user.name == self.name
        # Build namespace
        return {
            'name': self.name,
            'link': context.get_link(self),
            'module': shop_module,
            'dynamic_user_value': dynamic_user_value,
            'is_owner': is_owner,
            'is_authenticated': is_authenticated,
            'ctime':
            format_datetime(ctime, accept) if ctime else None,  # XXX Why ?
            'items': self.base_items + modules_items
        }

    #############################
    # Api
    #############################
    def send_confirm_url(self, context, email, subject, text, view):
        # XXX We override send_confirm_url to send mail
        # without setting subject with host, to use shop_uri
        # and not backoffice_uri (Since webmaster click from backoffice uri)
        # and to use good from_addr

        # Set the confirmation key
        if self.has_property('user_must_confirm'):
            key = self.get_property('user_must_confirm')
        else:
            key = generate_password(30)
            self.set_property('user_must_confirm', key)

        # Build the confirmation link
        shop = get_shop(context.resource)
        base_uri = shop.get_property('shop_uri')
        confirm_url = get_reference(base_uri)
        path = '/users/%s/%s' % (self.name, view)
        confirm_url.path = Path(path)
        confirm_url.query = {'key': key, 'username': self.get_login_name()}
        confirm_url = str(confirm_url)
        text = text.gettext(uri=confirm_url, key=key)
        # Get from_addr
        site_root = context.site_root
        if site_root.get_property('emails_from_addr'):
            user_name = site_root.get_property('emails_from_addr')
            user = self.get_resource('/users/%s' % user_name)
            from_addr = user.get_title(), user.get_property('email')
        else:
            from_addr = context.server.smtp_from
        # Subject
        subject = u'[%s] %s' % (base_uri, subject.gettext())
        # Send email
        context.root.send_email(email,
                                subject,
                                from_addr=from_addr,
                                text=text,
                                subject_with_host=False)

    ###############################
    # Computed schema
    ###############################
    computed_schema = {
        'nb_orders': Integer(title=MSG(u'Nb orders (even cancel)')),
        'address': Unicode(title=MSG(u'Last known user address'))
    }

    @property
    def nb_orders(self):
        # XXX Orders states
        root = self.get_root()
        queries = [
            PhraseQuery('format', 'order'),
            PhraseQuery('customer_id', self.name)
        ]
        return len(root.search(AndQuery(*queries)))

    @property
    def address(self):
        # XXX We should have a default address ?
        context = get_context()
        shop = get_shop(context.resource)
        addresses_h = shop.get_resource('addresses').handler
        records = addresses_h.search(user=self.name)
        if len(records) == 0:
            return None
        record = records[0]
        addresse = addresses_h.get_record_namespace(record.id)
        addr = u''
        for key in ['address_1', 'address_2', 'zipcode', 'town', 'country']:
            try:
                addr += u'%s ' % addresse[key]
            except Exception:
                # XXX Why ?
                addr += u'XXX'
        return addr
Пример #15
0
        # 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


register_resource_class(ShopUser)
register_resource_class(ShopUserFolder)
register_resource_class(Customers)
register_resource_class(AuthentificationLogs)

register_field('last_time', DateTime(is_stored=True))
register_field('user_group', String(is_indexed=True))
register_field('is_enabled', Boolean(is_indexed=True))
Пример #16
0
    rest_query = Rest_Query
    rest_create = Rest_Create
    rest_read = Rest_Read
    rest_update = Rest_Update
    rest_delete = Rest_Delete
    rest_schema = Rest_Schema


###########################################################################
# Register read-only fields
###########################################################################

# Path related fields
register_field('abspath', String(indexed=True, stored=True))
register_field('abspath_depth', Integer(indexed=True, stored=True))
register_field('parent_paths', String(multiple=True, indexed=True))
register_field('name', String(stored=True, indexed=True))
# Class related fields
register_field('format', String(indexed=True, stored=True))
register_field('base_classes', String(multiple=True, indexed=True))
# Referential integrity
register_field('links', String(multiple=True, indexed=True))
register_field('onchange_reindex', String(multiple=True, indexed=True))
# Full text search
register_field('text', Unicode(indexed=True))
# Various classifications
register_field('is_content', Boolean(indexed=True))
# Time events
register_field('next_time_event', DateTime(stored=True))
register_field('next_time_event_payload', String(stored=True))
Пример #17
0
    stock = Products_Stock()
    new_product = Product_NewProduct()

    def can_paste(self, source):
        return isinstance(source, Product)

    def get_document_types(self):
        return []


# Register fields
register_field('reference', String(is_indexed=True, is_stored=True))
register_field('stock_quantity', Integer(is_indexed=True, is_stored=True))
register_field('manufacturer', String(is_indexed=True))
register_field('supplier', String(is_indexed=True, multiple=True))
register_field('product_model', String(is_indexed=True, is_stored=True))
register_field('has_images', Boolean(is_indexed=True, is_stored=True))
register_field('has_reduction', Boolean(is_indexed=True))
register_field('not_buyable_by_groups', String(is_indexed=True, multiple=True))
register_field('ctime', DateTime(is_stored=True, is_indexed=True))
register_field('data', Unicode(is_indexed=True))
register_field('ht_price', Decimal(is_indexed=True, is_stored=True))
register_field('ttc_price', Decimal(is_indexed=True, is_stored=True))
# XXX xapian can't sort decimal
register_field('stored_price', Integer(is_indexed=False, is_stored=True))
register_field('stored_weight', Integer(is_indexed=False, is_stored=True))

# Register resources
register_resource_class(Product)
register_resource_class(Products)