365 lines
12 KiB
Text
365 lines
12 KiB
Text
===============================================================
|
|
loops - Linked Objects for Organization and Processing Services
|
|
===============================================================
|
|
|
|
>>> from zope import component
|
|
>>> from zope.traversing.api import getName
|
|
|
|
Let's set up a loops site with basic and example concepts and resources.
|
|
|
|
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
|
>>> site = placefulSetUp(True)
|
|
|
|
>>> from loops.organize.setup import SetupManager
|
|
>>> component.provideAdapter(SetupManager, name='organize')
|
|
>>> from loops.tests.setup import TestSite
|
|
>>> t = TestSite(site)
|
|
>>> concepts, resources, views = t.setup()
|
|
>>> loopsRoot = site['loops']
|
|
|
|
|
|
Compund Objects - Hierarchies with Ordered Components
|
|
=====================================================
|
|
|
|
>>> from loops.compound.base import Compound
|
|
>>> component.provideAdapter(Compound)
|
|
|
|
>>> tType = concepts.getTypeConcept()
|
|
>>> from loops.setup import addAndConfigureObject
|
|
>>> from loops.concept import Concept
|
|
>>> from loops.compound.interfaces import ICompound
|
|
|
|
We first create the compound type and one instance of the newly created
|
|
type. We also need an ``ispartof`` predicate.
|
|
|
|
>>> tCompound = addAndConfigureObject(concepts, Concept, 'compound',
|
|
... title='Compound',
|
|
... conceptType=tType, typeInterface=ICompound)
|
|
>>> c01 = addAndConfigureObject(concepts, Concept, 'c01',
|
|
... title='Compound #01', conceptType=tCompound)
|
|
>>> tPredicate = concepts.getPredicateType()
|
|
>>> isPartof = addAndConfigureObject(concepts, Concept, 'ispartof',
|
|
... title='is Part of', conceptType=tPredicate)
|
|
|
|
In order to access the compound concept's attributes we have to adapt
|
|
it.
|
|
|
|
>>> from loops.common import adapted
|
|
>>> aC01 = adapted(c01)
|
|
|
|
Now we are able to add resources to it.
|
|
|
|
>>> aC01.add(resources['d003.txt'])
|
|
>>> aC01.add(resources['d001.txt'])
|
|
|
|
>>> [getName(p) for p in aC01.getParts()]
|
|
['d003.txt', 'd001.txt']
|
|
|
|
>>> aC01.add(resources['d001.txt'], 0)
|
|
>>> [getName(p) for p in aC01.getParts()]
|
|
['d001.txt', 'd003.txt', 'd001.txt']
|
|
|
|
>>> aC01.add(resources['d002.txt'], -1)
|
|
>>> [getName(p) for p in aC01.getParts()]
|
|
['d001.txt', 'd003.txt', 'd002.txt', 'd001.txt']
|
|
|
|
We can reorder the parts of a compound.
|
|
|
|
>>> aC01.reorder([resources['d002.txt'], resources['d001.txt'], ])
|
|
>>> [getName(p) for p in aC01.getParts()]
|
|
['d002.txt', 'd001.txt', 'd003.txt', 'd001.txt']
|
|
|
|
And remove a part from the compound.
|
|
|
|
>>> aC01.remove(resources['d001.txt'], 1)
|
|
>>> [getName(p) for p in aC01.getParts()]
|
|
['d002.txt', 'd003.txt', 'd001.txt']
|
|
|
|
|
|
Blogs
|
|
=====
|
|
|
|
>>> from loops.compound.blog.post import BlogPost
|
|
>>> from loops.compound.blog.interfaces import IBlogPost
|
|
>>> component.provideAdapter(BlogPost, provides=IBlogPost)
|
|
|
|
>>> tBlog = addAndConfigureObject(concepts, Concept, 'blog', title='Blog',
|
|
... conceptType=tType)
|
|
>>> tBlogPost = addAndConfigureObject(concepts, Concept, 'blogpost',
|
|
... title='Blog Post', conceptType=tType,
|
|
... typeInterface=IBlogPost)
|
|
|
|
>>> myBlog = addAndConfigureObject(concepts, Concept, 'myblog', title='My Blog',
|
|
... conceptType=tBlog)
|
|
|
|
>>> firstPost = addAndConfigureObject(concepts, Concept, 'firstpost',
|
|
... title='My first post', conceptType=tBlogPost)
|
|
|
|
>>> aFirstPost = adapted(firstPost)
|
|
>>> aFirstPost.date
|
|
>>> aFirstPost.text = 'My first blog post.'
|
|
>>> aFirstPost.text
|
|
'My first blog post.'
|
|
>>> aFirstPost.creator
|
|
|
|
Blog and BlogPost views
|
|
-----------------------
|
|
|
|
>>> from loops.compound.blog.browser import BlogView, BlogPostView
|
|
>>> #from zope.publisher.browser import TestRequest
|
|
>>> from loops.tests.auth import TestRequest
|
|
|
|
The blog view automatically provides a portlet action for creating
|
|
a new post.
|
|
|
|
>>> view = BlogView(myBlog, TestRequest())
|
|
>>> for act in view.getActions('portlet'):
|
|
... print(act.name)
|
|
createBlogPost
|
|
|
|
>>> view = BlogPostView(firstPost, TestRequest())
|
|
>>> data = view.data
|
|
|
|
Automatic assignment of a blog post to the personal blog of its owner
|
|
---------------------------------------------------------------------
|
|
|
|
As all the following stuff relies on a blog being assigned to a person
|
|
we need the corresponding scaffolding from the loops.organize package.
|
|
|
|
>>> from loops.organize.tests import setupUtilitiesAndAdapters
|
|
>>> setupData = setupUtilitiesAndAdapters(loopsRoot)
|
|
|
|
We also have to set up some components for automatic setting of
|
|
security properties upon object creation.
|
|
|
|
>>> from loops.security.common import setDefaultSecurity
|
|
>>> component.provideHandler(setDefaultSecurity)
|
|
>>> from loops.compound.blog.security import BlogPostSecuritySetter
|
|
>>> component.provideAdapter(BlogPostSecuritySetter)
|
|
|
|
Let's start with defining a user (more precisely: a principal)
|
|
and a corresponding person.
|
|
|
|
>>> auth = setupData.auth
|
|
>>> tPerson = concepts['person']
|
|
|
|
>>> userJohn = auth.definePrincipal('users.john', 'John', login='john')
|
|
>>> persJohn = addAndConfigureObject(concepts, Concept, 'person.john',
|
|
... title='John Smith', conceptType=tPerson,
|
|
... userId='users.john')
|
|
|
|
>>> blogJohn = addAndConfigureObject(concepts, Concept, 'blog.john',
|
|
... title='John\'s Blog', conceptType=tBlog)
|
|
>>> persJohn.assignChild(blogJohn)
|
|
|
|
Let's now login as the newly defined user.
|
|
|
|
>>> from loops.tests.auth import login
|
|
>>> login(userJohn)
|
|
|
|
Let's also provide some general permission settings. These are necessary
|
|
as after logging in the permissions of the user will be checked by the
|
|
standard checker defined in the test setup.
|
|
|
|
>>> grantPermission = setupData.rolePermissions.grantPermissionToRole
|
|
>>> assignRole = setupData.principalRoles.assignRoleToPrincipal
|
|
|
|
>>> grantPermission('zope.View', 'zope.Member')
|
|
>>> grantPermission('zope.View', 'loops.Owner')
|
|
>>> grantPermission('zope.ManageContent', 'zope.ContentManager')
|
|
>>> grantPermission('loops.ViewRestricted', 'loops.Owner')
|
|
|
|
>>> assignRole('zope.Member', 'users.john')
|
|
|
|
The automatic assignment of the blog post is done in the form controller
|
|
used for creating the blog post.
|
|
|
|
>>> home = views['home']
|
|
>>> home.target = myBlog
|
|
|
|
>>> from loops.compound.blog.browser import CreateBlogPostForm, CreateBlogPost
|
|
>>> input = {'title': 'John\'s first post', 'text': 'Text of John\'s post',
|
|
... 'date': '2008-02-02T15:54:11',
|
|
... 'privateComment': 'John\'s private comment',
|
|
... 'form.type': '.loops/concepts/blogpost'}
|
|
>>> cbpForm = CreateBlogPostForm(home, TestRequest(form=input))
|
|
>>> cbpController = CreateBlogPost(cbpForm, cbpForm.request)
|
|
>>> cbpController.update()
|
|
False
|
|
|
|
>>> posts = blogJohn.getChildren()
|
|
>>> len(posts)
|
|
1
|
|
>>> postJohn0 = posts[0]
|
|
>>> postJohn0.title
|
|
"John's first post"
|
|
|
|
>>> postJohn0Text = adapted(postJohn0.getResources()[0])
|
|
>>> postJohn0Text.data
|
|
"Text of John's post"
|
|
|
|
|
|
Security
|
|
========
|
|
|
|
We first have to define some checkers that will be invoked when checking access
|
|
to attributes.
|
|
|
|
>>> from zope.security.checker import Checker, defineChecker
|
|
>>> checker = Checker(dict(title='zope.View', privateComment='loops.ViewRestricted'),
|
|
... dict(title='zope.ManageContent',
|
|
... privateComment='zope.ManageContent'))
|
|
>>> #defineChecker(Concept, checker)
|
|
>>> defineChecker(BlogPost, checker)
|
|
|
|
>>> from loops.resource import Resource, TextDocumentAdapter
|
|
>>> checker = Checker(dict(title='zope.View', data='zope.View'),
|
|
... dict(title='zope.ManageContent', data='zope.ManageContent'))
|
|
>>> #defineChecker(Resource, checker)
|
|
>>> defineChecker(TextDocumentAdapter, checker)
|
|
|
|
Standard security settings for blogs
|
|
------------------------------------
|
|
|
|
TODO...
|
|
|
|
A personal blog is a blog that is a direct child of a person with
|
|
an associated principal (i.e. a user id).
|
|
|
|
Blog posts in a personal blog can only be created by the owner of the blog.
|
|
More generally: A personal blog may receive only blog posts as children
|
|
that have the same owner as the blog itself.
|
|
|
|
A personal blog may only be assigned to other parents by the owner of
|
|
the blog.
|
|
|
|
Standard security settings for blog posts
|
|
-----------------------------------------
|
|
|
|
Blog posts may (only!) be edited by their owner (i.e. only the owner
|
|
has the ManageContent permission). (TODO: Also their parent assignments may be
|
|
changed only by the owner).
|
|
|
|
Note that we still are logged-in as user John.
|
|
|
|
>>> from zope.security import canAccess, canWrite, checkPermission
|
|
>>> canAccess(postJohn0, 'title')
|
|
True
|
|
>>> canWrite(postJohn0, 'title')
|
|
True
|
|
|
|
This settings are also valid for children/resources that are assigned
|
|
via an `is Part of` relation.
|
|
|
|
>>> canAccess(postJohn0Text, 'data')
|
|
True
|
|
>>> canWrite(postJohn0Text, 'data')
|
|
True
|
|
|
|
The private comment is only visible (and editable, of course) for the
|
|
owner of the blog post.
|
|
|
|
>>> aPostJohn0 = adapted(postJohn0)
|
|
>>> canAccess(aPostJohn0, 'privateComment')
|
|
True
|
|
>>> canWrite(aPostJohn0, 'privateComment')
|
|
True
|
|
|
|
So let's now switch to another user. On a global level, Martha also has
|
|
the ContentManager role, i.e. she is allowed to edit content objects.
|
|
Nevertheless she is not allowed to change John's blog post.
|
|
|
|
>>> userMartha = auth.definePrincipal('users.martha', 'Martha', login='martha')
|
|
>>> assignRole('zope.Member', 'users.martha')
|
|
>>> assignRole('zope.ContentManager', 'users.martha')
|
|
|
|
>>> login(userMartha)
|
|
|
|
>>> canAccess(postJohn0, 'title')
|
|
True
|
|
>>> canWrite(postJohn0, 'title')
|
|
False
|
|
|
|
>>> canAccess(postJohn0Text, 'data')
|
|
True
|
|
>>> canWrite(postJohn0Text, 'data')
|
|
False
|
|
|
|
>>> canAccess(aPostJohn0, 'privateComment')
|
|
False
|
|
>>> canWrite(aPostJohn0, 'privateComment')
|
|
False
|
|
|
|
A blog post marked as private is only visible for its owner.
|
|
|
|
>>> login(userJohn)
|
|
>>> aPostJohn0.private = True
|
|
>>> canAccess(postJohn0, 'title')
|
|
True
|
|
>>> canAccess(postJohn0Text, 'data')
|
|
True
|
|
|
|
>>> login(userMartha)
|
|
>>> canAccess(postJohn0, 'data')
|
|
False
|
|
>>> canAccess(postJohn0Text, 'data')
|
|
False
|
|
|
|
When we clear the `private` flag the post becomes visible again.
|
|
|
|
>>> aPostJohn0.private = False
|
|
>>> canAccess(postJohn0, 'title')
|
|
True
|
|
>>> canAccess(postJohn0Text, 'title')
|
|
True
|
|
|
|
|
|
Micro Articles
|
|
==============
|
|
|
|
>>> from loops.compound.microart.base import MicroArt
|
|
>>> from loops.compound.microart.interfaces import IMicroArt
|
|
>>> component.provideAdapter(MicroArt, provides=IMicroArt)
|
|
|
|
>>> tMicroArt = addAndConfigureObject(concepts, Concept, 'microart',
|
|
... title='MicroArt', conceptType=tType,
|
|
... typeInterface=IMicroArt)
|
|
|
|
>>> ma01 = addAndConfigureObject(concepts, Concept, 'ma01',
|
|
... conceptType=tMicroArt,
|
|
... title='Organizational Knowledge',
|
|
... story='Systemic KM talks about organizational knowledge.',
|
|
... insight='Organizational knowledge is not visible.',
|
|
... consequences='Use examples. Look for strucure and rules. '
|
|
... 'Knowledge shows itself in actions.',
|
|
... followUps='What about collective intelligence? '
|
|
... 'How does an organization express itself?')
|
|
|
|
>>> ma01._insight
|
|
'Organizational knowledge is not visible.'
|
|
>>> list(resources)
|
|
[..., 'ma01_story']
|
|
|
|
>>> adMa01 = adapted(ma01)
|
|
>>> adMa01.insight
|
|
'Organizational knowledge is not visible.'
|
|
>>> adMa01.story
|
|
'Systemic KM talks about organizational knowledge.'
|
|
|
|
|
|
Books, Sections, and Pages
|
|
==========================
|
|
|
|
>>> import os
|
|
>>> from loops.setup import importData
|
|
>>> importPath = os.path.join(os.path.dirname(__file__), 'book')
|
|
>>> importData(loopsRoot, importPath, 'loops_book_de.dmp')
|
|
|
|
>>> from loops.compound.book.browser import BookView, SectionView, TopicView
|
|
|
|
|
|
Fin de partie
|
|
=============
|
|
|
|
>>> placefulTearDown()
|
|
|