예제 #1
0
class ModelField_Base(DBResource):

    # Fields
    required = Boolean_Field(title=MSG(u'Required'))
    multiple = Boolean_Field(title=MSG(u'Multiple'))
    tip = Text_Field(title=MSG(u'Tip'))

    def get_owner(self):
        return self.parent.get_owner()

    def set_value(self, name, value, language=None):
        proxy = super(ModelField_Base, self)
        has_changed = proxy.set_value(name, value, language)
        if has_changed:
            class_id = str(self.parent.abspath)
            self.database._resources_registry.pop(class_id, None)

        return has_changed

    def get_field_kw(self, field):
        return {
            'multiple': self.get_value('multiple'),
            'required': self.get_value('required'),
            'title': self.get_title(),
            'widget': field.widget(tip=self.get_value('tip'))
        }

    # Views
    _fields = ['title', 'required', 'multiple', 'tip']
    new_instance = AutoAdd(fields=_fields, automatic_resource_name=True)
    edit = AutoEdit(fields=_fields)
예제 #2
0
class Captcha(Folder):

    class_id = 'config-captcha'
    class_title = MSG(u'Captcha')
    class_description = MSG(u'Feature to protect from spammers')
    class_icon48 = '/ui/ikaaro/icons/48x48/captcha.png'

    # Fields
    captcha_type = Select_Field(
        required=True, title=MSG(u"Captcha type"), datatype=CaptchaType,
        widget = Select_CaptchaWidget(has_empty_option=False))

    # Views
    class_views = ['edit']
    edit = AutoEdit(title=MSG(u'Edit captcha'), fields=['captcha_type'])

    # Configuration
    config_name = 'captcha'
    config_group = 'access'


    def init_resource(self, **kw):
        super(Captcha, self).init_resource(**kw)
        # Init several captcha
        self.make_resource('question', Captcha_Question)
        self.make_resource('recaptcha', Captcha_Recaptcha)


    def get_captcha(self):
        captcha_type = self.get_value('captcha_type')
        return self.get_resource(captcha_type)
예제 #3
0
class SEO(DBResource):

    class_id = 'config-seo'
    class_title = MSG(u'Search Engine Optimization')
    class_description = seo_description
    class_icon16 = '/ui/ikaaro/icons/16x16/search.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/search.png'

    # Fields
    google_site_verification = Char_Field(
        title=MSG(u'Google site verification key'))
    yahoo_site_verification = Char_Field(
        title=MSG(u'Yahoo site verification key'))
    bing_site_verification = Char_Field(
        title=MSG(u'Bing site verification key'))

    # Views
    class_views = ['edit']
    edit = AutoEdit(title=MSG(u'Search engine optimization'),
                    description=seo_description,
                    fields=[
                        'google_site_verification', 'yahoo_site_verification',
                        'bing_site_verification'
                    ])

    # Configuration
    config_name = 'seo'
    config_group = 'webmaster'
예제 #4
0
class AccessRule(DBResource):

    class_id = 'config-access-rule'
    class_title = MSG(u'Access rule')

    # Fields
    group = Select_Field(required=True,
                         title=MSG(u'User group'),
                         datatype=Groups_Datatype,
                         indexed=True,
                         stored=True)
    permission = Permissions_Field(required=True, indexed=True, stored=True)
    search_path = Path_Field(indexed=True, stored=True)
    search_path_depth = PathDepth_Field()
    search_format = SearchFormat_Field(indexed=True, stored=True)

    # Views
    class_views = ['edit', 'results', 'commit_log']
    _fields = [
        'group', 'permission', 'search_path', 'search_path_depth',
        'search_format'
    ]
    new_instance = AutoAdd(fields=_fields, automatic_resource_name=True)
    edit = AutoEdit(fields=_fields)
    results = AccessRule_Results

    # API
    def get_search_query(self):
        permission = self.get_value('permission')
        if permission == 'add':
            names = ['path']
        else:
            names = ['path', 'format']

        # Query
        query = AndQuery()
        for name in names:
            field_name = 'search_%s' % name
            field = self.get_field(field_name)
            value = field.get_value(self, field_name)
            if not value:
                continue

            if name == 'path':
                depth = self.get_value('search_path_depth')
                depth = None if depth == '*' else int(depth)
                subquery = get_base_path_query(value, 0, depth)
            elif field.multiple:
                err = "access rules don't yet support multiple fields"
                raise NotImplementedError, err
            else:
                subquery = PhraseQuery(name, value)

            query.append(subquery)

        # Ok
        return query
예제 #5
0
class ConfigRegister(DBResource):

    class_id = 'config-register'
    class_title = MSG(u'User registration')
    class_description = MSG(u'Configuration of the user registration process.')
    class_icon48 = '/ui/ikaaro/icons/48x48/signin.png'

    # Fields
    is_open = Boolean_Field(default=False,
                            title=MSG(u'Users can register by themselves'))
    tos = HTMLFile_Field(title=MSG(u"Terms of service"))

    # Views
    class_views = ['edit']
    edit = AutoEdit(title=class_title, fields=['is_open', 'tos'])

    # Configuration
    config_name = 'register'
    config_group = 'access'
예제 #6
0
class Captcha_Question(DBResource):

    class_id = 'config-captcha-question'
    class_title = MSG(u'Captcha question')
    class_views = ['edit']

    # Fields
    question = captcha_field(default=u'2 + 3', title=MSG(u"Question"))
    answer = captcha_field(default=u'5', title=MSG(u"Answer"))

    # Views
    edit = AutoEdit(fields=['question', 'answer'])

    # API
    def get_widget(self):
        return QuestionCaptchaWidget(question=self.get_value('question'))

    def get_datatype(self):
        return QuestionCaptchaDatatype(answer=self.get_value('answer'))
예제 #7
0
class Captcha_Recaptcha(DBResource):

    class_id = 'config-captcha-recaptcha'
    class_title = MSG(u'Recaptcha')
    class_views = ['edit']

    # Fields
    public_key = captcha_field(title=MSG(u"Recaptcha public key"))
    private_key = captcha_field(title=MSG(u"Recaptcha private key"))

    # Views
    edit = AutoEdit(fields=['public_key', 'private_key'])

    # API
    def get_widget(self):
        return RecaptchaWidget(public_key=self.get_value('public_key'))

    def get_datatype(self):
        return RecaptchaDatatype(private_key=self.get_value('private_key'))
예제 #8
0
class ModelField_Standard(ModelField_Base):

    class_id = 'model-field-standard'
    class_title = MSG(u'Standard field')

    # Fields
    field_type = FieldType_Field(required=True, title=MSG(u'Field type'))

    # API
    def build_field(self):
        field_type = self.get_value('field_type')
        field = self.field_type.fields_map[field_type]
        field_kw = self.get_field_kw(field)
        return field(**field_kw)

    # Views
    class_views = ['edit', 'commit_log']
    _fields = ModelField_Base._fields + ['field_type']
    new_instance = ModelField_Base.new_instance(fields=_fields)
    edit = AutoEdit(fields=_fields)
예제 #9
0
class Theme(Folder):

    class_id = 'config-theme'
    class_title = MSG(u'Theme')
    class_description = MSG(u'Allow to customize ikaaro skin')
    class_icon48 = 'icons/48x48/theme.png'

    # Fields
    logo = File_Field(title=MSG(u'Logo'))
    favicon = File_Field(title=MSG(u'Favicon'))
    banner = File_Field(title=MSG(u'Banner'))
    style = TextFile_Field(title=MSG(u'CSS Style'), class_handler=TextFile)

    def init_resource(self, **kw):
        super(Theme, self).init_resource(**kw)
        # Access
        self.set_value('share', 'everybody')
        # CSS file
        path = get_abspath('ui/themes/style.css')
        data = open(path).read()
        self.set_value('style', data)
        # Logo
        path = get_abspath('ui/themes/logo.png')
        data = open(path).read()
        self.set_value('logo', data)
        # Banner
        path = get_abspath('ui/themes/banner.jpg')
        data = open(path).read()
        self.set_value('banner', data)

    # Views
    class_views = [
        'edit', 'browse_content', 'preview_content', 'links', 'backlinks',
        'commit_log'
    ]

    edit = AutoEdit(fields=['favicon', 'logo', 'banner', 'style', 'share'])

    # Configuration
    config_name = 'theme'
    config_group = 'webmaster'
예제 #10
0
class ModelField_Choices(OrderedFolder, ModelField_Base):

    class_id = 'model-field-choices'
    class_title = MSG(u'Choices field')

    # Fields
    choices_widget = ChoicesWidget_Field(title=MSG(u'Widget to use'),
                                         required=True)

    def get_document_types(self):
        return [Choice]

    # API
    def build_field(self):
        options = [{
            'name': x.name,
            'value': x.get_title()
        } for x in self.get_resources_in_order()]
        field = Select_Field(widget=self.get_widget())
        field_kw = self.get_field_kw(field)
        return field(options=options, **field_kw)

    def get_widget(self):
        if self.get_value('choices_widget') == 'radio-checkbox':
            if self.get_value('multiple'):
                return CheckboxWidget
            return RadioWidget
        return SelectWidget

    # Views
    class_views = ['browse_content', 'add_choice', 'edit', 'commit_log']
    browse_content = ModelField_Choices_Browse
    add_choice = NewResource_Local(title=MSG(u'Add choice'))

    _fields = ModelField_Base._fields + ['choices_widget']
    new_instance = ModelField_Base.new_instance(fields=_fields)
    edit = AutoEdit(fields=_fields)
예제 #11
0
class DBResource(Resource):

    class_version = '20071215'
    class_description = None
    class_icon16 = 'icons/16x16/resource.png'
    class_icon48 = 'icons/48x48/resource.png'
    class_views = []
    context_menus = []

    def __init__(self, metadata):
        self.metadata = metadata

    def __eq__(self, resource):
        if not isinstance(resource, DBResource):
            error = "cannot compare DBResource and %s" % type(resource)
            raise TypeError, error

        return self.abspath == resource.abspath

    def __ne__(self, node):
        return not self.__eq__(node)

    #######################################################################
    # API / Tree
    #######################################################################
    @property
    def database(self):
        return self.metadata.database

    @lazy
    def parent(self):
        abspath = self.abspath
        if len(abspath) == 0:
            return None
        return self.get_resource(abspath[:-1])

    @property
    def name(self):
        return self.abspath.get_name()

    def get_root(self):
        return self.get_resource('/')

    def get_pathto(self, resource):
        return self.abspath.get_pathto(resource.abspath)

    #######################################################################
    # API / Folderish
    #######################################################################
    __fixed_handlers__ = []  # Resources that cannot be removed

    @property
    def handler(self):
        cls = FolderHandler
        key = self.metadata.key[:-9]
        handler = self.database.get_handler(key, cls=cls, soft=True)
        if handler is None:
            handler = cls()
            self.database.push_phantom(key, handler)

        return handler

    def get_handlers(self):
        """Return all the handlers attached to this resource, except the
        metadata.
        """
        handlers = [self.handler]
        # Fields
        for name, field in self.get_fields():
            if issubclass(field, File_Field):
                value = field.get_value(self, name)
                if value is not None:
                    handlers.append(value)

        # Ok
        return handlers

    def _get_names(self):
        folder = self.handler
        return [
            x[:-9] for x in folder.get_handler_names() if x[-9:] == '.metadata'
        ]

    def get_names(self, path='.'):
        resource = self.get_resource(path)
        return resource._get_names()

    def get_resource(self, path, soft=False):
        if type(path) is not Path:
            path = Path(path)

        # 1. Get the metadata
        if path.is_absolute():
            abspath = path
        else:
            abspath = self.abspath.resolve2(path)

        return self.database.get_resource(abspath, soft=soft)

    def get_resources(self, path='.'):
        here = self.get_resource(path)
        for name in here._get_names():
            yield here.get_resource(name)

    def make_resource_name(self):
        max_id = -1
        for name in self.get_names():
            # Mixing explicit and automatically generated names is allowed
            try:
                id = int(name)
            except ValueError:
                continue
            if id > max_id:
                max_id = id

        return str(max_id + 1)

    def make_resource(self, name, cls, soft=False, **kw):
        # Automatic name
        if name is None:
            name = self.make_resource_name()

        # Make a resource somewhere else
        if '/' in name:
            path = dirname(name)
            name = basename(name)
            resource = self.get_resource(path)
            resource.make_resource(name, cls, soft=soft, **kw)
            return

        # Soft
        if soft is True:
            resource = self.get_resource(name, soft=True)
            if resource:
                return resource

        # Make the metadata
        metadata = Metadata(cls=cls)
        self.handler.set_handler('%s.metadata' % name, metadata)
        metadata.set_property('mtime', get_context().timestamp)
        # Initialize
        resource = self.get_resource(name)
        self.database.add_resource(resource)
        resource.init_resource(**kw)
        # Ok
        return resource

    def del_resource(self, name, soft=False, ref_action='restrict'):
        """ref_action allows to specify which action is done before deleting
        the resource.
        ref_action can take 2 values:
        - 'restrict' (default value): do an integrity check
        - 'force': do nothing
        """
        database = self.database
        resource = self.get_resource(name, soft=soft)
        if soft and resource is None:
            return

        # Referential action
        if ref_action == 'restrict':
            # Check referencial-integrity (FIXME Check sub-resources too)
            path = str(resource.abspath)
            query_base_path = get_base_path_query(path)
            query = AndQuery(PhraseQuery('links', path),
                             NotQuery(query_base_path))
            results = database.search(query)
            # A resource may have been updated in the same transaction,
            # so not yet reindexed: we need to check that the resource
            # really links.
            for referrer in results.get_resources():
                if path in referrer.get_links():
                    err = 'cannot delete, resource "%s" is referenced'
                    raise ConsistencyError, err % path
        elif ref_action == 'force':
            # Do not check referencial-integrity
            pass
        else:
            raise ValueError, 'Incorrect ref_action "%s"' % ref_action

        # Events, remove
        path = str(resource.abspath)
        database.remove_resource(resource)
        # Remove
        fs = database.fs
        for handler in resource.get_handlers():
            # Skip empty folders and phantoms
            if fs.exists(handler.key):
                database.del_handler(handler.key)
        self.handler.del_handler('%s.metadata' % name)
        # Clear cookie
        context = get_context()
        cut, paths = context.get_cookie('ikaaro_cp', datatype=CopyCookie)
        if path in paths:
            context.del_cookie('ikaaro_cp')

    def copy_resource(self, source, target):
        raise NotImplementedError

    def move_resource(self, source, target):
        raise NotImplementedError

    def traverse_resources(self):
        yield self
        for name in self._get_names():
            resource = self.get_resource(name)
            for x in resource.traverse_resources():
                yield x

    #######################################################################
    # API / Views
    #######################################################################
    def get_default_view_name(self):
        views = self.class_views
        if not views:
            return None
        context = get_context()
        for view_name in views:
            view = getattr(self, view_name, None)
            if context.is_access_allowed(self, view):
                return view_name
        return views[0]

    def get_view(self, name, query=None):
        # To define a default view, override this
        if name is None:
            name = self.get_default_view_name()
            if name is None:
                return None

        # Explicit view, defined by name
        view = getattr(self, name, None)
        if is_prototype(view, BaseView):
            context = get_context()
            view = view(resource=self, context=context)  # bind
            return view

        return None

    def get_context_menus(self):
        return self.context_menus

    ########################################################################
    # Properties
    ########################################################################
    def get_value(self, name, language=None):
        field = self.get_field(name)
        if field is None:
            return None
        return field.get_value(self, name, language)

    def set_value(self, name, value, language=None, **kw):
        field = self.get_field(name)
        if field.multilingual and language is None:
            raise ValueError, 'Field %s is multilingual' % name
        if field is None:
            raise ValueError, 'Field %s do not exist' % name
        return field.set_value(self, name, value, language, **kw)

    def get_value_title(self, name, language=None):
        field = self.get_field(name)
        if field is None:
            return None
        return field.get_value_title(self, name, language)

    def get_brain_value(self, name):
        brain = get_context().database.search(
            PhraseQuery('abspath', str(self.abspath))).get_documents()[0]
        return getattr(brain, name, None)

    def get_html_field_body_stream(self, name, language=None):
        """Utility method, returns the stream for the given html field.
        """
        # 1. Check it is an html-file field
        field = self.get_field(name)
        if not is_prototype(field, HTMLFile_Field):
            raise ValueError, 'expected html-file field'

        # 2. Get the handler
        handler = field.get_value(self, name, language)
        if not handler:
            handler = field.class_handler()

        # 3. Get the body
        body = handler.get_body()
        if not body:
            raise ValueError, 'html file does not have a body'
        return body.get_content_elements()

    def get_property(self, name, language=None):
        property = self.metadata.get_property(name, language=language)
        if property:
            return property

        field = self.get_field(name)
        if field is None:
            return None

        default = field.get_default()
        if field.multiple:
            return [Property(x) for x in default]
        return Property(default)

    # XXX Backwards compatibility
    set_property = set_value

    def get_page_title(self):
        return self.get_title()

    def init_resource(self, **kw):
        """Return a Metadata object with sensible default values.
        """
        # Ownership
        owner = self.get_field('owner')
        if owner:
            user = get_context().user
            if user:
                self.set_value('owner', str(user.abspath))

        # Keyword parameters
        for name, value in kw.items():
            field = self.get_field(name)
            if field is None:
                raise ValueError, 'undefined field "%s"' % name
            if type(value) is dict:
                for lang in value:
                    field._set_value(self, name, value[lang], lang)
            else:
                field._set_value(self, name, value)

    def load_handlers(self):
        self.get_handlers()

    ########################################################################
    # Fields
    ########################################################################
    mtime = Datetime_Field(indexed=True, stored=True, readonly=True)
    last_author = Char_Field(indexed=False, stored=True, readonly=True)
    title = Text_Field(indexed=True, stored=True, title=MSG(u'Title'))
    description = Textarea_Field(indexed=True,
                                 title=MSG(u'Description'),
                                 hidden_by_default=True)
    subject = Text_Field(indexed=True,
                         title=MSG(u'Keywords'),
                         hidden_by_default=True)
    share = Share_Field

    @property
    def is_content(self):
        return self.parent.is_content

    def has_property(self, name, language=None):
        return self.metadata.has_property(name, language=language)

    def del_property(self, name):
        if self.has_property(name):
            self.database.change_resource(self)
            self.metadata.del_property(name)

    ########################################################################
    # Versioning
    ########################################################################
    def get_files_to_archive(self, content=False):
        metadata = self.metadata.key
        if content is True:
            folder = self.handler.key
            return [metadata, folder]
        return [metadata]

    def get_revisions(self,
                      n=None,
                      content=False,
                      author_pattern=None,
                      grep_pattern=None):
        if self.parent is None and content is True:
            files = None
        else:
            files = self.get_files_to_archive(content)

        worktree = self.database.worktree
        return worktree.git_log(files, n, author_pattern, grep_pattern)

    def get_owner(self):
        return self.get_value('owner')

    def get_share(self):
        return self.get_value('share')

    ########################################################################
    # Indexing
    ########################################################################
    def to_text(self):
        """This function must return:
           1) An unicode text.
            or
           2) A dict in a multilingual context:
              {'fr': u'....',
               'en': u'....' ....}
        """
        raise NotImplementedError

    def get_catalog_values(self):
        values = {}

        # Step 1. Automatically index fields
        languages = self.get_root().get_value('website_languages')
        for name, field in self.get_fields():
            if not field.indexed and not field.stored:
                continue

            if field.multilingual:
                value = {}
                for language in languages:
                    value[language] = field.get_value(self, name, language)
                values[name] = value
            else:
                values[name] = field.get_value(self, name)

        # Step 2. Index non-metadata properties
        # Path related fields
        abspath = self.abspath
        values['abspath'] = str(abspath)
        n = len(abspath)
        values['abspath_depth'] = n
        if n:
            values['parent_paths'] = [str(abspath[:i]) for i in range(n)]

        values['name'] = self.name
        values['is_content'] = self.is_content

        # Class related fields
        values['format'] = self.metadata.format
        values['base_classes'] = []
        for cls in self.__class__.__mro__:
            class_id = getattr(cls, 'class_id', None)
            if class_id:
                values['base_classes'].append(class_id)

        # Links to other resources
        values['owner'] = self.get_owner()
        values['share'] = self.get_share()
        values['links'] = list(self.get_links())
        values['onchange_reindex'] = self.get_onchange_reindex()

        # Full text
        context = get_context()
        try:
            server = context.server
        except AttributeError:
            server = None
        if server is not None and server.index_text:
            try:
                values['text'] = self.to_text()
            except NotImplementedError:
                pass
            except Exception:
                log = 'Indexation failed: %s' % abspath
                log_warning(log, domain='ikaaro')

        # Time events
        reminder, payload = self.next_time_event()
        values['next_time_event'] = reminder
        values['next_time_event_payload'] = dumps(payload)

        # Ok
        return values

    def get_onchange_reindex(self):
        return None

    #######################################################################
    # Time events
    #######################################################################
    def next_time_event(self):
        return None, None

    def time_event(self, payload):
        raise NotImplementedError

    #######################################################################
    # API
    #######################################################################
    def rename_handlers(self, new_name):
        """Consider we want to rename this resource to the given 'new_name',
        return the old a new names for all the attached handlers (except the
        metadata).

        This method is required by the "move_resource" method.
        """
        langs = self.get_resource('/').get_value('website_languages')

        aux = [(self.name, new_name)]
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field and issubclass(field, File_Field):
                old = '%s.%s' % (self.name, field_name)
                new = '%s.%s' % (new_name, field_name)
                if field.multilingual:
                    for language in langs:
                        aux.append(('%s.%s' % (old, language),
                                    '%s.%s' % (new, language)))
                else:
                    aux.append((old, new))

        return aux

    def _on_move_resource(self, source):
        """This method updates the links from/to other resources.  It is
        called when the resource has been moved and/or renamed.

        This method is called by 'Database._before_commit', the 'source'
        parameter is the place the resource has been moved from.
        """
        # (1) Update links to other resources
        self.update_incoming_links(Path(source))

        # (2) Update resources that link to me
        database = self.database
        target = self.abspath
        query = PhraseQuery('links', source)
        results = database.search(query).get_documents()
        for result in results:
            path = result.abspath
            path = database.resources_old2new.get(path, path)
            resource = self.get_resource(path)
            resource.update_links(source, target)

    def get_links(self):
        # Automatically from the fields
        languages = self.get_resource('/').get_value('website_languages')
        links = set()
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.get_links(links, self, field_name, languages)

        # Support for dynamic models
        class_id = self.metadata.format
        if class_id[0] == '/':
            links.add(class_id)

        # Ok
        return links

    def update_links(self, source, target):
        """The resource identified by 'source' is going to be moved to
        'target'.  Update our links to it.

        The parameters 'source' and 'target' are absolute 'Path' objects.
        """
        base = str(self.abspath)
        old_base = self.database.resources_new2old.get(base, base)
        old_base = Path(old_base)
        new_base = Path(base)
        languages = self.get_resource('/').get_value('website_languages')

        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.update_links(self, field_name, source, target, languages,
                                   old_base, new_base)

        self.database.change_resource(self)

    def update_incoming_links(self, source):
        """Update the relative links coming out from this resource after it
        was moved, so they are not broken. The old path is in parameter. The
        new path is "self.abspath".
        """
        languages = self.get_resource('/').get_value('website_languages')
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.update_incoming_links(self, field_name, source,
                                            languages)

    ########################################################################
    # Upgrade
    ########################################################################
    def get_next_versions(self):
        cls_version = self.class_version
        obj_version = self.metadata.version
        # Set zero version if the resource does not have a version
        if obj_version is None:
            obj_version = '00000000'

        # Get all the version numbers
        versions = []
        for cls in self.__class__.mro():
            for name in cls.__dict__.keys():
                if not name.startswith('update_'):
                    continue
                kk, version = name.split('_', 1)
                if len(version) != 8:
                    continue
                try:
                    int(version)
                except ValueError:
                    continue
                if version > obj_version and version <= cls_version:
                    versions.append(version)

        versions.sort()
        return versions

    def update(self, version):
        # Action
        getattr(self, 'update_%s' % version)()

        # If the action removes the resource, we are done
        metadata = self.metadata
        if metadata.key is None:
            return

        # Update version
        metadata.set_changed()
        metadata.version = version

    #######################################################################
    # Icons
    #######################################################################
    @classmethod
    def get_class_icon(cls, size=16):
        icon = getattr(cls, 'class_icon%s' % size, None)
        if icon is None:
            return None
        return '/ui/%s' % icon

    @classmethod
    def get_resource_icon(cls, size=16):
        icon = getattr(cls, 'icon%s' % size, None)
        if icon is None:
            return cls.get_class_icon(size)
        return ';icon%s' % size

    def get_method_icon(self, view, size='16x16', **kw):
        icon = getattr(view, 'icon', None)
        if icon is None:
            return None
        if callable(icon):
            icon = icon(self, **kw)
        return '/ui/icons/%s/%s' % (size, icon)

    #######################################################################
    # User interface
    #######################################################################
    def get_views(self):
        context = get_context()
        for name in self.class_views:
            view_name = name.split('?')[0]
            view = self.get_view(view_name)
            if context.is_access_allowed(self, view):
                yield name, view

    def get_title(self, language=None):
        title = self.get_value('title', language=language)
        if title:
            return title
        # Fallback to the resource's name
        return unicode(self.name)

    def get_edit_languages(self, context):
        root = self.get_root()
        site_languages = root.get_value('website_languages')
        default = root.get_default_edit_languages()

        # Can not use context.query[] because edit_language is not necessarily
        # defined
        datatype = String(multiple=True, default=default)
        edit_languages = context.get_query_value('edit_language', datatype)
        edit_languages = [x for x in edit_languages if x in site_languages]

        return edit_languages if edit_languages else default

    #######################################################################
    # Cut & Paste Resources
    #######################################################################
    def can_paste(self, source):
        """Is the source resource can be pasted into myself.
        Question is "can I handle this type of resource?"
        """
        raise NotImplementedError

    def can_paste_into(self, target):
        """Can I be pasted into the given target.
        Question is "Is this container compatible with myself?"
        """
        # No restriction by default. Functional modules will want to keep
        # their specific resources for them.
        return True

    # Views
    new_instance = AutoAdd(fields=['title', 'location'])
    edit = AutoEdit(fields=['title', 'description', 'subject', 'share'])
    remove = DBResource_Remove
    get_file = DBResource_GetFile
    get_image = DBResource_GetImage
    # Login/Logout
    login = LoginView
    logout = LogoutView
    # Popups
    add_image = DBResource_AddImage
    add_link = DBResource_AddLink
    add_media = DBResource_AddMedia
    # Commit log
    commit_log = DBResource_CommitLog
    changes = DBResource_Changes
    # Links
    backlinks = DBResource_Backlinks
    links = DBResource_Links
    # External editor
    http_put = Put_View
    http_delete = Delete_View
    # Rest (web services)
    rest_login = Rest_Login
    rest_query = Rest_Query
    rest_create = Rest_Create
    rest_read = Rest_Read
    rest_update = Rest_Update
    rest_delete = Rest_Delete
    rest_schema = Rest_Schema
예제 #12
0
class Folder(DBResource):

    class_id = 'folder'
    class_version = '20071215'
    class_title = MSG(u'Folder')
    class_description = MSG(u'Organize your files and documents with folders.')
    class_icon16 = '/ui/ikaaro/icons/16x16/folder.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/folder.png'
    class_views = ['view', 'browse_content', 'preview_content', 'edit',
                   'links', 'backlinks', 'commit_log']

    # Fields
    index = HTMLFile_Field(title=MSG(u'Index'))


    #########################################################################
    # Gallery properties
    SIZE_STEPS = (32, 48, 64, 128, 256, 512)


    def get_document_types(self):
        document_types = []
        for ancestor_class in reversed(self.__class__.__mro__):
            items = ancestor_class.__dict__.get('_register_document_types')
            if items:
                document_types.extend(items)

        # class_id to class
        database = self.database
        return [ database.get_resource_class(class_id)
                 for class_id in document_types ]


    #######################################################################
    # API
    #######################################################################
    def _make_file(self, name, filename, mimetype, body, default_language):
        from webpage import WebPage

        if type(name) is not str:
            raise TypeError, 'expected string, got %s' % repr(name)

        # Web Pages are first class citizens
        if mimetype == 'text/html':
            body = tidy_html(body)
            class_id = 'webpage'
        elif mimetype == 'application/xhtml+xml':
            class_id = 'webpage'
        else:
            class_id = mimetype
        cls = self.database.get_resource_class(class_id)

        # Special case: web pages
        kw = {'filename': filename, 'data': body}
        if issubclass(cls, WebPage):
            kk, kk, language = FileName.decode(filename)
            if language is None:
                text = XHTMLFile(string=body).to_text()
                language = guess_language(text) or default_language
            kw['data'] = {language: body}

        return self.make_resource(name, cls, **kw)


    def export_zip(self, paths):
        stringio = StringIO()
        archive = ZipFile(stringio, mode='w')

        def _add_resource(resource):
            for filename in resource.get_files_to_archive(True):
                if filename.endswith('.metadata'):
                    continue
                path = Path(self.handler.key).get_pathto(filename)
                archive.writestr(str(path), resource.handler.to_str())

        for path in paths:
            child = self.get_resource(path, soft=True)
            if child is None:
                continue
            # A Folder => we add its content
            if isinstance(child, Folder):
                for subchild in child.traverse_resources():
                    if subchild is None or isinstance(subchild, Folder):
                        continue
                    _add_resource(subchild)
            else:
                _add_resource(child)

        archive.close()
        return stringio.getvalue()


    def extract_archive(self, handler, default_language, filter=None,
                        postproc=None, update=False):
        change_resource = self.database.change_resource
        for path_str in handler.get_contents():
            # 1. Skip folders
            clean_path = "/".join([
              checkid(x) or 'file'
              if x else 'file' for x in path_str.split("/")])
            path = Path(clean_path)
            if path.endswith_slash:
                continue

            # Skip the owner file (garbage produced by microsoft)
            filename = path[-1]
            if filename.startswith('~$'):
                continue

            # 2. Create parent folders if needed
            folder = self
            for name in path[:-1]:
                name, title = process_name(name)
                subfolder = folder.get_resource(name, soft=True)
                if subfolder is None:
                    folder = folder.make_resource(name, Folder)
                    folder.set_value('title', title, default_language)
                elif not isinstance(subfolder, Folder):
                    raise RuntimeError, MSG_NAME_CLASH
                else:
                    folder = subfolder

            # 3. Find out the resource name and title, the file mimetype and
            # language
            mimetype = guess_mimetype(filename, 'application/octet-stream')
            name, extension, language = FileName.decode(filename)
            name, title = process_name(name)
            language = language or default_language
            # Keep the filename extension (except in webpages)
            if mimetype not in ('application/xhtml+xml', 'text/html'):
                name = FileName.encode((name, extension, None))

            # 4. The body
            body = handler.get_file(path_str)
            if filter:
                body = filter(path_str, mimetype, body)
                if body is None:
                    continue

            # 5. Update or make file
            file = folder.get_resource(name, soft=True)
            if file:
                if update is False:
                    msg = 'unexpected resource at {path}'
                    raise RuntimeError, msg.format(path=path_str)
                if mimetype == 'text/html':
                    body = tidy_html(body)
                    file_handler = file.get_handler(language)
                else:
                    file_handler = file.get_handler()
                old_body = file.handler.to_str()
                file_handler.load_state_from_string(body)
                if postproc:
                    postproc(file)
                # FIXME Comparing the bytes does not work for XML, so we use
                # this weak heuristic
                if len(old_body) != len(file.handler.to_str()):
                    change_resource(file)
            else:
                # Case 1: the resource does not exist
                file = folder._make_file(name, filename, mimetype, body,
                                         language)
                file.set_value('title', title, language=language)
                if postproc:
                    postproc(file)


    def can_paste(self, source):
        """Is the source resource can be pasted into myself.
        """
        allowed_types = tuple(self.get_document_types())
        return isinstance(source, allowed_types)


    def _resolve_source_target(self, source_path, target_path):
        if type(source_path) is not Path:
            source_path = Path(source_path)
        if type(target_path) is not Path:
            target_path = Path(target_path)

        # Load the handlers so they are of the right class, for resources
        # like that define explicitly the handler class.  This fixes for
        # instance copy&cut&paste of a tracker in a just started server.
        # TODO this is a work-around, there should be another way to define
        # explicitly the handler class.
        source = self.get_resource(source_path)
        for resource in source.traverse_resources():
            resource.load_handlers()

        return source_path, target_path


    def copy_resource(self, source_path, target_path, exclude_patterns=None):
        # Find out the source and target absolute URIs
        source_path, target_path = self._resolve_source_target(source_path,
                                                               target_path)

        # Get the source and target resources
        source = self.get_resource(source_path)
        parent_path = target_path.resolve2('..')
        target_parent = self.get_resource(parent_path)

        # Check compatibility
        if (not target_parent.can_paste(source)
                or not source.can_paste_into(target_parent)):
            message = 'resource type "{0}" cannot be copied into type "{1}"'
            message = message.format(source.class_title.gettext(),
                                     target_parent.class_title.gettext())
            raise ConsistencyError(message)

        # Copy the metadata
        folder = self.handler
        folder.copy_handler('%s.metadata' % source_path,
                            '%s.metadata' % target_path)

        # Copy the content
        database = self.database
        fs = database.fs
        new_name = target_path.get_name()
        for old_name, new_name in source.rename_handlers(new_name):
            if old_name is None:
                continue
            src_key = fs.resolve(source_path, old_name)
            dst_key = fs.resolve(target_path, new_name)
            if folder.has_handler(src_key):
                folder.copy_handler(src_key, dst_key, exclude_patterns)

        # Events, add
        resource = self.get_resource(target_path)
        database.add_resource(resource)
        # Set UUID
        resource.set_uuid()
        for x in resource.traverse_resources():
            x.set_uuid()
        # Ok
        return resource


    def move_resource(self, source_path, target_path):
        # Find out the source and target absolute URIs
        source_path, target_path = self._resolve_source_target(source_path,
                                                               target_path)

        # Get the source and target resources
        source = self.get_resource(source_path)
        parent_path = target_path.resolve2('..')
        target_parent = self.get_resource(parent_path)

        # Cannot move a resource to a subdirectory of itself
        abspath = self.abspath
        aux = source.abspath
        if aux.get_prefix(abspath) == aux:
            message = 'cannot move a resource to a subdirectory of itself'
            raise ConsistencyError, message

        # Check compatibility
        if (not target_parent.can_paste(source)
                or not source.can_paste_into(target_parent)):
            message = 'resource type "%r" cannot be moved into type "%r"'
            raise ConsistencyError, message % (source, target_parent)

        # Events, remove
        database = self.database
        new_path = self.abspath.resolve2(target_path)
        database.move_resource(source, new_path)

        # Move the metadata
        folder = self.handler
        folder.move_handler('%s.metadata' % source_path,
                            '%s.metadata' % target_path)
        # Move the content
        fs = database.fs
        new_name = target_path.get_name()
        for old_name, new_name in source.rename_handlers(new_name):
            if old_name is None:
                continue
            src_key = fs.resolve(source_path, old_name)
            dst_key = fs.resolve(target_path, new_name)
            if folder.has_handler(src_key):
                folder.move_handler(src_key, dst_key)


    def search_resources(self, cls=None, format=None):
        if cls is None:
            cls = DBResource

        for resource in self.get_resources():
            # Filter by base class
            if not isinstance(resource, cls):
                continue
            # Filter by class_id
            if format and resource.metadata.format != format:
                continue
            # All filters passed
            yield resource


    #######################################################################
    # User interface
    #######################################################################
    def get_view(self, name, query=None):
        # Add resource form
        if name == 'new_resource' and query:
            class_id = query.get('type')
            if class_id:
                cls = self.database.get_resource_class(class_id)
                view = cls.new_instance
                if is_prototype(view, BaseView):
                    context = get_context()
                    view = view(resource=self, context=context) # bind
                    # XXX Should we really check access here?
                    # Should raise forbidden, but callers are not ready.
                    root = context.root
                    user = context.user
                    if not root.has_permission(user, 'add', self, class_id):
                        return None
                    if not context.is_access_allowed(self, view):
                        return None
                    return view

        # Default
        return super(Folder, self).get_view(name, query)


    # Views
    view = Folder_View
    edit = AutoEdit(fields=['title', 'index', 'description', 'subject',
                            'share'])
    new_resource = Folder_NewResource
    browse_content = Folder_BrowseContent
    rename = Folder_Rename
    preview_content = Folder_PreviewContent
    thumb = Folder_Thumbnail
예제 #13
0
class MenuItem(OrderedFolder):

    class_id = 'config-menu-item'
    class_title = MSG(u'Menu')

    # Fields
    path = URI_Field(required=True, title=MSG(u'Path'),
                     widget=PathSelectorWidget)
    target = Target_Field

    def get_document_types(self):
        return [MenuItem]

    # Views
    class_views = ['edit', 'browse_content', 'add_menu', 'commit_log']
    _fields = ['title', 'path', 'target']
    new_instance = AutoAdd(fields=_fields)
    edit = AutoEdit(fields=_fields)
    browse_content = MenuItem_Browse
    add_menu = AddMenu


    # API
    def _is_allowed_to_access(self, context, uri):
        # Get the reference and path
        ref, path, view = split_reference(uri)

        # Broken entry
        if ref is None or path == '':
            return False

        # External link
        if ref.scheme:
            return True

        # Broken link
        resource = self.get_resource(path, soft=True)
        if resource is None:
            return False

        if view:
            # Remove the first '/;' of the view
            view = resource.get_view(view[2:], ref.query)
            if not view:
                return False
            # Check ACL
            return context.is_access_allowed(resource, view)

        # Check if the user can access to resource views
        # get_views checks ACLs with by calling is_access_allowed
        resource_views = list(resource.get_views())
        return len(resource_views) > 0


    def get_menu_namespace_level(self, context, url, use_first_child=False):
        menu_abspath = self.abspath
        here = context.resource
        here_abspath = here.abspath
        here_view_name = url[-1]
        here_abspath_and_view = '%s/%s' % (here_abspath, here_view_name)
        items = []

        for resource in self.get_resources_in_order():
            uri = resource.get_value('path')
            if not self._is_allowed_to_access(context, uri):
                continue
            ref, path, view = split_reference(uri)
            title = resource.get_value('title')
            target = resource.get_value('target')

            # Case 1: External link
            if ref.scheme:
                items.append({
                    'id': 'menu_%s' % resource.name,
                    'path': str(ref),
                    'real_path': None,
                    'title': title,
                    'description': None,
                    'in_path': False,
                    'active': False,
                    'class': None,
                    'target': target,
                    'items': []})
                continue

            # Case 2: Internal link
            # Sub level
            subtabs = resource.get_menu_namespace_level(context, url,
                                                        use_first_child)
            resource = self.get_resource(path, soft=True)
            item_id = 'menu_%s' % resource.name

            # Use first child by default we use the resource itself
            resource_path = path
            # Keep the real path to avoid highlight problems
            resource_original_path = path
            # use first child
            if use_first_child and subtabs:
                sub_path = subtabs[0]['real_path']
                # if the first child is an external link => skip it
                if sub_path is not None:
                    resource_path = sub_path

            # Set active, in_path
            active = in_path = False
            # add default view
            if view:
                resource_method = view[2:]
                item_id += '_%s' % resource_method
            else:
                resource_method = resource.get_default_view_name()
            resource_abspath_and_view = '%s/;%s' % (resource.abspath,
                                                    resource_method)
            if here_abspath_and_view == resource_abspath_and_view:
                active = True
            else:
                # Use the original path for the highlight
                res_abspath = menu_abspath.resolve2(resource_original_path)
                common_prefix = here_abspath.get_prefix(res_abspath)
                # Avoid to always set the root entree 'in_path'
                # If common prefix equals root abspath set in_path to False
                # otherwise compare common_prefix and res_abspath
                if common_prefix != Path('/'):
                    in_path = (common_prefix == res_abspath)

            # Build the new reference with the right path
            ref2 = deepcopy(ref)
            resource = self.get_resource(resource_path)
            ref2.path = context.get_link(resource)
            if view:
                ref2.path += view

            items.append({
                'id': item_id,
                'path': str(ref2),
                'real_path': resource.abspath,
                'title': title,
                'description': None, # FIXME
                'in_path': active or in_path,
                'active': active,
                'class': None,
                'target': target,
                'items': subtabs})

        # Set class
        x = None
        for i, item in enumerate(items):
            if item['active']:
                x = i
                break
            if item['in_path'] and x is None:
                x = i
                break
        if x is not None:
            items[x]['class'] = 'in-path'

        if len(items) > 0:
            # Add class "first" to the first item
            css = items[0]['class'] or ''
            items[0]['class'] = css + ' first'
            # Add class "last" to the last item
            css = items[-1]['class'] or ''
            items[-1]['class'] = css + ' last'

        return items
예제 #14
0
class User(DBResource):

    class_id = 'user'
    class_version = '20081217'
    class_title = MSG(u'User')
    class_icon16 = '/ui/ikaaro/icons/16x16/user.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/user.png'
    class_views = [
        'profile', 'edit_account', 'edit_preferences', 'edit_password',
        'edit_groups'
    ]

    # Fields
    firstname = Text_Field(multilingual=False,
                           indexed=True,
                           stored=True,
                           title=MSG(u'First Name'))
    lastname = Text_Field(multilingual=False,
                          indexed=True,
                          stored=True,
                          title=MSG(u'Last Name'))
    email = UserEmail_Field
    password = Password_Field(multiple=True)
    avatar = File_Field(title=MSG(u'Avatar'))
    user_language = Char_Field
    user_timezone = Char_Field
    user_state = UserState_Field
    groups = UserGroups_Field
    username = Char_Field(indexed=True, stored=True)  # Backwards compatibility

    # Remove some fields
    title = None
    description = None
    subject = None
    text = None

    ########################################################################
    # Indexing
    ########################################################################
    def get_catalog_values(self):
        values = super(User, self).get_catalog_values()

        # email domain
        email = self.get_value('email')
        if email and '@' in email:
            values['email_domain'] = email.split('@', 1)[1]

        # username (overrides default)
        values['username'] = self.get_login_name()

        # groups
        values['groups'] = self.get_value('groups')

        return values

    ########################################################################
    # API / Authentication
    ########################################################################
    def get_user_id(self):
        # Used by itools.web
        return str(self.name)

    def get_password(self):
        password = self.get_property('password')
        return password[-1] if password else None

    def get_auth_token(self):
        # Used by itools.web
        password = self.get_password()
        return password.value if password else None

    def authenticate(self, password):
        my_password = self.get_password()
        if my_password is None:
            return False
        algo = my_password.get_parameter('algo', 'sha1')
        salt = my_password.get_parameter('salt', '')

        password_hashed, salt = get_secure_hash(password, algo, salt)
        return password_hashed == my_password.value

    def _login(self, password, context):
        # We call this method '_login' to avoid a name clash with the login
        # view.

        if not self.authenticate(password):
            error = MSG_LOGIN_WRONG_NAME_OR_PASSWORD
        elif self.get_value('user_state') == 'inactive':
            error = ERROR(
                u'Your account has been canceled, contact the administrator '
                u' if you want to get access again.')
        else:
            error = None
            context.login(self)

        # To activate this feature set the lastlog field
        lastlog = self.get_field('lastlog')
        if lastlog:
            success = error is None
            self.set_value('lastlog', context.timestamp, success=success)

        # Ok
        return error

    def update_pending_key(self):
        state = self.get_property('user_state')
        if state.value == 'pending':
            # TODO Implement expiration
            return state.get_parameter('key')

        key = generate_password(30)
        self.set_value('user_state', 'pending', key=key)
        return key

    ########################################################################
    # API
    ########################################################################
    def get_owner(self):
        return str(self.abspath)

    def get_title(self, language=None):
        firstname = self.get_value('firstname')
        lastname = self.get_value('lastname')
        if firstname:
            if lastname:
                return '%s %s' % (firstname, lastname)
            return firstname
        if lastname:
            return lastname
        return self.get_login_name().decode('utf-8')

    login_name_property = 'email'

    def get_login_name(self):
        return self.get_value(self.login_name_property)

    def get_timezone(self):
        return self.get_value('user_timezone')

    #######################################################################
    # Views
    #######################################################################
    resend_confirmation = User_ResendConfirmation
    confirm_registration = User_ConfirmRegistration
    change_password_forgotten = User_ChangePasswordForgotten
    profile = User_Profile
    edit_account = User_EditAccount
    edit_preferences = User_EditPreferences
    edit_password = User_EditPassword
    edit_groups = AutoEdit(access='is_admin',
                           fields=['groups'],
                           title=MSG(u'Edit groups'))
예제 #15
0
class Folder(DBResource):

    class_id = 'folder'
    class_version = '20071215'
    class_title = MSG(u'Folder')
    class_description = MSG(u'Organize your files and documents with folders.')
    class_icon16 = '/ui/ikaaro/icons/16x16/folder.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/folder.png'
    class_views = ['view', 'browse_content', 'preview_content', 'edit',
                   'links', 'backlinks', 'commit_log']


    #########################################################################
    # Gallery properties
    #########################################################################
    SIZE_STEPS = (32, 48, 64, 128, 256, 512)


    #########################################################################
    # Folder specific API
    #########################################################################

    def get_document_types(self):
        document_types = []
        for ancestor_class in reversed(self.__class__.__mro__):
            items = ancestor_class.__dict__.get('_register_document_types')
            if items:
                document_types.extend(items)

        # class_id to class
        database = self.database
        return [ database.get_resource_class(class_id)
                 for class_id in document_types ]


    def traverse_resources(self):
        yield self
        for name in self._get_names():
            resource = self.get_resource(name)
            for x in resource.traverse_resources():
                yield x


    def make_resource_name(self):
        max_id = -1
        for name in self.get_names():
            # Mixing explicit and automatically generated names is allowed
            try:
                id = int(name)
            except ValueError:
                continue
            if id > max_id:
                max_id = id

        return str(max_id + 1)


    def make_resource(self, name, cls, soft=False, **kw):
        # Automatic name
        if name is None:
            name = self.make_resource_name()

        # Make a resource somewhere else
        if '/' in name:
            path = dirname(name)
            name = basename(name)
            resource = self.get_resource(path)
            resource.make_resource(name, cls, soft=soft, **kw)
            return

        # Soft
        if soft is True:
            resource = self.get_resource(name, soft=True)
            if resource:
                return resource

        # Make the metadata
        metadata = Metadata(cls=cls)
        self.handler.set_handler('%s.metadata' % name, metadata)
        metadata.set_property('mtime', get_context().timestamp)
        # Initialize
        resource = self.get_resource(name)
        self.database.add_resource(resource)
        resource.init_resource(**kw)
        # Ok
        return resource


    def del_resource(self, name, soft=False, ref_action='restrict'):
        """ref_action allows to specify which action is done before deleting
        the resource.
        ref_action can take 2 values:
        - 'restrict' (default value): do an integrity check
        - 'force': do nothing
        """
        database = self.database
        resource = self.get_resource(name, soft=soft)
        if soft and resource is None:
            return

        # Referential action
        if ref_action == 'restrict':
            # Check referencial-integrity
            path = str(resource.abspath)
            query = AndQuery(NotQuery(PhraseQuery('abspath', path)),
                             NotQuery(get_base_path_query(path)))
            sub_search = database.search(query)
            for sub_resource in resource.traverse_resources():
                path = str(sub_resource.abspath)
                query = PhraseQuery('links', path)
                results = sub_search.search(query)
                # A resource may have been updated in the same transaction,
                # so not yet reindexed: we need to check that the resource
                # really links.
                for referrer in results.get_resources():
                    if path in referrer.get_links():
                        err = 'cannot delete, resource "{}" is referenced'
                        raise ConsistencyError(err.format(path))
        elif ref_action == 'force':
            # Do not check referencial-integrity
            pass
        else:
            raise ValueError,('Incorrect ref_action "{}"'.format(ref_action))

        # Events, remove
        path = str(resource.abspath)
        database.remove_resource(resource)
        # Remove handlers
        for r in list(resource.traverse_resources()):
            for handler in [r.metadata] + r.get_fields_handlers():
                if database.has_handler(handler.key):
                    database.del_handler(handler.key)


    def _get_names(self):
        folder = self.handler
        for x in folder.get_handler_names():
            if x and x[-9:] == '.metadata':
                yield x[:-9]



    #######################################################################
    # API
    #######################################################################
    def _make_file(self, name, filename, mimetype, body, default_language):
        from webpage import WebPage

        if type(name) is not str:
            raise TypeError, 'expected string, got %s' % repr(name)

        # Web Pages are first class citizens
        if mimetype == 'text/html':
            body = tidy_html(body)
            class_id = 'webpage'
        elif mimetype == 'application/xhtml+xml':
            class_id = 'webpage'
        else:
            class_id = mimetype
        cls = self.database.get_resource_class(class_id)

        # Special case: web pages
        kw = {'filename': filename, 'data': body}
        if issubclass(cls, WebPage):
            kk, kk, language = FileName.decode(filename)
            if language is None:
                text = XHTMLFile(string=body).to_text()
                language = guess_language(text) or default_language
            kw['data'] = {language: body}

        return self.make_resource(name, cls, **kw)


    def export_zip(self, paths):
        stringio = StringIO()
        archive = ZipFile(stringio, mode='w')

        def _add_resource(resource):
            for filename in resource.get_files_to_archive(True):
                if filename.endswith('.metadata'):
                    continue
                path = Path(self.handler.key).get_pathto(filename)
                archive.writestr(str(path), resource.handler.to_str())

        for path in paths:
            child = self.get_resource(path, soft=True)
            if child is None:
                continue
            # A Folder => we add its content
            if isinstance(child, Folder):
                for subchild in child.traverse_resources():
                    if subchild is None or isinstance(subchild, Folder):
                        continue
                    _add_resource(subchild)
            else:
                _add_resource(child)

        archive.close()
        return stringio.getvalue()


    def extract_archive(self, handler, default_language, filter=None,
                        postproc=None, update=False):
        change_resource = self.database.change_resource
        for path_str in handler.get_contents():
            # 1. Skip folders
            clean_path = "/".join([
              checkid(x) or 'file'
              if x else 'file' for x in path_str.split("/")])
            path = Path(clean_path)
            if path.endswith_slash:
                continue

            # Skip the owner file (garbage produced by microsoft)
            filename = path[-1]
            if filename.startswith('~$'):
                continue

            # 2. Create parent folders if needed
            folder = self
            for name in path[:-1]:
                name, title = process_name(name)
                subfolder = folder.get_resource(name, soft=True)
                if subfolder is None:
                    folder = folder.make_resource(name, Folder)
                    folder.set_value('title', title, default_language)
                elif not isinstance(subfolder, Folder):
                    raise RuntimeError, MSG_NAME_CLASH
                else:
                    folder = subfolder

            # 3. Find out the resource name and title, the file mimetype and
            # language
            mimetype = guess_mimetype(filename, 'application/octet-stream')
            name, extension, language = FileName.decode(filename)
            name, title = process_name(name)
            language = language or default_language
            # Keep the filename extension (except in webpages)
            if mimetype not in ('application/xhtml+xml', 'text/html'):
                name = FileName.encode((name, extension, None))

            # 4. The body
            body = handler.get_file(path_str)
            if filter:
                body = filter(path_str, mimetype, body)
                if body is None:
                    continue

            # 5. Update or make file
            file = folder.get_resource(name, soft=True)
            if file:
                if update is False:
                    msg = 'unexpected resource at {path}'
                    raise RuntimeError, msg.format(path=path_str)
                if mimetype == 'text/html':
                    body = tidy_html(body)
                    file_handler = file.get_handler(language)
                else:
                    file_handler = file.get_handler()
                old_body = file.handler.to_str()
                file_handler.load_state_from_string(body)
                if postproc:
                    postproc(file)
                # FIXME Comparing the bytes does not work for XML, so we use
                # this weak heuristic
                if len(old_body) != len(file.handler.to_str()):
                    change_resource(file)
            else:
                # Case 1: the resource does not exist
                file = folder._make_file(name, filename, mimetype, body,
                                         language)
                file.set_value('title', title, language=language)
                if postproc:
                    postproc(file)


    def can_paste(self, source):
        """Is the source resource can be pasted into myself.
        """
        allowed_types = tuple(self.get_document_types())
        return isinstance(source, allowed_types)


    def _resolve_source_target(self, source_path, target_path):
        if type(source_path) is not Path:
            source_path = Path(source_path)
        if type(target_path) is not Path:
            target_path = Path(target_path)

        # Load the handlers so they are of the right class, for resources
        # like that define explicitly the handler class.  This fixes for
        # instance copy&cut&paste of a tracker in a just started server.
        # TODO this is a work-around, there should be another way to define
        # explicitly the handler class.
        source = self.get_resource(source_path)
        for resource in source.traverse_resources():
            resource.load_handlers()

        return source_path, target_path


    def copy_resource(self, source_path, target_path, exclude_patterns=None, check_if_authorized=True):
        # Find out the source and target absolute URIs
        source_path, target_path = self._resolve_source_target(source_path,
                                                               target_path)
        # Exclude patterns
        if exclude_patterns is None:
            exclude_patterns = []
        for exclude_pattern in exclude_patterns:
            if fnmatch.fnmatch(str(source_path), exclude_pattern):
                return
        # Get the source and target resources
        source = self.get_resource(source_path)
        childs = list(source.get_resources())
        parent_path = target_path.resolve2('..')
        target_parent = self.get_resource(parent_path)

        # Check compatibility
        if (check_if_authorized and
                (not target_parent.can_paste(source)
                 or not source.can_paste_into(target_parent))):
            message = u'resource type "{0}" cannot be copied into type "{1}"'
            message = message.format(source.class_title.gettext(),
                                     target_parent.class_title.gettext())
            raise ConsistencyError(message)

        # Copy the metadata
        folder = self.handler
        folder.copy_handler('%s.metadata' % source_path,
                            '%s.metadata' % target_path)

        # Copy the content
        database = self.database
        new_name = target_path.get_name()
        for old_name, new_name in source.rename_handlers(new_name):
            if old_name is None:
                continue
            src_key = Path(source_path).resolve(old_name)
            dst_key = Path(target_path).resolve(new_name)
            if folder.has_handler(src_key):
                folder.copy_handler(src_key, dst_key, exclude_patterns)
        # Events, add
        resource = self.get_resource(target_path)
        database.add_resource(resource)
        # Set ctime and mtime
        context = get_context()
        now = context.timestamp
        resource.set_uuid()
        resource.set_value('ctime', now)
        resource.set_value('mtime', now)
        # Childs
        for child in childs:
            source_path_child = source_path.resolve2(child.name)
            target_path_child = target_path.resolve2(child.name)
            self.copy_resource(source_path_child, target_path_child, exclude_patterns,
                check_if_authorized=False)
        # Ok
        return resource


    def move_resource(self, source_path, target_path, check_if_authorized=True):
        # Find out the source and target absolute URIs
        source_path, target_path = self._resolve_source_target(source_path,
                                                               target_path)

        # Get the source and target resources
        source = self.get_resource(source_path)
        parent_path = target_path.resolve2('..')
        target_parent = self.get_resource(parent_path)

        # Cannot move a resource to a subdirectory of itself
        abspath = self.abspath
        aux = source.abspath
        if aux.get_prefix(abspath) == aux:
            message = 'cannot move a resource to a subdirectory of itself'
            raise ConsistencyError, message

        # Check compatibility
        if (check_if_authorized and (not target_parent.can_paste(source)
                or not source.can_paste_into(target_parent))):
            message = 'resource type "%r" cannot be moved into type "%r"'
            raise ConsistencyError, message % (source, target_parent)

        # Events, remove
        database = self.database
        new_path = self.abspath.resolve2(target_path)
        database.move_resource(source, new_path)

        # Get childs
        childs = list(source.get_resources())

        # Move the metadata
        folder = self.handler
        folder.move_handler('%s.metadata' % source_path,
                            '%s.metadata' % target_path)
        # Move the content
        new_name = target_path.get_name()
        for old_name, new_name in source.rename_handlers(new_name):
            if old_name is None:
                continue
            src_key = Path(source_path).resolve(old_name)
            dst_key = Path(target_path).resolve(new_name)
            if folder.has_handler(src_key):
                folder.move_handler(src_key, dst_key)
        # Childs
        for child in childs:
            source_path_child = source_path.resolve2(child.name)
            target_path_child = target_path.resolve2(child.name)
            self.move_resource(source_path_child, target_path_child,
                check_if_authorized=False)



    def search_resources(self, cls=None, format=None):
        if cls is None:
            cls = DBResource

        for resource in self.get_resources():
            # Filter by base class
            if not isinstance(resource, cls):
                continue
            # Filter by class_id
            if format and resource.metadata.format != format:
                continue
            # All filters passed
            yield resource


    #######################################################################
    # User interface
    #######################################################################
    def get_view(self, name, query=None):
        # Add resource form
        if name == 'new_resource' and query:
            class_id = query.get('type')
            if class_id:
                cls = self.database.get_resource_class(class_id)
                view = cls.new_instance
                if is_prototype(view, BaseView):
                    context = get_context()
                    view = view(resource=self, context=context) # bind
                    # XXX Should we really check access here?
                    # Should raise forbidden, but callers are not ready.
                    root = context.root
                    user = context.user
                    if not root.has_permission(user, 'add', self, class_id):
                        return None
                    if not context.is_access_allowed(self, view):
                        return None
                    return view

        # Default
        return super(Folder, self).get_view(name, query)


    # Views
    view = Folder_BrowseContent()
    edit = AutoEdit(fields=['title', 'index', 'description', 'subject',
                            'share'])
    new_resource = Folder_NewResource()
    browse_content = Folder_BrowseContent()
    rename = Folder_Rename()
    preview_content = Folder_PreviewContent()
    thumb = Folder_Thumbnail()
예제 #16
0
class DBResource(Resource):

    class_version = '20071215'
    class_description = None
    class_icon16 = '/ui/ikaaro/icons/16x16/resource.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/resource.png'
    class_views = []

    # Internal
    _values = {}
    _values_title = {}
    _metadata = None
    _brain = None

    # Config
    context_menus = []

    # Fields
    uuid = UUID_Field()
    ctime = CTime_Field()
    mtime = MTime_Field()
    last_author = LastAuthor_Field()
    title = Title_Field()
    description = Description_Field()
    subject = Subject_Field()
    share = Share_Field()

    def __init__(self, abspath, database, metadata=None, brain=None):
        self.abspath = abspath
        self.database = database
        self._metadata = metadata
        self._brain = brain
        self._values = {}
        self._values_title = {}

    def __eq__(self, resource):
        if resource is None:
            return False
        if not isinstance(resource, DBResource):
            error = "cannot compare DBResource and %s" % type(resource)
            raise TypeError(error)
        return self.abspath == resource.abspath

    def __ne__(self, node):
        return not self.__eq__(node)

    #######################################################################
    # API / Tree
    #######################################################################
    @lazy
    def metadata(self):
        if self._metadata:
            return self._metadata
        self._metadata = self.database.get_metadata(self.abspath)
        return self._metadata

    @lazy
    def parent(self):
        abspath = self.abspath
        if len(abspath) == 0:
            return None
        return self.get_resource(abspath[:-1])

    @property
    def name(self):
        return self.abspath.get_name()

    def get_root(self):
        return self.get_resource('/')

    def get_pathto(self, resource):
        return self.abspath.get_pathto(resource.abspath)

    #######################################################################
    # API / Folderish
    #######################################################################
    __fixed_handlers__ = []  # Resources that cannot be removed

    @property
    def handler(self):
        cls = FolderHandler
        key = self.metadata.key[:-9]
        handler = self.database.get_handler(key, cls=cls, soft=True)
        if handler is None:
            handler = cls()
            self.database.push_handler(key, handler)
        return handler

    def get_handlers(self):
        """Return all the handlers attached to this resource, except the
        metadata.
        """
        handlers = [self.handler]
        return handlers + self.get_fields_handlers()

    def get_fields_handlers(self):
        handlers = []
        root = self.get_root()
        langs = root.get_value('website_languages')
        # Fields
        for name, field in self.get_fields():
            if is_prototype(field, File_Field):
                if field.multilingual:
                    for language in langs:
                        value = field.get_value(self, name, language)
                        if value is not None:
                            handlers.append(value)
                else:
                    value = field.get_value(self, name)
                    if value is not None:
                        handlers.append(value)
        # Ok
        return handlers

    def get_resource(self, path, soft=False):
        if type(path) is not Path:
            path = Path(path)

        # 1. Get the metadata
        if path.is_absolute():
            abspath = path
        else:
            abspath = self.abspath.resolve2(path)

        return self.database.get_resource(abspath, soft=soft)

    #######################################################################
    # Resource API
    #######################################################################

    def get_resources(self):
        for name in self._get_names():
            yield self.get_resource(name)

    def get_resource_by_uuid(self,
                             uuid,
                             context,
                             bases_class_id=None,
                             class_id=None):
        # Get query
        query = get_resource_by_uuid_query(uuid, bases_class_id, class_id)
        search = context.database.search(query)
        # Return resource
        if not search:
            return None
        return search.get_resources(size=1).next()

    def make_resource_name(self):
        raise NotImplementedError

    def make_resource(self, name, cls, soft=False, **kw):
        raise NotImplementedError

    def del_resource(self, name, soft=False, ref_action='restrict'):
        raise NotImplementedError

    def copy_resource(self, source, target, check_if_authorized=True):
        raise NotImplementedError

    def move_resource(self, source, target, check_if_authorized=True):
        raise NotImplementedError

    def traverse_resources(self):
        yield self

    def _get_names(self):
        return []

    def get_names(self, path='.'):
        resource = self.get_resource(path)
        return resource._get_names()

    #######################################################################
    # API / Views
    #######################################################################
    def get_default_view_name(self):
        views = self.class_views
        if not views:
            return None
        context = get_context()
        for view_name in views:
            view = getattr(self, view_name, None)
            if context.is_access_allowed(self, view):
                return view_name
        return views[0]

    def get_view(self, name, query=None):
        # To define a default view, override this
        if name is None:
            name = self.get_default_view_name()
            if name is None:
                return None

        # Explicit view, defined by name
        view = getattr(self, name, None)
        if is_prototype(view, ItoolsView):
            context = get_context()
            view = view(resource=self, context=context)  # bind
            return view

        return None

    def get_context_menus(self):
        return self.context_menus

    ########################################################################
    # Properties
    ########################################################################
    def check_if_context_exists(self):
        """ We cannot do:
          - resource.get_value(xxx)
          - resource.set_value('name', xxx)
        if there's no context.
        """
        context = get_context()
        if context is None:
            raise ValueError('Error: No context was defined')

    def get_value(self, name, language=None):
        field = self.get_field(name)
        if field is None:
            msg = 'field {name} is not defined on {class_id}'
            log_warning(msg.format(name=name, class_id=self.class_id))
            return None
        # Check context
        self.check_if_context_exists()
        # Check if field is obsolete
        if field.obsolete:
            msg = 'field {name} is obsolete on {class_id}'
            log_warning(msg.format(name=name, class_id=self.class_id))
        # TODO: Use decorator for cache
        # TODO: Reactivate when ready
        #cache_key = (name, language)
        #if self._values.has_key(cache_key):
        #    return self._values[cache_key]
        if self._brain and field.stored and not is_prototype(
                field.datatype, Decimal):
            try:
                value = self.get_value_from_brain(name, language)
            except Exception:
                # FIXME Sometimes we cannot get value from brain
                # We're tying to debug this problem
                msg = 'Warning: cannot get value from brain {0} {1}'
                msg = msg.format(self.abspath, name)
                print(msg)
                value = field.get_value(self, name, language)
        else:
            value = field.get_value(self, name, language)
        #self._values[cache_key] = value
        return value

    def get_value_from_brain(self, name, language=None):
        # If brain is loaded & field is stored get value from xapian
        field = self.get_field(name)
        brain_value = self._brain.get_value(name, language)
        if type(brain_value) is datetime:
            # Fix tzinfo for datetime values
            context = get_context()
            value = context.fix_tzinfo(brain_value)
        else:
            value = brain_value
        value = value or field.default
        if value is None:
            # Xapian do not index default value
            value = field.get_value(self, name, language)
        return value

    def set_value(self, name, value, language=None, **kw):
        self.check_if_context_exists()
        field = self.get_field(name)
        if field is None:
            raise ValueError('Field %s do not exist' % name)
        if field.multilingual and language is None and not isinstance(
                value, MSG):
            raise ValueError('Field %s is multilingual' % name)
        # TODO: Use decorator for cache
        self.clear_cache(name, language)
        # Set value
        return field.set_value(self, name, value, language, **kw)

    def clear_cache(self, name, language):
        cache_key = (name, language)
        if self._values.get(cache_key):
            del self._values[cache_key]
        if self._values_title.get(cache_key):
            del self._values_title[cache_key]
        self._brain = None

    def get_value_title(self, name, language=None, mode=None):
        # TODO: Use decorator for cache
        # TODO: Reactivate when ready
        #cache_key = (name, language)
        #if (self._values_title.has_key(cache_key) and
        #    self._values_title[cache_key].has_key(mode)):
        #    return self._values_title[cache_key][mode]
        field = self.get_field(name)
        if field is None:
            return None
        value_title = field.get_value_title(self, name, language, mode)
        #self._values_title.setdefault(cache_key, {})[mode] = value_title
        return value_title

    def get_brain_value(self, name):
        brain = get_context().database.search(
            PhraseQuery('abspath', str(self.abspath))).get_documents()[0]
        return getattr(brain, name, None)

    def get_html_field_body_stream(self, name, language=None):
        """Utility method, returns the stream for the given html field.
        """
        # 1. Check it is an html-file field
        field = self.get_field(name)
        if not is_prototype(field, HTMLFile_Field):
            raise ValueError, 'expected html-file field'

        # 2. Get the handler
        handler = field.get_value(self, name, language)
        if not handler:
            handler = field.class_handler()

        # 3. Get the body
        body = handler.get_body()
        if not body:
            raise ValueError, 'html file does not have a body'
        return body.get_content_elements()

    def get_property(self, name, language=None):
        property = self.metadata.get_property(name, language=language)
        if property:
            return property

        field = self.get_field(name)
        if field is None:
            return None

        default = field.get_default()
        if field.multiple:
            return [MetadataProperty(x, None) for x in default]
        return MetadataProperty(default, None)

    # XXX Backwards compatibility
    set_property = set_value

    def get_page_title(self):
        return self.get_title()

    def init_resource(self, **kw):
        """Return a Metadata object with sensible default values.
        """
        context = get_context()
        user = context.user
        now = context.fix_tzinfo(datetime.now())
        # UUID
        self.set_uuid()
        # Ctime
        if 'ctime' not in kw:
            self.set_value('ctime', now)
        # Mtime
        if 'mtime' not in kw:
            self.set_value('mtime', now)
        # Last author
        if 'last_author' not in kw and user:
            self.set_value('last_author', user.name)
        # Ownership
        owner = self.get_field('owner', soft=True)
        if owner and user:
            self.set_value('owner', str(user.abspath))

        # Keyword parameters
        for name, value in kw.items():
            field = self.get_field(name)
            if field is None:
                raise ValueError, 'undefined field "%s"' % name
            if type(value) is dict:
                for lang in value:
                    field._set_value(self, name, value[lang], lang)
            else:
                field._set_value(self, name, value)

    def set_uuid(self):
        self.set_value('uuid', uuid4().hex)

    def update_resource(self, context):
        """ Method called every time the resource is changed"""
        pass

    def load_handlers(self):
        self.get_handlers()

    def has_property(self, name, language=None):
        return self.metadata.has_property(name, language=language)

    def del_property(self, name):
        if self.has_property(name):
            self.reindex()
            self.metadata.del_property(name)

    ########################################################################
    # Versioning
    ########################################################################
    def get_files_to_archive(self, content=False):
        metadata = self.metadata.key
        if content is True:
            folder = self.handler.key
            return [metadata, folder]
        return [metadata]

    def get_revisions(self,
                      n=None,
                      content=False,
                      author_pattern=None,
                      grep_pattern=None):
        if self.parent is None and content is True:
            files = None
        else:
            files = self.get_files_to_archive(content)

        backend = self.database.backend
        return backend.git_log(files, n, author_pattern, grep_pattern)

    def get_owner(self):
        if self.get_field('owner'):
            return self.get_value('owner')
        return None

    def get_share(self):
        return self.get_value('share')

    ########################################################################
    # Indexing
    ########################################################################
    def to_text(self):
        """This function must return:
           1) An unicode text.
            or
           2) A dict in a multilingual context:
              {'fr': u'....',
               'en': u'....' ....}
        """
        return None

    def get_catalog_values(self):
        values = {}
        # Step 1. Automatically index fields
        root = self.get_root()
        languages = root.get_value('website_languages')
        for name, field in self.get_fields():
            if not field.indexed and not field.stored:
                continue
            if field.multilingual:
                value = {}
                for language in languages:
                    value[language] = field.get_value(self, name, language)
                values[name] = value
            else:
                values[name] = field.get_value(self, name)
        # Step 2. Index non-metadata properties
        # Path related fields
        abspath = self.abspath
        values['abspath'] = str(abspath)
        n = len(abspath)
        values['abspath_depth'] = n
        if n:
            values['parent_paths'] = [str(abspath[:i]) for i in range(n)]
        values['name'] = self.name
        # Class related fields
        values['format'] = self.metadata.format
        values['base_classes'] = self.get_base_classes()
        values['class_version'] = class_version_to_date(self.metadata.version)
        # Links to other resources
        values['owner'] = self.get_owner()
        values['share'] = self.get_share()
        values['links'] = list(self.get_links())
        values['onchange_reindex'] = self.get_onchange_reindex()
        # Full text indexation (not available in icms-init.py FIXME)
        context = get_context()
        server = context.server
        if server and server.index_text:
            try:
                values['text'] = self.to_text()
            except Exception:
                log = 'Indexation failed: %s' % abspath
                log_warning(log, domain='ikaaro')
        # Time events for the CRON
        reminder, payload = self.next_time_event()
        values['next_time_event'] = reminder
        if payload:
            values['next_time_event_payload'] = dumps(payload)
        else:
            values['next_time_event_payload'] = None
        # Ok
        return values

    def get_onchange_reindex(self):
        return None

    def get_base_classes(self):
        l = []
        for cls in self.__class__.__mro__:
            class_id = getattr(cls, 'class_id', None)
            if class_id:
                l.append(class_id)
        return l

    #######################################################################
    # Time events
    #######################################################################
    def next_time_event(self):
        return None, None

    def time_event(self, payload):
        raise NotImplementedError

    #######################################################################
    # API
    #######################################################################
    def rename_handlers(self, new_name):
        """Consider we want to rename this resource to the given 'new_name',
        return the old a new names for all the attached handlers (except the
        metadata).

        This method is required by the "move_resource" method.
        """
        langs = self.get_resource('/').get_value('website_languages')

        aux = []
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field and is_prototype(field, File_Field):
                old = '%s.%s' % (self.name, field_name)
                new = '%s.%s' % (new_name, field_name)
                if field.multilingual:
                    for language in langs:
                        aux.append(('%s.%s' % (old, language),
                                    '%s.%s' % (new, language)))
                else:
                    aux.append((old, new))

        return aux

    def _on_move_resource(self, source):
        """This method updates the links from/to other resources.  It is
        called when the resource has been moved and/or renamed.

        This method is called by 'Database._before_commit', the 'source'
        parameter is the place the resource has been moved from.
        """
        # (1) Update links to other resources
        self.update_incoming_links(Path(source))

        # (2) Update resources that link to me
        database = self.database
        target = self.abspath
        query = PhraseQuery('links', source)
        results = database.search(query).get_documents()
        for result in results:
            path = result.abspath
            path = database.resources_old2new.get(path, path)
            resource = self.get_resource(path)
            resource.update_links(source, target)

    def get_links(self):
        # Automatically from the fields
        languages = self.get_resource('/').get_value('website_languages')
        links = set()
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.get_links(links, self, field_name, languages)

        # Support for dynamic models
        class_id = self.metadata.format
        if class_id[0] == '/':
            links.add(class_id)

        # Ok
        return links

    def update_links(self, source, target):
        """The resource identified by 'source' is going to be moved to
        'target'.  Update our links to it.

        The parameters 'source' and 'target' are absolute 'Path' objects.
        """
        base = str(self.abspath)
        old_base = self.database.resources_new2old.get(base, base)
        old_base = Path(old_base)
        new_base = Path(base)
        languages = self.get_resource('/').get_value('website_languages')

        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.update_links(self, field_name, source, target, languages,
                                   old_base, new_base)
        self.reindex()

    def update_incoming_links(self, source):
        """Update the relative links coming out from this resource after it
        was moved, so they are not broken. The old path is in parameter. The
        new path is "self.abspath".
        """
        languages = self.get_resource('/').get_value('website_languages')
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.update_incoming_links(self, field_name, source,
                                            languages)

    ########################################################################
    # Upgrade
    ########################################################################
    def get_next_versions(self):
        cls_version = self.class_version
        obj_version = self.metadata.version
        # Set zero version if the resource does not have a version
        if obj_version is None:
            obj_version = '00000000'

        # Get all the version numbers
        versions = []
        for cls in self.__class__.mro():
            for name in cls.__dict__.keys():
                if not name.startswith('update_'):
                    continue
                kk, version = name.split('_', 1)
                if len(version) != 8:
                    continue
                try:
                    int(version)
                except ValueError:
                    continue
                if version > obj_version and version <= cls_version:
                    versions.append(version)

        versions.sort()
        return versions

    def update(self, version):
        """
        Update the ressource to a new version.
        :param version: The target version
        """
        # Action
        getattr(self, 'update_%s' % version)()
        # If the action removes the resource, we are done
        metadata = self.metadata
        if metadata.key is None:
            return
        # Update version
        metadata.set_changed()
        metadata.version = version
        # Set resource as changed (to reindex class_version)
        self.reindex()

    def change_class_id(self, new_class_id):
        self.metadata.change_class_id(new_class_id)
        # Return the changed resource
        return self.get_resource(self.abspath)

    def reindex(self):
        # Set resource as changed (to reindex resource)
        self.database.change_resource(self)

    #######################################################################
    # Icons
    #######################################################################
    @classmethod
    def get_class_icon(cls, size=16):
        return getattr(cls, 'class_icon%s' % size, None)

    @classmethod
    def get_resource_icon(cls, size=16):
        icon = getattr(cls, 'icon%s' % size, None)
        if icon is None:
            return cls.get_class_icon(size)
        return ';icon%s' % size

    def get_method_icon(self, view, size='16x16', **kw):
        icon = getattr(view, 'icon', None)
        if icon is None:
            return None
        if callable(icon):
            icon = icon(self, **kw)
        return '/ui/ikaaro/icons/%s/%s' % (size, icon)

    #######################################################################
    # User interface
    #######################################################################
    def get_views(self):
        context = get_context()
        for name in self.class_views:
            view_name = name.split('?')[0]
            view = self.get_view(view_name)
            if context.is_access_allowed(self, view):
                yield name, view

    def get_title(self, language=None):
        title = self.get_value('title', language=language)
        if title:
            return title
        # Fallback to the resource's name
        return unicode(self.name)

    def get_edit_languages(self, context):
        root = self.get_root()
        site_languages = root.get_value('website_languages')
        default = root.get_default_edit_languages()

        # Can not use context.query[] because edit_language is not necessarily
        # defined
        datatype = String(multiple=True, default=default)
        edit_languages = context.get_query_value('edit_language', datatype)
        edit_languages = [x for x in edit_languages if x in site_languages]

        return edit_languages if edit_languages else default

    #######################################################################
    # Cut & Paste Resources
    #######################################################################
    def can_paste(self, source):
        """Is the source resource can be pasted into myself.
        Question is "can I handle this type of resource?"
        """
        raise NotImplementedError

    def can_paste_into(self, target):
        """Can I be pasted into the given target.
        Question is "Is this container compatible with myself?"
        """
        # No restriction by default. Functional modules will want to keep
        # their specific resources for them.
        return True

    # Views
    new_instance = AutoAdd(fields=['title', 'location'])
    edit = AutoEdit(fields=['title', 'description', 'subject', 'share'])
    remove = DBResource_Remove()
    get_file = DBResource_GetFile()
    get_image = DBResource_GetImage()
    # Login/Logout
    login = LoginView()
    logout = LogoutView()
    # Popups
    add_image = DBResource_AddImage()
    add_link = DBResource_AddLink()
    add_media = DBResource_AddMedia()
    # Commit log
    commit_log = DBResource_CommitLog()
    changes = DBResource_Changes()
    # Links
    backlinks = DBResource_Backlinks()
    links = DBResource_Links()
    # Rest (web services)
    rest_login = Rest_Login()
    rest_query = Rest_Query()
    rest_create = Rest_Create()
    rest_read = Rest_Read()
    rest_update = Rest_Update()
    rest_delete = Rest_Delete()
    rest_schema = Rest_Schema()