psj.content Metadata
********************

:Test-Layer: unit

Metadata for content types.

PSJ supports metadata for content objects. This support is extremely
flexible, as it allows changing of metadata sets for certain content
types.

In this context Metadata is mostly handled as a set of metadata fields,
which are collected in a metadata set.

Metadata Fields
===============

Metadata fields represent one entry in a metadata set. Typical fields
are such as 'title', 'author' or 'publisher'. They do _not_ contain
any data, i.e. they describe a field, but do not provide its content.

Every metadata field must provide an identifier (the
'title'). Furthermore every metadata field provides a description,
which can be displayed in forms.

We create a simple field like this::

  >>> from pprint import pprint as pp
  >>> from psj.content.metadata import BaseField
  >>> field = BaseField('title')

  >>> field.title
  u'title'

  >>> field.description
  u''

Descriptions can contain umlauts::

  >>> field = BaseField('title', description='Söme Ümläuts')
  >>> print field.description
  Söme Ümläuts

Because we want to support different types of fields, which might
require different attributes, we defined a class for every supported
field type, all derived from `BaseField`.

Currently, the following field types are supported:

* TextLineField

* LinesField

* TextField

* BooleanField

* RelationField

* VocabularyField


TextLine fields
---------------

A TextLineField holds a line of unicode text. It has a title and a
default value::

  >>> from psj.content.metadata import TextLineField
  >>> field = TextLineField('title')

TextLineFields provide a default value to set, which is `None` by
default::

  >>> field.default is None
  True

We can set a default by passing it to the constructor::

  >>> field = TextLineField('title', default='foo')
  >>> field.title
  u'title'

  >>> field.description
  u''

  >>> field.default
  u'foo'

Of couse we can also pass unicode values for title and default::

  >>> field = TextLineField(u'title', default=u'bar')
  >>> field.title
  u'title'

  >>> field.description
  u''

  >>> field.default
  u'bar'

We can get a dict representation of `TextFields`::


  >>> pp(field.getDict(), width=20)
  {'default': u'bar',
   'description': u'',
   'id': 'title',
   'title': u'title',
   'type': 'TextLine'}


Text fields
-----------

Text fields are in fact textline fields. The difference is, that they
are rendered differently, as textareas instead of simple text lines.

A TextField holds a string of unicode text, includung line breaks
etc. It has a title and a default value::

  >>> from psj.content.metadata import TextField
  >>> field = TextField('title')

TextFields provide a default value to set, which is `None` by
default::

  >>> field.default is None
  True

We can set a default by passing it to the constructor::

  >>> field = TextField('title', default='foo')
  >>> field.title
  u'title'

  >>> field.description
  u''

  >>> field.default
  u'foo'

Of couse we can also pass unicode values for title and default::

  >>> field = TextField(u'title', default=u'bar')
  >>> field.title
  u'title'

  >>> field.default
  u'bar'

We can get a dict representation of `TextFields`::


  >>> pp(field.getDict(), width=20)
  {'default': u'bar',
   'description': u'',
   'id': 'title',
   'title': u'title',
   'type': 'Text'}


Lines fields
------------

Lines fields store lines of text. This way you can store a set of
terms, one line representing one term.

A LinesField holds a list of strings of unicode text. It has a title
and a default value::

  >>> from psj.content.metadata import LinesField
  >>> field = LinesField('title')

TextFields provide a default value to set, which is an empty list by
default::

  >>> field.default
  []

We can set a default by passing it to the constructor::

  >>> field = LinesField('title', default='foo')
  >>> field.title
  u'title'

  >>> field.description
  u''

  >>> field.default
  [u'foo']

Of couse we can also pass unicode values for title and default::

  >>> field = LinesField(u'title', description='baz', default=u'bar')
  >>> field.title
  u'title'

  >>> field.description
  'baz'

  >>> field.default
  [u'bar']

We can get a dict representation of `LinesFields`::


  >>> pp(field.getDict(), width=20)
  {'default': (u'bar',),
   'description': 'baz',
   'id': 'title',
   'title': u'title',
   'type': 'Lines'}


Boolean fields
--------------

A BooleanField holds a boolean value, of curse. It has a title and a
default value::

  >>> from psj.content.metadata import BooleanField
  >>> field = BooleanField('My Title')

Boolean fields provide a default value to set, which is `False` by
default::

  >>> field.default
  False

We can set a default by passing it to the constructor. The default
value naturally can only be `True` or `False`::

  >>> field = BooleanField('My Title', default=True)
  >>> field.title
  u'My Title'

  >>> field.default
  True

Of couse we can also pass unicode values for the title::

  >>> field = BooleanField(u'title', default=True)
  >>> field.title
  u'title'

  >>> field.default
  True

We can get a dict representation of `BooleanField`s::

  >>> pp(field.getDict(), width=20)
  {'default': True,
   'description': u'',
   'id': 'title',
   'title': u'title',
   'type': 'Boolean'}


Relation Fields
---------------

Relation fields represent someway links to other objects. They have
a title attribute and an `allowed` attribute to tell the types of
allowed references. The latter can be a tuple of strings or a string
with commas.

We create a relation field like this::

  >>> from psj.content.metadata import RelationField
  >>> field = RelationField('title', allowed=('Type1', 'Type2'))

  >>> field.title
  u'title'

  >>> field.description
  u''

  >>> field.allowed
  ('Type1', 'Type2')

Now we pass the list of types as comma separated string list::

  >>> field = RelationField('title', allowed="Type1,Type2")
  >>> field.allowed
  ('Type1', 'Type2')

We can get a dict representation of `RelationField`s::

  >>> pp(field.getDict(), width=20)
  {'allowed': 'Type1,Type2',
   'description': u'',
   'id': 'title',
   'title': u'title',
   'type': 'Relation'}


Vocabulary Fields
-----------------

Vocabulary fields hold references to site wide defined vocabularies.

We create a vocabulary field like this::

  >>> from psj.content.metadata import VocabularyField
  >>> field = VocabularyField('title')
  >>> field.title
  u'title'

  >>> field.description
  u''

Vocabularies have a `vocab` attribute, which holds the name of a
vocabulary. We can get a dict representation of `VocabularyField`s::

  >>> pp(field.getDict(), width=20)
  {'description': u'',
   'id': 'title',
   'multi': False,
   'title': u'title',
   'type': 'Vocabulary',
   'vocab': None}

Furthermore vocabularies can offer to select several values at
once. Setting the `multi` attribute to `True` we request that::

  >>> field = VocabularyField('title', multi=True)
  >>> field.multi
  True

If we pass a string for `multi` it will be converted to a boolean
value::

  >>> field = VocabularyField('title', multi='True')
  >>> field.multi
  True

  >>> field = VocabularyField('title', multi='False')
  >>> field.multi
  False

  >>> field = VocabularyField('title', multi='blah')
  >>> field.multi
  False


MetadataSets
============

Creating Metadata Sets
----------------------

Metadatasets contain a name, an id and an ordered list of fields.

We create a simple, empty metadata set like this::

   >>> from psj.content.metadata import MetadataSet
   >>> mset = MetadataSet()

This set is empty::

   >>> list(mset)
   []

This metadata set is unnamed::

   >>> mset.id
   'unnamed'

   >>> mset.name
   u'Unnamed'

We can pass a name using the `name` keyword::

   >>> mset = MetadataSet(name='MyName')
   >>> mset.name
   u'MyName'

   >>> mset = MetadataSet(name=u'MyName')
   >>> mset.name
   u'MyName'

We can also populate a metadata set on creation time with values::

   >>> mset = MetadataSet(name='myset1', fields=(
   ...          dict(title='title', type='TextLine'),
   ...          dict(title='description', type='TextLine'),
   ...          dict(title='important', type='Boolean', default=True),
   ...        ))

   >>> sorted(list(mset))
   ['description', 'important', 'title']   

The `important` field is of type boolean::

   >>> field = mset.get('important')
   >>> field
   <psj.content.metadata.metadata.BooleanField object at 0x...>

   >>> field.default
   True
   

Modifying Metadata Sets
------------------------

The basic things we can do with metadata sets are to add and remove
metadata items::

   >>> from psj.content.metadata import TextLineField
   >>> mset = MetadataSet()
   >>> item = TextLineField('address')
   >>> mset.add(item)
   >>> list(mset)
   ['address']

We can also get a certain entry by key::

   >>> mset.get('address')
   <psj.content.metadata.metadata.TextLineField object at 0x...>

If we specify a default value, this will be returned instead of `None`
if an entry cannot be found::

   >>> mset.get('foo', 'DefaultStuff')
   'DefaultStuff'

We can remove an entry by name (key)::

   >>> item = TextLineField('title')
   >>> mset.add(item)
   >>> list(mset)
   ['address', 'title']

   >>> mset.remove('title')
   >>> list(mset)
   ['address']

