===============================================================
loops - Linked Objects for Organization and Processing Services
===============================================================

  ($Id$)

  >>> 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.

  >>> 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(myBlog, 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


Fin de partie
=============

  >>> placefulTearDown()

