Merge lp:~justas.sadzevicius/schooltool/schooltool.section_spanning into lp:~schooltool-owners/schooltool/schooltool

Proposed by Justas Sadzevičius on 2009-03-30
Status: Merged
Merged at revision: not available
Proposed branch: lp:~justas.sadzevicius/schooltool/schooltool.section_spanning
Merge into: lp:~schooltool-owners/schooltool/schooltool
Diff against target: None lines
To merge this branch: bzr merge lp:~justas.sadzevicius/schooltool/schooltool.section_spanning
To post a comment you must log in.

Section spanning core

2462. By Justas Sadzevičius on 2009-04-28

Simple section cross-term copying interface.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/schooltool/course/configure.zcml'
2--- src/schooltool/course/configure.zcml 2008-10-13 13:14:27 +0000
3+++ src/schooltool/course/configure.zcml 2009-03-21 12:52:42 +0000
4@@ -178,6 +178,10 @@
5 <adapter
6 factory=".section.PersonInstructorAdapter" />
7
8+ <!-- Validators -->
9+ <adapter factory=".section.SectionLinkContinuinityValidationSubscriber"
10+ name="validate_term_continuinity"/>
11+
12 <!-- Views -->
13 <include package=".browser" />
14
15
16=== modified file 'src/schooltool/course/interfaces.py'
17--- src/schooltool/course/interfaces.py 2008-09-04 12:25:48 +0000
18+++ src/schooltool/course/interfaces.py 2009-03-21 12:52:42 +0000
19@@ -97,6 +97,15 @@
20 size = zope.interface.Attribute(
21 """The number of member students in the section.""")
22
23+ previous = zope.interface.Attribute(
24+ """The previous section.""")
25+
26+ next = zope.interface.Attribute(
27+ """The next section.""")
28+
29+ linked_sections = zope.interface.Attribute(
30+ """Chain of sections linked by previous/next with this one.""")
31+
32
33 class ISectionContainer(container.interfaces.IContainer):
34 """A container for Sections."""
35
36=== modified file 'src/schooltool/course/section.py'
37--- src/schooltool/course/section.py 2008-10-08 10:52:54 +0000
38+++ src/schooltool/course/section.py 2009-03-22 13:06:04 +0000
39@@ -22,13 +22,17 @@
40 $Id$
41 """
42 from persistent import Persistent
43+import rwproperty
44 import zope.interface
45
46+from zope.event import notify
47 from zope.annotation.interfaces import IAttributeAnnotatable
48-from zope.app.container.interfaces import IObjectRemovedEvent
49+from zope.app.container.interfaces import IObjectRemovedEvent, INameChooser
50 from zope.app.container import btree, contained
51 from zope.component import adapts
52 from zope.interface import implements
53+from zope.proxy import sameProxiedObjects
54+from zope.security.proxy import removeSecurityProxy
55
56 from schooltool.relationship import RelationshipProperty
57 from schooltool.app import membership
58@@ -41,7 +45,9 @@
59 from schooltool.common import SchoolToolMessage as _
60 from schooltool.app import relationships
61 from schooltool.course import interfaces, booking
62+from schooltool.schoolyear.subscriber import EventAdapterSubscriber
63 from schooltool.schoolyear.subscriber import ObjectEventAdapterSubscriber
64+from schooltool.schoolyear.interfaces import ISubscriber
65 from schooltool.schoolyear.interfaces import ISchoolYear
66 from schooltool.securitypolicy.crowds import Crowd, AggregateCrowd
67 from schooltool.course.interfaces import ICourseContainer
68@@ -51,6 +57,16 @@
69 from schooltool.course.interfaces import ILearner, IInstructor
70
71
72+class InvalidSectionLinkException(Exception):
73+ pass
74+
75+
76+class SectionBeforeLinkingEvent(object):
77+ def __init__(self, first, second):
78+ self.first = first
79+ self.second = second
80+
81+
82 class Section(Persistent, contained.Contained):
83
84 zope.interface.implements(interfaces.ISectionContained,
85@@ -58,6 +74,9 @@
86
87 _location = None
88
89+ _previous = None
90+ _next = None
91+
92 def __init__(self, title="Section", description=None, schedule=None):
93 self.title = title
94 self.description = description
95@@ -79,6 +98,89 @@
96 size = size + len(member.members)
97 return size
98
99+ def _unlinkRangeTo(self, other):
100+ """Unlink sections between self and the other in self.linked_sections."""
101+ linked = self.linked_sections
102+ if other not in linked or self is other:
103+ return
104+ idx_first, idx_last = sorted([linked.index(self), linked.index(other)])
105+ linked[idx_first]._next = None
106+ for section in linked[idx_first+1:idx_last]:
107+ section._previous = None
108+ section._next = None
109+ linked[idx_last]._previous = None
110+
111+ @rwproperty.getproperty
112+ def previous(self):
113+ return self._previous
114+
115+ @rwproperty.setproperty
116+ def previous(self, new):
117+ new = removeSecurityProxy(new)
118+ if new is self._previous:
119+ return
120+ if new is self:
121+ raise InvalidSectionLinkException(
122+ _('Cannot assign section as previous to itself'))
123+
124+ notify(SectionBeforeLinkingEvent(new, self))
125+
126+ if new is not None:
127+ self._unlinkRangeTo(new)
128+
129+ old_prev = self._previous
130+ self._previous = None
131+ if old_prev is not None:
132+ old_prev.next = None
133+ self._previous = new
134+
135+ if new is not None:
136+ new.next = self
137+
138+ @rwproperty.getproperty
139+ def next(self):
140+ return self._next
141+
142+ @rwproperty.setproperty
143+ def next(self, new):
144+ new = removeSecurityProxy(new)
145+ if new is self._next:
146+ return
147+ if new is self:
148+ raise InvalidSectionLinkException(
149+ _('Cannot assign section as next to itself'))
150+
151+ notify(SectionBeforeLinkingEvent(self, new))
152+
153+ if new is not None:
154+ self._unlinkRangeTo(new)
155+
156+ old_next = self._next
157+ self._next = None
158+ if old_next is not None:
159+ old_next.previous = None
160+ self._next = new
161+
162+ if new is not None:
163+ new.previous = self
164+
165+ @property
166+ def linked_sections(self):
167+ sections = [self]
168+
169+ pit = self.previous
170+ while pit:
171+ sections.insert(0, pit)
172+ pit = pit.previous
173+
174+ nit = self.next
175+ while nit:
176+ sections.append(nit)
177+ nit = nit.next
178+
179+ return sections
180+
181+
182 instructors = RelationshipProperty(relationships.URIInstruction,
183 relationships.URISection,
184 relationships.URIInstructor)
185@@ -266,3 +368,46 @@
186 section_container = ISectionContainer(self.object)
187 for section_id in list(section_container.keys()):
188 del section_container[section_id]
189+
190+
191+class SectionLinkContinuinityValidationSubscriber(EventAdapterSubscriber):
192+ adapts(SectionBeforeLinkingEvent)
193+ implements(ISubscriber)
194+
195+ def __call__(self):
196+ if (self.event.first is None or
197+ self.event.second is None):
198+ return # unlinking sections
199+
200+ first_term = ITerm(self.event.first)
201+ second_term = ITerm(self.event.second)
202+ if sameProxiedObjects(first_term, second_term):
203+ raise InvalidSectionLinkException(
204+ _("Cannot link sections in same term"))
205+
206+ if first_term.first > second_term.first:
207+ raise InvalidSectionLinkException(
208+ _("Sections are not in subsequent terms"))
209+
210+ if not sameProxiedObjects(ISchoolYear(first_term),
211+ ISchoolYear(second_term)):
212+ raise InvalidSectionLinkException(
213+ _("Cannot link sections in different school years"))
214+
215+
216+def copySection(section, target_term):
217+ """Create a copy of a section in a desired term."""
218+ section_copy = Section(section.title, section.description)
219+ sections = ISectionContainer(target_term)
220+ name = section.__name__
221+ if name in sections:
222+ name = INameChooser(sections).chooseName(name, section_copy)
223+ sections[name] = section_copy
224+ for course in section.courses:
225+ section_copy.courses.add(course)
226+ for instructor in section.instructors:
227+ section_copy.instructors.add(instructor)
228+ for member in section.members:
229+ section_copy.members.add(member)
230+ return section_copy
231+
232
233=== modified file 'src/schooltool/course/tests/test_course.py'
234--- src/schooltool/course/tests/test_course.py 2007-07-14 15:18:18 +0000
235+++ src/schooltool/course/tests/test_course.py 2009-03-21 12:52:42 +0000
236@@ -25,6 +25,8 @@
237 from zope.testing import doctest
238 from zope.interface.verify import verifyObject
239
240+from schooltool.relationship.tests import setUp, tearDown
241+
242
243 def doctest_CourseContainer():
244 r"""Schooltool toplevel container for Courses.
245@@ -46,6 +48,7 @@
246 Traceback (most recent call last):
247 ...
248 InvalidItemType: ...
249+
250 """
251
252
253@@ -74,9 +77,7 @@
254
255 To test the relationship we need to do some setup:
256
257- >>> from schooltool.relationship.tests import setUp, tearDown
258 >>> from schooltool.app.relationships import enforceCourseSectionConstraint
259- >>> setUp()
260 >>> import zope.event
261 >>> old_subscribers = zope.event.subscribers[:]
262 >>> zope.event.subscribers.append(enforceCourseSectionConstraint)
263@@ -131,16 +132,13 @@
264 That's it:
265
266 >>> zope.event.subscribers[:] = old_subscribers
267- >>> tearDown()
268+
269 """
270
271
272 def doctest_Section():
273 r"""Tests for course section groups.
274
275- >>> from schooltool.relationship.tests import setUp, tearDown
276- >>> setUp()
277-
278 >>> from schooltool.course.section import Section
279 >>> section = Section(title="section 1", description="advanced")
280 >>> from schooltool.course.interfaces import ISection
281@@ -237,9 +235,216 @@
282 US History
283 English
284
285- We're done:
286-
287- >>> tearDown()
288+ """
289+
290+
291+def doctest_Section_linking():
292+ r"""Tests for course section linking properties (previous, next and
293+ linked_sections)
294+
295+ The purpose of this test is to check that:
296+ * sections can be linked via Section.previous and Section.next.
297+ * Section.linked_sections return sections linked with the Section
298+ in a correct order.
299+ * it is impossible to create linking loops.
300+
301+ >>> from schooltool.course.section import Section
302+
303+ >>> def section_link_str(s):
304+ ... return '%s <- %s -> %s' % (
305+ ... not s.previous and 'None' or s.previous.title,
306+ ... s.title,
307+ ... not s.next and 'None' or s.next.title)
308+
309+ >>> def print_sections(sections):
310+ ... '''Print prev and next links for all sections in section_list'''
311+ ... for s in sections:
312+ ... print section_link_str(s)
313+
314+ >>> def print_linked(section_list):
315+ ... '''Print linked sections for all sections in section_list'''
316+ ... for section in section_list:
317+ ... linked_str = ', '.join(
318+ ... [s.title for s in section.linked_sections])
319+ ... print section.title, 'spans:', linked_str
320+
321+ Create some sections.
322+
323+ >>> sections = [Section('Sec0'), Section('Sec1'), Section('Sec2')]
324+
325+ By default sections are not linked.
326+
327+ >>> print_sections(sections)
328+ None <- Sec0 -> None
329+ None <- Sec1 -> None
330+ None <- Sec2 -> None
331+
332+ And each section spans only itself.
333+
334+ >>> print_linked(sections)
335+ Sec0 spans: Sec0
336+ Sec1 spans: Sec1
337+ Sec2 spans: Sec2
338+
339+ Assign s0 as previous section to s1. s0 'next' section is also updated.
340+ A list of linked sections updated for s0 and s1.
341+
342+ >>> sections[1].previous = sections[0]
343+
344+ >>> print_sections(sections)
345+ None <- Sec0 -> Sec1
346+ Sec0 <- Sec1 -> None
347+ None <- Sec2 -> None
348+
349+ >>> print_linked(sections)
350+ Sec0 spans: Sec0, Sec1
351+ Sec1 spans: Sec0, Sec1
352+ Sec2 spans: Sec2
353+
354+ Assign s2 as next section to s1.
355+
356+ >>> sections[1].next = sections[2]
357+
358+ >>> print_sections(sections)
359+ None <- Sec0 -> Sec1
360+ Sec0 <- Sec1 -> Sec2
361+ Sec1 <- Sec2 -> None
362+
363+ >>> print_linked(sections)
364+ Sec0 spans: Sec0, Sec1, Sec2
365+ Sec1 spans: Sec0, Sec1, Sec2
366+ Sec2 spans: Sec0, Sec1, Sec2
367+
368+ Let's test section unlinking...
369+
370+ >>> sections[1].previous = None
371+ >>> print_sections(sections)
372+ None <- Sec0 -> None
373+ None <- Sec1 -> Sec2
374+ Sec1 <- Sec2 -> None
375+
376+ >>> print_linked(sections)
377+ Sec0 spans: Sec0
378+ Sec1 spans: Sec1, Sec2
379+ Sec2 spans: Sec1, Sec2
380+
381+ >>> sections[2].previous = None
382+ >>> print_sections(sections)
383+ None <- Sec0 -> None
384+ None <- Sec1 -> None
385+ None <- Sec2 -> None
386+
387+ >>> print_linked(sections)
388+ Sec0 spans: Sec0
389+ Sec1 spans: Sec1
390+ Sec2 spans: Sec2
391+
392+ And now some extreme cases. Try the section as next/prev to itself.
393+
394+ >>> sections[0].previous = sections[0]
395+ Traceback (most recent call last):
396+ ...
397+ InvalidSectionLinkException: Cannot assign section as previous to itself
398+
399+ >>> sections[0].next = sections[0]
400+ Traceback (most recent call last):
401+ ...
402+ InvalidSectionLinkException: Cannot assign section as next to itself
403+
404+ Create a long list of linked sections.
405+
406+ >>> sections = [Section('Sec0')]
407+ >>> for n in range(5):
408+ ... new_sec = Section('Sec%d' % (n+1))
409+ ... new_sec.previous = sections[-1]
410+ ... sections.append(new_sec)
411+
412+ >>> print_sections(sections)
413+ None <- Sec0 -> Sec1
414+ Sec0 <- Sec1 -> Sec2
415+ Sec1 <- Sec2 -> Sec3
416+ Sec2 <- Sec3 -> Sec4
417+ Sec3 <- Sec4 -> Sec5
418+ Sec4 <- Sec5 -> None
419+
420+ Try to introduce a loop by assigning a previous section.
421+
422+ >>> sections[4].previous = sections[1]
423+
424+ Note that sections 2 and 3 are removed from the linked list thus avoiding
425+ the loop.
426+
427+ >>> print_sections(sections)
428+ None <- Sec0 -> Sec1
429+ Sec0 <- Sec1 -> Sec4
430+ None <- Sec2 -> None
431+ None <- Sec3 -> None
432+ Sec1 <- Sec4 -> Sec5
433+ Sec4 <- Sec5 -> None
434+
435+ >>> [s.title for s in sections[0].linked_sections]
436+ ['Sec0', 'Sec1', 'Sec4', 'Sec5']
437+
438+ Let's reubild the list of 5 linked sections.
439+
440+ >>> sections[4].previous = sections[3]
441+ >>> sections[3].previous = sections[2]
442+ >>> sections[2].previous = sections[1]
443+
444+ >>> print_linked(sections)
445+ Sec0 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
446+ Sec1 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
447+ Sec2 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
448+ Sec3 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
449+ Sec4 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
450+ Sec5 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
451+
452+
453+ Try to introduce a loop by assigning a next section.
454+
455+ >>> sections[1].next = sections[4]
456+
457+ Note that sections 2 and 3 are removed from the linked list thus avoiding
458+ the loop again.
459+
460+ >>> print_sections(sections)
461+ None <- Sec0 -> Sec1
462+ Sec0 <- Sec1 -> Sec4
463+ None <- Sec2 -> None
464+ None <- Sec3 -> None
465+ Sec1 <- Sec4 -> Sec5
466+ Sec4 <- Sec5 -> None
467+
468+ >>> [s.title for s in sections[0].linked_sections]
469+ ['Sec0', 'Sec1', 'Sec4', 'Sec5']
470+
471+ Let's reubild the list of 5 linked sections.
472+
473+ >>> sections[3].next = sections[4]
474+ >>> sections[2].next = sections[3]
475+ >>> sections[1].next = sections[2]
476+
477+ >>> print_linked(sections)
478+ Sec0 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
479+ Sec1 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
480+ Sec2 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
481+ Sec3 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
482+ Sec4 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
483+ Sec5 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
484+
485+ And try to introduce another loop.
486+
487+ >>> sections[0].previous = sections[3]
488+
489+ Sections between Sec0 and Sec3 were unlinked to avoid the loop.
490+
491+ >>> print_linked(sections)
492+ Sec0 spans: Sec3, Sec0
493+ Sec1 spans: Sec1
494+ Sec2 spans: Sec2
495+ Sec3 spans: Sec3, Sec0
496+ Sec4 spans: Sec4, Sec5
497+ Sec5 spans: Sec4, Sec5
498
499 """
500
501@@ -247,9 +452,6 @@
502 def doctest_PersonInstructorCrowd():
503 """Unit test for the PersonInstructorCrowd
504
505- >>> from schooltool.relationship.tests import setUp, tearDown
506- >>> setUp()
507-
508 We'll need a section, a group, and a couple of persons:
509
510 >>> from schooltool.course.section import Section
511@@ -284,18 +486,12 @@
512 >>> PersonInstructorsCrowd(p2).contains(p1)
513 False
514
515- Cleanup.
516-
517- >>> tearDown()
518 """
519
520
521 def doctest_PersonLearnerAdapter(self):
522 """Tests for PersonLearnerAdapter.
523
524- >>> from schooltool.relationship.tests import setUp, tearDown
525- >>> setUp()
526-
527 We'll need a person, a group, and a couple of sections:
528
529 >>> from schooltool.course.section import Section
530@@ -320,16 +516,14 @@
531 >>> [section.title for section in learner.sections()]
532 ['section 1', 'section 2']
533
534- Cleanup.
535-
536- >>> tearDown()
537 """
538
539
540 def test_suite():
541- return unittest.TestSuite([
542- doctest.DocTestSuite(optionflags=doctest.ELLIPSIS),
543- ])
544+ optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
545+ suite = doctest.DocTestSuite(optionflags=optionflags,
546+ setUp=setUp, tearDown=tearDown)
547+ return suite
548
549
550 if __name__ == '__main__':
551
552=== added file 'src/schooltool/course/tests/test_subscribers.py'
553--- src/schooltool/course/tests/test_subscribers.py 1970-01-01 00:00:00 +0000
554+++ src/schooltool/course/tests/test_subscribers.py 2009-03-22 13:06:04 +0000
555@@ -0,0 +1,262 @@
556+#
557+# SchoolTool - common information systems platform for school administration
558+# Copyright (c) 2005 Shuttleworth Foundation
559+#
560+# This program is free software; you can redistribute it and/or modify
561+# it under the terms of the GNU General Public License as published by
562+# the Free Software Foundation; either version 2 of the License, or
563+# (at your option) any later version.
564+#
565+# This program is distributed in the hope that it will be useful,
566+# but WITHOUT ANY WARRANTY; without even the implied warranty of
567+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
568+# GNU General Public License for more details.
569+#
570+# You should have received a copy of the GNU General Public License
571+# along with this program; if not, write to the Free Software
572+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
573+#
574+"""
575+Unit tests for course and section subscriber validation.
576+
577+$Id$
578+"""
579+
580+import unittest
581+from datetime import date, timedelta
582+from zope.testing import doctest
583+
584+from schooltool.schoolyear.testing import (setUp, tearDown,
585+ provideStubUtility,
586+ provideStubAdapter)
587+from schooltool.schoolyear.ftesting import schoolyear_functional_layer
588+from schooltool.app.interfaces import ISchoolToolApplication
589+from schooltool.schoolyear.interfaces import ISchoolYearContainer
590+from schooltool.schoolyear.schoolyear import SchoolYear
591+from schooltool.term.interfaces import ITerm
592+from schooltool.term.term import Term
593+from schooltool.course.interfaces import ISectionContainer
594+from schooltool.course.section import Section
595+
596+
597+def setUpSchoolYear(year=2000):
598+ year_container = ISchoolYearContainer(ISchoolToolApplication(None))
599+ sy = year_container[str(year)] = SchoolYear(
600+ str(year), date(year, 1, 1), date(year+1, 1, 1) - timedelta(1))
601+ return sy
602+
603+
604+def setUpTerms(schoolyear, term_count=3):
605+ term_delta = timedelta(
606+ ((schoolyear.last - schoolyear.first) / term_count).days)
607+ start_date = schoolyear.first
608+ for n in range(term_count):
609+ finish_date = start_date + term_delta - timedelta(1)
610+ schoolyear['Term%d' % (n+1)] = Term(
611+ 'Term %d' % (n+1), start_date, finish_date)
612+ start_date = finish_date + timedelta(1)
613+
614+
615+def setUpSections(term_list, sections_per_term=1):
616+ for term in term_list:
617+ sections = ISectionContainer(term)
618+ for n in range(sections_per_term):
619+ name = 'Sec%d'%(n+1)
620+ sections[name] = Section(name)
621+
622+
623+def doctest_Section_linking_terms():
624+ r"""Tests for section linking term continuinity validations.
625+
626+ Set up a school year with three terms, each containing two sections.
627+
628+ >>> year = setUpSchoolYear(2000)
629+ >>> setUpTerms(year, 3)
630+ >>> setUpSections(year.values(), sections_per_term=2)
631+
632+ Let's make Sec1 span the three terms.
633+
634+ >>> s1_t1 = ISectionContainer(year['Term1'])['Sec1']
635+ >>> s1_t2 = ISectionContainer(year['Term2'])['Sec1']
636+ >>> s1_t3 = ISectionContainer(year['Term3'])['Sec1']
637+
638+ >>> s1_t2.previous = s1_t1
639+ >>> s1_t2.next = s1_t3
640+
641+ >>> for s in s1_t2.linked_sections:
642+ ... print '%s, %s' % (ITerm(s).title, s.title)
643+ Term 1, Sec1
644+ Term 2, Sec1
645+ Term 3, Sec1
646+
647+ We cannot link a section to another section in the same term.
648+
649+ >>> s2_t1 = ISectionContainer(year['Term1'])['Sec2']
650+ >>> s2_t2 = ISectionContainer(year['Term2'])['Sec2']
651+ >>> s2_t3 = ISectionContainer(year['Term3'])['Sec2']
652+
653+ >>> s2_t2.next = s1_t2
654+ Traceback (most recent call last):
655+ ...
656+ InvalidSectionLinkException: Cannot link sections in same term
657+
658+ >>> s2_t2.previous = s1_t2
659+ Traceback (most recent call last):
660+ ...
661+ InvalidSectionLinkException: Cannot link sections in same term
662+
663+ Cannot set previous section in the future.
664+
665+ >>> s2_t2.previous = s1_t3
666+ Traceback (most recent call last):
667+ ...
668+ InvalidSectionLinkException: Sections are not in subsequent terms
669+
670+ Or set next section in the past.
671+
672+ >>> s2_t2.next = s1_t1
673+ Traceback (most recent call last):
674+ ...
675+ InvalidSectionLinkException: Sections are not in subsequent terms
676+
677+ Notice that though we tried to link Sec2 with Sec1, we didn't change it's
678+ linked_sections, becouse all our assigments were invalid.
679+
680+ >>> for s in s1_t2.linked_sections:
681+ ... print '%s, %s' % (ITerm(s).title, s.title)
682+ Term 1, Sec1
683+ Term 2, Sec1
684+ Term 3, Sec1
685+
686+ Let's test an unusual case: continue Section 1 from Term 1 as Section 2
687+ in the last term.
688+
689+ >>> s2_t3.previous = s1_t1
690+
691+ Section 2 in third term now continues Section 1.
692+
693+ >>> for s in s1_t1.linked_sections:
694+ ... print '%s, %s' % (ITerm(s).title, s.title)
695+ Term 1, Sec1
696+ Term 3, Sec2
697+
698+ Section 1 now spans only terms 2 and 3.
699+
700+ >>> for s in s1_t2.linked_sections:
701+ ... print '%s, %s' % (ITerm(s).title, s.title)
702+ Term 2, Sec1
703+ Term 3, Sec1
704+
705+ """
706+
707+
708+def doctest_Section_linking_schoolyears():
709+ r"""Tests for section linking SchoolYear validations.
710+
711+ Set up a school year with three terms, each containing two sections.
712+
713+ >>> def setUpYearWithSection(year):
714+ ... year = setUpSchoolYear(year)
715+ ... setUpTerms(year, 1)
716+ ... setUpSections(year.values(), sections_per_term=1)
717+ ... return year
718+
719+ >>> year0 = setUpYearWithSection(2000)
720+ >>> year1 = setUpYearWithSection(2001)
721+ >>> year2 = setUpYearWithSection(2002)
722+
723+ >>> sec_year_0 = ISectionContainer(year0['Term1'])['Sec1']
724+ >>> sec_year_1 = ISectionContainer(year1['Term1'])['Sec1']
725+ >>> sec_year_2 = ISectionContainer(year2['Term1'])['Sec1']
726+
727+ We cannot link sections in the different school years.
728+
729+ >>> sec_year_1.previous = sec_year_0
730+ Traceback (most recent call last):
731+ ...
732+ InvalidSectionLinkException:
733+ Cannot link sections in different school years
734+
735+ >>> sec_year_1.next = sec_year_2
736+ Traceback (most recent call last):
737+ ...
738+ InvalidSectionLinkException:
739+ Cannot link sections in different school years
740+
741+ """
742+
743+
744+def doctest_copySection():
745+ r"""Test for copySection.
746+
747+ >>> from schooltool.course.section import Section
748+ >>> from schooltool.course.course import Course
749+ >>> from schooltool.person.person import Person
750+
751+ Create a section with a course, instructor and several members.
752+
753+ >>> year = setUpSchoolYear(2000)
754+ >>> setUpTerms(year, 2)
755+
756+ >>> section = Section('English A')
757+ >>> section.instructors.add(Person('teacher', 'Mr. Jones'))
758+ >>> section.members.add(Person('first','First'))
759+ >>> section.members.add(Person('second','Second'))
760+ >>> section.members.add(Person('third','Third'))
761+ >>> section.courses.add(Course(title="English"))
762+ >>> ISectionContainer(year['Term1'])['Sec1'] = section
763+
764+ Let's copy it to another term.
765+
766+ >>> from schooltool.course.section import copySection
767+
768+ >>> new_section = copySection(section, year['Term2'])
769+
770+ Sectoin's copy was created.
771+
772+ >>> new_section is not section
773+ True
774+
775+ Sec1 was available as section id in the new term, so it was preserved.
776+
777+ >>> print new_section.__name__
778+ Sec1
779+
780+ Courses, instructors and members were copied.
781+
782+ >>> for course in new_section.courses:
783+ ... print course.title
784+ English
785+
786+ >>> for person in new_section.instructors:
787+ ... print person.title
788+ Mr. Jones
789+
790+ >>> for person in new_section.members:
791+ ... print person.title
792+ First
793+ Second
794+ Third
795+
796+ If original section's __name__ is already present in the target term, an
797+ alternative is chosen.
798+
799+ >>> other_section = copySection(section, year['Term2'])
800+ >>> print other_section.__name__
801+ 1
802+
803+ """
804+
805+
806+def test_suite():
807+ optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
808+ suite = doctest.DocTestSuite(optionflags=optionflags,
809+ extraglobs={'provideAdapter': provideStubAdapter,
810+ 'provideUtility': provideStubUtility},
811+ setUp=setUp, tearDown=tearDown)
812+ suite.layer = schoolyear_functional_layer
813+ return suite
814+
815+
816+if __name__ == '__main__':
817+ unittest.main(defaultTest='test_suite')
818
819=== modified file 'src/schooltool/relationship/tests/__init__.py'
820--- src/schooltool/relationship/tests/__init__.py 2006-05-05 18:03:22 +0000
821+++ src/schooltool/relationship/tests/__init__.py 2009-03-21 12:52:42 +0000
822@@ -60,7 +60,7 @@
823 return cmp(self.uri, other.uri)
824
825
826-def setUp():
827+def setUp(test=None):
828 """Set up for schooltool.relationship doctests.
829
830 Calls Zope's placelessSetUp, sets up annotations and relationships.
831@@ -70,7 +70,7 @@
832 setUpRelationships()
833
834
835-def tearDown():
836+def tearDown(test=None):
837 """Tear down for schooltool.relationshp doctests."""
838 setup.placelessTearDown()
839

Subscribers

People subscribed via source and target branches