:mod:`waeup.sirp.university.certificate` -- Certificates for WAeUP ****************************************************************** .. module:: waeup.sirp.university.certificate Components that represent and manage certificates. :Test-Layer: unit Because certificates make use of components registered with the Zope Component Architecture (ZCA), we first have to grok the `waeup` package. This happens automatically in real-world use: >>> import grok >>> grok.testing.grok('waeup') Content Classes (models and containers) ======================================= :class:`Certificate` -------------------- .. class:: Certificate([code=u'NA',[ title=u'Unnamed Certificate',[ study_mode=None,[ start_level=None,[ end_level=None,[ application_category=None,[ m_prefix=u'',[ max_pass = u'']]]]]]]]) Create a certificate object with the given parameters. .. attribute:: grok.implements(ICertificate) All parameters are optional: >>> from waeup.sirp.university.certificate import Certificate >>> mycertificate = Certificate() Certificates have the attributes required by the `ICertificate` interface: >>> from waeup.sirp.interfaces import ICertificate >>> ICertificate.providedBy(mycertificate) True >>> from zope.interface.verify import verifyObject >>> verifyObject(ICertificate, mycertificate) True Beside the attributes, certificates are containers for certificate-courses (see :class:`CertificateCourse`). Each certificate course can be accessed by the code of the course it wraps. .. attribute:: title Each certificate has a title: >>> mycertificate.title u'Unnamed Certificate' .. attribute:: code Each certificate holds a code, which might be a shortcut or abbreviation of the real certificate name. By default the code is ``NA`` (=not assigned): >>> mycertificate.code u'NA' .. attribute:: review_state The review state can have one of the ``checking`` states defined in the WAeUP workflow. These are at least the states ``checked`` and ``unchecked``. After a certificate is created, the review state is ``unchecked``: >>> mycertificate.review_state 'unchecked' .. seealso:: :meth:`Certificate.check` -- mark a certificate as ``checked`` .. attribute:: study_mode Each :class:`Certificate` instance has a study mode: >>> print mycertificate.study_mode None .. XXX: This is not a proper description .. attribute:: start_level Each :class:`Certificate` instance has a start level: >>> print mycertificate.start_level None .. XXX: This is not a proper description .. attribute:: end_level Each :class:`Certificate` instance has a end level: >>> print mycertificate.end_level None .. XXX: This is not a proper description .. attribute:: application_category Each :class:`Certificate` instance has an application category: >>> print mycertificate.application_category None .. XXX: This is not a proper description .. attribute:: max_pass Each :class:`Certificate` instance has a maximum number of passes: >>> mycertificate.max_pass u'' .. XXX: This is not a proper description .. method:: check() Mark a certificate instance's review state as ``checked``: >>> mycertificate.review_state 'unchecked' >>> mycertificate.check() >>> mycertificate.review_state 'checked' We cannot uncheck a certificate: >>> mycertificate.review_state = 'unchecked' Traceback (most recent call last): ... NoTransitionAvailableError The only states accepted at all are ``checked`` and ``unchecked``. If we want to set something else this won't work: >>> mycertificate.review_state = 'nonsense' Traceback (most recent call last): ... NoTransitionAvailableError .. method:: addCourseRef(course[, level=100,[ core_or_elective=True]]) Add a reference to a course. A course is an object implementing :class:`waeup.sirp.interfaces.ICourse`. Please don't be confused by the term 'reference'. This just means an ordinary :class:`waeup.sirp.university.course.Course` object in almost all cases. As this object will normaly be one stored in a department, the course here will simply become a reference to the 'real' one in the department container. .. method:: delCourseRef(code) Remove a course from a certificate. The course must be given by its code number. :class:`CertificateCourse` -------------------------- .. class:: CertificateCourse(course[, level=100[, core_or_elective=True]]) Create a certificate course. A certificate-course is a course (:class:`Course`) which is part of a certificate. Normally, certificate-courses are held in certificates and refer to an existing :class:`Course` instance held elsewhere. A certificate can require several courses and one course can be required by several certificates. .. attribute:: course An instance of :class:`ICourse`. .. attribute:: level An integer telling the level to which this course applies. .. attribute:: core_or_elective A bool stating whether this course is required or optional to get the certificate. Utilities ========= :class:`CertificateFactory` --------------------------- .. class:: CertificateFactory() .. attribute:: grok.name(u'waeup.Certificate') .. attribute:: grok.implements(IFactory) A named utility to deliver new instances of :class:`Certificate` without the need to import the implementation before: >>> from zope.component import createObject >>> mycertificate = createObject(u'waeup.Certificate') >>> mycertificate The factory complies with the specifications from the :class:`IFactory` insterface: >>> from zope.interface.verify import verifyClass >>> from zope.component.interfaces import IFactory >>> from waeup.sirp.university.certificate import CertificateFactory >>> verifyClass(IFactory, CertificateFactory) True This means also, that we can get the interfaces of the created object from the factory: >>> certificate_factory = CertificateFactory() >>> certificate_factory.getInterfaces() :class:`CertificateCourseFactory` --------------------------------- .. class:: CertificateCourseFactory() .. attribute:: grok.name(u'waeup.CertificateCourse') .. attribute:: grok.implements(IFactory) A named utility to deliver new instances of :class:`CertificateCourse` without the need to import the implementation before: >>> from zope.component import createObject >>> mycertificatecourse = createObject(u'waeup.CertificateCourse') >>> mycertificatecourse The factory complies with the specifications from the :class:`IFactory` insterface: >>> from zope.interface.verify import verifyClass >>> from zope.component.interfaces import IFactory >>> from waeup.sirp.university.certificate import CertificateCourseFactory >>> verifyClass(IFactory, CertificateCourseFactory) True This means also, that we can get the interfaces of the created object from the factory: >>> certcourse_factory = CertificateCourseFactory() >>> certcourse_factory.getInterfaces() Event Subscribers ================= .. function:: removedCourseHandler(course, event) An event subscriber triggered for :class:`grok.IObjectRemovedEvent`s, when an :class:`ICourse` instance is removed from a container. Tries to remove all referencing :class:`CertificateCourse` instances that reference the removed course. To accomplish that, the parents of the removed course are looked up for a certifcate container which contains a certificate-course that contains a reference to the deleted course. .. seealso:: :ref:`removecertificatecourses` **handles:** :class:`ICourse` **event type:** :class:`grok.IObjectRemovedEvent` Examples ======== Certificates ------------ We can create certificates: >>> from waeup.sirp.university.certificate import Certificate >>> mycertificate = Certificate() >>> mycertificate Another way to create certificates is by asking for a factory called ``waeup.Certificate``. This way we can create a factory without importing a class: >>> from zope.component import createObject >>> mycertificate = createObject(u'waeup.Certificate') >>> mycertificate CertificateCourses ------------------ :class:`CertificateCourse` instances comply with the :class:`ICertificateCourse` interface: >>> from waeup.sirp.interfaces import ICertificateCourse >>> from waeup.sirp.university.certificate import CertificateCourse >>> mycertcourse = CertificateCourse(None, 200, False) >>> ICertificateCourse.providedBy(mycertcourse) True >>> from zope.interface.verify import verifyObject >>> verifyObject(ICertificateCourse, mycertcourse) True Also instances of :class:`CertificateCourse` can be created by asking the component architechture: >>> from zope.component import createObject >>> mycertcourse = createObject(u'waeup.CertificateCourse') >>> mycertcourse .. _removecertificatecourses: Persistence of certificate courses ---------------------------------- If a certificate course requires a certain course and this is course is deleted, also the referencing certificate course is deleted. We setup a data structure that reflects typical usage. It looks like this:: Department-Instance | +---> courses | | | +--------------------> Course-Instance | ^ +---> certificates | | | +-----> Certificate-Instance | | | +------> Certificate-Course The certifcate-Course here refers to a Course-Instance. In Python we build such a structure like this (from top to bottom): >>> from zope.component import createObject >>> mydept = createObject('waeup.Department') In real world use this data will be stored in a ZODB. We setup our own litte ZODB backend (which is easy!): >>> from ZODB import FileStorage, DB >>> dbpath = 'tinyData.fs' >>> class TinyZODB(object): ... def __init__(self, path=dbpath): ... self.storage = FileStorage.FileStorage(path) ... self.db = DB(self.storage) ... self.connection = self.db.open() ... self.dbroot = self.connection.root() ... def close(self): ... self.connection.close() ... self.db.close() ... self.storage.close() Now we can use this ZODB as backend database and store our data structure: >>> import transaction >>> db = TinyZODB() >>> dbroot = db.dbroot >>> dbroot['mydept'] = mydept >>> mycourse = createObject('waeup.Course') >>> mycourse.code = 'MYCOURSE' >>> mydept.courses.addCourse(mycourse) >>> mycert = createObject('waeup.Certificate') >>> mycert.code = 'MYCERT' >>> mydept.certificates.addCertificate(mycert) >>> mycert.addCourseRef(mycourse) >>> transaction.commit() The data is now stored in the ZODB. We can close the DB, reopen it later and the data will still be there: >>> db.close() >>> newdb = TinyZODB() >>> newdbroot = newdb.dbroot >>> list(newdbroot) ['mydept'] The certificate-course we stored in the certificate is indeed a reference to the course, not a copy of it: >>> course = newdbroot['mydept'].courses['MYCOURSE'] >>> certcourse = newdbroot['mydept'].certificates['MYCERT']['MYCOURSE_100'] >>> certcourse.course is course True So, we can be sure that modifications to the course are immediately reflected in the certcourse.