Teaching levels
===============

A simple implementation to define a set of courses taught to
students at a given level.  This concept is implemented by linking a
level within a schoolyear to a course in the same schoolyear.

Ability to define a student's level, as well as any UI whatsoever,
are not implemented yet, so levels are pretty useless at this point.

Level data model
----------------

There is a root container, initialized on application startup.

    >>> from schooltool.app.interfaces import ISchoolToolApplication
    >>> from schooltool.app.interfaces import ApplicationInitializationEvent
    >>> from schooltool.app.main import initializeSchoolToolPlugins

    >>> app = ISchoolToolApplication(None)
    >>> initializeSchoolToolPlugins(ApplicationInitializationEvent(app))

    >>> sorted(app.keys())
    ['schooltool.level.level', 'schooltool.schoolyear']

    >>> app['schooltool.level.level']
    <schooltool.level.level.LevelContainerContainer ...>

Sets of levels are stored in it for every school year.  Levels do
not work without active school year.

    >>> from schooltool.level.interfaces import ILevelContainer
    >>> print ILevelContainer(app, None)
    None

When application has an active schoolyear, you can adapt it to
ILevelContainer to get the relevant levels.

    >>> from schooltool.schoolyear.interfaces import ISchoolYearContainer
    >>> schoolyears = ISchoolYearContainer(app)

    >>> from datetime import date
    >>> from schooltool.schoolyear.schoolyear import SchoolYear
    >>> schoolyears['2005'] = SchoolYear(
    ...     "2005", date(2005, 9, 1), date(2005, 12, 30))

    >>> print schoolyears.getActiveSchoolYear().title
    2005

    >>> print ILevelContainer(app, None)
    <schooltool.level.level.LevelContainer ...>

Of course, you can obtain the level container directly from a school year.

    >>> schoolyears['2006'] = SchoolYear(
    ...     "2006", date(2006, 9, 1), date(2006, 12, 30))

    >>> print ILevelContainer(schoolyears['2006'], None)
    <schooltool.level.level.LevelContainer ...>

    >>> bool(ILevelContainer(schoolyears['2005']) is
    ...      ILevelContainer(schoolyears['2006']))
    False

Levels themselves are simple objects with a title.

    >>> from schooltool.level.level import Level
    >>> levels_2005 = ILevelContainer(schoolyears['2005'])
    >>> levels_2005['2'] = Level(u'Second-years 2005')
    >>> levels_2005['1'] = Level(u'First-years 2005')

    >>> levels_2006 = ILevelContainer(schoolyears['2006'])
    >>> levels_2006['1'] = Level(u'First-years 2006')

    >>> [level.title
    ...  for level in ILevelContainer(schoolyears['2005']).values()]
    [u'Second-years 2005', u'First-years 2005']

    >>> [level.title
    ...  for level in ILevelContainer(schoolyears['2006']).values()]
    [u'First-years 2006']

Level containers are ordered.

    >>> ILevelContainer(schoolyears['2005']).updateOrder(['1', '2'])
    >>> [level.title
    ...  for level in ILevelContainer(schoolyears['2005']).values()]
    [u'First-years 2005', u'Second-years 2005']


Linking levels to courses
-------------------------

Levels are linked to courses via schooltool.relationship.  There
is a helper attribute to manage the relationships.

    >>> math = CourseStub('Math')

    >>> level_1_2005 = ILevelContainer(schoolyears['2005'])['1']
    >>> level_1_2005.courses.add(math)

    >>> level_1_2006 = ILevelContainer(schoolyears['2006'])['1']
    >>> level_1_2006.courses.add(math)

    >>> from schooltool.level.level import URILevelCourses, URILevel
    >>> print [level.title
    ...     for level in getRelatedObjects(
    ...         math, URILevel, rel_type=URILevelCourses)]
    [u'First-years 2005', u'First-years 2006']

It is important to note that when a school year is deleted, the
corresponding level container is also deleted and all level
relationships are lost.

    >>> top_level_container = ILevelContainer(schoolyears['2005']).__parent__
    >>> len(top_level_container)
    2

    >>> del schoolyears['2006']
    >>> len(top_level_container)
    1

    >>> print [level.title
    ...     for level in getRelatedObjects(
    ...         math, URILevel, rel_type=URILevelCourses)]
    [u'First-years 2005']


Helpers
-------

If you need a dropdown of levels in a form, there is a special
level vocabulary.

    >>> from zope.schema import Choice
    >>> levels_field = Choice(
    ...    title=u"Levels",
    ...    vocabulary='schooltool.level.level.levelvocabulary')

It works with contexts adaptable to ISchoolYear.

    >>> context = schoolyears['2005']
    >>> levels = levels_field.bind(context)

    >>> for term in levels.vocabulary:
    ...     print '%s: %s' % (term.token, term.title)
    1-: First-years 2005
    2-: Second-years 2005

    >>> from zope.interface.verify import verifyObject
    >>> from zope.schema.interfaces import IVocabularyTokenized
    >>> verifyObject(IVocabularyTokenized, levels.vocabulary)
    True
