===============================================================
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=u'Compound',
  ...                   conceptType=tType, typeInterface=ICompound)
  >>> c01 = addAndConfigureObject(concepts, Concept, 'c01',
  ...                    title=u'Compound #01', conceptType=tCompound)
  >>> tPredicate = concepts.getPredicateType()
  >>> isPartof = addAndConfigureObject(concepts, Concept, 'ispartof',
  ...                   title=u'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[u'd003.txt'])
  >>> aC01.add(resources[u'd001.txt'])
  >>> [getName(p) for p in aC01.getParts()]
  [u'd003.txt', u'd001.txt']
  >>> aC01.add(resources[u'd001.txt'], 0)
  >>> [getName(p) for p in aC01.getParts()]
  [u'd001.txt', u'd003.txt', u'd001.txt']
  >>> aC01.add(resources[u'd002.txt'], -1)
  >>> [getName(p) for p in aC01.getParts()]
  [u'd001.txt', u'd003.txt', u'd002.txt', u'd001.txt']
We can reorder the parts of a compound.
  >>> aC01.reorder([resources[u'd002.txt'], resources[u'd001.txt'], ])
  >>> [getName(p) for p in aC01.getParts()]
  [u'd002.txt', u'd001.txt', u'd003.txt', u'd001.txt']
And remove a part from the compound.
  >>> aC01.remove(resources[u'd001.txt'], 1)
  >>> [getName(p) for p in aC01.getParts()]
  [u'd002.txt', u'd003.txt', u'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=u'Blog',
  ...                               conceptType=tType)
  >>> tBlogPost = addAndConfigureObject(concepts, Concept, 'blogpost',
  ...                               title=u'Blog Post', conceptType=tType,
  ...                               typeInterface=IBlogPost)
  >>> myBlog = addAndConfigureObject(concepts, Concept, 'myblog', title=u'My Blog',
  ...                               conceptType=tBlog)
  >>> firstPost = addAndConfigureObject(concepts, Concept, 'firstpost',
  ...                               title=u'My first post', conceptType=tBlogPost)
  >>> aFirstPost = adapted(firstPost)
  >>> aFirstPost.date
  >>> aFirstPost.text = u'My first blog post.'
  >>> aFirstPost.text
  u'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', u'John', login='john')
  >>> persJohn = addAndConfigureObject(concepts, Concept, 'person.john',
  ...                   title=u'John Smith', conceptType=tPerson,
  ...                   userId='users.john')
  >>> blogJohn = addAndConfigureObject(concepts, Concept, 'blog.john',
  ...                   title=u'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': u'John\'s first post', 'text': u'Text of John\'s post',
  ...          'date': '2008-02-02T15:54:11',
  ...          'privateComment': u'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
  u"John's first post"
  >>> postJohn0Text = adapted(postJohn0.getResources()[0])
  >>> postJohn0Text.data
  u"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', u'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=u'MicroArt', conceptType=tType,
  ...                                   typeInterface=IMicroArt)
  >>> ma01 = addAndConfigureObject(concepts, Concept, 'ma01',
  ...               conceptType=tMicroArt,
  ...               title=u'Organizational Knowledge',
  ...               story=u'Systemic KM talks about organizational knowledge.',
  ...               insight=u'Organizational knowledge is not visible.',
  ...               consequences=u'Use examples. Look for strucure and rules. '
  ...                       u'Knowledge shows itself in actions.',
  ...               followUps=u'What about collective intelligence? '
  ...                       u'How does an organization express itself?')
  >>> ma01._insight
  u'Organizational knowledge is not visible.'
  >>> list(resources)
  [..., u'ma01_story']
  >>> adMa01 = adapted(ma01)
  >>> adMa01.insight
  u'Organizational knowledge is not visible.'
  >>> adMa01.story
  u'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()