Basic ZClass Tests
==================

We can create ZClasses from Python, It's a bit complicated, as
ZClasses were designed mainly to be used from the web.

First, we need to install the ZClass-aware class factory in our
database:

    >>> import Zope2.App.ClassFactory
    >>> some_database.classFactory = Zope2.App.ClassFactory.ClassFactory

To do anything, we need a working Zope object space:

    >>> conn = some_database.open()
    >>> from OFS.Application import Application
    >>> app = Application()
    >>> conn.root()['Application'] = app
    >>> from OFS.Application import initialize
    >>> initialize(app)
    >>> app.manage_addFolder('sandbox')
    >>> sandbox = app.sandbox

Once we have an object space, we need to create a product to hold the ZClass:

    >>> app.Control_Panel.Products.manage_addProduct('test', '')
    >>> test = app.Control_Panel.Products['test']

Then we can create the ZClass in the product:

    >>> test.manage_addZClass('C', zope_object=True,  CreateAFactory=True)

Having created a ZClass, we can create an instance.

When setting the 'CreateAFactory' flag, a factory will be created which
can be called from the web to create a new instance of 'C'. It also places
an option for adding 'C's in the Add-menu in the ZMI.

    >>> factory = test.C_factory

    >>> factory.__class__
    <class 'App.Factory.Factory'>

The other objects created are:

    - a permission "C_add_permission" required to access the factory,
    >>> test.C_add_permission.meta_type
    'Zope Permission'

    - an initial add-form "C_addForm" which calls
    >>> test.C_addForm.meta_type
    'DTML Method'

    - the add-script "C_add" for creating a new 'C' instance
    >>> test.C_add.meta_type
    'Script (Python)'

The factory stores the name of the initial page and its permission name:

    >>> factory.initial, factory.permission
    ('C_addForm', 'Add Cs')

We only need a simple add-script in this scenario:

    >>> factory.initial = 'C_add'

    >>> test.C_add.ZPythonScript_edit('',
    ...     '##parameters=dispatcher, request\n'
    ...     'return container.C.createInObjectManager('
    ...          'request["id"], request)\n')
    ...

Anonymous users are usually not allowed to create new content:

    add a new user...
    >>> app.acl_users._addUser('admin', 'pass', 'pass', (), ())
    >>> user = app.acl_users.getUser('admin').__of__(app.acl_users)

    log in as user 'admin'...
    >>> from AccessControl.SecurityManagement import newSecurityManager
    >>> newSecurityManager(None, user)

Now simulate a browser request to add a 'C' instance with id 'z':

    >>> request = {'id': 'z'}
    >>> from zExceptions.unauthorized import Unauthorized
    >>> try:
    ...     sandbox.manage_addProduct['test'].C_factory.index_html(request)
    ... except Unauthorized:
    ...     print 'not authorized'
    not authorized

All right, allow the admin user to 'Add Cs':

    >>> bool(user.has_permission('Add Cs', sandbox))
    False

    >>> sandbox.manage_addLocalRoles('admin', ['Admin'])
    >>> sandbox.manage_permission('Add Cs', roles=['Admin'])
    
    >>> bool(user.has_permission('Add Cs', sandbox))
    True

Try again:
    
    >>> request = {'id': 'z'}
    >>> sandbox.manage_addProduct['test'].C_factory.index_html(request)
    <C at /sandbox/z>
    >>> app.sandbox.z
    <C at /sandbox/z>

Log out:

    >>> from AccessControl.SecurityManagement import noSecurityManager
    >>> noSecurityManager()


From python there is a much simpler way for creating a ZClass instance:

    >>> c = test.C()
    >>> c._setId('c')
    >>> app._setObject('c', c)
    'c'

Now, ZClass instances aren't very interesting by themselves.  We can
give them data by defining property sheets:

    >>> test.C.propertysheets.common.manage_addCommonSheet('basic', '')
    >>> test.C.propertysheets.common['basic'].manage_addProperty(
    ...     'x', 'hee ', 'string')
    >>> app.c.x
    'hee '
    >>> test.C.propertysheets.common['basic'].manage_addProperty(
    ...                   'y', 42, 'int')
    >>> app.c.y
    42

Of course, we can change the data:

    >>> app.c.x = 'hi '
    >>> app.c.y = 3
    >>> app.c.x, app.c.y
    ('hi ', 3)

We can also add methods, such as Python scripts:

    >>> test.C.propertysheets.methods.manage_addProduct[
    ...      'PythonScripts'].manage_addPythonScript('eek')
    ''
    >>> test.C.propertysheets.methods['eek'].ZPythonScript_edit('',
    ...      'return container.x * container.y')

    >>> app.c.eek()
    'hi hi hi '

Let's commit our changes:

    >>> import transaction
    >>> transaction.commit()

We can access the class in another connection. We'll use an explicit
transaction manager so that we can use the second connection without
creating a separate thread:

    >>> tm2 = transaction.TransactionManager()
    >>> conn2 = some_database.open(transaction_manager=tm2)
    >>> app2 = conn2.root()['Application']
    >>> test2 = app2.Control_Panel.Products['test']
    >>> c2 = test2.C()
    >>> c2._setId('c2')
    >>> app2._setObject('c2', c2)
    'c2'

    >>> app2.c2.x = '*'
    >>> print app2.c2.x, app2.c2.y, app2.c2.eek(), '!'
    * 42 ****************************************** !

    >>> print app.c.x, app.c.y, app.c.eek(), '!'
    hi  3 hi hi hi  !

    >>> tm2.commit()


Of course, we should be able to see the new object created in the
other connection:

    >>> conn.sync()
    >>> app.c2.eek()
    '******************************************'

We can copy instances:

    >>> c3 = app.c2._getCopy(app)
    >>> c3 is app.c2.aq_base
    False

    >>> app.c3 = c3
    >>> app.c3.eek()
    '******************************************'

But the copies share a common class:

    >>> c3.__class__ is app.c2.__class__
    True
