--- a/doc/tutorials/advanced/part01_create-cube.rst Thu Feb 21 19:08:44 2019 +0100
+++ b/doc/tutorials/advanced/part01_create-cube.rst Thu Feb 21 18:46:39 2019 +0100
@@ -72,11 +72,11 @@
__depends__ = {'cubicweb': '>= 3.26.7',
'cubicweb-file': '>= 1.9.0',
- 'cubicweb-folder': '>= 1.1.0',
- 'cubicweb-person': '>= 1.2.0',
- 'cubicweb-comment': '>= 1.2.0',
- 'cubicweb-tag': '>= 1.2.0',
- 'cubicweb-zone': None}
+ 'cubicweb-folder': '>= 1.1.0',
+ 'cubicweb-person': '>= 1.2.0',
+ 'cubicweb-comment': '>= 1.2.0',
+ 'cubicweb-tag': '>= 1.2.0',
+ 'cubicweb-zone': None}
Notice that you can express minimal version of the cube that should be used,
`None` meaning whatever version available. All packages starting with 'cubicweb-'
@@ -102,27 +102,34 @@
from yams.buildobjs import RelationDefinition
+
class comments(RelationDefinition):
- subject = 'Comment'
- object = 'File'
- cardinality = '1*'
- composite = 'object'
+ subject = 'Comment'
+ object = 'File'
+ # a Comment can be on only one File
+ # but a File can have several comments
+ cardinality = '1*'
+ composite = 'object'
+
class tags(RelationDefinition):
- subject = 'Tag'
- object = 'File'
+ subject = 'Tag'
+ object = 'File'
+
class filed_under(RelationDefinition):
- subject = 'File'
- object = 'Folder'
+ subject = 'File'
+ object = 'Folder'
+
class situated_in(RelationDefinition):
- subject = 'File'
- object = 'Zone'
+ subject = 'File'
+ object = 'Zone'
+
class displayed_on(RelationDefinition):
- subject = 'Person'
- object = 'File'
+ subject = 'Person'
+ object = 'File'
This schema:
--- a/doc/tutorials/advanced/part02_security.rst Thu Feb 21 19:08:44 2019 +0100
+++ b/doc/tutorials/advanced/part02_security.rst Thu Feb 21 18:46:39 2019 +0100
@@ -74,23 +74,25 @@
from yams.constraints import StaticVocabularyConstraint
+
class visibility(RelationDefinition):
- subject = ('Folder', 'File', 'Comment')
- object = 'String'
- constraints = [StaticVocabularyConstraint(('public', 'authenticated',
- 'restricted', 'parent'))]
- default = 'parent'
- cardinality = '11' # required
+ subject = ('Folder', 'File', 'Comment')
+ object = 'String'
+ constraints = [StaticVocabularyConstraint(('public', 'authenticated',
+ 'restricted', 'parent'))]
+ default = 'parent'
+ cardinality = '11' # required
+
class may_be_read_by(RelationDefinition):
__permissions__ = {
- 'read': ('managers', 'users'),
- 'add': ('managers',),
- 'delete': ('managers',),
- }
+ 'read': ('managers', 'users'),
+ 'add': ('managers',),
+ 'delete': ('managers',),
+ }
- subject = ('Folder', 'File', 'Comment',)
- object = 'CWUser'
+ subject = ('Folder', 'File', 'Comment',)
+ object = 'CWUser'
We can note the following points:
@@ -203,32 +205,37 @@
from cubicweb.predicates import is_instance
from cubicweb.server import hook
+
class SetVisibilityOp(hook.DataOperationMixIn, hook.Operation):
- def precommit_event(self):
- for eid in self.get_data():
- entity = self.cnx.entity_from_eid(eid)
- if entity.visibility == 'parent':
- entity.cw_set(visibility=u'authenticated')
+ def precommit_event(self):
+ for eid in self.get_data():
+ entity = self.cnx.entity_from_eid(eid)
+
+ if entity.visibility == 'parent':
+ entity.cw_set(visibility=u'authenticated')
+
class SetVisibilityHook(hook.Hook):
- __regid__ = 'sytweb.setvisibility'
- __select__ = hook.Hook.__select__ & is_instance('Folder', 'File', 'Comment')
- events = ('after_add_entity',)
+ __regid__ = 'sytweb.setvisibility'
+ __select__ = hook.Hook.__select__ & is_instance('Folder', 'File', 'Comment')
+ events = ('after_add_entity',)
- def __call__(self):
- SetVisibilityOp.get_instance(self._cw).add_data(self.entity.eid)
+ def __call__(self):
+ SetVisibilityOp.get_instance(self._cw).add_data(self.entity.eid)
+
class SetParentVisibilityHook(hook.Hook):
- __regid__ = 'sytweb.setparentvisibility'
- __select__ = hook.Hook.__select__ & hook.match_rtype('filed_under', 'comments')
- events = ('after_add_relation',)
+ __regid__ = 'sytweb.setparentvisibility'
+ __select__ = hook.Hook.__select__ & hook.match_rtype('filed_under', 'comments')
+ events = ('after_add_relation',)
- def __call__(self):
- parent = self._cw.entity_from_eid(self.eidto)
- child = self._cw.entity_from_eid(self.eidfrom)
- if child.visibility == 'parent':
- child.cw_set(visibility=parent.visibility)
+ def __call__(self):
+ parent = self._cw.entity_from_eid(self.eidto)
+ child = self._cw.entity_from_eid(self.eidfrom)
+
+ if child.visibility == 'parent':
+ child.cw_set(visibility=parent.visibility)
Notice:
@@ -272,29 +279,32 @@
# relations where the "parent" entity is the object
O_RELS = set(('filed_under', 'comments',))
+
class AddEntitySecurityPropagationHook(hook.PropagateRelationHook):
- """propagate permissions when new entity are added"""
- __regid__ = 'sytweb.addentity_security_propagation'
- __select__ = (hook.PropagateRelationHook.__select__
- & hook.match_rtype_sets(S_RELS, O_RELS))
- main_rtype = 'may_be_read_by'
- subject_relations = S_RELS
- object_relations = O_RELS
+ """propagate permissions when new entity are added"""
+ __regid__ = 'sytweb.addentity_security_propagation'
+ __select__ = (hook.PropagateRelationHook.__select__
+ & hook.match_rtype_sets(S_RELS, O_RELS))
+ main_rtype = 'may_be_read_by'
+ subject_relations = S_RELS
+ object_relations = O_RELS
+
class AddPermissionSecurityPropagationHook(hook.PropagateRelationAddHook):
- """propagate permissions when new entity are added"""
- __regid__ = 'sytweb.addperm_security_propagation'
- __select__ = (hook.PropagateRelationAddHook.__select__
- & hook.match_rtype('may_be_read_by',))
- subject_relations = S_RELS
- object_relations = O_RELS
+ """propagate permissions when new entity are added"""
+ __regid__ = 'sytweb.addperm_security_propagation'
+ __select__ = (hook.PropagateRelationAddHook.__select__
+ & hook.match_rtype('may_be_read_by',))
+ subject_relations = S_RELS
+ object_relations = O_RELS
+
class DelPermissionSecurityPropagationHook(hook.PropagateRelationDelHook):
- __regid__ = 'sytweb.delperm_security_propagation'
- __select__ = (hook.PropagateRelationDelHook.__select__
- & hook.match_rtype('may_be_read_by',))
- subject_relations = S_RELS
- object_relations = O_RELS
+ __regid__ = 'sytweb.delperm_security_propagation'
+ __select__ = (hook.PropagateRelationDelHook.__select__
+ & hook.match_rtype('may_be_read_by',))
+ subject_relations = S_RELS
+ object_relations = O_RELS
* the `AddEntitySecurityPropagationHook` will propagate the relation
when `filed_under` or `comments` relations are added
@@ -324,6 +334,7 @@
from cubicweb.devtools import testlib
from cubicweb import Binary
+
class SecurityTC(testlib.CubicWebTC):
def test_visibility_propagation(self):
@@ -379,6 +390,7 @@
self.assertEquals(2, len(cnx.execute('File X'))) # now toto has access to photo2
self.assertEquals(1, len(cnx.execute('Folder X'))) # and to restricted folder
+
if __name__ == '__main__':
from unittest import main
main()
--- a/doc/tutorials/advanced/part03_bfss.rst Thu Feb 21 19:08:44 2019 +0100
+++ b/doc/tutorials/advanced/part03_bfss.rst Thu Feb 21 18:46:39 2019 +0100
@@ -22,6 +22,7 @@
from cubicweb.server import hook
from cubicweb.server.sources import storages
+
class ServerStartupHook(hook.Hook):
__regid__ = 'sytweb.serverstartup'
events = ('server_startup', 'server_maintenance')
--- a/doc/tutorials/advanced/part04_ui-base.rst Thu Feb 21 19:08:44 2019 +0100
+++ b/doc/tutorials/advanced/part04_ui-base.rst Thu Feb 21 18:46:39 2019 +0100
@@ -28,26 +28,27 @@
from cubicweb.web.views import error
from cubicweb.predicates import anonymous_user
+
class FourOhFour(error.FourOhFour):
- __select__ = error.FourOhFour.__select__ & anonymous_user()
+ __select__ = error.FourOhFour.__select__ & anonymous_user()
- def call(self):
- self.w(u"<h1>%s</h1>" % self._cw._('this resource does not exist'))
- self.w(u"<p>%s</p>" % self._cw._('have you tried to login?'))
+ def call(self):
+ self.w(u"<h1>%s</h1>" % self._cw._('this resource does not exist'))
+ self.w(u"<p>%s</p>" % self._cw._('have you tried to login?'))
class LoginBox(component.CtxComponent):
- """display a box containing links to all startup views"""
- __regid__ = 'sytweb.loginbox'
- __select__ = component.CtxComponent.__select__ & anonymous_user()
+ """display a box containing links to all startup views"""
+ __regid__ = 'sytweb.loginbox'
+ __select__ = component.CtxComponent.__select__ & anonymous_user()
- title = _('Authenticate yourself')
- order = 70
+ title = _('Authenticate yourself')
+ order = 70
- def render_body(self, w):
- cw = self._cw
- form = cw.vreg['forms'].select('logform', cw)
- form.render(w=w, table_class='', display_progress_div=False)
+ def render_body(self, w):
+ cw = self._cw
+ form = cw.vreg['forms'].select('logform', cw)
+ form.render(w=w, table_class='', display_progress_div=False)
The first class provides a new specific implementation of the default page you
get on 404 error, to display an adapted message to anonymous user.
@@ -86,19 +87,21 @@
from cubicweb.web.views import startup
+
class IndexView(startup.IndexView):
- def call(self, **kwargs):
- self.w(u'<div>\n')
- if self._cw.cnx.session.anonymous_session:
- self.w(u'<h4>%s</h4>\n' % self._cw._('Public Albums'))
- else:
- self.w(u'<h4>%s</h4>\n' % self._cw._('Albums for %s') % self._cw.user.login)
- self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
- self.w(u'</div>\n')
+ def call(self, **kwargs):
+ self.w(u'<div>\n')
+ if self._cw.cnx.session.anonymous_session:
+ self.w(u'<h4>%s</h4>\n' % self._cw._('Public Albums'))
+ else:
+ self.w(u'<h4>%s</h4>\n' % self._cw._('Albums for %s') % self._cw.user.login)
+ self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
+ self.w(u'</div>\n')
+
def registration_callback(vreg):
- vreg.register_all(globals().values(), __name__, (IndexView,))
- vreg.register_and_replace(IndexView, startup.IndexView)
+ vreg.register_all(globals().values(), __name__, (IndexView,))
+ vreg.register_and_replace(IndexView, startup.IndexView)
As you can see, we override the default index view found in
`cubicweb.web.views.startup`, geting back nothing but its identifier and selector
@@ -149,9 +152,9 @@
.. sourcecode:: python
class File(AnyEntity):
- """customized class for File entities"""
- __regid__ = 'File'
- fetch_attrs, cw_fetch_order = fetch_config(['data_name', 'title'])
+ """customized class for File entities"""
+ __regid__ = 'File'
+ fetch_attrs, cw_fetch_order = fetch_config(['data_name', 'title'])
By default, `fetch_config` will return a `cw_fetch_order` method that will order
@@ -175,16 +178,16 @@
class FolderITreeAdapter(folder.FolderITreeAdapter):
- def different_type_children(self, entities=True):
- rql = self.entity.cw_related_rql(self.tree_relation,
- self.parent_role, ('File',))
- rset = self._cw.execute(rql, {'x': self.entity.eid})
- if entities:
- return list(rset.entities())
- return rset
+ def different_type_children(self, entities=True):
+ rql = self.entity.cw_related_rql(self.tree_relation,
+ self.parent_role, ('File',))
+ rset = self._cw.execute(rql, {'x': self.entity.eid})
+ if entities:
+ return list(rset.entities())
+ return rset
def registration_callback(vreg):
- vreg.register_and_replace(FolderITreeAdapter, folder.FolderITreeAdapter)
+ vreg.register_and_replace(FolderITreeAdapter, folder.FolderITreeAdapter)
As you can see, we simple inherit from the adapter defined in the `folder` cube,
then we override the `different_type_children` method to give a clue to the ORM's
@@ -216,23 +219,23 @@
class FileIPrevNextAdapter(navigation.IPrevNextAdapter):
- __select__ = is_instance('File')
+ __select__ = is_instance('File')
- def previous_entity(self):
- rset = self._cw.execute('File F ORDERBY FDN DESC LIMIT 1 WHERE '
- 'X filed_under FOLDER, F filed_under FOLDER, '
- 'F data_name FDN, X data_name > FDN, X eid %(x)s',
- {'x': self.entity.eid})
- if rset:
- return rset.get_entity(0, 0)
+ def previous_entity(self):
+ rset = self._cw.execute('File F ORDERBY FDN DESC LIMIT 1 WHERE '
+ 'X filed_under FOLDER, F filed_under FOLDER, '
+ 'F data_name FDN, X data_name > FDN, X eid %(x)s',
+ {'x': self.entity.eid})
+ if rset:
+ return rset.get_entity(0, 0)
- def next_entity(self):
- rset = self._cw.execute('File F ORDERBY FDN ASC LIMIT 1 WHERE '
- 'X filed_under FOLDER, F filed_under FOLDER, '
- 'F data_name FDN, X data_name < FDN, X eid %(x)s',
- {'x': self.entity.eid})
- if rset:
- return rset.get_entity(0, 0)
+ def next_entity(self):
+ rset = self._cw.execute('File F ORDERBY FDN ASC LIMIT 1 WHERE '
+ 'X filed_under FOLDER, F filed_under FOLDER, '
+ 'F data_name FDN, X data_name < FDN, X eid %(x)s',
+ {'x': self.entity.eid})
+ if rset:
+ return rset.get_entity(0, 0)
The `IPrevNext` interface implemented by the adapter simply consist in the
@@ -279,11 +282,11 @@
from cubicweb.web.views import ibreadcrumbs
class FileIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
- __select__ = is_instance('File')
+ __select__ = is_instance('File')
- def parent_entity(self):
- if self.entity.filed_under:
- return self.entity.filed_under[0]
+ def parent_entity(self):
+ if self.entity.filed_under:
+ return self.entity.filed_under[0]
In that case, we simply use attribute notation provided by the ORM to get the
folder in which the current file (e.g. `self.entity`) is located.