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

Proposed by Justas Sadzevičius
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.
Revision history for this message
Justas Sadzevičius (justas.sadzevicius) wrote :

Section spanning core

2462. By Justas Sadzevičius

Simple section cross-term copying interface.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/schooltool/course/configure.zcml'
--- src/schooltool/course/configure.zcml 2008-10-13 13:14:27 +0000
+++ src/schooltool/course/configure.zcml 2009-03-21 12:52:42 +0000
@@ -178,6 +178,10 @@
178 <adapter178 <adapter
179 factory=".section.PersonInstructorAdapter" />179 factory=".section.PersonInstructorAdapter" />
180180
181 <!-- Validators -->
182 <adapter factory=".section.SectionLinkContinuinityValidationSubscriber"
183 name="validate_term_continuinity"/>
184
181 <!-- Views -->185 <!-- Views -->
182 <include package=".browser" />186 <include package=".browser" />
183187
184188
=== modified file 'src/schooltool/course/interfaces.py'
--- src/schooltool/course/interfaces.py 2008-09-04 12:25:48 +0000
+++ src/schooltool/course/interfaces.py 2009-03-21 12:52:42 +0000
@@ -97,6 +97,15 @@
97 size = zope.interface.Attribute(97 size = zope.interface.Attribute(
98 """The number of member students in the section.""")98 """The number of member students in the section.""")
9999
100 previous = zope.interface.Attribute(
101 """The previous section.""")
102
103 next = zope.interface.Attribute(
104 """The next section.""")
105
106 linked_sections = zope.interface.Attribute(
107 """Chain of sections linked by previous/next with this one.""")
108
100109
101class ISectionContainer(container.interfaces.IContainer):110class ISectionContainer(container.interfaces.IContainer):
102 """A container for Sections."""111 """A container for Sections."""
103112
=== modified file 'src/schooltool/course/section.py'
--- src/schooltool/course/section.py 2008-10-08 10:52:54 +0000
+++ src/schooltool/course/section.py 2009-03-22 13:06:04 +0000
@@ -22,13 +22,17 @@
22$Id$22$Id$
23"""23"""
24from persistent import Persistent24from persistent import Persistent
25import rwproperty
25import zope.interface26import zope.interface
2627
28from zope.event import notify
27from zope.annotation.interfaces import IAttributeAnnotatable29from zope.annotation.interfaces import IAttributeAnnotatable
28from zope.app.container.interfaces import IObjectRemovedEvent30from zope.app.container.interfaces import IObjectRemovedEvent, INameChooser
29from zope.app.container import btree, contained31from zope.app.container import btree, contained
30from zope.component import adapts32from zope.component import adapts
31from zope.interface import implements33from zope.interface import implements
34from zope.proxy import sameProxiedObjects
35from zope.security.proxy import removeSecurityProxy
3236
33from schooltool.relationship import RelationshipProperty37from schooltool.relationship import RelationshipProperty
34from schooltool.app import membership38from schooltool.app import membership
@@ -41,7 +45,9 @@
41from schooltool.common import SchoolToolMessage as _45from schooltool.common import SchoolToolMessage as _
42from schooltool.app import relationships46from schooltool.app import relationships
43from schooltool.course import interfaces, booking47from schooltool.course import interfaces, booking
48from schooltool.schoolyear.subscriber import EventAdapterSubscriber
44from schooltool.schoolyear.subscriber import ObjectEventAdapterSubscriber49from schooltool.schoolyear.subscriber import ObjectEventAdapterSubscriber
50from schooltool.schoolyear.interfaces import ISubscriber
45from schooltool.schoolyear.interfaces import ISchoolYear51from schooltool.schoolyear.interfaces import ISchoolYear
46from schooltool.securitypolicy.crowds import Crowd, AggregateCrowd52from schooltool.securitypolicy.crowds import Crowd, AggregateCrowd
47from schooltool.course.interfaces import ICourseContainer53from schooltool.course.interfaces import ICourseContainer
@@ -51,6 +57,16 @@
51from schooltool.course.interfaces import ILearner, IInstructor57from schooltool.course.interfaces import ILearner, IInstructor
5258
5359
60class InvalidSectionLinkException(Exception):
61 pass
62
63
64class SectionBeforeLinkingEvent(object):
65 def __init__(self, first, second):
66 self.first = first
67 self.second = second
68
69
54class Section(Persistent, contained.Contained):70class Section(Persistent, contained.Contained):
5571
56 zope.interface.implements(interfaces.ISectionContained,72 zope.interface.implements(interfaces.ISectionContained,
@@ -58,6 +74,9 @@
5874
59 _location = None75 _location = None
6076
77 _previous = None
78 _next = None
79
61 def __init__(self, title="Section", description=None, schedule=None):80 def __init__(self, title="Section", description=None, schedule=None):
62 self.title = title81 self.title = title
63 self.description = description82 self.description = description
@@ -79,6 +98,89 @@
79 size = size + len(member.members)98 size = size + len(member.members)
80 return size99 return size
81100
101 def _unlinkRangeTo(self, other):
102 """Unlink sections between self and the other in self.linked_sections."""
103 linked = self.linked_sections
104 if other not in linked or self is other:
105 return
106 idx_first, idx_last = sorted([linked.index(self), linked.index(other)])
107 linked[idx_first]._next = None
108 for section in linked[idx_first+1:idx_last]:
109 section._previous = None
110 section._next = None
111 linked[idx_last]._previous = None
112
113 @rwproperty.getproperty
114 def previous(self):
115 return self._previous
116
117 @rwproperty.setproperty
118 def previous(self, new):
119 new = removeSecurityProxy(new)
120 if new is self._previous:
121 return
122 if new is self:
123 raise InvalidSectionLinkException(
124 _('Cannot assign section as previous to itself'))
125
126 notify(SectionBeforeLinkingEvent(new, self))
127
128 if new is not None:
129 self._unlinkRangeTo(new)
130
131 old_prev = self._previous
132 self._previous = None
133 if old_prev is not None:
134 old_prev.next = None
135 self._previous = new
136
137 if new is not None:
138 new.next = self
139
140 @rwproperty.getproperty
141 def next(self):
142 return self._next
143
144 @rwproperty.setproperty
145 def next(self, new):
146 new = removeSecurityProxy(new)
147 if new is self._next:
148 return
149 if new is self:
150 raise InvalidSectionLinkException(
151 _('Cannot assign section as next to itself'))
152
153 notify(SectionBeforeLinkingEvent(self, new))
154
155 if new is not None:
156 self._unlinkRangeTo(new)
157
158 old_next = self._next
159 self._next = None
160 if old_next is not None:
161 old_next.previous = None
162 self._next = new
163
164 if new is not None:
165 new.previous = self
166
167 @property
168 def linked_sections(self):
169 sections = [self]
170
171 pit = self.previous
172 while pit:
173 sections.insert(0, pit)
174 pit = pit.previous
175
176 nit = self.next
177 while nit:
178 sections.append(nit)
179 nit = nit.next
180
181 return sections
182
183
82 instructors = RelationshipProperty(relationships.URIInstruction,184 instructors = RelationshipProperty(relationships.URIInstruction,
83 relationships.URISection,185 relationships.URISection,
84 relationships.URIInstructor)186 relationships.URIInstructor)
@@ -266,3 +368,46 @@
266 section_container = ISectionContainer(self.object)368 section_container = ISectionContainer(self.object)
267 for section_id in list(section_container.keys()):369 for section_id in list(section_container.keys()):
268 del section_container[section_id]370 del section_container[section_id]
371
372
373class SectionLinkContinuinityValidationSubscriber(EventAdapterSubscriber):
374 adapts(SectionBeforeLinkingEvent)
375 implements(ISubscriber)
376
377 def __call__(self):
378 if (self.event.first is None or
379 self.event.second is None):
380 return # unlinking sections
381
382 first_term = ITerm(self.event.first)
383 second_term = ITerm(self.event.second)
384 if sameProxiedObjects(first_term, second_term):
385 raise InvalidSectionLinkException(
386 _("Cannot link sections in same term"))
387
388 if first_term.first > second_term.first:
389 raise InvalidSectionLinkException(
390 _("Sections are not in subsequent terms"))
391
392 if not sameProxiedObjects(ISchoolYear(first_term),
393 ISchoolYear(second_term)):
394 raise InvalidSectionLinkException(
395 _("Cannot link sections in different school years"))
396
397
398def copySection(section, target_term):
399 """Create a copy of a section in a desired term."""
400 section_copy = Section(section.title, section.description)
401 sections = ISectionContainer(target_term)
402 name = section.__name__
403 if name in sections:
404 name = INameChooser(sections).chooseName(name, section_copy)
405 sections[name] = section_copy
406 for course in section.courses:
407 section_copy.courses.add(course)
408 for instructor in section.instructors:
409 section_copy.instructors.add(instructor)
410 for member in section.members:
411 section_copy.members.add(member)
412 return section_copy
413
269414
=== modified file 'src/schooltool/course/tests/test_course.py'
--- src/schooltool/course/tests/test_course.py 2007-07-14 15:18:18 +0000
+++ src/schooltool/course/tests/test_course.py 2009-03-21 12:52:42 +0000
@@ -25,6 +25,8 @@
25from zope.testing import doctest25from zope.testing import doctest
26from zope.interface.verify import verifyObject26from zope.interface.verify import verifyObject
2727
28from schooltool.relationship.tests import setUp, tearDown
29
2830
29def doctest_CourseContainer():31def doctest_CourseContainer():
30 r"""Schooltool toplevel container for Courses.32 r"""Schooltool toplevel container for Courses.
@@ -46,6 +48,7 @@
46 Traceback (most recent call last):48 Traceback (most recent call last):
47 ...49 ...
48 InvalidItemType: ...50 InvalidItemType: ...
51
49 """52 """
5053
5154
@@ -74,9 +77,7 @@
7477
75 To test the relationship we need to do some setup:78 To test the relationship we need to do some setup:
7679
77 >>> from schooltool.relationship.tests import setUp, tearDown
78 >>> from schooltool.app.relationships import enforceCourseSectionConstraint80 >>> from schooltool.app.relationships import enforceCourseSectionConstraint
79 >>> setUp()
80 >>> import zope.event81 >>> import zope.event
81 >>> old_subscribers = zope.event.subscribers[:]82 >>> old_subscribers = zope.event.subscribers[:]
82 >>> zope.event.subscribers.append(enforceCourseSectionConstraint)83 >>> zope.event.subscribers.append(enforceCourseSectionConstraint)
@@ -131,16 +132,13 @@
131 That's it:132 That's it:
132133
133 >>> zope.event.subscribers[:] = old_subscribers134 >>> zope.event.subscribers[:] = old_subscribers
134 >>> tearDown()135
135 """136 """
136137
137138
138def doctest_Section():139def doctest_Section():
139 r"""Tests for course section groups.140 r"""Tests for course section groups.
140141
141 >>> from schooltool.relationship.tests import setUp, tearDown
142 >>> setUp()
143
144 >>> from schooltool.course.section import Section142 >>> from schooltool.course.section import Section
145 >>> section = Section(title="section 1", description="advanced")143 >>> section = Section(title="section 1", description="advanced")
146 >>> from schooltool.course.interfaces import ISection144 >>> from schooltool.course.interfaces import ISection
@@ -237,9 +235,216 @@
237 US History235 US History
238 English236 English
239237
240 We're done:238 """
241239
242 >>> tearDown()240
241def doctest_Section_linking():
242 r"""Tests for course section linking properties (previous, next and
243 linked_sections)
244
245 The purpose of this test is to check that:
246 * sections can be linked via Section.previous and Section.next.
247 * Section.linked_sections return sections linked with the Section
248 in a correct order.
249 * it is impossible to create linking loops.
250
251 >>> from schooltool.course.section import Section
252
253 >>> def section_link_str(s):
254 ... return '%s <- %s -> %s' % (
255 ... not s.previous and 'None' or s.previous.title,
256 ... s.title,
257 ... not s.next and 'None' or s.next.title)
258
259 >>> def print_sections(sections):
260 ... '''Print prev and next links for all sections in section_list'''
261 ... for s in sections:
262 ... print section_link_str(s)
263
264 >>> def print_linked(section_list):
265 ... '''Print linked sections for all sections in section_list'''
266 ... for section in section_list:
267 ... linked_str = ', '.join(
268 ... [s.title for s in section.linked_sections])
269 ... print section.title, 'spans:', linked_str
270
271 Create some sections.
272
273 >>> sections = [Section('Sec0'), Section('Sec1'), Section('Sec2')]
274
275 By default sections are not linked.
276
277 >>> print_sections(sections)
278 None <- Sec0 -> None
279 None <- Sec1 -> None
280 None <- Sec2 -> None
281
282 And each section spans only itself.
283
284 >>> print_linked(sections)
285 Sec0 spans: Sec0
286 Sec1 spans: Sec1
287 Sec2 spans: Sec2
288
289 Assign s0 as previous section to s1. s0 'next' section is also updated.
290 A list of linked sections updated for s0 and s1.
291
292 >>> sections[1].previous = sections[0]
293
294 >>> print_sections(sections)
295 None <- Sec0 -> Sec1
296 Sec0 <- Sec1 -> None
297 None <- Sec2 -> None
298
299 >>> print_linked(sections)
300 Sec0 spans: Sec0, Sec1
301 Sec1 spans: Sec0, Sec1
302 Sec2 spans: Sec2
303
304 Assign s2 as next section to s1.
305
306 >>> sections[1].next = sections[2]
307
308 >>> print_sections(sections)
309 None <- Sec0 -> Sec1
310 Sec0 <- Sec1 -> Sec2
311 Sec1 <- Sec2 -> None
312
313 >>> print_linked(sections)
314 Sec0 spans: Sec0, Sec1, Sec2
315 Sec1 spans: Sec0, Sec1, Sec2
316 Sec2 spans: Sec0, Sec1, Sec2
317
318 Let's test section unlinking...
319
320 >>> sections[1].previous = None
321 >>> print_sections(sections)
322 None <- Sec0 -> None
323 None <- Sec1 -> Sec2
324 Sec1 <- Sec2 -> None
325
326 >>> print_linked(sections)
327 Sec0 spans: Sec0
328 Sec1 spans: Sec1, Sec2
329 Sec2 spans: Sec1, Sec2
330
331 >>> sections[2].previous = None
332 >>> print_sections(sections)
333 None <- Sec0 -> None
334 None <- Sec1 -> None
335 None <- Sec2 -> None
336
337 >>> print_linked(sections)
338 Sec0 spans: Sec0
339 Sec1 spans: Sec1
340 Sec2 spans: Sec2
341
342 And now some extreme cases. Try the section as next/prev to itself.
343
344 >>> sections[0].previous = sections[0]
345 Traceback (most recent call last):
346 ...
347 InvalidSectionLinkException: Cannot assign section as previous to itself
348
349 >>> sections[0].next = sections[0]
350 Traceback (most recent call last):
351 ...
352 InvalidSectionLinkException: Cannot assign section as next to itself
353
354 Create a long list of linked sections.
355
356 >>> sections = [Section('Sec0')]
357 >>> for n in range(5):
358 ... new_sec = Section('Sec%d' % (n+1))
359 ... new_sec.previous = sections[-1]
360 ... sections.append(new_sec)
361
362 >>> print_sections(sections)
363 None <- Sec0 -> Sec1
364 Sec0 <- Sec1 -> Sec2
365 Sec1 <- Sec2 -> Sec3
366 Sec2 <- Sec3 -> Sec4
367 Sec3 <- Sec4 -> Sec5
368 Sec4 <- Sec5 -> None
369
370 Try to introduce a loop by assigning a previous section.
371
372 >>> sections[4].previous = sections[1]
373
374 Note that sections 2 and 3 are removed from the linked list thus avoiding
375 the loop.
376
377 >>> print_sections(sections)
378 None <- Sec0 -> Sec1
379 Sec0 <- Sec1 -> Sec4
380 None <- Sec2 -> None
381 None <- Sec3 -> None
382 Sec1 <- Sec4 -> Sec5
383 Sec4 <- Sec5 -> None
384
385 >>> [s.title for s in sections[0].linked_sections]
386 ['Sec0', 'Sec1', 'Sec4', 'Sec5']
387
388 Let's reubild the list of 5 linked sections.
389
390 >>> sections[4].previous = sections[3]
391 >>> sections[3].previous = sections[2]
392 >>> sections[2].previous = sections[1]
393
394 >>> print_linked(sections)
395 Sec0 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
396 Sec1 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
397 Sec2 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
398 Sec3 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
399 Sec4 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
400 Sec5 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
401
402
403 Try to introduce a loop by assigning a next section.
404
405 >>> sections[1].next = sections[4]
406
407 Note that sections 2 and 3 are removed from the linked list thus avoiding
408 the loop again.
409
410 >>> print_sections(sections)
411 None <- Sec0 -> Sec1
412 Sec0 <- Sec1 -> Sec4
413 None <- Sec2 -> None
414 None <- Sec3 -> None
415 Sec1 <- Sec4 -> Sec5
416 Sec4 <- Sec5 -> None
417
418 >>> [s.title for s in sections[0].linked_sections]
419 ['Sec0', 'Sec1', 'Sec4', 'Sec5']
420
421 Let's reubild the list of 5 linked sections.
422
423 >>> sections[3].next = sections[4]
424 >>> sections[2].next = sections[3]
425 >>> sections[1].next = sections[2]
426
427 >>> print_linked(sections)
428 Sec0 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
429 Sec1 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
430 Sec2 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
431 Sec3 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
432 Sec4 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
433 Sec5 spans: Sec0, Sec1, Sec2, Sec3, Sec4, Sec5
434
435 And try to introduce another loop.
436
437 >>> sections[0].previous = sections[3]
438
439 Sections between Sec0 and Sec3 were unlinked to avoid the loop.
440
441 >>> print_linked(sections)
442 Sec0 spans: Sec3, Sec0
443 Sec1 spans: Sec1
444 Sec2 spans: Sec2
445 Sec3 spans: Sec3, Sec0
446 Sec4 spans: Sec4, Sec5
447 Sec5 spans: Sec4, Sec5
243448
244 """449 """
245450
@@ -247,9 +452,6 @@
247def doctest_PersonInstructorCrowd():452def doctest_PersonInstructorCrowd():
248 """Unit test for the PersonInstructorCrowd453 """Unit test for the PersonInstructorCrowd
249454
250 >>> from schooltool.relationship.tests import setUp, tearDown
251 >>> setUp()
252
253 We'll need a section, a group, and a couple of persons:455 We'll need a section, a group, and a couple of persons:
254456
255 >>> from schooltool.course.section import Section457 >>> from schooltool.course.section import Section
@@ -284,18 +486,12 @@
284 >>> PersonInstructorsCrowd(p2).contains(p1)486 >>> PersonInstructorsCrowd(p2).contains(p1)
285 False487 False
286488
287 Cleanup.
288
289 >>> tearDown()
290 """489 """
291490
292491
293def doctest_PersonLearnerAdapter(self):492def doctest_PersonLearnerAdapter(self):
294 """Tests for PersonLearnerAdapter.493 """Tests for PersonLearnerAdapter.
295494
296 >>> from schooltool.relationship.tests import setUp, tearDown
297 >>> setUp()
298
299 We'll need a person, a group, and a couple of sections:495 We'll need a person, a group, and a couple of sections:
300496
301 >>> from schooltool.course.section import Section497 >>> from schooltool.course.section import Section
@@ -320,16 +516,14 @@
320 >>> [section.title for section in learner.sections()]516 >>> [section.title for section in learner.sections()]
321 ['section 1', 'section 2']517 ['section 1', 'section 2']
322518
323 Cleanup.
324
325 >>> tearDown()
326 """519 """
327520
328521
329def test_suite():522def test_suite():
330 return unittest.TestSuite([523 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
331 doctest.DocTestSuite(optionflags=doctest.ELLIPSIS),524 suite = doctest.DocTestSuite(optionflags=optionflags,
332 ])525 setUp=setUp, tearDown=tearDown)
526 return suite
333527
334528
335if __name__ == '__main__':529if __name__ == '__main__':
336530
=== added file 'src/schooltool/course/tests/test_subscribers.py'
--- src/schooltool/course/tests/test_subscribers.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/course/tests/test_subscribers.py 2009-03-22 13:06:04 +0000
@@ -0,0 +1,262 @@
1#
2# SchoolTool - common information systems platform for school administration
3# Copyright (c) 2005 Shuttleworth Foundation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19"""
20Unit tests for course and section subscriber validation.
21
22$Id$
23"""
24
25import unittest
26from datetime import date, timedelta
27from zope.testing import doctest
28
29from schooltool.schoolyear.testing import (setUp, tearDown,
30 provideStubUtility,
31 provideStubAdapter)
32from schooltool.schoolyear.ftesting import schoolyear_functional_layer
33from schooltool.app.interfaces import ISchoolToolApplication
34from schooltool.schoolyear.interfaces import ISchoolYearContainer
35from schooltool.schoolyear.schoolyear import SchoolYear
36from schooltool.term.interfaces import ITerm
37from schooltool.term.term import Term
38from schooltool.course.interfaces import ISectionContainer
39from schooltool.course.section import Section
40
41
42def setUpSchoolYear(year=2000):
43 year_container = ISchoolYearContainer(ISchoolToolApplication(None))
44 sy = year_container[str(year)] = SchoolYear(
45 str(year), date(year, 1, 1), date(year+1, 1, 1) - timedelta(1))
46 return sy
47
48
49def setUpTerms(schoolyear, term_count=3):
50 term_delta = timedelta(
51 ((schoolyear.last - schoolyear.first) / term_count).days)
52 start_date = schoolyear.first
53 for n in range(term_count):
54 finish_date = start_date + term_delta - timedelta(1)
55 schoolyear['Term%d' % (n+1)] = Term(
56 'Term %d' % (n+1), start_date, finish_date)
57 start_date = finish_date + timedelta(1)
58
59
60def setUpSections(term_list, sections_per_term=1):
61 for term in term_list:
62 sections = ISectionContainer(term)
63 for n in range(sections_per_term):
64 name = 'Sec%d'%(n+1)
65 sections[name] = Section(name)
66
67
68def doctest_Section_linking_terms():
69 r"""Tests for section linking term continuinity validations.
70
71 Set up a school year with three terms, each containing two sections.
72
73 >>> year = setUpSchoolYear(2000)
74 >>> setUpTerms(year, 3)
75 >>> setUpSections(year.values(), sections_per_term=2)
76
77 Let's make Sec1 span the three terms.
78
79 >>> s1_t1 = ISectionContainer(year['Term1'])['Sec1']
80 >>> s1_t2 = ISectionContainer(year['Term2'])['Sec1']
81 >>> s1_t3 = ISectionContainer(year['Term3'])['Sec1']
82
83 >>> s1_t2.previous = s1_t1
84 >>> s1_t2.next = s1_t3
85
86 >>> for s in s1_t2.linked_sections:
87 ... print '%s, %s' % (ITerm(s).title, s.title)
88 Term 1, Sec1
89 Term 2, Sec1
90 Term 3, Sec1
91
92 We cannot link a section to another section in the same term.
93
94 >>> s2_t1 = ISectionContainer(year['Term1'])['Sec2']
95 >>> s2_t2 = ISectionContainer(year['Term2'])['Sec2']
96 >>> s2_t3 = ISectionContainer(year['Term3'])['Sec2']
97
98 >>> s2_t2.next = s1_t2
99 Traceback (most recent call last):
100 ...
101 InvalidSectionLinkException: Cannot link sections in same term
102
103 >>> s2_t2.previous = s1_t2
104 Traceback (most recent call last):
105 ...
106 InvalidSectionLinkException: Cannot link sections in same term
107
108 Cannot set previous section in the future.
109
110 >>> s2_t2.previous = s1_t3
111 Traceback (most recent call last):
112 ...
113 InvalidSectionLinkException: Sections are not in subsequent terms
114
115 Or set next section in the past.
116
117 >>> s2_t2.next = s1_t1
118 Traceback (most recent call last):
119 ...
120 InvalidSectionLinkException: Sections are not in subsequent terms
121
122 Notice that though we tried to link Sec2 with Sec1, we didn't change it's
123 linked_sections, becouse all our assigments were invalid.
124
125 >>> for s in s1_t2.linked_sections:
126 ... print '%s, %s' % (ITerm(s).title, s.title)
127 Term 1, Sec1
128 Term 2, Sec1
129 Term 3, Sec1
130
131 Let's test an unusual case: continue Section 1 from Term 1 as Section 2
132 in the last term.
133
134 >>> s2_t3.previous = s1_t1
135
136 Section 2 in third term now continues Section 1.
137
138 >>> for s in s1_t1.linked_sections:
139 ... print '%s, %s' % (ITerm(s).title, s.title)
140 Term 1, Sec1
141 Term 3, Sec2
142
143 Section 1 now spans only terms 2 and 3.
144
145 >>> for s in s1_t2.linked_sections:
146 ... print '%s, %s' % (ITerm(s).title, s.title)
147 Term 2, Sec1
148 Term 3, Sec1
149
150 """
151
152
153def doctest_Section_linking_schoolyears():
154 r"""Tests for section linking SchoolYear validations.
155
156 Set up a school year with three terms, each containing two sections.
157
158 >>> def setUpYearWithSection(year):
159 ... year = setUpSchoolYear(year)
160 ... setUpTerms(year, 1)
161 ... setUpSections(year.values(), sections_per_term=1)
162 ... return year
163
164 >>> year0 = setUpYearWithSection(2000)
165 >>> year1 = setUpYearWithSection(2001)
166 >>> year2 = setUpYearWithSection(2002)
167
168 >>> sec_year_0 = ISectionContainer(year0['Term1'])['Sec1']
169 >>> sec_year_1 = ISectionContainer(year1['Term1'])['Sec1']
170 >>> sec_year_2 = ISectionContainer(year2['Term1'])['Sec1']
171
172 We cannot link sections in the different school years.
173
174 >>> sec_year_1.previous = sec_year_0
175 Traceback (most recent call last):
176 ...
177 InvalidSectionLinkException:
178 Cannot link sections in different school years
179
180 >>> sec_year_1.next = sec_year_2
181 Traceback (most recent call last):
182 ...
183 InvalidSectionLinkException:
184 Cannot link sections in different school years
185
186 """
187
188
189def doctest_copySection():
190 r"""Test for copySection.
191
192 >>> from schooltool.course.section import Section
193 >>> from schooltool.course.course import Course
194 >>> from schooltool.person.person import Person
195
196 Create a section with a course, instructor and several members.
197
198 >>> year = setUpSchoolYear(2000)
199 >>> setUpTerms(year, 2)
200
201 >>> section = Section('English A')
202 >>> section.instructors.add(Person('teacher', 'Mr. Jones'))
203 >>> section.members.add(Person('first','First'))
204 >>> section.members.add(Person('second','Second'))
205 >>> section.members.add(Person('third','Third'))
206 >>> section.courses.add(Course(title="English"))
207 >>> ISectionContainer(year['Term1'])['Sec1'] = section
208
209 Let's copy it to another term.
210
211 >>> from schooltool.course.section import copySection
212
213 >>> new_section = copySection(section, year['Term2'])
214
215 Sectoin's copy was created.
216
217 >>> new_section is not section
218 True
219
220 Sec1 was available as section id in the new term, so it was preserved.
221
222 >>> print new_section.__name__
223 Sec1
224
225 Courses, instructors and members were copied.
226
227 >>> for course in new_section.courses:
228 ... print course.title
229 English
230
231 >>> for person in new_section.instructors:
232 ... print person.title
233 Mr. Jones
234
235 >>> for person in new_section.members:
236 ... print person.title
237 First
238 Second
239 Third
240
241 If original section's __name__ is already present in the target term, an
242 alternative is chosen.
243
244 >>> other_section = copySection(section, year['Term2'])
245 >>> print other_section.__name__
246 1
247
248 """
249
250
251def test_suite():
252 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
253 suite = doctest.DocTestSuite(optionflags=optionflags,
254 extraglobs={'provideAdapter': provideStubAdapter,
255 'provideUtility': provideStubUtility},
256 setUp=setUp, tearDown=tearDown)
257 suite.layer = schoolyear_functional_layer
258 return suite
259
260
261if __name__ == '__main__':
262 unittest.main(defaultTest='test_suite')
0263
=== modified file 'src/schooltool/relationship/tests/__init__.py'
--- src/schooltool/relationship/tests/__init__.py 2006-05-05 18:03:22 +0000
+++ src/schooltool/relationship/tests/__init__.py 2009-03-21 12:52:42 +0000
@@ -60,7 +60,7 @@
60 return cmp(self.uri, other.uri)60 return cmp(self.uri, other.uri)
6161
6262
63def setUp():63def setUp(test=None):
64 """Set up for schooltool.relationship doctests.64 """Set up for schooltool.relationship doctests.
6565
66 Calls Zope's placelessSetUp, sets up annotations and relationships.66 Calls Zope's placelessSetUp, sets up annotations and relationships.
@@ -70,7 +70,7 @@
70 setUpRelationships()70 setUpRelationships()
7171
7272
73def tearDown():73def tearDown(test=None):
74 """Tear down for schooltool.relationshp doctests."""74 """Tear down for schooltool.relationshp doctests."""
75 setup.placelessTearDown()75 setup.placelessTearDown()
7676

Subscribers

People subscribed via source and target branches