Index: /main/waeup.sirp/trunk/src/waeup/sirp/README.txt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/README.txt	(revision 6620)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/README.txt	(revision 6621)
@@ -32,5 +32,5 @@
 
   >>> u['students']
-  <waeup.sirp.student.studentcontainer.StudentContainer object at 0x...>
+  <waeup.sirp.students.container.StudentsContainer object at 0x...>
 
   >>> u['hostels']
Index: /main/waeup.sirp/trunk/src/waeup/sirp/app.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/app.py	(revision 6620)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/app.py	(revision 6621)
@@ -42,7 +42,5 @@
         self['datacenter'] = DataCenter()
         self['images'] = ImageStorage()
-
-        self['students'] = createObject(u'waeup.StudentContainer')
-        self['hostels'] = createObject(u'waeup.HostelContainer')
+        self['students'] = createObject(u'waeup.StudentsContainer')
         self._createPlugins()
 
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/__init__.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/__init__.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/__init__.py	(revision 6621)
@@ -0,0 +1,14 @@
+"""This package contains everything regarding students.
+"""
+# Make this a package.
+from waeup.sirp.students.students import (
+    Student, StudentFactory
+    )
+from waeup.sirp.students.container import StudentsContainer
+
+
+__all__ = [
+    'Student',
+    'StudentFactory',
+    'StudentsContainer',
+    ]
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 6621)
@@ -0,0 +1,99 @@
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""UI components for students and related components.
+"""
+import sys
+import grok
+
+from datetime import datetime
+from zope.formlib.widget import CustomWidgetFactory
+from zope.formlib.form import setUpEditWidgets
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from zope.traversing.browser import absoluteURL
+
+from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
+from reportlab.pdfgen import canvas
+from reportlab.lib.units import cm
+from reportlab.lib.pagesizes import A4
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.platypus import (Frame, Paragraph, Image,
+    Table, Spacer)
+from reportlab.platypus.tables import TableStyle
+
+from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
+from waeup.sirp.accesscodes.workflow import USED
+from waeup.sirp.browser import (
+    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
+from waeup.sirp.browser.breadcrumbs import Breadcrumb
+from waeup.sirp.browser.layout import NullValidator
+from waeup.sirp.browser.pages import add_local_role, del_local_roles
+from waeup.sirp.browser.resources import datepicker, tabs, datatable
+from waeup.sirp.browser.viewlets import (
+    ManageActionButton, PrimaryNavTab, LeftSidebarLink
+    )
+from waeup.sirp.image.browser.widget import (
+    ThumbnailWidget, EncodingImageFileWidget,
+    )
+from waeup.sirp.image.image import createWAeUPImageFile
+from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
+from waeup.sirp.permissions import get_users_with_local_roles
+from waeup.sirp.university.interfaces import ICertificate
+from waeup.sirp.widgets.datewidget import (
+    FriendlyDateWidget, FriendlyDateDisplayWidget)
+from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
+from waeup.sirp.widgets.objectwidget import (
+    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
+from waeup.sirp.widgets.multilistwidget import (
+    MultiListWidget, MultiListDisplayWidget)
+from waeup.sirp.students.interfaces import (
+    IStudentsContainer,
+    )
+
+class StudentsTab(PrimaryNavTab):
+    """Students tab in primary navigation.
+    """
+
+    grok.context(IWAeUPObject)
+    grok.order(3)
+    grok.require('waeup.viewStudents')
+    grok.template('primarynavtab')
+
+    pnav = 4
+    tab_title = u'Students'
+
+    @property
+    def link_target(self):
+        return self.view.application_url('students')
+
+class StudentsContainerPage(WAeUPDisplayFormPage):
+    """The standard view for regular student containers.
+    """
+    grok.context(IStudentsContainer)
+    grok.name('index')
+    grok.require('waeup.viewStudents')
+    grok.template('studentscontainerpage')
+    pnav = 3
+
+    form_fields = grok.AutoFields(IStudentsContainer).omit('title')
+    #form_fields['description'].custom_widget = ReSTDisplayWidget
+
+    @property
+    def title(self):
+        return "Students"
+
+    @property
+    def label(self):
+        return self.title
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/primarynavtab.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/primarynavtab.pt	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/primarynavtab.pt	(revision 6621)
@@ -0,0 +1,7 @@
+<li tal:attributes="class viewlet/active">
+  <a href="#"
+     tal:attributes="href viewlet/link_target"
+     tal:content="viewlet/tab_title">
+    Primary Tab Title
+  </a>
+</li>
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/studentscontainerpage.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/studentscontainerpage.pt	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/studentscontainerpage.pt	(revision 6621)
@@ -0,0 +1,16 @@
+<h2 tal:content="view/label">Title</h2>
+
+<table class="zebra" tal:condition="layout/isAuthenticated">
+  <tbody>
+    <tal:block repeat="widget view/widgets">
+      <tr>
+        <td class="fieldname">
+          <tal:block replace="python:widget.label"/>:
+        </td>
+        <td class="field">
+          <tal:block tal:replace="structure widget" />
+        </td>
+      </tr>
+    </tal:block>
+  </tbody>
+</table>
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/container.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/container.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/container.py	(revision 6621)
@@ -0,0 +1,50 @@
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Containers for students.
+"""
+import grok
+import os
+from zope.component.interfaces import IFactory
+from zope.interface import implementedBy
+from waeup.sirp.students.interfaces import IStudentsContainer
+
+class StudentsContainer(grok.Container):
+    """
+    The node containing the student models
+    """
+
+    grok.implements(IStudentsContainer)
+
+    def archive(self, id=None):
+        raise NotImplementedError()
+
+    def clear(self, id=None, archive=True):
+        raise NotImplementedError()
+    
+class StudentsContainerFactory(grok.GlobalUtility):
+    """A factory for students containers.
+    """
+    grok.implements(IFactory)
+    grok.name(u'waeup.StudentsContainer')
+    title = u"Create a new students container.",
+    description = u"This factory instantiates new students containers."
+
+    def __call__(self, *args, **kw):
+        return StudentsContainer()
+
+    def getInterfaces(self):
+        return implementedBy(StudentsContainer)
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py	(revision 6621)
@@ -0,0 +1,49 @@
+##
+## interfaces.py
+from zope.interface import Interface
+from zope import schema
+from waeup.sirp.interfaces import IWAeUPObject
+
+class IStudent(IWAeUPObject):
+    """Representation of a student.
+    """
+    name = schema.TextLine(
+        title = u'Name of student',
+        default = u'Nobody',
+        required = True,
+        )
+
+class IStudentsContainer(IWAeUPObject):
+    """An students container contains university students.
+
+    """
+
+    def archive(id=None):
+        """Create on-dist archive of students.
+
+        If id is `None`, all students are archived.
+
+        If id contains a single id string, only the respective
+        students are archived.
+
+        If id contains a list of id strings all of the respective
+        students types are saved to disk.
+        """
+
+    def clear(id=None, archive=True):
+        """Remove students of type given by 'id'.
+
+        Optionally archive the students.
+
+        If id is `None`, all students are archived.
+
+        If id contains a single id string, only the respective
+        students are archived.
+
+        If id contains a list of id strings all of the respective
+        student types are saved to disk.
+
+        If `archive` is ``False`` none of the archive-handling is done
+        and respective students are simply removed from the
+        database.
+        """
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/students.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/students.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/students.py	(revision 6621)
@@ -0,0 +1,56 @@
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Container for the various objects owned by students.
+"""
+import grok
+from grok import index
+from zope.component.interfaces import IFactory
+from zope.interface import implementedBy
+from waeup.sirp.students.interfaces import IStudent
+from waeup.sirp.utils.helpers import attrs_to_fields
+
+class Student(grok.Container):
+    """This is a student container for the various objects
+    owned by students.
+    """
+    grok.implements(IStudent)
+    grok.provides(IStudent)
+
+    def __init__(self):
+        super(Student, self).__init__()
+        #IWorkflowInfo(self).fireTransition('init')
+        return
+
+# Set all attributes of Student required in IStudent as field
+# properties. Doing this, we do not have to set initial attributes
+# ourselves and as a bonus we get free validation when an attribute is
+# set.
+Student = attrs_to_fields(Student)
+
+class StudentFactory(grok.GlobalUtility):
+    """A factory for students.
+    """
+    grok.implements(IFactory)
+    grok.name(u'waeup.Student')
+    title = u"Create a new student.",
+    description = u"This factory instantiates new student instances."
+
+    def __call__(self, *args, **kw):
+        return Student()
+
+    def getInterfaces(self):
+        return implementedBy(Student)
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/__init__.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/__init__.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/__init__.py	(revision 6621)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 6621)
@@ -0,0 +1,116 @@
+##
+## test_browser.py
+## Login : <uli@pu.smp.net>
+## Started on  Tue Mar 29 11:31:11 2011 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Test the student-related UI components.
+"""
+import shutil
+import tempfile
+from StringIO import StringIO
+from datetime import datetime
+from mechanize import LinkNotFoundError
+from zope.component import createObject
+from zope.component.hooks import setSite, clearSite
+from zope.security.interfaces import Unauthorized
+from zope.testbrowser.testing import Browser
+from hurry.workflow.interfaces import IWorkflowInfo
+from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
+from waeup.sirp.app import University
+from waeup.sirp.students.container import StudentsContainer
+from waeup.sirp.students.students import Student
+from waeup.sirp.university.faculty import Faculty
+from waeup.sirp.university.department import Department
+
+PH_LEN = 2059  # Length of placeholder file
+
+class StudentsFullSetup(FunctionalTestCase):
+    # A test case that only contains a setup and teardown
+    #
+    # Complete setup for students handlings is rather complex and
+    # requires lots of things created before we can start. This is a
+    # setup that does all this, creates a university, creates PINs,
+    # etc.  so that we do not have to bother with that in different
+    # test cases.
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(StudentsFullSetup, self).setUp()
+
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        # we add the site immediately after creation to the
+        # ZODB. Catalogs and other local utilities are not setup
+        # before that step.
+        self.app = self.getRootFolder()['app']
+        # Set site here. Some of the following setup code might need
+        # to access grok.getSite() and should get our new app then
+        setSite(app)
+
+        self.container_path = 'http://localhost/app/students'
+        self.manage_container_path = self.container_path + '/@@manage'
+        self.add_student_path = self.container_path + '/@@add'
+
+        # Populate university
+        certificate = createObject('waeup.Certificate')
+        certificate.code = 'CERT1'
+        certificate.application_category = 'basic'
+        self.app['faculties']['fac1'] = Faculty()
+        self.app['faculties']['fac1']['dep1'] = Department()
+        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
+            certificate)
+
+        # Put the prepopulated site into test ZODB and prepare test
+        # browser
+        self.browser = Browser()
+        self.browser.handleErrors = False
+
+    def tearDown(self):
+        super(StudentsFullSetup, self).tearDown()
+        clearSite()
+        shutil.rmtree(self.dc_root)
+
+
+
+class StudentsContainerUITests(StudentsFullSetup):
+    # Tests for StudentsContainer class views and pages
+
+    layer = FunctionalLayer
+
+    def test_anonymous_access(self):
+        # Anonymous users can't access students containers
+        self.assertRaises(
+            Unauthorized, self.browser.open, self.manage_container_path)
+        return
+
+    def test_manage_access(self):
+        # Managers can access the view page of students
+        # containers and can perform actions
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.container_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.url, self.container_path)
+        return
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_container.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_container.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_container.py	(revision 6621)
@@ -0,0 +1,73 @@
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Tests for students containers.
+"""
+import unittest
+from zope.interface.verify import verifyClass, verifyObject
+from zope.component.interfaces import IFactory
+from zope.app.testing.functional import FunctionalTestCase
+from waeup.sirp.students.interfaces import (
+    IStudentsContainer,
+    )
+from waeup.sirp.students.container import (
+    StudentsContainer, StudentsContainerFactory,
+    )
+from waeup.sirp.testing import FunctionalLayer
+
+class StudentsContainerTestCase(unittest.TestCase):
+
+    def test_interfaces(self):
+        # Make sure the correct interfaces are implemented.
+        self.assertTrue(
+            verifyClass(
+                IStudentsContainer, StudentsContainer)
+            )
+        self.assertTrue(
+            verifyObject(
+                IStudentsContainer, StudentsContainer())
+            )
+        return
+
+    def test_base(self):
+        # We cannot call the fundamental methods of a base in that case
+        container = StudentsContainer()
+        self.assertRaises(
+            NotImplementedError, container.archive)
+        self.assertRaises(
+            NotImplementedError, container.clear)
+
+class StudentsContainerFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        self.factory = StudentsContainerFactory()
+
+    def tearDown(self):
+        pass
+
+    def test_interfaces(self):
+        verifyClass(IFactory, StudentsContainerFactory)
+        verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, StudentsContainer)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(IStudentsContainer)
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_students.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_students.py	(revision 6621)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_students.py	(revision 6621)
@@ -0,0 +1,75 @@
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Tests for students and related.
+"""
+import unittest
+from StringIO import StringIO
+from zope.app.testing.functional import FunctionalTestCase
+from zope.component import (
+    provideAdapter, adapts, getGlobalSiteManager, provideUtility)
+from zope.component.hooks import setSite
+from zope.component.interfaces import IFactory
+from zope.interface import verify, implements
+from zope.location.interfaces import ILocation
+from zope.publisher.base import TestRequest
+from zope.publisher.interfaces import NotFound
+from waeup.sirp.students.students import (
+    Student, StudentFactory,
+    )
+from waeup.sirp.students.interfaces import (
+    IStudent,
+    )
+from waeup.sirp.testing import FunctionalLayer
+
+class StudentTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(StudentTest, self).setUp()
+        self.student = Student()
+        return
+
+    def tearDown(self):
+        super(StudentTest, self).tearDown()
+        return
+
+    def test_interfaces(self):
+        verify.verifyClass(IStudent, Student)
+        verify.verifyObject(IStudent, self.student)
+        return
+
+class StudentFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        self.factory = StudentFactory()
+
+    def tearDown(self):
+        pass
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, StudentFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, Student)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(IStudent)
