.. _defining_content:

================
Defining Content
================

:term:`Resource` is the term that Substance D uses to describe an object
placed in the :term:`resource tree`.

Ideally, all resources in your resource tree will be :term:`content`. "Content"
is the term that Substance D uses to describe resource objects that are
particularly well-behaved when they appear in the SDI management interface.
The Substance D management interface (aka :term:`SDI`) is a set of views
imposed upon the resource tree that allow you to add, delete, change and
otherwise manage resources.

You can convince the management interface that your particular resources are
content.  To define a resource as content, you need to associate a resource
with a :term:`content type`.

.. _registering_content:

Registering Content
===================

In order to add new content to the system, you need to associate a
:term:`resource factory` with a :term:`content type`.  A resource factory that
generates content must have these properties:

- It must be a class, or a factory function that returns an instance of a
  resource class.

- Instances of the resource class must be *persistent* (it must derive from
  the ``persistent.Persistent`` class or a class that derives from Persistent
  such as :class:`substanced.folder.Folder`).

- The resource class or factory must be decorated with the ``@content``
  decorator, or must be added at configuration time via
  ``config.add_content_type``.

- It must have a *type*.  A type acts as a globally unique categorization
  marker, and allows the content to be constructed, enumerated, and
  introspected by various Substance D UI elements such as "add forms", and
  queries by the management interface for the icon class of a resource.  A
  type can be any hashable Python object, but it's most often a string.

Here's an example which defines a content resource factory as a class:

.. code-block:: python

   # in a module named blog.resources

   from persistent import Persistent
   from substanced.content import content

   @content('Blog Entry')
   class BlogEntry(Persistent):
       def __init__(self, title='', body=''):
           self.title = title
           self.body = body

Here's an example of defining a content resource factory using a function
instead:

.. code-block:: python

   # in a module named blog.resources

   from persistent import Persistent
   from substanced.content import content

   class BlogEntry(Persistent):
       def __init__(self, title, body):
           self.title = title
           self.body = body

   @content('Blog Entry')
   def make_blog_entry(title='', body=''):
       return BlogEntry(title, body)

When a resource factory is not a class, Substance D will wrap the resource
factory in something that changes the resource object returned from the
factory.  In the above case, the BlogEntry instance returned from
``make_blog_entry`` will be changed; its ``__factory_type__`` attribute will be
mutated.

Notice that when we decorate a resource factory class with ``@content``, and
the class' ``__init__`` function takes arguments, we provide those arguments
with default values.  This is mandatory if you'd like your content objects to
participate in a "dump".  Dumping a resource requires that the resource be
creatable without any mandatory arguments.  It's a similar story if our factory
is a function; the function decorated by the ``@content`` decorator should
provide defaults to any argument.  In general, a resource factory can take
arguments, but each parameter of the factory's callable should be given a
default value.  This also means that all arguments to a resource factory
should be keyword arguments, and not positional arguments.

In order to activate a ``@content`` decorator, it must be *scanned* using the
Pyramid ``config.scan()`` machinery:

.. code-block:: python

   # in a module named blog.__init__

   from pyramid.config import Configurator

   def main(global_config, **settings):
       config = Configurator()
       config.include('substanced')
       config.scan('blog.resources')
       # .. and so on ...

Instead of using the ``@content`` decorator, you can alternately add a
content resource imperatively at configuration time using the
``add_content_type`` method of the Configurator:

.. code-block:: python

   # in a module named blog.__init__

   from pyramid.config import Configurator
   from .resources import BlogEntry

   def main(global_config, **settings):
       config = Configurator()
       config.include('substanced')
       config.add_content_type('Blog Entry', BlogEntry)

This does the same thing as using the ``@content`` decorator, but you don't
need to ``scan()`` your resources if you use ``add_content_type`` instead of
the ``@content`` decorator.

Once a content type has been defined (and scanned, if it's been defined using
a decorator), an instance of the resource can be constructed from within a
view that lives in your application:

.. code-block:: python

   # in a module named blog.views

   from pyramid.httpexceptions import HTTPFound
   from pyramid.view import (
       view_config,
       view_defaults,
       )

   @view_config(name='add_blog_entry', request_method='POST')
   def add_blogentry(context, request):
       title = request.POST['title']
       body = request.POST['body']
       entry = request.registry.content.create('Blog Entry', title, body)
       context[title] = entry
       return HTTPFound(request.resource_url(entry))

The arguments passed to ``request.registry.content.create`` must start with
the content type, and must be followed with whatever arguments are required
by the resource factory.

Creating an instance of content this way isn't particularly more useful than
creating an instance of the resource object by calling its class ``__init__``
directly unless you're building a highly abstract system.  But even if you're
not building a very abstract system, types can be very useful.  For instance,
types can be enumerated:

.. code-block:: python

   # in a module named blog.views

   @view_config(name='show_types', renderer='show_types.pt')
   def show_types(request):
       all_types = request.registry.content.all()
       return {'all_types':all_types}

``request.registry.content.all()`` will return all the types you've defined
and scanned.

Metadata
========

A content's type can be associated with metadata about that type, including the
content type's name, its icon in the SDI management interface, an add view
name, and other things.  Pass arbitrary keyword arguments to the ``@content``
decorator or ``config.add_content_type`` to specify metadata.

Names
-----

You can associate a content type registration with a name that shows up when
someone attempts to add such a piece of content using the SDI management
interface "Add" tab by passing a ``name`` keyword argument to ``@content``
or ``config.add_content_type``.

.. code-block:: python

   # in a module named blog.resources

   from persistent import Persistent
   from substanced.content import content

   @content('Blog Entry', name='Cool Blog Entry')
   class BlogEntry(Persistent):
       def __init__(self, title='', body=''):
           self.title = title
           self.body = body

Once you've done this, the "Add" tab in the SDI management interface will
show your content as addable using this name instead of the type name.

Icons
-----

You can associate a content type registration with a management view icon class
by passing an ``icon`` keyword argument to ``@content`` or ``add_content_type``.

.. code-block:: python

   # in a module named blog.resources

   from persistent import Persistent
   from substanced.content import content

   @content('Blog Entry', icon='glyphicon glyphicon-file')
   class BlogEntry(Persistent):
       def __init__(self, title='', body=''):
           self.title = title
           self.body = body

Once you've done this, content you add to a folder in the sytem will display
the icon next to it in the contents view of the management interface and in
the breadcrumb list.  The available icon class names are listed at
http://getbootstrap.com/components/#glyphicons .  For glyphicon icons, you'll
need to use two classnames: ``glyphicon`` and ``glyphicon-foo``, separated by
a space.

You can also pass a callback as an ``icon`` argument:

.. code-block:: python

   from persistent import Persistent
   from substanced.content import content

   def blogentry_icon(context, request):
       if context.body:
           return 'glyphicon glyphicon-file'
       else:
           return 'glyphicon glyphicon-gift'

   @content('Blog Entry', icon=blogentry_icon)
   class BlogEntry(Persistent):
       def __init__(self, title='', body=''):
           self.title = title
           self.body = body

A callable used as ``icon`` must accept two arguments: ``context`` and
``request``.  ``context`` will be an instance of the type and ``request`` will
be the current request; your callback will be called at the time the folder
view is drawn.  The callable should return either an icon class name or
``None``. For example, the above ``blogentry_icon`` callable tells the SDI to
use an icon representing a file if the blogentry has a body, otherwise show an
icon representing gift.

Add Views
---------

You can associate a content type with a view that will allow the type to be
added by passing the name of the add view as a keyword argument to
``@content`` or ``add_content_type``.

.. code-block:: python

   # in a module named blog.resources

   from persistent import Persistent
   from substanced.content import content

   @content('Blog Entry', add_view='add_blog_entry')
   class BlogEntry(Persistent):
       def __init__(self, title='', body=''):
           self.title = title
           self.body = body

Once you've done this, if the button is clicked in the "Add" tab for this
content type, the related view will be presented to the user.

You can also pass a callback as an ``add_view`` argument:

.. code-block:: python

   from persistent import Persistent
   from substanced.content import content
   from substanced.folder import Folder

   def add_blog_entry(context, request):
       if request.registry.content.istype(context, 'Blog'):
           return 'add_blog_entry'

   @content('Blog')
   class Blog(Folder):
       pass

   @content('Blog Entry', add_view=add_blog_entry)
   class BlogEntry(Persistent):
       def __init__(self, title='', body=''):
           self.title = title
           self.body = body

A callable used as ``add_view`` must accept two arguments: ``context`` and
``request``.  ``context`` will be the potential parent object of the content
(when the SDI folder view is drawn), and ``request`` will be the current
request at the time the folder view is drawn.  The callable should return
either a view name or ``None`` if the content should not be addable in this
circumstance.  For example, the above ``add_blog_entry`` callable asserts that
Blog Entry content should only be addable if the context we're adding to is of
type Blog; it returns None otherwise, signifying that the content is not
addable in this circumstance.

Obtaining Metadata About a Content Object's Type
------------------------------------------------

Return the icon class name for the blogentry's content type or
``None`` if it does not exist::

  request.registry.content.metadata(blogentry, 'icon')

Return the icon for the blogentry's content type or
``glyphicon glyphicon-file`` if it does not exist::

  request.registry.content.metadata(blogentry, 'icon',
                                    'glyphicon glyphicon-file')


.. _affecting_content_creation:

Affecting Content Creation
==========================

In some cases you might want your resource to perform some actions that
can only take place after it has been seated in its container, but
before the creation events have fired. The ``@content`` decorator and
``add_content_type`` method both support an ``after_create`` argument,
pointed at a callable.

For example:

.. code-block:: python

    @content(
        'Document',
        icon='glyphicon glyphicon-align-left',
        add_view='add_document',
        propertysheets = (
            ('Basic', DocumentPropertySheet),
            ),
        after_create='after_creation'
        )
    class Document(Persistent):

        name = renamer()

        def __init__(self, title, body):
            self.title = title
            self.body = body

        def after_creation(self, inst, registry):
            pass

If the value provided for ``after_create`` is a string, it's assumed to
be a method of the created object. If it's a sequence, each value
should be a string or a callable, which will be called in turn. The
callable(s) are passed the instance being created and the registry.
Afterwards, :class:`substanced.event.ContentCreatedEvent` is emitted.

Construction of the root folder in Substance D is a special case. Most
Substance D applications will start with:

.. code-block:: python

    from substanced.db import root_factory
    def main(global_config, **settings):
        """ This function returns a Pyramid WSGI application.
        """
        config = Configurator(settings=settings, root_factory=root_factory)

The :py:func:`substanced.db.root_factory` callable contains the following
line:

.. code-block:: python

    app_root = registry.content.create('Root')

In many cases you want to perform some extra work on the ``Root``. For
example, you might want to create a catalog with indexes. Substance D
emits an event when the root is created, so you can subscribe to that
event and perform some actions:

.. code-block:: python

    from substanced.root import Root
    from substanced.event import subscribe_created
    from substanced.catalog import Catalog

    @subscribe_created(Root)
    def root_created(event):
        root = event.object
        catalog = Catalog()
        catalogs = root['catalogs']
        catalogs.add_service('catalog', catalog)
        catalog.update_indexes('system', reindex=True)
        catalog.update_indexes('sdidemo', reindex=True)


Names and Renaming
==================

A resource's "name" (``__name__``) is important to the system in
Substance D. For example, traversal uses the value in URLs and paths to
walk through hierarchy. Containers need to know when a resource's
``__name__`` changes.

To help support this, Substance D provides
:py:func:`substanced.util.renamer`. You use it as a class attribute
wrapper on resources that want "managed" names. These resources then
gain a ``name`` attribute with a getter/setter from ``renamer``.
Getting the ``name`` returns the ``__name__``. Setting ``name`` grabs
the container and calls the ``rename`` method on the folder.

For example:

.. code-block:: python

    class Document(Persistent):
        name = renamer()

Special Colander Support
========================

Forms and schemas for resources become pretty easy in Substance D. To
make it easier for forms to interact with the Substance D machinery,
it includes some special Colander schema nodes you can use on your forms.

``NameSchemaNode``
------------------

If you want your form to affect the ``__name__`` of a resource,
certain constraints become applicable. These constraints might be
different, so you might want to know if you are on an add form versus
an edit form. :py:class:`substanced.schema.NameSchemaNode` provides a
schema node and default widget that bundles up the common rules for this.
For example:

.. code-block:: python

    class BlogEntrySchema(Schema):
        name = NameSchemaNode()

The above provides the basics of support for editing a name property,
especially when combined with the ``renamer()`` utility mentioned above.

By default the name is limited to 100 characters. ``NameSchemaNode``
accepts an argument that can set a different limit:

.. code-block:: python

    class BlogEntrySchema(Schema):
        name = NameSchemaNode(max_len=20)

You can also provide an ``editing`` argument, either as a boolean or a
callable which returns a boolean, which determines whether the form is
rendered in "editing" mode. For example:

.. code-block:: python

    class BlogEntrySchema(Schema):
        name = NameSchemaNode(
            editing=lambda c, r: r.registry.content.istype(c, 'BlogEntry')
            )

``PermissionSchemaNode``
------------------------

A form might want to allow selection of zero or more permissions from
the site's defined list of permissions.
:py:class:`PermissionSchemaNode` collects the possible
state from the system, the currently-assigned values, and presents a
widget that manages the values.

``MultireferenceIdSchemaNode``
------------------------------

References are a very powerful facility in Substance D. Naturally
you'll want your application's forms to assign references.
:py:class:`MultireferenceIdSchemaNode` gives a schema node and widget
that allows multiple selections of possible values in the system for
references, including the current assignments.

As an example, the built-in :py:class:`substanced.principal.UserSchema`
uses this schema node:

.. code-block:: python

    class UserSchema(Schema):
        """ The property schema for :class:`substanced.principal.User`
        objects."""
        groupids = MultireferenceIdSchemaNode(
            choices_getter=groups_choices,
            title='Groups',
            )

Overriding Existing Content Types
=================================

Perhaps you would like to slightly adjust an existing content type,
such as ``Folder``, without re-implementing it. For exampler,
perhaps you would like to override just the ``add_view`` and provide
your own view, such as:

.. code-block:: python

    @mgmt_view(
        context=IFolder,
        name='my_add_folder',
        tab_condition=False,
        permission='sdi.add-content',
        renderer='substanced.sdi:templates/form.pt'
    )
    class MyAddFolderView(AddFolderView):

        def before(self, form):
            # Perform some custom work before validation
            pass

With this you can override any of the view predicates (such as
``permission``) and override any part of the form handling (such as
adding a ``before`` that performs some custom processing.)

To make this happen, you can re-register, so to speak,
the content type during startup:

.. code-block:: python

    from substanced.folder import Folder
    from .views import MyAddFolderView
    config.add_content_type('Folder', Folder,
                            add_view='my_add_folder',
                            icon='glyphicon glyphicon-folder-close')

This, however, keeps the same content type class. You can also go
further by overriding the content type definition itself:

.. code-block:: python

    @content(
        'Folder',
        icon='glyphicon glyphicon-folder-close',
        add_view='my_add_folder',
    )
    @implementer(IFolder)
    class MyFolder(Folder):

        def send_email(self):
            pass

The class for the ``Folder`` content type has now been replaced. Instead
of ``substanced.folder.Folder`` it is ``MyFolder``.

.. note::

    Overriding a content type is a pain-free way to make a custom
    ``Root`` object. You could supply your own ``root_factory`` to
    the ``Configurator`` but that means replicating all its rather
    complicated goings-on. Instead, provide your own content type
    factory, as above, for ``Root``.


Adding Automatic Naming for Content
===================================

On some sites you don't want to set the name for every piece of content you
create. Substance D provides support for this with a special kind of folder.
You can configure your site to use the autonaming folder by overriding
the standard folder:

.. code-block:: python

    from substanced.folder import SequentialAutoNamingFolder
    from substanced.interaces import IFolder
    from zope.interface import implementer

    @content(
       'Folder',
        icon='glyphicon glyphicon-folder-close',
        add_view='add_folder',
    )
    @implementer(IFolder)
    class  MyFolder(SequentialAutoNamingFolder):
        """ Override Folder content type """

The ``add view`` for Documents can then be edited to no longer require a name:

.. code-block:: python

    def add_success(self, appstruct):
        registry = self.request.registry
        document = registry.content.create('Document', **appstruct)
        self.context.add_next(document)
        return HTTPFound(
            self.request.sdiapi.mgmt_path(self.context, '@@contents')
        )

.. note::

    This does not apply to the root object.


Affecting the Tab Order for Management Views
============================================

The ``tab_order`` parameter overrides the mgmt_view tab settings
for a content type. Its value should be a sequence of view names, each
corresponding to a tab that will appear in the management interface. Any
registered view names that are omitted from this sequence will be placed
after the other tabs.


Handling Content Events
=======================

Adding and modifying data related to content is, thanks to the framework,
easy to do. Sometimes, though, you want to intervene and, for example,
perform some extra work when content resources are added. Substance D
has several framework events you can subscribe to using
:ref:`Pyramid events <pyramid:events_chapter>`.

The :py:mod:`substanced.events` module imports these events as interfaces
from :py:mod:`substanced.interfaces` and then provides decorator
subscribers as convenience for each:

- :py:class:`substanced.interfaces.IObjectAdded` as subscriber
  ``@subscriber_added``

- :py:class:`substanced.interfaces.IObjectWillBeAdded` as subscriber
  ``@subscriber_will_be_added``

- :py:class:`substanced.interfaces.IObjectRemoved` as subscriber
  ``@subscriber_removed``

- :py:class:`substanced.interfaces.IObjectWillBeRemoved` as subscriber
  ``@subscriber_will_be_removed``

- :py:class:`substanced.interfaces.IObjectModified` as subscriber
  ``@subscriber_modified``

- :py:class:`substanced.interfaces.IACLModified` as subscriber
  ``@subscriber_acl_modified``

- :py:class:`substanced.interfaces.IContentCreated` as subscriber
  ``@subscriber_created``

As an example, the
:py:func:`substanced.principal.subscribers.user_added` function is a
subscriber to the ``IObjectAdded`` event:

.. code-block:: python

    @subscribe_added(IUser)
    def user_added(event):
        """ Give each user permission to change their own password."""
        if event.loading: # fbo dump/load
            return
        user = event.object
        registry = event.registry
        set_acl(
            user,
            [(Allow, get_oid(user), ('sdi.view', 'sdi.change-password'))],
            registry=registry,
            )

As with the rest of Pyramid, you can do imperative configuration if you
don't like decorator-based configuration, using
``config.add_content_subscriber`` Both the declarative and imperative
forms result in :func:`substanced.event.add_content_subscriber`.

.. note::

    While the event subscriber is de-coupled logically from the action
    that triggers the event, both the action and the subscriber run
    in the same transaction.

The ``IACLModified`` event (and ``@subscriber_acl_modified`` subscriber)
is used internally by Substance D to re-index information in the system
catalog's ACL index. Substance D also uses this event to maintain
references between resources and principals. Substance D applications
can use this in different ways, for example recording a security audit
trail on security changes.

Sometimes when you perform operations on objects you don't want to
perform the standard events. For example, in folder contents you can
select a number of resources and move them to another folder. Normally
this would fire content change events that re-index the files. This is
fairly pointless: the content of the file hasn't changed.

If you looked at the interface for one of the content events,
you would see some extra information supported. For example, in
:py:class:`substanced.interfaces.IObjectWillBeAdded`:

.. code-block:: python

    class IObjectWillBeAdded(IObjectEvent):
        """ An event type sent when an before an object is added """
        object = Attribute('The object being added')
        parent = Attribute('The folder to which the object is being added')
        name = Attribute('The name which the object is being added to the folder '
                         'with')
        moving = Attribute('None or the folder from which the object being added '
                           'was moved')
        loading = Attribute('Boolean indicating that this add is part of a load '
                            '(during a dump load process)')
        duplicating = Attribute('The object being duplicated or ``None``')

``moving``, ``loading``, and ``duplicating`` are flags that can be set
on the event when certain actions are triggered. These help in cases
such as the one above: certain subscribers might want "flavors" of
standard events and, in some cases, handle the event in a different
way. This helps avoid lots of special-case events or the need for a
hierarchy of events.

Thus in the case above, the catalog subscriber can see that the changes
triggered by the event where in the special case of "moving". This can
be seen in :attr:`substanced.catalog.subscribers.object_added`.
