cubicweb/schemas/base.py
author Denis Laxalde <denis.laxalde@logilab.fr>
Thu, 09 Mar 2017 16:36:33 +0100
changeset 12053 c3c9f2e1424c
parent 11775 39cf9e55ada8
child 12539 10159a3d1d72
permissions -rw-r--r--
[pyramid] Add a "pyramid" instance configuration type In a new module 'cubicweb.pyramid.config' we define a "pyramid" instance configuration type. The noticeable feature of this configuration is that it manages a 'development.ini' file that gets installed in application home (along with `.conf` file). This file is templated and includes generated values for secrets of session and authtk tokens. This means that we can just call: pserve etc/cubicweb.d/<appname>/development.ini or gunicorn --paste etc/cubicweb.d/<appname>/development.ini -b :8080 just after instance creation to get a pyramid instance running without having to hack around a 'pyramid.ini' file. This patch drops 'development.ini' from skeleton and moves it in cubicweb/pyramid so that it gets installed at instance creation which is more appropriate than in cube creation. The new configuration class sets "cubicweb.bwcompat" setting to false so it is not intended to replace the "all-in-one" configuration type (which would require a bit more work). This configuration is close to the the 'repository' configuration type with just a couple of options from WebConfiguration that are needed for Pyramid (anonymous user/password plus some miscellaneous options that I'm not so sure are really needed). Note, in particular, that we do not pull CORS settings to be injected as a WSGI middleware like in wsgi_application_from_cwconfig() since I believe this should be left as an end-user responsibility and since this can be defined in a standard way in paste configuration. This configuration inherits from ServerConfiguration but registers the same appobjects as WebConfiguration. In cubicweb.web.request._CubicWebRequestBase, we guard against access to "uiprops" and "datadir_url" of the config because this new "pyramid" config does not have these (this does not make sense without bwcompat mode). At some point, we should either avoid using `cw_request`'s pyramid request attribute or make cubicweb's web request really independant of existing implementation and drop these assumptions.

# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
"""core CubicWeb schema, but not necessary at bootstrap time"""


from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
                            SubjectRelation, String, Bytes, TZDatetime, Password)

from cubicweb import _
from cubicweb.schema import (
    RQLConstraint, WorkflowableEntityType, ERQLExpression, RRQLExpression,
    PUB_SYSTEM_REL_PERMS, PUB_SYSTEM_ATTR_PERMS, RO_ATTR_PERMS)


class CWUser(WorkflowableEntityType):
    """define a CubicWeb user"""
    __permissions__ = {
        'read':   ('managers', 'users', ERQLExpression('X identity U')),
        'add':    ('managers',),
        'delete': ('managers',),
        'update': ('managers', ERQLExpression('X identity U, NOT U in_group G, G name "guests"'),),
        }

    login     = String(required=True, unique=True, maxsize=64,
                       description=_('unique identifier used to connect to the application'))
    upassword = Password(required=True) # password is a reserved word for mysql
    firstname = String(maxsize=64)
    surname   = String(maxsize=64)
    last_login_time = TZDatetime(description=_('last connection date'))
    in_group = SubjectRelation('CWGroup', cardinality='+*',
                               constraints=[RQLConstraint('NOT O name "owners"')],
                               description=_('groups grant permissions to the user'))


class EmailAddress(EntityType):
    """an electronic mail address associated to a short alias"""
    __permissions__ = {
        # application that wishes public email, or use it for something else
        # than users (eg Company, Person), should explicitly change permissions
        'read':   ('managers', ERQLExpression('U use_email X')),
        'add':    ('managers', 'users',),
        'delete': ('managers', 'owners', ERQLExpression('P use_email X, U has_update_permission P')),
        'update': ('managers', 'owners', ERQLExpression('P use_email X, U has_update_permission P')),
        }

    alias   = String(fulltextindexed=True, maxsize=56)
    address = String(required=True,  fulltextindexed=True,
                     indexed=True, unique=True, maxsize=128)
    prefered_form = SubjectRelation('EmailAddress', cardinality='?*',
                                    description=_('when multiple addresses are equivalent \
(such as python-projects@logilab.org and python-projects@lists.logilab.org), set this \
to indicate which is the preferred form.'))

class use_email(RelationType):
    fulltext_container = 'subject'


class use_email_relation(RelationDefinition):
    """user's email account"""
    name = "use_email"
    __permissions__ = {
        'read':   ('managers', 'users', 'guests',),
        'add':    ('managers', RRQLExpression('U has_update_permission S'),),
        'delete': ('managers', RRQLExpression('U has_update_permission S'),),
        }
    subject = "CWUser"
    object = "EmailAddress"
    cardinality = '*?'
    composite = 'subject'


class primary_email(RelationDefinition):
    """the prefered email"""
    __permissions__ = {
        'read':   ('managers', 'users', 'guests',),
        'add':    ('managers', RRQLExpression('U has_update_permission S'),),
        'delete': ('managers', RRQLExpression('U has_update_permission S'),),
        }
    subject = "CWUser"
    object = "EmailAddress"
    cardinality = '??'
    constraints= [RQLConstraint('S use_email O')]


class prefered_form(RelationType):
    __permissions__ = {
        'read':   ('managers', 'users', 'guests',),
        # XXX should have update __permissions__ on both subject and object,
        #     though by doing this we will probably have no way to add
        #     this relation in the web ui. The easiest way to acheive this
        #     is probably to be able to have "U has_update_permission O" as
        #     RQLConstraint of the relation definition, though this is not yet
        #     possible
        'add':    ('managers', RRQLExpression('U has_update_permission S'),),
        'delete': ('managers', RRQLExpression('U has_update_permission S'),),
        }

class in_group(RelationType):
    """core relation indicating a user's groups"""
    __permissions__ = PUB_SYSTEM_REL_PERMS

class owned_by(RelationType):
    """core relation indicating owners of an entity. This relation
    implicitly put the owner into the owners group for the entity
    """
    __permissions__ = {
        'read':   ('managers', 'users', 'guests'),
        'add':    ('managers', RRQLExpression('S owned_by U'),),
        'delete': ('managers', RRQLExpression('S owned_by U'),),
        }
    # 0..n cardinality for entities created by internal session (no attached user)
    # and to support later deletion of a user which has created some entities
    cardinality = '**'
    subject = '*'
    object = 'CWUser'

class created_by(RelationType):
    """core relation indicating the original creator of an entity"""
    __permissions__ = {
        'read':   ('managers', 'users', 'guests'),
        'add':    ('managers',),
        'delete': ('managers',),
        }
    # 0..1 cardinality for entities created by internal session (no attached user)
    # and to support later deletion of a user which has created some entities
    cardinality = '?*'
    subject = '*'
    object = 'CWUser'


class creation_date(RelationType):
    """creation time of an entity"""
    __permissions__ = PUB_SYSTEM_ATTR_PERMS
    cardinality = '11'
    subject = '*'
    object = 'TZDatetime'


class modification_date(RelationType):
    """latest modification time of an entity"""
    __permissions__ = PUB_SYSTEM_ATTR_PERMS
    cardinality = '11'
    subject = '*'
    object = 'TZDatetime'


class cwuri(RelationType):
    """internal entity uri"""
    __permissions__ = RO_ATTR_PERMS
    cardinality = '11'
    subject = '*'
    object = 'String'


# XXX find a better relation name
class for_user(RelationType):
    """link a property to the user which want this property customization. Unless
    you're a site manager, this relation will be handled automatically.
    """
    __permissions__ = {
        'read':   ('managers', 'users', 'guests'),
        'add':    ('managers',),
        'delete': ('managers',),
        }
    inlined = True
    subject = 'CWProperty'
    object = 'CWUser'
    composite = 'object'
    cardinality = '?*'


class ExternalUri(EntityType):
    """a URI representing an object in external data store"""
    uri = String(required=True, unique=True, maxsize=256,
                 description=_('the URI of the object'))


class same_as(RelationType):
    """generic relation to specify that an external entity represent the same
    object as a local one:
       http://www.w3.org/TR/owl-ref/#sameAs-def
    """
    #NOTE: You'll have to explicitly declare which entity types can have a
    #same_as relation
    __permissions__ = {
        'read':   ('managers', 'users', 'guests',),
        'add':    ('managers', 'users'),
        'delete': ('managers', 'owners'),
        }
    cardinality = '**'
    symmetric = True
    # NOTE: the 'object = ExternalUri' declaration will still be mandatory
    #       in the cube's schema.
    object = 'ExternalUri'


class CWCache(EntityType):
    """a simple cache entity characterized by a name and
    a validity date.

    The target application is responsible for updating timestamp
    when necessary to invalidate the cache (typically in hooks).

    Also, checkout the AppObject.get_cache() method.
    """
    # XXX only handle by hooks, shouldn't be readable/editable at all through
    # the ui and so no permissions should be granted, no?
    __permissions__ = {
        'read':   ('managers', 'users', 'guests'),
        'add':    ('managers',),
        'update': ('managers', 'users',), # XXX
        'delete': ('managers',),
        }

    name = String(required=True, unique=True, maxsize=128,
                  description=_('name of the cache'))
    timestamp = TZDatetime(default='NOW')


class CWSource(EntityType):
    __permissions__ = {
        'read':   ('managers', 'users', 'guests'),
        'add':    ('managers',),
        'update': ('managers',),
        'delete': ('managers',),
        }
    name = String(required=True, unique=True, maxsize=128,
                  description=_('name of the source'))
    type = String(required=True, maxsize=20, description=_('type of the source'))
    config = String(description=_('source\'s configuration. One key=value per '
                                  'line, authorized keys depending on the '
                                  'source\'s type'),
                    __permissions__={
                        'read':   ('managers',),
                        'add':    ('managers',),
                        'update': ('managers',),
                        })
    # put this here and not in a subclass even if it's only for some sources
    # since having subclasses on generic relation (cw_source) double the number
    # of rdef in the schema, and make ms planning harder since queries solutions
    # may changes when sources are specified
    url = String(description=_('URLs from which content will be imported. You can put one url per line'))
    parser = String(description=_('parser to use to extract entities from content retrieved at given URLs.'))
    latest_retrieval = TZDatetime(description=_('latest synchronization time'))
    in_synchronization = TZDatetime(description=_('start timestamp of the currently in synchronization, or NULL when no synchronization in progress.'))


ENTITY_MANAGERS_PERMISSIONS = {
    'read':   ('managers',),
    'add':    ('managers',),
    'update': ('managers',),
    'delete': ('managers',),
    }
RELATION_MANAGERS_PERMISSIONS = {
    'read':   ('managers',),
    'add':    ('managers',),
    'delete': ('managers',),
    }


class CWSourceHostConfig(EntityType):
    __permissions__ = ENTITY_MANAGERS_PERMISSIONS
    __unique_together__ = [('match_host', 'cw_host_config_of')]
    match_host = String(required=True, maxsize=128,
                        description=_('regexp matching host(s) to which this config applies'))
    config = String(required=True,
                    description=_('Source\'s configuration for a particular host. '
                                  'One key=value per line, authorized keys '
                                  'depending on the source\'s type, overriding '
                                  'values defined on the source.'),
                    __permissions__={
                        'read':   ('managers',),
                        'add':    ('managers',),
                        'update': ('managers',),
                        })


class cw_host_config_of(RelationDefinition):
    __permissions__ = RELATION_MANAGERS_PERMISSIONS
    subject = 'CWSourceHostConfig'
    object = 'CWSource'
    cardinality = '1*'
    composite = 'object'
    inlined = True

class cw_source(RelationDefinition):
    __permissions__ = {
        'read':   ('managers', 'users', 'guests'),
        'add':    ('managers',),
        'delete': ('managers',),
        }
    subject = '*'
    object = 'CWSource'
    cardinality = '1*'
    composite = 'object'


class CWDataImport(EntityType):
    __permissions__ = ENTITY_MANAGERS_PERMISSIONS
    start_timestamp = TZDatetime()
    end_timestamp = TZDatetime()
    log = String()
    status = String(required=True, internationalizable=True, indexed=True,
                    default='in progress',
                    vocabulary=[_('in progress'), _('success'), _('failed')])


class cw_import_of(RelationDefinition):
    __permissions__ = RELATION_MANAGERS_PERMISSIONS
    subject = 'CWDataImport'
    object = 'CWSource'
    cardinality = '1*'
    composite = 'object'


# "abtract" relation types, no definition in cubicweb itself ###################

class identical_to(RelationType):
    """identical to"""
    symmetric = True
    __permissions__ = {
        'read':   ('managers', 'users', 'guests',),
        # XXX should have update __permissions__ on both subject and object,
        #     though by doing this we will probably have no way to add
        #     this relation in the web ui. The easiest way to acheive this
        #     is probably to be able to have "U has_update_permission O" as
        #     RQLConstraint of the relation definition, though this is not yet
        #     possible
        'add':    ('managers', RRQLExpression('U has_update_permission S'),),
        'delete': ('managers', RRQLExpression('U has_update_permission S'),),
        }

class see_also(RelationType):
    """generic relation to link one entity to another"""
    symmetric = True
    __permissions__ = {
        'read':   ('managers', 'users', 'guests',),
        'add':    ('managers', RRQLExpression('U has_update_permission S'),),
        'delete': ('managers', RRQLExpression('U has_update_permission S'),),
        }


class CWSession(EntityType):
    """Persistent session.

    Used by cubicweb.pyramid to store the session data.
    """
    __permissions__ = {
        'read':   ('managers',),
        'add':    (),
        'update': (),
        'delete': (),
    }
    cwsessiondata = Bytes()