source: waeup/branches/ulif-rewrite/src/waeup/university/certificate.txt @ 4383

Last change on this file since 4383 was 4383, checked in by uli, 16 years ago

Test delCourseRef().

File size: 11.9 KB
Line 
1:mod:`waeup.university.certificate` -- Certificates for WAeUP
2*************************************************************
3
4.. module:: waeup.university.certificate
5
6Components that represent and manage certificates.
7
8:Test-Layer: unit
9
10Because certificates make use of components registered with the Zope
11Component Architecture (ZCA), we first have to grok the `waeup`
12package. This happens automatically in real-world use:
13
14    >>> import grok
15    >>> grok.testing.grok('waeup')
16
17
18Content Classes (models and containers)
19=======================================
20
21
22:class:`Certificate`
23--------------------
24
25.. class:: Certificate([code=u'NA',[ title=u'Unnamed Certificate',[ category=None,[ study_mode=None,[ start_level=None,[ end_level=None,[ application_category=None,[ m_prefix=u'',[  max_pass = u'']]]]]]]]])
26
27   Create a certificate object with the given parameters.
28
29   .. attribute:: grok.implements(ICertificate)
30
31   All parameters are optional:
32
33     >>> from waeup.university.certificate import Certificate
34     >>> mycertificate = Certificate()
35
36   Certificates have the attributes required by the `ICertificate` interface:
37
38     >>> from waeup.interfaces import ICertificate
39     >>> ICertificate.providedBy(mycertificate)
40     True
41
42     >>> from zope.interface.verify import verifyObject
43     >>> verifyObject(ICertificate, mycertificate)
44     True
45
46   Beside the attributes, certificates are containers for
47   certificate-courses (see :class:`CertificateCourse`). Each
48   certificate course can be accessed by the code of the course it wraps.
49
50   .. attribute:: title
51
52      Each certificate has a title:
53
54        >>> mycertificate.title
55        u'Unnamed Certificate'
56
57   .. attribute:: code
58
59      Each certificate holds a code, which might be a shortcut or
60      abbreviation of the real certificate name. By default the code
61      is ``NA`` (=not assigned):
62
63        >>> mycertificate.code
64        u'NA'
65
66   .. attribute:: review_state
67
68      The review state can have one of the ``checking`` states defined
69      in the WAeUP workflow. These are at least the states ``checked``
70      and ``unchecked``. After a certificate is created, the review
71      state is ``unchecked``:
72
73        >>> mycertificate.review_state
74        'unchecked'
75
76      .. seealso::
77         :meth:`Certificate.check` -- mark a certificate as ``checked``
78
79   .. attribute:: category
80
81      Each :class:`Certificate` instance has a category:
82
83        >>> print mycertificate.category
84        None
85
86      .. XXX: This is not a proper description
87
88   .. attribute:: study_mode
89
90      Each :class:`Certificate` instance has a study mode:
91
92        >>> print mycertificate.study_mode
93        None
94
95      .. XXX: This is not a proper description
96
97   .. attribute:: start_level
98
99      Each :class:`Certificate` instance has a start level:
100
101        >>> print mycertificate.start_level
102        None
103
104      .. XXX: This is not a proper description
105
106   .. attribute:: end_level
107
108      Each :class:`Certificate` instance has a end level:
109
110        >>> print mycertificate.end_level
111        None
112
113      .. XXX: This is not a proper description
114   
115
116   .. attribute:: application_category
117
118      Each :class:`Certificate` instance has an application category:
119
120        >>> print mycertificate.application_category
121        None
122
123      .. XXX: This is not a proper description
124
125   
126   .. attribute:: m_prefix
127
128      Each :class:`Certificate` instance has an ``m_prefix``:
129
130        >>> mycertificate.m_prefix
131        u''
132
133      .. XXX: This is not a proper description
134
135   
136   .. attribute:: max_pass
137
138      Each :class:`Certificate` instance has a maximum number of passes:
139
140        >>> mycertificate.max_pass
141        u''
142
143      .. XXX: This is not a proper description
144
145
146   .. method:: check()
147
148      Mark a certificate instance's review state as ``checked``:
149
150        >>> mycertificate.review_state
151        'unchecked'
152
153        >>> mycertificate.check()
154        >>> mycertificate.review_state
155        'checked'
156
157      We cannot uncheck a certificate:
158
159        >>> mycertificate.review_state = 'init'
160        Traceback (most recent call last):
161        ...
162        InvalidTransitionError: Transition 'init' requires 'None'
163          as source state (is: 'checked')
164
165   .. method:: addCourseRef(course[, level=100,[ core_or_elective=True]])
166
167      Add a reference to a course. A course is an object implementing
168      :class:`waeup.interfaces.ICourse`.
169
170      Please don't be confused by the term 'reference'. This just
171      means an ordinary :class:`waeup.university.course.Course` object
172      in almost all cases. As this object will normaly be one stored
173      in a department, the course here will simply become a reference
174      to the 'real' one in the department container.
175
176   .. method:: delCourseRef(code)
177
178      Remove a course from a certificate.
179
180      The course must be given by its code number.
181
182:class:`CertificateCourse`
183--------------------------
184
185.. class:: CertificateCourse(course[, level=100[, core_or_elective=True]])
186
187   Create a certificate course.
188
189   A certificate-course is a course (:class:`Course`) which is part of
190   a certificate. Normally, certificate-courses are held in
191   certificates and refer to an existing :class:`Course` instance held
192   elsewhere.
193
194   A certificate can require several courses and one
195   course can be required by several certificates.
196
197   .. attribute:: course
198
199      An instance of :class:`ICourse`.
200
201   .. attribute:: level
202
203      An integer telling the level to which this course applies.
204
205   .. attribute:: core_or_elective
206
207      A bool stating whether this course is required or optional to
208      get the certificate.
209
210
211
212Utilities
213=========
214
215:class:`CertificateFactory`
216---------------------------
217
218.. class:: CertificateFactory()
219
220   .. attribute:: grok.name(u'waeup.Certificate')
221
222   .. attribute:: grok.implements(IFactory)
223
224   A named utility to deliver new instances of :class:`Certificate`
225   without the need to import the implementation before:
226
227     >>> from zope.component import createObject
228     >>> mycertificate = createObject(u'waeup.Certificate')
229     >>> mycertificate
230     <waeup.university.certificate.Certificate object at 0x...>
231
232Event Subscribers
233=================
234
235.. function:: removedCourseHandler(course, event)
236
237   An event subscriber triggered for
238   :class:`grok.IObjectRemovedEvent`s, when an :class:`ICourse`
239   instance is removed from a container.
240
241   Tries to remove all referencing :class:`CertificateCourse`
242   instances that reference the removed course.
243
244   To accomplish that, the parents of the removed course are looked up
245   for a certifcate container which contains a certificate-course that
246   contains a reference to the deleted course.
247
248   .. seealso:: :ref:`removecertificatecourses`
249
250   **handles:**
251     :class:`ICourse`
252
253   **event type:**
254     :class:`grok.IObjectRemovedEvent`
255
256Examples
257========
258
259Certificates
260------------
261
262We can create certificates:
263
264    >>> from waeup.university.certificate import Certificate
265    >>> mycertificate = Certificate()
266    >>> mycertificate
267    <waeup.university.certificate.Certificate object at 0x...>
268
269Another way to create certificates is by asking for a factory called
270``waeup.Certificate``. This way we can create a factory without
271importing a class:
272
273    >>> from zope.component import createObject
274    >>> mycertificate = createObject(u'waeup.Certificate')
275    >>> mycertificate
276    <waeup.university.certificate.Certificate object at 0x...>
277
278CertificateCourses
279------------------
280
281:class:`CertificateCourse` instances comply with the
282:class:`ICertificateCourse` interface:
283
284    >>> from waeup.interfaces import ICertificateCourse
285    >>> from waeup.university.certificate import CertificateCourse
286    >>> mycertcourse = CertificateCourse(None, 200, False)
287    >>> ICertificateCourse.providedBy(mycertcourse)
288    True
289
290    >>> from zope.interface.verify import verifyObject
291    >>> verifyObject(ICertificateCourse, mycertcourse)
292    True
293
294Also instances of :class:`CertificateCourse` can be created by asking
295the component architechture:
296
297    >>> from zope.component import createObject
298    >>> mycertcourse = createObject(u'waeup.CertificateCourse')
299    >>> mycertcourse
300    <waeup.university.certificate.CertificateCourse object at 0x...>
301
302.. _removecertificatecourses:
303
304Persistence of certificate courses
305----------------------------------
306
307If a certificate course requires a certain course and this is course
308is deleted, also the referencing certificate course is deleted.
309
310We setup a data structure that reflects typical usage. It looks like
311this::
312
313    Department-Instance
314    |
315    +---> courses
316    |        |
317    |        +--------------------> Course-Instance
318    |                                        ^
319    +---> certificates                       |
320             |                               |
321             +-----> Certificate-Instance    |
322                        |                    |
323                        +------> Certificate-Course
324
325The certifcate-Course here refers to a Course-Instance.
326
327In Python we build such a structure like this (from top to bottom):
328
329    >>> from zope.component import createObject
330    >>> mydept = createObject('waeup.Department')
331
332In real world use this data will be stored in a ZODB. We setup our own
333litte ZODB backend (which is easy!):
334
335    >>> from ZODB import FileStorage, DB
336    >>> dbpath = 'tinyData.fs'
337    >>> class TinyZODB(object):
338    ...   def __init__(self, path=dbpath):
339    ...     self.storage = FileStorage.FileStorage(path)
340    ...     self.db = DB(self.storage)
341    ...     self.connection = self.db.open()
342    ...     self.dbroot = self.connection.root()
343    ...   def close(self):
344    ...     self.connection.close()
345    ...     self.db.close()
346    ...     self.storage.close()
347
348Now we can use this ZODB as backend database and store our data
349structure:
350
351    >>> import transaction
352    >>> db = TinyZODB()
353    >>> dbroot = db.dbroot
354    >>> dbroot['mydept'] = mydept
355    >>> mycourse = createObject('waeup.Course')
356    >>> mycourse.code = 'MYCOURSE'
357    >>> mydept.courses.addCourse(mycourse)
358    >>> mycert = createObject('waeup.Certificate')
359    >>> mycert.code = 'MYCERT'
360    >>> mydept.certificates.addCertificate(mycert)
361    >>> mycert.addCourseRef(mycourse)
362
363    >>> transaction.commit()
364
365The data is now stored in the ZODB. We can close the DB, reopen it
366later and the data will still be there:
367
368    >>> db.close()
369    >>> newdb = TinyZODB()
370    >>> newdbroot = newdb.dbroot
371    >>> list(newdbroot)
372    ['mydept']
373
374The certificate-course we stored in the certificate is indeed a
375reference to the course, not a copy of it:
376
377    >>> course = newdbroot['mydept'].courses['MYCOURSE']
378    >>> certcourse = newdbroot['mydept'].certificates['MYCERT']['MYCOURSE']
379    >>> certcourse.course is course
380    True
381
382So, we can be sure that modifications to the course are immediately
383reflected in the certcourse.
384
385The interesting question now is: what happens to ``mycertcourse`` when
386the referenced course ``mycourse`` is deleted?
387
388Let's have a look:
389
390    >>> list(newdbroot['mydept'].certificates['MYCERT'].keys())
391    [u'MYCOURSE']
392
393    >>> del newdbroot['mydept'].courses['MYCOURSE']
394    >>> newdbroot['mydept']._p_changed = True
395    >>> transaction.commit()
396    >>> list(newdbroot['mydept'].certificates['MYCERT'].keys())
397    []
398
399As we can see, the course was also removed from the certificate.
400
401We can of course also remove existing courses from certificates by
402using :meth:`delCourseRef`. This will not remove the same course from
403any course container:
404
405    >>> mycourses = newdbroot['mydept'].courses
406    >>> mycert = newdbroot['mydept'].certificates['MYCERT']
407    >>> mycourses.addCourse(mycourse)
408    >>> mycert.addCourseRef(mycourse)
409    >>> c1 = mycourses['MYCOURSE']
410    >>> c2 = mycert['MYCOURSE'].course
411    >>> c1 is c2
412    True
413
414    >>> mycert.delCourseRef('MYCOURSE')
415
416The certificate now has no ``MYCOURSE`` course anymore, while the
417courses container still has:
418
419    >>> 'MYCOURSE' in mycert.keys()
420    False
421
422    >>> mycourses['MYCOURSE']
423    <waeup.university.course.Course object at 0x...>
424
425Clean up:
426
427    >>> import os
428    >>> os.unlink(dbpath)
Note: See TracBrowser for help on using the repository browser.