source: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py @ 13766

Last change on this file since 13766 was 13766, checked in by Henrik Bettermann, 9 years ago

Create new exporter for lecturers and further restrict the usage of
exporters at course level.

  • Property svn:keywords set to Id
File size: 201.5 KB
Line 
1## $Id: test_browser.py 13766 2016-03-08 06:22:12Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Test the student-related UI components.
20"""
21import shutil
22import tempfile
23import pytz
24import base64
25from datetime import datetime, timedelta, date
26from StringIO import StringIO
27import os
28import grok
29from zc.async.testing import wait_for_result
30from zope.event import notify
31from zope.component import createObject, queryUtility, getUtility
32from zope.component.hooks import setSite, clearSite
33from zope.catalog.interfaces import ICatalog
34from zope.security.interfaces import Unauthorized
35from zope.securitypolicy.interfaces import IPrincipalRoleManager
36from zope.testbrowser.testing import Browser
37from zope.interface import implementedBy
38from zope.schema.fieldproperty import FieldProperty
39from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
40from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
41from waeup.kofa.app import University
42from waeup.kofa.payments.interfaces import IPayer
43from waeup.kofa.students.interfaces import IStudentsUtils
44from waeup.kofa.students.student import Student
45from waeup.kofa.students.studylevel import StudentStudyLevel
46from waeup.kofa.university.faculty import Faculty
47from waeup.kofa.university.department import Department
48from waeup.kofa.interfaces import IUserAccount, IJobManager
49from waeup.kofa.authentication import LocalRoleSetEvent
50from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
51from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
52from waeup.kofa.browser.tests.test_pdf import samples_dir
53
54PH_LEN = 15911  # Length of placeholder file
55
56SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
57SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
58
59def lookup_submit_value(name, value, browser):
60    """Find a button with a certain value."""
61    for num in range(0, 100):
62        try:
63            button = browser.getControl(name=name, index=num)
64            if button.value.endswith(value):
65                return button
66        except IndexError:
67            break
68    return None
69
70class StudentsFullSetup(FunctionalTestCase):
71    # A test case that only contains a setup and teardown
72    #
73    # Complete setup for students handlings is rather complex and
74    # requires lots of things created before we can start. This is a
75    # setup that does all this, creates a university, creates PINs,
76    # etc.  so that we do not have to bother with that in different
77    # test cases.
78
79    layer = FunctionalLayer
80
81    def setUp(self):
82        super(StudentsFullSetup, self).setUp()
83
84        # Setup a sample site for each test
85        app = University()
86        self.dc_root = tempfile.mkdtemp()
87        app['datacenter'].setStoragePath(self.dc_root)
88
89        # Prepopulate the ZODB...
90        self.getRootFolder()['app'] = app
91        # we add the site immediately after creation to the
92        # ZODB. Catalogs and other local utilities are not setup
93        # before that step.
94        self.app = self.getRootFolder()['app']
95        # Set site here. Some of the following setup code might need
96        # to access grok.getSite() and should get our new app then
97        setSite(app)
98
99        # Add student with subobjects
100        student = createObject('waeup.Student')
101        student.firstname = u'Anna'
102        student.lastname = u'Tester'
103        student.reg_number = u'123'
104        student.matric_number = u'234'
105        student.sex = u'm'
106        student.email = 'aa@aa.ng'
107        student.phone = u'1234'
108        student.date_of_birth = date(1981, 2, 4)
109        self.app['students'].addStudent(student)
110        self.student_id = student.student_id
111        self.student = self.app['students'][self.student_id]
112
113        # Set password
114        IUserAccount(
115            self.app['students'][self.student_id]).setPassword('spwd')
116
117        self.login_path = 'http://localhost/app/login'
118        self.container_path = 'http://localhost/app/students'
119        self.manage_container_path = self.container_path + '/@@manage'
120        self.add_student_path = self.container_path + '/addstudent'
121        self.student_path = self.container_path + '/' + self.student_id
122        self.manage_student_path = self.student_path + '/manage_base'
123        self.trigtrans_path = self.student_path + '/trigtrans'
124        self.clearance_path = self.student_path + '/view_clearance'
125        self.personal_path = self.student_path + '/view_personal'
126        self.edit_clearance_path = self.student_path + '/cedit'
127        self.manage_clearance_path = self.student_path + '/manage_clearance'
128        self.edit_personal_path = self.student_path + '/edit_personal'
129        self.manage_personal_path = self.student_path + '/manage_personal'
130        self.studycourse_path = self.student_path + '/studycourse'
131        self.payments_path = self.student_path + '/payments'
132        self.acco_path = self.student_path + '/accommodation'
133        self.history_path = self.student_path + '/history'
134
135        # Create 5 access codes with prefix'PWD'
136        pin_container = self.app['accesscodes']
137        pin_container.createBatch(
138            datetime.utcnow(), 'some_userid', 'PWD', 9.99, 5)
139        pins = pin_container['PWD-1'].values()
140        self.pwdpins = [x.representation for x in pins]
141        self.existing_pwdpin = self.pwdpins[0]
142        parts = self.existing_pwdpin.split('-')[1:]
143        self.existing_pwdseries, self.existing_pwdnumber = parts
144        # Create 5 access codes with prefix 'CLR'
145        pin_container.createBatch(
146            datetime.now(), 'some_userid', 'CLR', 9.99, 5)
147        pins = pin_container['CLR-1'].values()
148        pins[0].owner = u'Hans Wurst'
149        self.existing_clrac = pins[0]
150        self.existing_clrpin = pins[0].representation
151        parts = self.existing_clrpin.split('-')[1:]
152        self.existing_clrseries, self.existing_clrnumber = parts
153        # Create 2 access codes with prefix 'HOS'
154        pin_container.createBatch(
155            datetime.now(), 'some_userid', 'HOS', 9.99, 2)
156        pins = pin_container['HOS-1'].values()
157        self.existing_hosac = pins[0]
158        self.existing_hospin = pins[0].representation
159        parts = self.existing_hospin.split('-')[1:]
160        self.existing_hosseries, self.existing_hosnumber = parts
161
162        # Populate university
163        self.certificate = createObject('waeup.Certificate')
164        self.certificate.code = u'CERT1'
165        self.certificate.application_category = 'basic'
166        self.certificate.study_mode = 'ug_ft'
167        self.certificate.start_level = 100
168        self.certificate.end_level = 500
169        self.certificate.school_fee_1 = 40000.0
170        self.certificate.school_fee_2 = 20000.0
171        self.app['faculties']['fac1'] = Faculty(code=u'fac1')
172        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
173        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
174            self.certificate)
175        self.course = createObject('waeup.Course')
176        self.course.code = 'COURSE1'
177        self.course.semester = 1
178        self.course.credits = 10
179        self.course.passmark = 40
180        self.app['faculties']['fac1']['dep1'].courses.addCourse(
181            self.course)
182        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCertCourse(
183            self.course, level=100)
184
185        # Configure university and hostels
186        self.app['hostels'].accommodation_states = ['admitted']
187        self.app['hostels'].accommodation_session = 2004
188        delta = timedelta(days=10)
189        self.app['hostels'].startdate = datetime.now(pytz.utc) - delta
190        self.app['hostels'].enddate = datetime.now(pytz.utc) + delta
191        self.app['configuration'].carry_over = True
192        configuration = createObject('waeup.SessionConfiguration')
193        configuration.academic_session = 2004
194        configuration.clearance_fee = 3456.0
195        configuration.transcript_fee = 4567.0
196        configuration.booking_fee = 123.4
197        configuration.maint_fee = 987.0
198        configuration.transfer_fee = 456.0
199        configuration.late_registration_fee = 345.0
200        self.app['configuration'].addSessionConfiguration(configuration)
201
202        # Create a hostel with two beds
203        hostel = Hostel()
204        hostel.hostel_id = u'hall-1'
205        hostel.hostel_name = u'Hall 1'
206        hostel.maint_fee = 876.0
207        self.app['hostels'].addHostel(hostel)
208        bed = Bed()
209        bed.bed_id = u'hall-1_A_101_A'
210        bed.bed_number = 1
211        bed.owner = NOT_OCCUPIED
212        bed.bed_type = u'regular_male_fr'
213        self.app['hostels'][hostel.hostel_id].addBed(bed)
214        bed = Bed()
215        bed.bed_id = u'hall-1_A_101_B'
216        bed.bed_number = 2
217        bed.owner = NOT_OCCUPIED
218        bed.bed_type = u'regular_female_fr'
219        self.app['hostels'][hostel.hostel_id].addBed(bed)
220
221        # Set study course attributes of test student
222        self.student['studycourse'].certificate = self.certificate
223        self.student['studycourse'].current_session = 2004
224        self.student['studycourse'].entry_session = 2004
225        self.student['studycourse'].current_verdict = 'A'
226        self.student['studycourse'].current_level = 100
227        # Update the catalog
228        notify(grok.ObjectModifiedEvent(self.student))
229
230        # Put the prepopulated site into test ZODB and prepare test
231        # browser
232        self.browser = Browser()
233        self.browser.handleErrors = False
234
235    def tearDown(self):
236        super(StudentsFullSetup, self).tearDown()
237        clearSite()
238        shutil.rmtree(self.dc_root)
239
240
241
242class StudentsContainerUITests(StudentsFullSetup):
243    # Tests for StudentsContainer class views and pages
244
245    layer = FunctionalLayer
246
247    def test_anonymous_access(self):
248        # Anonymous users can't access students containers
249        self.assertRaises(
250            Unauthorized, self.browser.open, self.container_path)
251        self.assertRaises(
252            Unauthorized, self.browser.open, self.manage_container_path)
253        return
254
255    def test_manage_access(self):
256        # Managers can access the view page of students
257        # containers and can perform actions
258        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
259        self.browser.open(self.container_path)
260        self.assertEqual(self.browser.headers['Status'], '200 Ok')
261        self.assertEqual(self.browser.url, self.container_path)
262        self.browser.getLink("Manage students section").click()
263        self.assertEqual(self.browser.headers['Status'], '200 Ok')
264        self.assertEqual(self.browser.url, self.manage_container_path)
265        return
266
267    def test_add_search_delete_students(self):
268        # Managers can add search and remove students
269        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
270        self.browser.open(self.manage_container_path)
271        self.browser.getLink("Add student").click()
272        self.assertEqual(self.browser.headers['Status'], '200 Ok')
273        self.assertEqual(self.browser.url, self.add_student_path)
274        self.browser.getControl(name="form.firstname").value = 'Bob'
275        self.browser.getControl(name="form.lastname").value = 'Tester'
276        self.browser.getControl(name="form.reg_number").value = '123'
277        self.browser.getControl("Create student").click()
278        self.assertTrue('Registration number exists already'
279            in self.browser.contents)
280        self.browser.getControl(name="form.reg_number").value = '1234'
281        self.browser.getControl("Create student").click()
282        self.assertTrue('Student record created' in self.browser.contents)
283
284        # Registration and matric numbers must be unique
285        self.browser.getLink("Manage").click()
286        self.browser.getControl(name="form.reg_number").value = '123'
287        self.browser.getControl("Save").click()
288        self.assertMatches('...Registration number exists...',
289                           self.browser.contents)
290        self.browser.getControl(name="form.reg_number").value = '789'
291        self.browser.getControl(name="form.matric_number").value = '234'
292        self.browser.getControl("Save").click()
293        self.assertMatches('...Matriculation number exists...',
294                           self.browser.contents)
295
296        # We can find a student with a certain student_id
297        self.browser.open(self.container_path)
298        self.browser.getControl("Find student(s)").click()
299        self.assertTrue('Empty search string' in self.browser.contents)
300        self.browser.getControl(name="searchtype").value = ['student_id']
301        self.browser.getControl(name="searchterm").value = self.student_id
302        self.browser.getControl("Find student(s)").click()
303        self.assertTrue('Anna Tester' in self.browser.contents)
304
305        # We can find a student in a certain session
306        self.browser.open(self.container_path)
307        self.browser.getControl(name="searchtype").value = ['current_session']
308        self.browser.getControl(name="searchterm").value = '2004'
309        self.browser.getControl("Find student(s)").click()
310        self.assertTrue('Anna Tester' in self.browser.contents)
311        # Session fileds require integer values
312        self.browser.open(self.container_path)
313        self.browser.getControl(name="searchtype").value = ['current_session']
314        self.browser.getControl(name="searchterm").value = '2004/2005'
315        self.browser.getControl("Find student(s)").click()
316        self.assertTrue('Only year dates allowed' in self.browser.contents)
317        self.browser.open(self.manage_container_path)
318        self.browser.getControl(name="searchtype").value = ['current_session']
319        self.browser.getControl(name="searchterm").value = '2004/2005'
320        self.browser.getControl("Find student(s)").click()
321        self.assertTrue('Only year dates allowed' in self.browser.contents)
322
323        # We can find a student in a certain study_mode
324        self.browser.open(self.container_path)
325        self.browser.getControl(name="searchtype").value = ['current_mode']
326        self.browser.getControl(name="searchterm").value = 'ug_ft'
327        self.browser.getControl("Find student(s)").click()
328        self.assertTrue('Anna Tester' in self.browser.contents)
329
330        # We can find a student in a certain department
331        self.browser.open(self.container_path)
332        self.browser.getControl(name="searchtype").value = ['depcode']
333        self.browser.getControl(name="searchterm").value = 'dep1'
334        self.browser.getControl("Find student(s)").click()
335        self.assertTrue('Anna Tester' in self.browser.contents)
336
337        # We can find a student by searching for all kind of name parts
338        self.browser.open(self.manage_container_path)
339        self.browser.getControl("Find student(s)").click()
340        self.assertTrue('Empty search string' in self.browser.contents)
341        self.browser.getControl(name="searchtype").value = ['fullname']
342        self.browser.getControl(name="searchterm").value = 'Anna Tester'
343        self.browser.getControl("Find student(s)").click()
344        self.assertTrue('Anna Tester' in self.browser.contents)
345        self.browser.open(self.manage_container_path)
346        self.browser.getControl(name="searchtype").value = ['fullname']
347        self.browser.getControl(name="searchterm").value = 'Anna'
348        self.browser.getControl("Find student(s)").click()
349        self.assertTrue('Anna Tester' in self.browser.contents)
350        self.browser.open(self.manage_container_path)
351        self.browser.getControl(name="searchtype").value = ['fullname']
352        self.browser.getControl(name="searchterm").value = 'Tester'
353        self.browser.getControl("Find student(s)").click()
354        self.assertTrue('Anna Tester' in self.browser.contents)
355        self.browser.open(self.manage_container_path)
356        self.browser.getControl(name="searchtype").value = ['fullname']
357        self.browser.getControl(name="searchterm").value = 'An'
358        self.browser.getControl("Find student(s)").click()
359        self.assertFalse('Anna Tester' in self.browser.contents)
360        self.browser.open(self.manage_container_path)
361        self.browser.getControl(name="searchtype").value = ['fullname']
362        self.browser.getControl(name="searchterm").value = 'An*'
363        self.browser.getControl("Find student(s)").click()
364        self.assertTrue('Anna Tester' in self.browser.contents)
365        self.browser.open(self.manage_container_path)
366        self.browser.getControl(name="searchtype").value = ['fullname']
367        self.browser.getControl(name="searchterm").value = 'tester'
368        self.browser.getControl("Find student(s)").click()
369        self.assertTrue('Anna Tester' in self.browser.contents)
370        self.browser.open(self.manage_container_path)
371        self.browser.getControl(name="searchtype").value = ['fullname']
372        self.browser.getControl(name="searchterm").value = 'Tester Ana'
373        self.browser.getControl("Find student(s)").click()
374        self.assertFalse('Anna Tester' in self.browser.contents)
375        self.browser.open(self.manage_container_path)
376        self.browser.getControl(name="searchtype").value = ['fullname']
377        self.browser.getControl(name="searchterm").value = 'Tester Anna'
378        self.browser.getControl("Find student(s)").click()
379        self.assertTrue('Anna Tester' in self.browser.contents)
380        # The old searchterm will be used again
381        self.browser.getControl("Find student(s)").click()
382        self.assertTrue('Anna Tester' in self.browser.contents)
383
384        # We can find suspended students
385        self.student.suspended = True
386        notify(grok.ObjectModifiedEvent(self.student))
387        self.browser.open(self.manage_container_path)
388        self.browser.getControl(name="searchtype").value = ['suspended']
389        self.browser.getControl("Find student(s)").click()
390        self.assertTrue('Anna Tester' in self.browser.contents)
391        self.browser.open(self.container_path)
392        self.browser.getControl(name="searchtype").value = ['suspended']
393        self.browser.getControl("Find student(s)").click()
394        self.assertTrue('Anna Tester' in self.browser.contents)
395
396        # The catalog is informed when studycourse objects have been
397        # edited
398        self.browser.open(self.studycourse_path + '/manage')
399        self.browser.getControl(name="form.current_session").value = ['2010']
400        self.browser.getControl(name="form.entry_session").value = ['2010']
401        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
402        self.browser.getControl("Save").click()
403
404        # We can find the student in the new session
405        self.browser.open(self.manage_container_path)
406        self.browser.getControl(name="searchtype").value = ['current_session']
407        self.browser.getControl(name="searchterm").value = '2010'
408        self.browser.getControl("Find student(s)").click()
409        self.assertTrue('Anna Tester' in self.browser.contents)
410
411        ctrl = self.browser.getControl(name='entries')
412        ctrl.getControl(value=self.student_id).selected = True
413        self.browser.getControl("Remove selected", index=0).click()
414        self.assertTrue('Successfully removed' in self.browser.contents)
415        self.browser.getControl(name="searchtype").value = ['student_id']
416        self.browser.getControl(name="searchterm").value = self.student_id
417        self.browser.getControl("Find student(s)").click()
418        self.assertTrue('No student found' in self.browser.contents)
419
420        self.browser.open(self.container_path)
421        self.browser.getControl(name="searchtype").value = ['student_id']
422        self.browser.getControl(name="searchterm").value = self.student_id
423        self.browser.getControl("Find student(s)").click()
424        self.assertTrue('No student found' in self.browser.contents)
425        return
426
427class OfficerUITests(StudentsFullSetup):
428    # Tests for Student class views and pages
429
430    def test_student_properties(self):
431        self.student['studycourse'].current_level = 100
432        self.assertEqual(self.student.current_level, 100)
433        self.student['studycourse'].current_session = 2011
434        self.assertEqual(self.student.current_session, 2011)
435        self.student['studycourse'].current_verdict = 'A'
436        self.assertEqual(self.student.current_verdict, 'A')
437        return
438
439    def test_studylevelmanagepage(self):
440        studylevel = StudentStudyLevel()
441        studylevel.level = 100
442        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
443        self.student['studycourse'].addStudentStudyLevel(
444            cert,studylevel)
445        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
446        self.browser.open(self.studycourse_path + '/100/manage')
447        self.assertEqual(self.browser.url, self.studycourse_path + '/100/manage')
448        self.assertEqual(self.browser.headers['Status'], '200 Ok')
449
450    def test_basic_auth(self):
451        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
452        self.browser.open('http://localhost/app')
453        self.browser.getLink("Logout").click()
454        self.assertTrue('You have been logged out' in self.browser.contents)
455        # But we are still logged in since we've used basic authentication here.
456        # Wikipedia says: Existing browsers retain authentication information
457        # until the tab or browser is closed or the user clears the history.
458        # HTTP does not provide a method for a server to direct clients to
459        # discard these cached credentials. This means that there is no
460        # effective way for a server to "log out" the user without closing
461        # the browser. This is a significant defect that requires browser
462        # manufacturers to support a "logout" user interface element ...
463        self.assertTrue('Manager' in self.browser.contents)
464
465    def test_basic_auth_base64(self):
466        auth_token = base64.b64encode('mgr:mgrpw')
467        self.browser.addHeader('Authorization', 'Basic %s' % auth_token)
468        self.browser.open(self.manage_container_path)
469        self.assertEqual(self.browser.headers['Status'], '200 Ok')
470
471    def test_manage_access(self):
472        # Managers can access the pages of students
473        # and can perform actions
474        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
475        self.browser.open(self.student_path)
476        self.assertEqual(self.browser.headers['Status'], '200 Ok')
477        self.assertEqual(self.browser.url, self.student_path)
478        self.browser.getLink("Trigger").click()
479        self.assertEqual(self.browser.headers['Status'], '200 Ok')
480        # Managers can trigger transitions
481        self.browser.getControl(name="transition").value = ['admit']
482        self.browser.getControl("Save").click()
483        # Managers can edit base
484        self.browser.open(self.student_path)
485        self.browser.getLink("Manage").click()
486        self.assertEqual(self.browser.url, self.manage_student_path)
487        self.assertEqual(self.browser.headers['Status'], '200 Ok')
488        self.browser.getControl(name="form.firstname").value = 'John'
489        self.browser.getControl(name="form.lastname").value = 'Tester'
490        self.browser.getControl(name="form.reg_number").value = '345'
491        self.browser.getControl(name="password").value = 'secret'
492        self.browser.getControl(name="control_password").value = 'secret'
493        self.browser.getControl("Save").click()
494        self.assertMatches('...Form has been saved...',
495                           self.browser.contents)
496        self.browser.open(self.student_path)
497        self.browser.getLink("Clearance Data").click()
498        self.assertEqual(self.browser.headers['Status'], '200 Ok')
499        self.assertEqual(self.browser.url, self.clearance_path)
500        self.browser.getLink("Manage").click()
501        self.assertEqual(self.browser.headers['Status'], '200 Ok')
502        self.assertEqual(self.browser.url, self.manage_clearance_path)
503        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
504        self.browser.getControl("Save").click()
505        self.assertMatches('...Form has been saved...',
506                           self.browser.contents)
507
508        self.browser.open(self.student_path)
509        self.browser.getLink("Personal Data").click()
510        self.assertEqual(self.browser.headers['Status'], '200 Ok')
511        self.assertEqual(self.browser.url, self.personal_path)
512        self.browser.getLink("Manage").click()
513        self.assertEqual(self.browser.headers['Status'], '200 Ok')
514        self.assertEqual(self.browser.url, self.manage_personal_path)
515        self.browser.open(self.personal_path)
516        self.assertTrue('Updated' in self.browser.contents)
517        self.browser.getLink("Edit").click()
518        self.assertEqual(self.browser.headers['Status'], '200 Ok')
519        self.assertEqual(self.browser.url, self.edit_personal_path)
520        self.browser.getControl("Save").click()
521        # perm_address is required in IStudentPersonalEdit
522        self.assertMatches('...Required input is missing...',
523                           self.browser.contents)
524        self.browser.getControl(name="form.perm_address").value = 'My address!'
525        self.browser.getControl("Save").click()
526        self.assertMatches('...Form has been saved...',
527                           self.browser.contents)
528
529        # Managers can browse all subobjects
530        self.browser.open(self.student_path)
531        self.browser.getLink("Payments").click()
532        self.assertEqual(self.browser.headers['Status'], '200 Ok')
533        self.assertEqual(self.browser.url, self.payments_path)
534        self.browser.open(self.student_path)
535        self.browser.getLink("Accommodation").click()
536        self.assertEqual(self.browser.headers['Status'], '200 Ok')
537        self.assertEqual(self.browser.url, self.acco_path)
538        self.browser.open(self.student_path)
539        self.browser.getLink("History").click()
540        self.assertEqual(self.browser.headers['Status'], '200 Ok')
541        self.assertEqual(self.browser.url, self.history_path)
542        self.assertMatches('...Admitted by Manager...',
543                           self.browser.contents)
544        # Only the Application Slip does not exist
545        self.assertFalse('Application Slip' in self.browser.contents)
546        return
547
548    def test_flash_notice(self):
549        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
550        self.browser.open(self.student_path)
551        self.assertFalse('alert alert-warning' in self.browser.contents)
552        self.student.flash_notice = u'Happy Birthday!'
553        self.browser.open(self.student_path)
554        self.assertTrue(
555            '<div><div class="alert alert-warning">Happy Birthday!</div>'
556            in self.browser.contents)
557        return
558
559    def test_manage_contact_student(self):
560        # Managers can contact student
561        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
562        # Remove required FieldProperty attribute first ...
563        delattr(Student, 'email')
564        # ... and replace by None
565        self.student.email = None
566        # Now we have to add  the FieldProperty attribute again. Otherwise
567        # many other tests below will fail.
568        iface = list(implementedBy(Student))[0]
569        field_property = FieldProperty(iface['email'])
570        setattr(Student, 'email', field_property)
571        self.browser.open(self.student_path)
572        self.browser.getLink("Send email").click()
573        self.browser.getControl(name="form.subject").value = 'Important subject'
574        self.browser.getControl(name="form.body").value = 'Hello!'
575        self.browser.getControl("Send message now").click()
576        self.assertTrue('An smtp server error occurred' in self.browser.contents)
577        self.student.email = 'xx@yy.zz'
578        self.browser.getControl("Send message now").click()
579        self.assertTrue('Your message has been sent' in self.browser.contents)
580        return
581
582    def test_manage_remove_department(self):
583        # Lazy student is studying CERT1
584        lazystudent = Student()
585        lazystudent.firstname = u'Lazy'
586        lazystudent.lastname = u'Student'
587        self.app['students'].addStudent(lazystudent)
588        student_id = lazystudent.student_id
589        student_path = self.container_path + '/' + student_id
590        lazystudent['studycourse'].certificate = self.certificate
591        notify(grok.ObjectModifiedEvent(lazystudent))
592        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
593        self.browser.open(student_path + '/studycourse')
594        self.assertTrue('CERT1' in self.browser.contents)
595        # After some years the department is removed
596        del self.app['faculties']['fac1']['dep1']
597        # So CERT1 does no longer exist and lazy student's
598        # certificate reference is removed too
599        self.browser.open(student_path + '/studycourse')
600        self.assertEqual(self.browser.headers['Status'], '200 Ok')
601        self.assertEqual(self.browser.url, student_path + '/studycourse')
602        self.assertFalse('CERT1' in self.browser.contents)
603        self.assertMatches('...<div>--</div>...',
604                           self.browser.contents)
605
606    def test_manage_upload_file(self):
607        # Managers can upload a file via the StudentClearanceManageFormPage
608        # The image is stored even if form has errors
609        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
610        self.browser.open(self.manage_clearance_path)
611        # No birth certificate has been uploaded yet
612        # Browsing the link shows a placerholder image
613        self.browser.open('birth_certificate')
614        self.assertEqual(
615            self.browser.headers['content-type'], 'image/jpeg')
616        self.assertEqual(len(self.browser.contents), PH_LEN)
617        # Create a pseudo image file and select it to be uploaded in form
618        # as birth certificate
619        self.browser.open(self.manage_clearance_path)
620        image = open(SAMPLE_IMAGE, 'rb')
621        ctrl = self.browser.getControl(name='birthcertificateupload')
622        file_ctrl = ctrl.mech_control
623        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
624        # The Save action does not upload files
625        self.browser.getControl("Save").click() # submit form
626        self.assertFalse(
627            '<a target="image" href="birth_certificate">'
628            in self.browser.contents)
629        # ... but the correct upload submit button does
630        image = open(SAMPLE_IMAGE)
631        ctrl = self.browser.getControl(name='birthcertificateupload')
632        file_ctrl = ctrl.mech_control
633        file_ctrl.add_file(image, filename='my_birth_certificate.jpg')
634        self.browser.getControl(
635            name='upload_birthcertificateupload').click()
636        # There is a correct <img> link included
637        self.assertTrue(
638            'href="http://localhost/app/students/K1000000/birth_certificate"'
639            in self.browser.contents)
640        # Browsing the link shows a real image
641        self.browser.open('birth_certificate')
642        self.assertEqual(
643            self.browser.headers['content-type'], 'image/jpeg')
644        self.assertEqual(len(self.browser.contents), 2787)
645        # We can't reupload a file. The existing file must be deleted first.
646        self.browser.open(self.manage_clearance_path)
647        self.assertFalse(
648            'upload_birthcertificateupload' in self.browser.contents)
649        # File must be deleted first
650        self.browser.getControl(name='delete_birthcertificateupload').click()
651        self.assertTrue(
652            'birth_certificate deleted' in self.browser.contents)
653        # Uploading a file which is bigger than 150k will raise an error
654        big_image = StringIO(open(SAMPLE_IMAGE, 'rb').read() * 75)
655        ctrl = self.browser.getControl(name='birthcertificateupload')
656        file_ctrl = ctrl.mech_control
657        file_ctrl.add_file(big_image, filename='my_birth_certificate.jpg')
658        self.browser.getControl(
659            name='upload_birthcertificateupload').click()
660        self.assertTrue(
661            'Uploaded file is too big' in self.browser.contents)
662        # we do not rely on filename extensions given by uploaders
663        image = open(SAMPLE_IMAGE, 'rb') # a jpg-file
664        ctrl = self.browser.getControl(name='birthcertificateupload')
665        file_ctrl = ctrl.mech_control
666        # tell uploaded file is bmp
667        file_ctrl.add_file(image, filename='my_birth_certificate.bmp')
668        self.browser.getControl(
669            name='upload_birthcertificateupload').click()
670        self.assertTrue(
671            # jpg file was recognized
672            'File birth_certificate.jpg uploaded.' in self.browser.contents)
673        # Delete file again
674        self.browser.getControl(name='delete_birthcertificateupload').click()
675        self.assertTrue(
676            'birth_certificate deleted' in self.browser.contents)
677        # File names must meet several conditions
678        bmp_image = open(SAMPLE_IMAGE_BMP, 'rb')
679        ctrl = self.browser.getControl(name='birthcertificateupload')
680        file_ctrl = ctrl.mech_control
681        file_ctrl.add_file(bmp_image, filename='my_birth_certificate.bmp')
682        self.browser.getControl(
683            name='upload_birthcertificateupload').click()
684        self.assertTrue('Only the following extensions are allowed'
685            in self.browser.contents)
686
687        # Managers can upload a file via the StudentBaseManageFormPage
688        self.browser.open(self.manage_student_path)
689        image = open(SAMPLE_IMAGE_BMP, 'rb')
690        ctrl = self.browser.getControl(name='passportuploadmanage')
691        file_ctrl = ctrl.mech_control
692        file_ctrl.add_file(image, filename='my_photo.bmp')
693        self.browser.getControl(
694            name='upload_passportuploadmanage').click()
695        self.assertTrue('jpg file format expected'
696            in self.browser.contents)
697        ctrl = self.browser.getControl(name='passportuploadmanage')
698        file_ctrl = ctrl.mech_control
699        image = open(SAMPLE_IMAGE, 'rb')
700        file_ctrl.add_file(image, filename='my_photo.jpg')
701        self.browser.getControl(
702            name='upload_passportuploadmanage').click()
703        self.assertTrue(
704            'src="http://localhost/app/students/K1000000/passport.jpg"'
705            in self.browser.contents)
706        # We remove the passport file again
707        self.browser.open(self.manage_student_path)
708        self.browser.getControl('Delete').click()
709        self.browser.open(self.student_path + '/clearance_slip.pdf')
710        self.assertEqual(self.browser.headers['Status'], '200 Ok')
711        self.assertEqual(self.browser.headers['Content-Type'],
712                         'application/pdf')
713        # We want to see the signature fields.
714        IWorkflowState(self.student).setState('cleared')
715        self.browser.open(self.student_path + '/clearance_slip.pdf')
716        self.assertEqual(self.browser.headers['Status'], '200 Ok')
717        self.assertEqual(self.browser.headers['Content-Type'],
718                         'application/pdf')
719        path = os.path.join(samples_dir(), 'clearance_slip.pdf')
720        open(path, 'wb').write(self.browser.contents)
721        print "Sample PDF clearance_slip.pdf written to %s" % path
722
723    def test_manage_course_lists(self):
724        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
725        self.browser.open(self.student_path)
726        self.browser.getLink("Study Course").click()
727        self.assertEqual(self.browser.headers['Status'], '200 Ok')
728        self.assertEqual(self.browser.url, self.studycourse_path)
729        self.assertTrue('Undergraduate Full-Time' in self.browser.contents)
730        self.browser.getLink("Manage").click()
731        self.assertTrue('Manage study course' in self.browser.contents)
732        # Before we can select a level, the certificate must
733        # be selected and saved
734        self.browser.getControl(name="form.certificate").value = ['CERT1']
735        self.browser.getControl(name="form.current_session").value = ['2004']
736        self.browser.getControl(name="form.current_verdict").value = ['A']
737        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
738        self.browser.getControl("Save").click()
739        # Now we can save also the current level which depends on start and end
740        # level of the certificate
741        self.browser.getControl(name="form.current_level").value = ['100']
742        self.browser.getControl("Save").click()
743        # Managers can add and remove any study level (course list)
744        self.browser.getControl(name="addlevel").value = ['100']
745        self.browser.getControl("Add study level").click()
746        self.assertMatches(
747            '...You must select a session...', self.browser.contents)
748        self.browser.getControl(name="addlevel").value = ['100']
749        self.browser.getControl(name="level_session").value = ['2004']
750        self.browser.getControl("Add study level").click()
751        self.assertMatches('...<span>100</span>...', self.browser.contents)
752        self.assertEqual(self.student['studycourse']['100'].level, 100)
753        self.assertEqual(self.student['studycourse']['100'].level_session, 2004)
754        self.browser.getControl(name="addlevel").value = ['100']
755        self.browser.getControl(name="level_session").value = ['2004']
756        self.browser.getControl("Add study level").click()
757        self.assertMatches('...This level exists...', self.browser.contents)
758        self.browser.getControl("Remove selected").click()
759        self.assertMatches(
760            '...No study level selected...', self.browser.contents)
761        self.browser.getControl(name="val_id").value = ['100']
762        self.browser.getControl(name="level_session").value = ['2004']
763        self.browser.getControl("Remove selected").click()
764        self.assertMatches('...Successfully removed...', self.browser.contents)
765        # Removing levels is properly logged
766        logfile = os.path.join(
767            self.app['datacenter'].storage, 'logs', 'students.log')
768        logcontent = open(logfile).read()
769        self.assertTrue('zope.mgr - students.browser.StudyCourseManageFormPage '
770                        '- K1000000 - removed: 100' in logcontent)
771        # Add level again
772        self.browser.getControl(name="addlevel").value = ['100']
773        self.browser.getControl(name="level_session").value = ['2004']
774        self.browser.getControl("Add study level").click()
775
776        # Managers can view and manage course lists
777        self.browser.getLink("100").click()
778        self.assertMatches(
779            '...: Study Level 100 (Year 1)...', self.browser.contents)
780        self.browser.getLink("Manage").click()
781        self.browser.getControl(name="form.level_session").value = ['2002']
782        self.browser.getControl("Save").click()
783        self.browser.getControl("Remove selected").click()
784        self.assertMatches('...No ticket selected...', self.browser.contents)
785        ctrl = self.browser.getControl(name='val_id')
786        ctrl.getControl(value='COURSE1').selected = True
787        self.browser.getControl("Remove selected", index=0).click()
788        self.assertTrue('Successfully removed' in self.browser.contents)
789        # Removing course tickets is properly logged
790        logfile = os.path.join(
791            self.app['datacenter'].storage, 'logs', 'students.log')
792        logcontent = open(logfile).read()
793        self.assertTrue('zope.mgr - students.browser.StudyLevelManageFormPage '
794        '- K1000000 - removed: COURSE1 at 100' in logcontent)
795        self.browser.getLink("here").click()
796        self.browser.getControl(name="form.course").value = ['COURSE1']
797        self.course.credits = 100
798        self.browser.getControl("Add course ticket").click()
799        self.assertMatches(
800            '...Total credits exceed 50...', self.browser.contents)
801        self.course.credits = 10
802        self.browser.getControl("Add course ticket").click()
803        self.assertTrue('Successfully added' in self.browser.contents)
804        # We can do the same by adding the course on the manage page directly
805        del self.student['studycourse']['100']['COURSE1']
806        self.browser.getControl(name="course").value = 'COURSE1'
807        self.browser.getControl("Add course ticket").click()
808        self.assertTrue('Successfully added' in self.browser.contents)
809        self.browser.getLink("here").click()
810        self.browser.getControl(name="form.course").value = ['COURSE1']
811        self.browser.getControl("Add course ticket").click()
812        self.assertTrue('The ticket exists' in self.browser.contents)
813        self.browser.getControl("Cancel").click()
814        self.browser.getLink("COURSE1").click()
815        self.browser.getLink("Manage").click()
816        self.browser.getControl("Save").click()
817        self.assertTrue('Form has been saved' in self.browser.contents)
818        # Grade and weight have been determined
819        self.browser.open(self.studycourse_path + '/100/COURSE1')
820        self.assertFalse('Grade' in self.browser.contents)
821        self.assertFalse('Weight' in self.browser.contents)
822        self.student['studycourse']['100']['COURSE1'].score = 55
823        self.browser.open(self.studycourse_path + '/100/COURSE1')
824        self.assertTrue('Grade' in self.browser.contents)
825        self.assertTrue('Weight' in self.browser.contents)
826        self.assertEqual(self.student['studycourse']['100']['COURSE1'].grade, 'C')
827        self.assertEqual(self.student['studycourse']['100']['COURSE1'].weight, 3)
828        # We add another ticket to check if GPA will be correctly calculated
829        # (and rounded)
830        courseticket = createObject('waeup.CourseTicket')
831        courseticket.code = 'ANYCODE'
832        courseticket.title = u'Any TITLE'
833        courseticket.credits = 13
834        courseticket.score = 66
835        courseticket.semester = 1
836        courseticket.dcode = u'ANYDCODE'
837        courseticket.fcode = u'ANYFCODE'
838        self.student['studycourse']['100']['COURSE2'] = courseticket
839        self.browser.open(self.student_path + '/studycourse/100')
840        # total credits
841        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[1], 23)
842        # weigheted credits = 3 * 10 + 4 * 13
843        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[2], 82.0)
844        # sgpa = 82 / 23
845        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.565)
846        # Carry-over courses will be collected when next level is created
847        self.browser.open(self.student_path + '/studycourse/manage')
848        # Add next level
849        self.student['studycourse']['100']['COURSE1'].score = 10
850        self.browser.getControl(name="addlevel").value = ['200']
851        self.browser.getControl(name="level_session").value = ['2005']
852        self.browser.getControl("Add study level").click()
853        self.browser.getLink("200").click()
854        self.assertMatches(
855            '...: Study Level 200 (Year 2)...', self.browser.contents)
856        # Since COURSE1 has score 10 it becomes a carry-over course
857        # in level 200
858        self.assertEqual(
859            sorted(self.student['studycourse']['200'].keys()), [u'COURSE1'])
860        self.assertTrue(
861            self.student['studycourse']['200']['COURSE1'].carry_over)
862        # Passed and failed courses have been counted
863        self.assertEqual(
864            self.student['studycourse']['100'].passed_params,
865            (1, 1, 13, 10, ['COURSE1']))
866        self.assertEqual(
867            self.student['studycourse']['200'].passed_params,
868            (0, 0, 0, 0, []))
869        # And also cumulative params can be calculated. Meanwhile we have the
870        # following courses: COURSE1 and COURSE2 in level 100 and
871        # COURSE1 as carry-over course in level 200.
872        self.assertEqual(
873            self.student['studycourse']['100'].cumulative_params,
874            (2.261, 23, 52.0, 23, 13))
875        # COURSE1 in level 200 is not taken into consideration
876        # when calculating the gpa.
877        self.assertEqual(
878            self.student['studycourse']['200'].cumulative_params,
879            (2.261, 23, 52.0, 33, 13))
880        return
881
882    def test_gpa_calculation_with_carryover(self):
883        studylevel = createObject(u'waeup.StudentStudyLevel')
884        studylevel.level = 100
885        studylevel.level_session = 2005
886        self.student['studycourse'].entry_mode = 'ug_ft'
887        self.student['studycourse'].addStudentStudyLevel(
888            self.certificate, studylevel)
889        # First course has been added automatically.
890        # Set score above passmark.
891        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark + 1
892        # GPA is 1.
893        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 1.0)
894        # Set score below passmark.
895        studylevel['COURSE1'].score = studylevel['COURSE1'].passmark - 1
896        # GPA is still 0.
897        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 0.0)
898        studylevel2 = createObject(u'waeup.StudentStudyLevel')
899        studylevel2.level = 200
900        studylevel2.level_session = 2006
901        self.student['studycourse'].addStudentStudyLevel(
902            self.certificate, studylevel2)
903        # Carry-over course has been autonatically added.
904        studylevel2['COURSE1'].score = 66
905        # The score of the carry-over course is now used for calculation of the
906        # GPA at level 100 ...
907        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 4.0)
908        # ... but not at level 200
909        self.assertEqual(self.student['studycourse']['200'].gpa_params_rectified[0], 0.0)
910        return
911
912    def test_manage_payments(self):
913        # Managers can add online school fee payment tickets
914        # if certain requirements are met
915        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
916        self.browser.open(self.payments_path)
917        IWorkflowState(self.student).setState('cleared')
918        self.browser.getLink("Add current session payment ticket").click()
919        self.browser.getControl(name="form.p_category").value = ['schoolfee']
920        self.browser.getControl("Create ticket").click()
921        self.assertMatches('...ticket created...',
922                           self.browser.contents)
923        ctrl = self.browser.getControl(name='val_id')
924        value = ctrl.options[0]
925        self.browser.getLink(value).click()
926        self.assertMatches('...Amount Authorized...',
927                           self.browser.contents)
928        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
929        payment_url = self.browser.url
930        logfile = os.path.join(
931            self.app['datacenter'].storage, 'logs', 'students.log')
932        logcontent = open(logfile).read()
933        self.assertTrue(
934            ' zope.mgr - students.browser.OnlinePaymentAddFormPage - '
935            'K1000000 - added: %s' % value
936            in logcontent)
937        # The pdf payment slip can't yet be opened
938        #self.browser.open(payment_url + '/payment_slip.pdf')
939        #self.assertMatches('...Ticket not yet paid...',
940        #                   self.browser.contents)
941
942        # The same payment (with same p_item, p_session and p_category)
943        # can be initialized a second time if the former ticket is not yet paid.
944        self.browser.open(self.payments_path)
945        self.browser.getLink("Add current session payment ticket").click()
946        self.browser.getControl(name="form.p_category").value = ['schoolfee']
947        self.browser.getControl("Create ticket").click()
948        self.assertMatches('...Payment ticket created...',
949                           self.browser.contents)
950
951        # The ticket can be found in the payments_catalog
952        cat = queryUtility(ICatalog, name='payments_catalog')
953        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
954        self.assertEqual(len(results), 2)
955        self.assertTrue(results[0] is self.student['payments'][value])
956        # Managers can approve the payment
957        # If, by some reason, the state has already changed,
958        # an access code is created after approval.
959        IWorkflowState(self.student).setState('school fee paid')
960        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
961        self.browser.open(payment_url)
962        self.browser.getLink("Approve payment").click()
963        self.assertMatches('...Payment approved...',
964                          self.browser.contents)
965        # Approval is logged in students.log ...
966        logcontent = open(logfile).read()
967        self.assertTrue(
968            'zope.mgr - students.browser.OnlinePaymentApproveView '
969            '- K1000000 - schoolfee payment approved'
970            in logcontent)
971        # ... and in payments.log
972        logfile = os.path.join(
973            self.app['datacenter'].storage, 'logs', 'payments.log')
974        logcontent = open(logfile).read()
975        self.assertTrue(
976            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
977            in logcontent)
978        # The authorized amount has been stored in the new access code
979        self.assertEqual(
980            self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
981
982        # The catalog has been updated
983        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
984        self.assertTrue(len(results), 0)
985        results = list(cat.searchResults(p_state=('paid', 'paid')))
986        self.assertTrue(len(results), 1)
987        self.assertTrue(results[0] is self.student['payments'][value])
988
989        # Payments can't be approved twice
990        self.browser.open(payment_url + '/approve')
991        self.assertMatches('...This ticket has already been paid...',
992                          self.browser.contents)
993
994        # Now the first ticket is paid and no more ticket of same type
995        # (with same p_item, p_session and p_category) can be added.
996        # First we have to reset the workflow state.
997        IWorkflowState(self.student).setState('cleared')
998        self.browser.open(self.payments_path)
999        self.browser.getLink("Add current session payment ticket").click()
1000        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1001        self.browser.getControl("Create ticket").click()
1002        self.assertMatches(
1003            '...This type of payment has already been made...',
1004            self.browser.contents)
1005
1006        # Managers can open the pdf payment slip
1007        self.browser.open(payment_url)
1008        self.browser.getLink("Download payment slip").click()
1009        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1010        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
1011
1012        # Managers can remove online school fee payment tickets
1013        self.browser.open(self.payments_path)
1014        self.browser.getControl("Remove selected").click()
1015        self.assertMatches('...No payment selected...', self.browser.contents)
1016        ctrl = self.browser.getControl(name='val_id')
1017        value = ctrl.options[0]
1018        ctrl.getControl(value=value).selected = True
1019        self.browser.getControl("Remove selected", index=0).click()
1020        self.assertTrue('Successfully removed' in self.browser.contents)
1021
1022        # Managers can add online clearance payment tickets
1023        self.browser.open(self.payments_path + '/addop')
1024        self.browser.getControl(name="form.p_category").value = ['clearance']
1025        self.browser.getControl("Create ticket").click()
1026        self.assertMatches('...ticket created...',
1027                           self.browser.contents)
1028
1029        # Managers can approve the payment
1030        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
1031        ctrl = self.browser.getControl(name='val_id')
1032        value = ctrl.options[1] # The clearance payment is the second in the table
1033        self.browser.getLink(value).click()
1034        self.browser.open(self.browser.url + '/approve')
1035        self.assertMatches('...Payment approved...',
1036                          self.browser.contents)
1037        expected = '''...
1038        <td>
1039          <span>Paid</span>
1040        </td>...'''
1041        self.assertMatches(expected,self.browser.contents)
1042        # The new CLR-0 pin has been created
1043        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
1044        pin = self.app['accesscodes']['CLR-0'].keys()[0]
1045        ac = self.app['accesscodes']['CLR-0'][pin]
1046        self.assertEqual(ac.owner, self.student_id)
1047        self.assertEqual(ac.cost, 3456.0)
1048
1049        # Managers can add online transcript payment tickets
1050        self.browser.open(self.payments_path + '/addop')
1051        self.browser.getControl(name="form.p_category").value = ['transcript']
1052        self.browser.getControl("Create ticket").click()
1053        self.assertMatches('...ticket created...',
1054                           self.browser.contents)
1055
1056        # Managers can approve the payment
1057        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
1058        ctrl = self.browser.getControl(name='val_id')
1059        value = ctrl.options[2] # The clearance payment is the third in the table
1060        self.browser.getLink(value).click()
1061        self.browser.open(self.browser.url + '/approve')
1062        self.assertMatches('...Payment approved...',
1063                          self.browser.contents)
1064        expected = '''...
1065        <td>
1066          <span>Paid</span>
1067        </td>...'''
1068        self.assertMatches(expected,self.browser.contents)
1069        # The new CLR-0 pin has been created
1070        self.assertEqual(len(self.app['accesscodes']['TSC-0']),1)
1071        pin = self.app['accesscodes']['TSC-0'].keys()[0]
1072        ac = self.app['accesscodes']['TSC-0'][pin]
1073        self.assertEqual(ac.owner, self.student_id)
1074        self.assertEqual(ac.cost, 4567.0)
1075        return
1076
1077    def test_add_transfer_payment(self):
1078        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1079        self.browser.open(self.payments_path)
1080        self.browser.getLink("Add current session payment ticket").click()
1081        self.browser.getControl(name="form.p_category").value = ['transfer']
1082        self.browser.getControl(name="new_programme").value = 'my new study course'
1083        self.browser.getControl("Create ticket").click()
1084        self.assertMatches('...ticket created...',
1085                           self.browser.contents)
1086        ctrl = self.browser.getControl(name='val_id')
1087        value = ctrl.options[0]
1088        self.browser.getLink(value).click()
1089        self.assertMatches('...my new study course...',
1090                           self.browser.contents)
1091        self.assertEqual(self.student['payments'][value].p_item, u'my new study course')
1092
1093    def test_manage_payments_bypass_ac_creation(self):
1094        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1095        self.browser.open(self.payments_path)
1096        IWorkflowState(self.student).setState('cleared')
1097        self.browser.getLink("Add current session payment ticket").click()
1098        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1099        self.browser.getControl("Create ticket").click()
1100        ctrl = self.browser.getControl(name='val_id')
1101        value = ctrl.options[0]
1102        self.browser.getLink(value).click()
1103        payment_url = self.browser.url
1104        logfile = os.path.join(
1105            self.app['datacenter'].storage, 'logs', 'students.log')
1106        # The ticket can be found in the payments_catalog
1107        cat = queryUtility(ICatalog, name='payments_catalog')
1108        results = list(cat.searchResults(p_state=('unpaid', 'unpaid')))
1109        self.assertTrue(len(results), 1)
1110        self.assertTrue(results[0] is self.student['payments'][value])
1111        # Managers can approve the payment
1112        self.browser.open(payment_url)
1113        self.browser.getLink("Approve payment").click()
1114        self.assertMatches('...Payment approved...',
1115                          self.browser.contents)
1116        # Approval is logged in students.log ...
1117        logcontent = open(logfile).read()
1118        self.assertTrue(
1119            'zope.mgr - students.browser.OnlinePaymentApproveView '
1120            '- K1000000 - schoolfee payment approved'
1121            in logcontent)
1122        # ... and in payments.log
1123        logfile = os.path.join(
1124            self.app['datacenter'].storage, 'logs', 'payments.log')
1125        logcontent = open(logfile).read()
1126        self.assertTrue(
1127            '"zope.mgr",K1000000,%s,schoolfee,40000.0,AP,,,,,,\n' % value
1128            in logcontent)
1129        # Student is in state school fee paid, no activation
1130        # code was necessary.
1131        self.assertEqual(self.student.state, 'school fee paid')
1132        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
1133        return
1134
1135    def test_payment_disabled(self):
1136        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1137        self.browser.open(self.payments_path)
1138        IWorkflowState(self.student).setState('cleared')
1139        self.browser.getLink("Add current session payment ticket").click()
1140        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1141        self.browser.getControl("Create ticket").click()
1142        self.assertMatches('...ticket created...',
1143                           self.browser.contents)
1144        self.app['configuration']['2004'].payment_disabled = ['sf_all']
1145        self.browser.getLink("Add current session payment ticket").click()
1146        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1147        self.browser.getControl("Create ticket").click()
1148        self.assertMatches('...Payment temporarily disabled...',
1149                           self.browser.contents)
1150        return
1151
1152    def test_manage_balance_payments(self):
1153
1154        # Login
1155        #self.browser.open(self.login_path)
1156        #self.browser.getControl(name="form.login").value = self.student_id
1157        #self.browser.getControl(name="form.password").value = 'spwd'
1158        #self.browser.getControl("Login").click()
1159
1160        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1161        self.browser.open(self.payments_path)
1162
1163        # Managers can add previous school fee payment tickets in any state.
1164        IWorkflowState(self.student).setState('courses registered')
1165        self.browser.open(self.payments_path)
1166        self.browser.getLink("Add balance payment ticket").click()
1167
1168        # Previous session payment form is provided
1169        self.assertEqual(self.student.current_session, 2004)
1170        self.browser.getControl(name="form.p_category").value = ['schoolfee']
1171        self.browser.getControl(name="form.balance_session").value = ['2004']
1172        self.browser.getControl(name="form.balance_level").value = ['300']
1173        self.browser.getControl(name="form.balance_amount").value = '-567.8'
1174        self.browser.getControl("Create ticket").click()
1175        self.assertMatches('...Amount must be greater than 0...',
1176                           self.browser.contents)
1177        self.browser.getControl(name="form.balance_amount").value = '0'
1178        self.browser.getControl("Create ticket").click()
1179        self.assertMatches('...Amount must be greater than 0...',
1180                           self.browser.contents)
1181        self.browser.getControl(name="form.balance_amount").value = '567.8'
1182        self.browser.getControl("Create ticket").click()
1183        self.assertMatches('...ticket created...',
1184                           self.browser.contents)
1185        ctrl = self.browser.getControl(name='val_id')
1186        value = ctrl.options[0]
1187        self.browser.getLink(value).click()
1188        self.assertMatches('...Amount Authorized...',
1189                           self.browser.contents)
1190        self.assertEqual(self.student['payments'][value].amount_auth, 567.8)
1191        # Payment attributes are properly set
1192        self.assertEqual(self.student['payments'][value].p_session, 2004)
1193        self.assertEqual(self.student['payments'][value].p_level, 300)
1194        self.assertEqual(self.student['payments'][value].p_item, u'Balance')
1195        self.assertEqual(self.student['payments'][value].p_category, 'schoolfee')
1196        # Adding payment tickets is logged.
1197        logfile = os.path.join(
1198            self.app['datacenter'].storage, 'logs', 'students.log')
1199        logcontent = open(logfile).read()
1200        self.assertTrue('zope.mgr - students.browser.BalancePaymentAddFormPage '
1201                        '- K1000000 - added: %s' % value in logcontent)
1202
1203    def test_manage_accommodation(self):
1204        logfile = os.path.join(
1205            self.app['datacenter'].storage, 'logs', 'students.log')
1206        # Managers can add online booking fee payment tickets and open the
1207        # callback view (see test_manage_payments)
1208        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1209        self.browser.open(self.payments_path)
1210        self.browser.getLink("Add current session payment ticket").click()
1211        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1212        # If student is not in accommodation session, payment cannot be processed
1213        self.app['hostels'].accommodation_session = 2011
1214        self.browser.getControl("Create ticket").click()
1215        self.assertMatches('...Your current session does not match...',
1216                           self.browser.contents)
1217        self.app['hostels'].accommodation_session = 2004
1218        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
1219        self.browser.getControl("Create ticket").click()
1220        ctrl = self.browser.getControl(name='val_id')
1221        value = ctrl.options[0]
1222        self.browser.getLink(value).click()
1223        self.browser.open(self.browser.url + '/approve')
1224        # The new HOS-0 pin has been created
1225        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
1226        pin = self.app['accesscodes']['HOS-0'].keys()[0]
1227        ac = self.app['accesscodes']['HOS-0'][pin]
1228        self.assertEqual(ac.owner, self.student_id)
1229        parts = pin.split('-')[1:]
1230        sfeseries, sfenumber = parts
1231        # Managers can use HOS code and book a bed space with it
1232        self.browser.open(self.acco_path)
1233        self.browser.getControl("Book accommodation").click()
1234        self.assertMatches('...You are in the wrong...',
1235                           self.browser.contents)
1236        IWorkflowInfo(self.student).fireTransition('admit')
1237        # An existing HOS code can only be used if students
1238        # are in accommodation session
1239        self.student['studycourse'].current_session = 2003
1240        self.browser.getControl("Book accommodation").click()
1241        self.assertMatches('...Your current session does not match...',
1242                           self.browser.contents)
1243        self.student['studycourse'].current_session = 2004
1244        # All requirements are met and ticket can be created
1245        self.browser.getControl("Book accommodation").click()
1246        self.assertMatches('...Activation Code:...',
1247                           self.browser.contents)
1248        self.browser.getControl(name="ac_series").value = sfeseries
1249        self.browser.getControl(name="ac_number").value = sfenumber
1250        self.browser.getControl("Create bed ticket").click()
1251        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1252                           self.browser.contents)
1253        # Bed has been allocated
1254        bed1 = self.app['hostels']['hall-1']['hall-1_A_101_A']
1255        self.assertTrue(bed1.owner == self.student_id)
1256        # BedTicketAddPage is now blocked
1257        self.browser.getControl("Book accommodation").click()
1258        self.assertMatches('...You already booked a bed space...',
1259            self.browser.contents)
1260        # The bed ticket displays the data correctly
1261        self.browser.open(self.acco_path + '/2004')
1262        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
1263                           self.browser.contents)
1264        self.assertMatches('...2004/2005...', self.browser.contents)
1265        self.assertMatches('...regular_male_fr...', self.browser.contents)
1266        self.assertMatches('...%s...' % pin, self.browser.contents)
1267        # Booking is properly logged
1268        logcontent = open(logfile).read()
1269        self.assertTrue('zope.mgr - students.browser.BedTicketAddPage '
1270            '- K1000000 - booked: hall-1_A_101_A' in logcontent)
1271        # Managers can relocate students if the student's bed_type has changed
1272        self.browser.getLink("Relocate student").click()
1273        self.assertMatches(
1274            "...Student can't be relocated...", self.browser.contents)
1275        self.student.sex = u'f'
1276        self.browser.getLink("Relocate student").click()
1277        self.assertMatches(
1278            "...Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1279        self.assertTrue(bed1.owner == NOT_OCCUPIED)
1280        bed2 = self.app['hostels']['hall-1']['hall-1_A_101_B']
1281        self.assertTrue(bed2.owner == self.student_id)
1282        self.assertTrue(self.student['accommodation'][
1283            '2004'].bed_type == u'regular_female_fr')
1284        # Relocation is properly logged
1285        logcontent = open(logfile).read()
1286        self.assertTrue('zope.mgr - students.accommodation.BedTicket '
1287            '- K1000000 - relocated: hall-1_A_101_B' in logcontent)
1288        # The payment object still shows the original payment item
1289        payment_id = self.student['payments'].keys()[0]
1290        payment = self.student['payments'][payment_id]
1291        self.assertTrue(payment.p_item == u'regular_male_fr')
1292        # Managers can relocate students if the bed's bed_type has changed
1293        bed1.bed_type = u'regular_female_fr'
1294        bed2.bed_type = u'regular_male_fr'
1295        notify(grok.ObjectModifiedEvent(bed1))
1296        notify(grok.ObjectModifiedEvent(bed2))
1297        self.browser.getLink("Relocate student").click()
1298        self.assertMatches(
1299            "...Student relocated...", self.browser.contents)
1300        self.assertMatches(
1301            "... Hall 1, Block A, Room 101, Bed A...", self.browser.contents)
1302        self.assertMatches(bed1.owner, self.student_id)
1303        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1304        # Managers can't relocate students if bed is reserved
1305        self.student.sex = u'm'
1306        bed1.bed_type = u'regular_female_reserved'
1307        notify(grok.ObjectModifiedEvent(bed1))
1308        self.browser.getLink("Relocate student").click()
1309        self.assertMatches(
1310            "...Students in reserved beds can't be relocated...",
1311            self.browser.contents)
1312        # Managers can relocate students if booking has been cancelled but
1313        # other bed space has been manually allocated after cancellation
1314        old_owner = bed1.releaseBed()
1315        self.assertMatches(old_owner, self.student_id)
1316        bed2.owner = self.student_id
1317        self.browser.open(self.acco_path + '/2004')
1318        self.assertMatches(
1319            "...booking cancelled...", self.browser.contents)
1320        self.browser.getLink("Relocate student").click()
1321        # We didn't informed the catalog therefore the new owner is not found
1322        self.assertMatches(
1323            "...There is no free bed in your category regular_male_fr...",
1324            self.browser.contents)
1325        # Now we fire the event properly
1326        notify(grok.ObjectModifiedEvent(bed2))
1327        self.browser.getLink("Relocate student").click()
1328        self.assertMatches(
1329            "...Student relocated...", self.browser.contents)
1330        self.assertMatches(
1331            "... Hall 1, Block A, Room 101, Bed B...", self.browser.contents)
1332          # Managers can delete bed tickets
1333        self.browser.open(self.acco_path)
1334        ctrl = self.browser.getControl(name='val_id')
1335        value = ctrl.options[0]
1336        ctrl.getControl(value=value).selected = True
1337        self.browser.getControl("Remove selected", index=0).click()
1338        self.assertMatches('...Successfully removed...', self.browser.contents)
1339        # The bed has been properly released by the event handler
1340        self.assertMatches(bed1.owner, NOT_OCCUPIED)
1341        self.assertMatches(bed2.owner, NOT_OCCUPIED)
1342        return
1343
1344    def test_manage_workflow(self):
1345        # Managers can pass through the whole workflow
1346        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1347        student = self.app['students'][self.student_id]
1348        self.browser.open(self.trigtrans_path)
1349        self.assertTrue(student.clearance_locked)
1350        self.browser.getControl(name="transition").value = ['admit']
1351        self.browser.getControl("Save").click()
1352        self.assertTrue(student.clearance_locked)
1353        self.browser.getControl(name="transition").value = ['start_clearance']
1354        self.browser.getControl("Save").click()
1355        self.assertFalse(student.clearance_locked)
1356        self.browser.getControl(name="transition").value = ['request_clearance']
1357        self.browser.getControl("Save").click()
1358        self.assertTrue(student.clearance_locked)
1359        self.browser.getControl(name="transition").value = ['clear']
1360        self.browser.getControl("Save").click()
1361        # Managers approve payment, they do not pay
1362        self.assertFalse('pay_first_school_fee' in self.browser.contents)
1363        self.browser.getControl(
1364            name="transition").value = ['approve_first_school_fee']
1365        self.browser.getControl("Save").click()
1366        self.browser.getControl(name="transition").value = ['reset6']
1367        self.browser.getControl("Save").click()
1368        # In state returning the pay_school_fee transition triggers some
1369        # changes of attributes
1370        self.browser.getControl(name="transition").value = ['approve_school_fee']
1371        self.browser.getControl("Save").click()
1372        self.assertEqual(student['studycourse'].current_session, 2005) # +1
1373        self.assertEqual(student['studycourse'].current_level, 200) # +100
1374        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
1375        self.assertEqual(student['studycourse'].previous_verdict, 'A')
1376        self.browser.getControl(name="transition").value = ['register_courses']
1377        self.browser.getControl("Save").click()
1378        self.browser.getControl(name="transition").value = ['validate_courses']
1379        self.browser.getControl("Save").click()
1380        self.browser.getControl(name="transition").value = ['return']
1381        self.browser.getControl("Save").click()
1382        return
1383
1384    def test_manage_pg_workflow(self):
1385        # Managers can pass through the whole workflow
1386        IWorkflowState(self.student).setState('school fee paid')
1387        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1388        student = self.app['students'][self.student_id]
1389        self.browser.open(self.trigtrans_path)
1390        self.assertTrue('<option value="reset6">' in self.browser.contents)
1391        self.assertTrue('<option value="register_courses">' in self.browser.contents)
1392        self.assertTrue('<option value="reset5">' in self.browser.contents)
1393        self.certificate.study_mode = 'pg_ft'
1394        self.browser.open(self.trigtrans_path)
1395        self.assertFalse('<option value="reset6">' in self.browser.contents)
1396        self.assertFalse('<option value="register_courses">' in self.browser.contents)
1397        self.assertTrue('<option value="reset5">' in self.browser.contents)
1398        return
1399
1400    def test_manage_import(self):
1401        # Managers can import student data files
1402        datacenter_path = 'http://localhost/app/datacenter'
1403        # Prepare a csv file for students
1404        open('students.csv', 'wb').write(
1405"""firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex,password
1406Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,m,mypwd1
1407Claus,Finau,2,1990-01-03,100001,aa@aa.ng,1234,m,mypwd1
1408Brit,Berson,3,1990-01-04,100001,aa@aa.ng,1234,m,mypwd1
1409""")
1410        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1411        self.browser.open(datacenter_path)
1412        self.browser.getLink('Upload data').click()
1413        filecontents = StringIO(open('students.csv', 'rb').read())
1414        filewidget = self.browser.getControl(name='uploadfile:file')
1415        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
1416        self.browser.getControl(name='SUBMIT').click()
1417        self.browser.getLink('Process data').click()
1418        self.browser.getLink("Switch maintenance mode").click()
1419        button = lookup_submit_value(
1420            'select', 'students_zope.mgr.csv', self.browser)
1421        button.click()
1422        importerselect = self.browser.getControl(name='importer')
1423        modeselect = self.browser.getControl(name='mode')
1424        importerselect.getControl('Student Processor').selected = True
1425        modeselect.getControl(value='create').selected = True
1426        self.browser.getControl('Proceed to step 3').click()
1427        self.assertTrue('Header fields OK' in self.browser.contents)
1428        self.browser.getControl('Perform import').click()
1429        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1430        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1431        self.assertTrue('Batch processing finished' in self.browser.contents)
1432        open('studycourses.csv', 'wb').write(
1433"""reg_number,matric_number,certificate,current_session,current_level
14341,,CERT1,2008,100
1435,100001,CERT1,2008,100
1436,100002,CERT1,2008,100
1437""")
1438        self.browser.open(datacenter_path)
1439        self.browser.getLink('Upload data').click()
1440        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1441        filewidget = self.browser.getControl(name='uploadfile:file')
1442        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1443        self.browser.getControl(name='SUBMIT').click()
1444        self.browser.getLink('Process data').click()
1445        # Meanwhile maintenance mode is disabled again.
1446        self.browser.getLink("Switch maintenance mode").click()
1447        button = lookup_submit_value(
1448            'select', 'studycourses_zope.mgr.csv', self.browser)
1449        button.click()
1450        importerselect = self.browser.getControl(name='importer')
1451        modeselect = self.browser.getControl(name='mode')
1452        importerselect.getControl(
1453            'StudentStudyCourse Processor (update only)').selected = True
1454        modeselect.getControl(value='create').selected = True
1455        self.browser.getControl('Proceed to step 3').click()
1456        self.assertTrue('Update mode only' in self.browser.contents)
1457        self.browser.getControl('Proceed to step 3').click()
1458        self.assertTrue('Header fields OK' in self.browser.contents)
1459        self.browser.getControl('Perform import').click()
1460        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1461        self.assertTrue('Successfully processed 2 rows'
1462                        in self.browser.contents)
1463        # The students are properly indexed and we can
1464        # thus find a student in  the department
1465        self.browser.open(self.manage_container_path)
1466        self.browser.getControl(name="searchtype").value = ['depcode']
1467        self.browser.getControl(name="searchterm").value = 'dep1'
1468        self.browser.getControl("Find student(s)").click()
1469        self.assertTrue('Aaren Pieri' in self.browser.contents)
1470        # We can search for a new student by name ...
1471        self.browser.getControl(name="searchtype").value = ['fullname']
1472        self.browser.getControl(name="searchterm").value = 'Claus'
1473        self.browser.getControl("Find student(s)").click()
1474        self.assertTrue('Claus Finau' in self.browser.contents)
1475        # ... and check if the imported password has been properly set
1476        ctrl = self.browser.getControl(name='entries')
1477        value = ctrl.options[0]
1478        claus = self.app['students'][value]
1479        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1480        return
1481
1482    def init_clearance_officer(self):
1483        # Create clearance officer
1484        self.app['users'].addUser('mrclear', 'mrclearsecret')
1485        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1486        self.app['users']['mrclear'].title = 'Carlo Pitter'
1487        # Clearance officers need not necessarily to get
1488        # the StudentsOfficer site role
1489        #prmglobal = IPrincipalRoleManager(self.app)
1490        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1491        # Assign local ClearanceOfficer role
1492        self.department = self.app['faculties']['fac1']['dep1']
1493        prmlocal = IPrincipalRoleManager(self.department)
1494        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1495        IWorkflowState(self.student).setState('clearance started')
1496        # Add another student for testing
1497        other_student = Student()
1498        other_student.firstname = u'Dep2'
1499        other_student.lastname = u'Student'
1500        self.app['students'].addStudent(other_student)
1501        self.other_student_path = (
1502            'http://localhost/app/students/%s' % other_student.student_id)
1503        # Login as clearance officer
1504        self.browser.open(self.login_path)
1505        self.browser.getControl(name="form.login").value = 'mrclear'
1506        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1507        self.browser.getControl("Login").click()
1508
1509    def test_handle_clearance_by_co(self):
1510        self.init_clearance_officer()
1511        self.assertMatches('...You logged in...', self.browser.contents)
1512        # CO is landing on index page.
1513        self.assertEqual(self.browser.url, 'http://localhost/app/index')
1514        # CO can see his roles
1515        self.browser.getLink("My Roles").click()
1516        self.assertMatches(
1517            '...<div>Academics Officer (view only)</div>...',
1518            self.browser.contents)
1519        # But not his local role ...
1520        self.assertFalse('Clearance Officer' in self.browser.contents)
1521        # ... because we forgot to notify the department that the local role
1522        # has changed.
1523        notify(LocalRoleSetEvent(
1524            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1525            granted=True))
1526        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1527        self.assertTrue('Clearance Officer' in self.browser.contents)
1528        self.assertMatches(
1529            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1530            self.browser.contents)
1531        # CO can view the student ...
1532        self.browser.open(self.clearance_path)
1533        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1534        self.assertEqual(self.browser.url, self.clearance_path)
1535        # ... but not other students.
1536        self.assertRaises(
1537            Unauthorized, self.browser.open, self.other_student_path)
1538        # Clearance is disabled for this session.
1539        self.browser.open(self.clearance_path)
1540        self.assertFalse('Clear student' in self.browser.contents)
1541        self.browser.open(self.student_path + '/clear')
1542        self.assertTrue('Clearance is disabled for this session'
1543            in self.browser.contents)
1544        self.app['configuration']['2004'].clearance_enabled = True
1545        # Only in state clearance requested the CO does see the 'Clear' button.
1546        self.browser.open(self.clearance_path)
1547        self.assertFalse('Clear student' in self.browser.contents)
1548        self.browser.open(self.student_path + '/clear')
1549        self.assertTrue('Student is in wrong state.'
1550            in self.browser.contents)
1551        IWorkflowInfo(self.student).fireTransition('request_clearance')
1552        self.browser.open(self.clearance_path)
1553        self.assertTrue('Clear student' in self.browser.contents)
1554        self.browser.getLink("Clear student").click()
1555        self.assertTrue('Student has been cleared' in self.browser.contents)
1556        self.assertTrue('cleared' in self.browser.contents)
1557        self.browser.open(self.history_path)
1558        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1559        # Hide real name.
1560        self.app['users']['mrclear'].public_name = 'My Public Name'
1561        self.browser.open(self.clearance_path)
1562        self.browser.getLink("Reject clearance").click()
1563        self.assertEqual(
1564            self.browser.url, self.student_path + '/reject_clearance')
1565        # Type comment why.
1566        self.browser.getControl(name="form.officer_comment").value = (
1567            'Dear Student,\n'
1568            'You did not fill properly.')
1569        self.browser.getControl("Save comment").click()
1570        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1571        url = ('http://localhost/app/students/K1000000/'
1572              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1573              '&subject=Clearance+has+been+annulled.')
1574        # CO does now see the prefilled contact form and can send a message.
1575        self.assertEqual(self.browser.url, url)
1576        self.assertTrue('clearance started' in self.browser.contents)
1577        self.assertTrue('name="form.subject" size="20" type="text" '
1578            'value="Clearance has been annulled."'
1579            in self.browser.contents)
1580        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1581            in self.browser.contents)
1582        self.browser.getControl("Send message now").click()
1583        self.assertTrue('Your message has been sent' in self.browser.contents)
1584        # The comment has been stored ...
1585        self.assertEqual(self.student.officer_comment,
1586            u'Dear Student,\nYou did not fill properly.')
1587        # ... and logged.
1588        logfile = os.path.join(
1589            self.app['datacenter'].storage, 'logs', 'students.log')
1590        logcontent = open(logfile).read()
1591        self.assertTrue(
1592            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1593            'K1000000 - comment: Dear Student,<br>You did not fill '
1594            'properly.\n' in logcontent)
1595        self.browser.open(self.history_path)
1596        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1597            self.browser.contents)
1598        IWorkflowInfo(self.student).fireTransition('request_clearance')
1599        self.browser.open(self.clearance_path)
1600        self.browser.getLink("Reject clearance").click()
1601        self.browser.getControl("Save comment").click()
1602        self.assertTrue('Clearance request has been rejected'
1603            in self.browser.contents)
1604        self.assertTrue('clearance started' in self.browser.contents)
1605        # The CO can't clear students if not in state
1606        # clearance requested.
1607        self.browser.open(self.student_path + '/clear')
1608        self.assertTrue('Student is in wrong state'
1609            in self.browser.contents)
1610        # The CO can go to his department throug the my_roles page ...
1611        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1612        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1613        # ... and view the list of students.
1614        self.browser.getLink("Show students").click()
1615        self.browser.getControl(name="session").value = ['2004']
1616        self.browser.getControl(name="level").value = ['200']
1617        self.browser.getControl("Show").click()
1618        self.assertFalse(self.student_id in self.browser.contents)
1619        self.browser.getControl(name="session").value = ['2004']
1620        self.browser.getControl(name="level").value = ['100']
1621        self.browser.getControl("Show").click()
1622        self.assertTrue(self.student_id in self.browser.contents)
1623        # The comment is indicated by 'yes'.
1624        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1625        # Check if the enquiries form is not pre-filled with officer_comment
1626        # (regression test).
1627        self.browser.getLink("Logout").click()
1628        self.browser.open('http://localhost/app/enquiries')
1629        self.assertFalse(
1630            'You did not fill properly'
1631            in self.browser.contents)
1632        # When a student is cleared the comment is automatically deleted
1633        IWorkflowInfo(self.student).fireTransition('request_clearance')
1634        IWorkflowInfo(self.student).fireTransition('clear')
1635        self.assertEqual(self.student.officer_comment, None)
1636        return
1637
1638    def test_handle_mass_clearance_by_co(self):
1639        self.init_clearance_officer()
1640        # Additional setups according to test above
1641        notify(LocalRoleSetEvent(
1642            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1643            granted=True))
1644        self.app['configuration']['2004'].clearance_enabled = True
1645        IWorkflowState(self.student).setState('clearance requested')
1646        # Update the catalog
1647        notify(grok.ObjectModifiedEvent(self.student))
1648        # The CO can go to the department and clear all students in department
1649        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1650        self.browser.getLink("Clear all students").click()
1651        self.assertTrue('1 students have been cleared' in self.browser.contents)
1652        self.browser.open(self.history_path)
1653        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1654        logfile = os.path.join(
1655            self.app['datacenter'].storage, 'logs', 'students.log')
1656        logcontent = open(logfile).read()
1657        self.assertTrue(
1658            'INFO - mrclear - K1000000 - Cleared' in logcontent)
1659        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1660        self.browser.getLink("Clear all students").click()
1661        self.assertTrue('0 students have been cleared' in self.browser.contents)
1662        return
1663
1664    def test_handle_courses_by_ca(self):
1665        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1666        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1667        self.app['users']['mrsadvise'].title = u'Helen Procter'
1668        # Assign local CourseAdviser100 role for a certificate
1669        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1670        prmlocal = IPrincipalRoleManager(cert)
1671        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1672        IWorkflowState(self.student).setState('school fee paid')
1673        # Login as course adviser.
1674        self.browser.open(self.login_path)
1675        self.browser.getControl(name="form.login").value = 'mrsadvise'
1676        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1677        self.browser.getControl("Login").click()
1678        self.assertMatches('...You logged in...', self.browser.contents)
1679        # CO can see his roles.
1680        self.browser.getLink("My Roles").click()
1681        self.assertMatches(
1682            '...<div>Academics Officer (view only)</div>...',
1683            self.browser.contents)
1684        # But not his local role ...
1685        self.assertFalse('Course Adviser' in self.browser.contents)
1686        # ... because we forgot to notify the certificate that the local role
1687        # has changed.
1688        notify(LocalRoleSetEvent(
1689            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1690        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1691        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1692        self.assertMatches(
1693            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1694            self.browser.contents)
1695        # CA can view the student ...
1696        self.browser.open(self.student_path)
1697        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1698        self.assertEqual(self.browser.url, self.student_path)
1699        # ... but not other students.
1700        other_student = Student()
1701        other_student.firstname = u'Dep2'
1702        other_student.lastname = u'Student'
1703        self.app['students'].addStudent(other_student)
1704        other_student_path = (
1705            'http://localhost/app/students/%s' % other_student.student_id)
1706        self.assertRaises(
1707            Unauthorized, self.browser.open, other_student_path)
1708        # We add study level 110 to the student's studycourse.
1709        studylevel = StudentStudyLevel()
1710        studylevel.level = 110
1711        self.student['studycourse'].addStudentStudyLevel(
1712            cert,studylevel)
1713        L110_student_path = self.studycourse_path + '/110'
1714        # The CA can neither see the Validate nor the Edit button.
1715        self.browser.open(L110_student_path)
1716        self.assertFalse('Validate courses' in self.browser.contents)
1717        self.assertFalse('Edit' in self.browser.contents)
1718        IWorkflowInfo(self.student).fireTransition('register_courses')
1719        self.browser.open(L110_student_path)
1720        self.assertFalse('Validate courses' in self.browser.contents)
1721        self.assertFalse('Edit' in self.browser.contents)
1722        # Only in state courses registered and only if the current level
1723        # corresponds with the name of the study level object
1724        # the 100L CA does see the 'Validate' button but not
1725        # the edit button.
1726        self.student['studycourse'].current_level = 110
1727        self.browser.open(L110_student_path)
1728        self.assertFalse('Edit' in self.browser.contents)
1729        self.assertTrue('Validate courses' in self.browser.contents)
1730        # But a 100L CA does not see the button at other levels.
1731        studylevel2 = StudentStudyLevel()
1732        studylevel2.level = 200
1733        self.student['studycourse'].addStudentStudyLevel(
1734            cert,studylevel2)
1735        L200_student_path = self.studycourse_path + '/200'
1736        self.browser.open(L200_student_path)
1737        self.assertFalse('Edit' in self.browser.contents)
1738        self.assertFalse('Validate courses' in self.browser.contents)
1739        self.browser.open(L110_student_path)
1740        self.browser.getLink("Validate courses").click()
1741        self.assertTrue('Course list has been validated' in self.browser.contents)
1742        self.assertTrue('courses validated' in self.browser.contents)
1743        self.assertEqual(self.student['studycourse']['110'].validated_by,
1744            'Helen Procter')
1745        self.assertMatches(
1746            '<YYYY-MM-DD hh:mm:ss>',
1747            self.student['studycourse']['110'].validation_date.strftime(
1748                "%Y-%m-%d %H:%M:%S"))
1749        self.browser.getLink("Reject courses").click()
1750        self.assertTrue('Course list request has been annulled.'
1751            in self.browser.contents)
1752        urlmessage = 'Course+list+request+has+been+annulled.'
1753        self.assertEqual(self.browser.url, self.student_path +
1754            '/contactstudent?subject=%s' % urlmessage)
1755        self.assertTrue('school fee paid' in self.browser.contents)
1756        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1757        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1758        IWorkflowInfo(self.student).fireTransition('register_courses')
1759        self.browser.open(L110_student_path)
1760        self.browser.getLink("Reject courses").click()
1761        self.assertTrue('Course list has been unregistered'
1762            in self.browser.contents)
1763        self.assertTrue('school fee paid' in self.browser.contents)
1764        # CA does now see the contact form and can send a message.
1765        self.browser.getControl(name="form.subject").value = 'Important subject'
1766        self.browser.getControl(name="form.body").value = 'Course list rejected'
1767        self.browser.getControl("Send message now").click()
1768        self.assertTrue('Your message has been sent' in self.browser.contents)
1769        # The CA does now see the Edit button and can edit
1770        # current study level.
1771        self.browser.open(L110_student_path)
1772        self.browser.getLink("Edit").click()
1773        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1774            in self.browser.contents)
1775        # The CA can't validate courses if not in state
1776        # courses registered.
1777        self.browser.open(L110_student_path + '/validate_courses')
1778        self.assertTrue('Student is in the wrong state'
1779            in self.browser.contents)
1780        # The CA can go to his certificate through the my_roles page ...
1781        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1782        self.browser.getLink(
1783            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1784        # ... and view the list of students.
1785        self.browser.getLink("Show students").click()
1786        self.browser.getControl(name="session").value = ['2004']
1787        self.browser.getControl(name="level").value = ['100']
1788        self.browser.getControl("Show").click()
1789        self.assertTrue(self.student_id in self.browser.contents)
1790
1791    def test_handle_courses_by_lecturer(self):
1792        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1793        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1794        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1795        # Add course ticket
1796        studylevel = createObject(u'waeup.StudentStudyLevel')
1797        studylevel.level = 100
1798        studylevel.level_session = 2004
1799        self.student['studycourse'].addStudentStudyLevel(
1800            self.certificate, studylevel)
1801        # Assign local Lecturer role for a certificate.
1802        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1803        prmlocal = IPrincipalRoleManager(course)
1804        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1805        # Login as lecturer.
1806        self.browser.open(self.login_path)
1807        self.browser.getControl(name="form.login").value = 'mrslecturer'
1808        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1809        self.browser.getControl("Login").click()
1810        self.assertMatches('...You logged in...', self.browser.contents)
1811        # CO can see her roles.
1812        self.browser.getLink("My Roles").click()
1813        self.assertMatches(
1814            '...<div>Academics Officer (view only)</div>...',
1815            self.browser.contents)
1816        # But not her local role ...
1817        self.assertFalse('Lecturer' in self.browser.contents)
1818        # ... because we forgot to notify the course that the local role
1819        # has changed.
1820        notify(LocalRoleSetEvent(
1821            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1822        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1823        self.assertTrue('Lecturer' in self.browser.contents)
1824        self.assertMatches(
1825            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1826            self.browser.contents)
1827        # The lecturer can go to her course ...
1828        self.browser.getLink(
1829            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1830        # Lecturers can neither filter students ...
1831        self.assertRaises(
1832            Unauthorized, self.browser.open,
1833            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
1834        # ... nor access the student ...
1835        self.assertRaises(
1836            Unauthorized, self.browser.open, self.student_path)
1837        # ... nor the respective course ticket since
1838        # editing course tickets by lecturers is not feasible.
1839        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1840        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1841        self.assertRaises(
1842            Unauthorized, self.browser.open, course_ticket_path)
1843        # Course results can be batch edited via the edit_courses view.
1844        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
1845        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
1846        self.browser.getLink("Update scores").click()
1847        self.assertTrue('Score editing disabled' in self.browser.contents)
1848        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
1849        self.browser.getLink("Update scores").click()
1850        self.assertTrue('Current academic session not set' in self.browser.contents)
1851        self.app['configuration'].current_academic_session = 2004
1852        self.browser.getLink("Update scores").click()
1853        self.assertFalse(
1854            '<input type="text" name="scores" class="span1" />'
1855            in self.browser.contents)
1856        IWorkflowState(self.student).setState('courses validated')
1857        # Student must be in state 'courses validated'.
1858        self.browser.open(
1859            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1860        self.assertTrue(
1861            '<input type="text" name="scores" class="span1" />'
1862            in self.browser.contents)
1863        self.browser.getControl(name="scores", index=0).value = '55'
1864        self.browser.getControl("Update scores").click()
1865        # New score has been set.
1866        self.assertEqual(
1867            self.student['studycourse']['100']['COURSE1'].score, 55)
1868        # Score editing has been logged.
1869        logfile = os.path.join(
1870            self.app['datacenter'].storage, 'logs', 'students.log')
1871        logcontent = open(logfile).read()
1872        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1873                        'K1000000 100/COURSE1 score updated (55)' in logcontent)
1874        # Non-integer scores won't be accepted.
1875        self.browser.open(
1876            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1877        self.assertTrue('value="55" />' in self.browser.contents)
1878        self.browser.getControl(name="scores", index=0).value = 'abc'
1879        self.browser.getControl("Update scores").click()
1880        self.assertTrue('Error: Score(s) of Anna Tester have not be updated'
1881            in self.browser.contents)
1882        # Scores can be removed.
1883        self.browser.open(
1884            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1885        self.browser.getControl(name="scores", index=0).value = ''
1886        self.browser.getControl("Update scores").click()
1887        self.assertEqual(
1888            self.student['studycourse']['100']['COURSE1'].score, None)
1889        logcontent = open(logfile).read()
1890        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1891                        'K1000000 100/COURSE1 score updated (None)' in logcontent)
1892
1893    def test_change_current_mode(self):
1894        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1895        self.browser.open(self.clearance_path)
1896        self.assertFalse('Employer' in self.browser.contents)
1897        self.browser.open(self.manage_clearance_path)
1898        self.assertFalse('Employer' in self.browser.contents)
1899        self.browser.open(self.edit_clearance_path)
1900        self.assertFalse('Employer' in self.browser.contents)
1901        # Now we change the study mode of the certificate and a different
1902        # interface is used by clearance views.
1903        self.certificate.study_mode = 'pg_ft'
1904        # Invariants are not being checked here?!
1905        self.certificate.end_level = 100
1906        self.browser.open(self.clearance_path)
1907        self.assertTrue('Employer' in self.browser.contents)
1908        self.browser.open(self.manage_clearance_path)
1909        self.assertTrue('Employer' in self.browser.contents)
1910        IWorkflowState(self.student).setState('clearance started')
1911        self.browser.open(self.edit_clearance_path)
1912        self.assertTrue('Employer' in self.browser.contents)
1913
1914    def test_find_students_in_faculties(self):
1915        # Create local students manager in faculty
1916        self.app['users'].addUser('mrmanager', 'mrmanagersecret')
1917        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1918        self.app['users']['mrmanager'].title = u'Volk Wagen'
1919        # Assign LocalStudentsManager role for faculty
1920        fac = self.app['faculties']['fac1']
1921        prmlocal = IPrincipalRoleManager(fac)
1922        prmlocal.assignRoleToPrincipal(
1923            'waeup.local.LocalStudentsManager', 'mrmanager')
1924        notify(LocalRoleSetEvent(
1925            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1926            granted=True))
1927        # Login as manager
1928        self.browser.open(self.login_path)
1929        self.browser.getControl(name="form.login").value = 'mrmanager'
1930        self.browser.getControl(name="form.password").value = 'mrmanagersecret'
1931        self.browser.getControl("Login").click()
1932        self.assertMatches('...You logged in...', self.browser.contents)
1933        # Manager can see his roles
1934        self.browser.getLink("My Roles").click()
1935        self.assertMatches(
1936            '...<span>Students Manager</span>...',
1937            self.browser.contents)
1938        # The manager can go to his faculty
1939        self.browser.getLink(
1940            "http://localhost/app/faculties/fac1").click()
1941        # and find students
1942        self.browser.getLink("Find students").click()
1943        self.browser.getControl("Find student").click()
1944        self.assertTrue('Empty search string' in self.browser.contents)
1945        self.browser.getControl(name="searchtype").value = ['student_id']
1946        self.browser.getControl(name="searchterm").value = self.student_id
1947        self.browser.getControl("Find student").click()
1948        self.assertTrue('Anna Tester' in self.browser.contents)
1949
1950    def test_activate_deactivate_buttons(self):
1951        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1952        self.browser.open(self.student_path)
1953        self.browser.getLink("Deactivate").click()
1954        self.assertTrue(
1955            'Student account has been deactivated.' in self.browser.contents)
1956        self.assertTrue(
1957            'Base Data (account deactivated)' in self.browser.contents)
1958        self.assertTrue(self.student.suspended)
1959        self.browser.getLink("Activate").click()
1960        self.assertTrue(
1961            'Student account has been activated.' in self.browser.contents)
1962        self.assertFalse(
1963            'Base Data (account deactivated)' in self.browser.contents)
1964        self.assertFalse(self.student.suspended)
1965        # History messages have been added ...
1966        self.browser.getLink("History").click()
1967        self.assertTrue(
1968            'Student account deactivated by Manager<br />' in self.browser.contents)
1969        self.assertTrue(
1970            'Student account activated by Manager<br />' in self.browser.contents)
1971        # ... and actions have been logged.
1972        logfile = os.path.join(
1973            self.app['datacenter'].storage, 'logs', 'students.log')
1974        logcontent = open(logfile).read()
1975        self.assertTrue('zope.mgr - students.browser.StudentDeactivateView - '
1976                        'K1000000 - account deactivated' in logcontent)
1977        self.assertTrue('zope.mgr - students.browser.StudentActivateView - '
1978                        'K1000000 - account activated' in logcontent)
1979
1980    def test_manage_student_transfer(self):
1981        # Add second certificate
1982        self.certificate2 = createObject('waeup.Certificate')
1983        self.certificate2.code = u'CERT2'
1984        self.certificate2.study_mode = 'ug_ft'
1985        self.certificate2.start_level = 999
1986        self.certificate2.end_level = 999
1987        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1988            self.certificate2)
1989
1990        # Add study level to old study course
1991        studylevel = createObject(u'waeup.StudentStudyLevel')
1992        studylevel.level = 200
1993        self.student['studycourse'].addStudentStudyLevel(
1994            self.certificate, studylevel)
1995        studylevel = createObject(u'waeup.StudentStudyLevel')
1996        studylevel.level = 999
1997        self.student['studycourse'].addStudentStudyLevel(
1998            self.certificate, studylevel)
1999
2000        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2001        self.browser.open(self.student_path)
2002        self.browser.getLink("Transfer").click()
2003        self.browser.getControl(name="form.certificate").value = ['CERT2']
2004        self.browser.getControl(name="form.current_session").value = ['2011']
2005        self.browser.getControl(name="form.current_level").value = ['200']
2006        self.browser.getControl("Transfer").click()
2007        self.assertTrue(
2008            'Current level does not match certificate levels'
2009            in self.browser.contents)
2010        self.browser.getControl(name="form.current_level").value = ['999']
2011        self.browser.getControl("Transfer").click()
2012        self.assertTrue('Successfully transferred' in self.browser.contents)
2013        # The catalog has been updated
2014        cat = queryUtility(ICatalog, name='students_catalog')
2015        results = list(
2016            cat.searchResults(
2017            certcode=('CERT2', 'CERT2')))
2018        self.assertTrue(results[0] is self.student)
2019        results = list(
2020            cat.searchResults(
2021            current_session=(2011, 2011)))
2022        self.assertTrue(results[0] is self.student)
2023        # Add study level to new study course
2024        studylevel = createObject(u'waeup.StudentStudyLevel')
2025        studylevel.level = 999
2026        self.student['studycourse'].addStudentStudyLevel(
2027            self.certificate, studylevel)
2028
2029        # Edit and add pages are locked for old study courses
2030        self.browser.open(self.student_path + '/studycourse/manage')
2031        self.assertFalse('The requested form is locked' in self.browser.contents)
2032        self.browser.open(self.student_path + '/studycourse_1/manage')
2033        self.assertTrue('The requested form is locked' in self.browser.contents)
2034
2035        self.browser.open(self.student_path + '/studycourse/start_session')
2036        self.assertFalse('The requested form is locked' in self.browser.contents)
2037        self.browser.open(self.student_path + '/studycourse_1/start_session')
2038        self.assertTrue('The requested form is locked' in self.browser.contents)
2039
2040        IWorkflowState(self.student).setState('school fee paid')
2041        self.browser.open(self.student_path + '/studycourse/add')
2042        self.assertFalse('The requested form is locked' in self.browser.contents)
2043        self.browser.open(self.student_path + '/studycourse_1/add')
2044        self.assertTrue('The requested form is locked' in self.browser.contents)
2045
2046        self.browser.open(self.student_path + '/studycourse/999/manage')
2047        self.assertFalse('The requested form is locked' in self.browser.contents)
2048        self.browser.open(self.student_path + '/studycourse_1/999/manage')
2049        self.assertTrue('The requested form is locked' in self.browser.contents)
2050
2051        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
2052        self.assertFalse('The requested form is locked' in self.browser.contents)
2053        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
2054        self.assertTrue('The requested form is locked' in self.browser.contents)
2055
2056        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
2057        self.assertFalse('The requested form is locked' in self.browser.contents)
2058        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
2059        self.assertTrue('The requested form is locked' in self.browser.contents)
2060
2061        self.browser.open(self.student_path + '/studycourse/999/add')
2062        self.assertFalse('The requested form is locked' in self.browser.contents)
2063        self.browser.open(self.student_path + '/studycourse_1/999/add')
2064        self.assertTrue('The requested form is locked' in self.browser.contents)
2065
2066        self.browser.open(self.student_path + '/studycourse/999/edit')
2067        self.assertFalse('The requested form is locked' in self.browser.contents)
2068        self.browser.open(self.student_path + '/studycourse_1/999/edit')
2069        self.assertTrue('The requested form is locked' in self.browser.contents)
2070
2071        # Revert transfer
2072        self.browser.open(self.student_path + '/studycourse_1')
2073        self.browser.getLink("Reactivate").click()
2074        self.browser.getControl("Revert now").click()
2075        self.assertTrue('Previous transfer reverted' in self.browser.contents)
2076        results = list(
2077            cat.searchResults(
2078            certcode=('CERT1', 'CERT1')))
2079        self.assertTrue(results[0] is self.student)
2080        self.assertEqual([i for i in self.student.keys()],
2081            [u'accommodation', u'payments', u'studycourse'])
2082
2083    def test_login_as_student(self):
2084        # StudentImpersonators can login as student
2085        # Create clearance officer
2086        self.app['users'].addUser('mrofficer', 'mrofficersecret')
2087        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
2088        self.app['users']['mrofficer'].title = 'Harry Actor'
2089        prmglobal = IPrincipalRoleManager(self.app)
2090        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
2091        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
2092        # Login as student impersonator
2093        self.browser.open(self.login_path)
2094        self.browser.getControl(name="form.login").value = 'mrofficer'
2095        self.browser.getControl(name="form.password").value = 'mrofficersecret'
2096        self.browser.getControl("Login").click()
2097        self.assertMatches('...You logged in...', self.browser.contents)
2098        self.browser.open(self.student_path)
2099        self.browser.getLink("Login as").click()
2100        self.browser.getControl("Set password now").click()
2101        temp_password = self.browser.getControl(name='form.password').value
2102        self.browser.getControl("Login now").click()
2103        self.assertMatches(
2104            '...You successfully logged in as...', self.browser.contents)
2105        # We are logged in as student and can see the 'My Data' tab
2106        self.assertMatches(
2107            '...<a href="#" class="dropdown-toggle" data-toggle="dropdown">...',
2108            self.browser.contents)
2109        self.assertMatches(
2110            '...My Data...',
2111            self.browser.contents)
2112        self.browser.getLink("Logout").click()
2113        # The student can't login with the original password ...
2114        self.browser.open(self.login_path)
2115        self.browser.getControl(name="form.login").value = self.student_id
2116        self.browser.getControl(name="form.password").value = 'spwd'
2117        self.browser.getControl("Login").click()
2118        self.assertMatches(
2119            '...Your account has been temporarily deactivated...',
2120            self.browser.contents)
2121        # ... but with the temporary password
2122        self.browser.open(self.login_path)
2123        self.browser.getControl(name="form.login").value = self.student_id
2124        self.browser.getControl(name="form.password").value = temp_password
2125        self.browser.getControl("Login").click()
2126        self.assertMatches('...You logged in...', self.browser.contents)
2127        # Creation of temp_password is properly logged
2128        logfile = os.path.join(
2129            self.app['datacenter'].storage, 'logs', 'students.log')
2130        logcontent = open(logfile).read()
2131        self.assertTrue(
2132            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
2133            'temp_password generated: %s' % temp_password in logcontent)
2134
2135    def test_transcripts(self):
2136        studylevel = createObject(u'waeup.StudentStudyLevel')
2137        studylevel.level = 100
2138        studylevel.level_session = 2005
2139        self.student['studycourse'].entry_mode = 'ug_ft'
2140        self.student['studycourse'].addStudentStudyLevel(
2141            self.certificate, studylevel)
2142        studylevel2 = createObject(u'waeup.StudentStudyLevel')
2143        studylevel2.level = 110
2144        studylevel2.level_session = 2006
2145        self.student['studycourse'].addStudentStudyLevel(
2146            self.certificate, studylevel2)
2147        # Add second course (COURSE has been added automatically)
2148        courseticket = createObject('waeup.CourseTicket')
2149        courseticket.code = 'ANYCODE'
2150        courseticket.title = u'Any TITLE'
2151        courseticket.credits = 13
2152        courseticket.score = 66
2153        courseticket.semester = 1
2154        courseticket.dcode = u'ANYDCODE'
2155        courseticket.fcode = u'ANYFCODE'
2156        self.student['studycourse']['110']['COURSE2'] = courseticket
2157        self.student['studycourse']['100']['COURSE1'].score = 55
2158        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2159        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2160        # Get transcript data
2161        td = self.student['studycourse'].getTranscriptData()
2162        self.assertEqual(td[0][0]['level_key'], '100')
2163        self.assertEqual(td[0][0]['sgpa'], 3.0)
2164        self.assertEqual(td[0][0]['level'].level, 100)
2165        self.assertEqual(td[0][0]['level'].level_session, 2005)
2166        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2167        self.assertEqual(td[0][1]['level_key'], '110')
2168        self.assertEqual(td[0][1]['sgpa'], 4.0)
2169        self.assertEqual(td[0][1]['level'].level, 110)
2170        self.assertEqual(td[0][1]['level'].level_session, 2006)
2171        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2172        self.assertEqual(td[1], 3.57)
2173        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2174        self.browser.open(self.student_path + '/studycourse/transcript')
2175        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2176        self.assertTrue('Transcript' in self.browser.contents)
2177        # Officers can open the pdf transcript
2178        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2179        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2180        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2181        path = os.path.join(samples_dir(), 'transcript.pdf')
2182        open(path, 'wb').write(self.browser.contents)
2183        print "Sample PDF transcript.pdf written to %s" % path
2184
2185    def test_process_transcript_request(self):
2186        IWorkflowState(self.student).setState('transcript requested')
2187        notify(grok.ObjectModifiedEvent(self.student))
2188        self.student.transcript_comment = (
2189            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2190            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2191            'Address line2\n\n')
2192        # Create transcript officer
2193        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2194        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2195        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2196        prmglobal = IPrincipalRoleManager(self.app)
2197        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2198        # Login as transcript officer
2199        self.browser.open(self.login_path)
2200        self.browser.getControl(name="form.login").value = 'mrtranscript'
2201        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2202        self.browser.getControl("Login").click()
2203        self.assertMatches('...You logged in...', self.browser.contents)
2204        # Officer can see his roles
2205        self.browser.getLink("My Roles").click()
2206        self.assertMatches(
2207            '...<div>Transcript Officer</div>...',
2208            self.browser.contents)
2209        # Officer can search for students in state 'transcripr requested'
2210        self.browser.open(self.container_path)
2211        self.browser.getControl(name="searchtype").value = ['transcript']
2212        self.browser.getControl("Find student(s)").click()
2213        self.assertTrue('Anna Tester' in self.browser.contents)
2214        self.browser.getLink("K1000000").click()
2215        self.browser.getLink("Manage transcript request").click()
2216        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2217        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2218        'Address line2<br><br></p>' in self.browser.contents)
2219        self.browser.getControl(name="comment").value = (
2220            'Hello,\nYour transcript has been sent to the address provided.')
2221        self.browser.getControl("Save comment and mark as processed").click()
2222        self.assertTrue(
2223            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2224            'been sent to the address provided.\n\n'
2225            in self.student.transcript_comment)
2226        # The comment has been logged
2227        logfile = os.path.join(
2228            self.app['datacenter'].storage, 'logs', 'students.log')
2229        logcontent = open(logfile).read()
2230        self.assertTrue(
2231            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
2232            'K1000000 - comment: Hello,<br>'
2233            'Your transcript has been sent to the address provided'
2234            in logcontent)
2235
2236class StudentUITests(StudentsFullSetup):
2237    # Tests for Student class views and pages
2238
2239    def test_student_change_password(self):
2240        # Students can change the password
2241        self.student.personal_updated = datetime.utcnow()
2242        self.browser.open(self.login_path)
2243        self.browser.getControl(name="form.login").value = self.student_id
2244        self.browser.getControl(name="form.password").value = 'spwd'
2245        self.browser.getControl("Login").click()
2246        self.assertEqual(self.browser.url, self.student_path)
2247        self.assertTrue('You logged in' in self.browser.contents)
2248        # Change password
2249        self.browser.getLink("Change password").click()
2250        self.browser.getControl(name="change_password").value = 'pw'
2251        self.browser.getControl(
2252            name="change_password_repeat").value = 'pw'
2253        self.browser.getControl("Save").click()
2254        self.assertTrue('Password must have at least' in self.browser.contents)
2255        self.browser.getControl(name="change_password").value = 'new_password'
2256        self.browser.getControl(
2257            name="change_password_repeat").value = 'new_passssword'
2258        self.browser.getControl("Save").click()
2259        self.assertTrue('Passwords do not match' in self.browser.contents)
2260        self.browser.getControl(name="change_password").value = 'new_password'
2261        self.browser.getControl(
2262            name="change_password_repeat").value = 'new_password'
2263        self.browser.getControl("Save").click()
2264        self.assertTrue('Password changed' in self.browser.contents)
2265        # We are still logged in. Changing the password hasn't thrown us out.
2266        self.browser.getLink("Base Data").click()
2267        self.assertEqual(self.browser.url, self.student_path)
2268        # We can logout
2269        self.browser.getLink("Logout").click()
2270        self.assertTrue('You have been logged out' in self.browser.contents)
2271        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2272        # We can login again with the new password
2273        self.browser.getLink("Login").click()
2274        self.browser.open(self.login_path)
2275        self.browser.getControl(name="form.login").value = self.student_id
2276        self.browser.getControl(name="form.password").value = 'new_password'
2277        self.browser.getControl("Login").click()
2278        self.assertEqual(self.browser.url, self.student_path)
2279        self.assertTrue('You logged in' in self.browser.contents)
2280        return
2281
2282    def test_forbidden_name(self):
2283        self.student.lastname = u'<TAG>Tester</TAG>'
2284        self.browser.open(self.login_path)
2285        self.browser.getControl(name="form.login").value = self.student_id
2286        self.browser.getControl(name="form.password").value = 'spwd'
2287        self.browser.getControl("Login").click()
2288        self.assertTrue('XXX: Base Data' in self.browser.contents)
2289        self.assertTrue('&lt;TAG&gt;Tester&lt;/TAG&gt;' in self.browser.contents)
2290        self.assertFalse('<TAG>Tester</TAG>' in self.browser.contents)
2291        return
2292
2293    def test_setpassword(self):
2294        # Set password for first-time access
2295        student = Student()
2296        student.reg_number = u'123456'
2297        student.firstname = u'Klaus'
2298        student.lastname = u'Tester'
2299        self.app['students'].addStudent(student)
2300        setpassword_path = 'http://localhost/app/setpassword'
2301        student_path = 'http://localhost/app/students/%s' % student.student_id
2302        self.browser.open(setpassword_path)
2303        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2304        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2305        self.browser.getControl(name="reg_number").value = '223456'
2306        self.browser.getControl("Set").click()
2307        self.assertMatches('...No student found...',
2308                           self.browser.contents)
2309        self.browser.getControl(name="reg_number").value = '123456'
2310        self.browser.getControl(name="ac_number").value = '999999'
2311        self.browser.getControl("Set").click()
2312        self.assertMatches('...Access code is invalid...',
2313                           self.browser.contents)
2314        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2315        self.browser.getControl("Set").click()
2316        self.assertMatches('...Password has been set. Your Student Id is...',
2317                           self.browser.contents)
2318        self.browser.getControl("Set").click()
2319        self.assertMatches(
2320            '...Password has already been set. Your Student Id is...',
2321            self.browser.contents)
2322        existing_pwdpin = self.pwdpins[1]
2323        parts = existing_pwdpin.split('-')[1:]
2324        existing_pwdseries, existing_pwdnumber = parts
2325        self.browser.getControl(name="ac_series").value = existing_pwdseries
2326        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2327        self.browser.getControl(name="reg_number").value = '123456'
2328        self.browser.getControl("Set").click()
2329        self.assertMatches(
2330            '...You are using the wrong Access Code...',
2331            self.browser.contents)
2332        # The student can login with the new credentials
2333        self.browser.open(self.login_path)
2334        self.browser.getControl(name="form.login").value = student.student_id
2335        self.browser.getControl(
2336            name="form.password").value = self.existing_pwdnumber
2337        self.browser.getControl("Login").click()
2338        self.assertEqual(self.browser.url, student_path)
2339        self.assertTrue('You logged in' in self.browser.contents)
2340        return
2341
2342    def test_student_login(self):
2343        # Student cant login if their password is not set
2344        self.student.password = None
2345        self.browser.open(self.login_path)
2346        self.browser.getControl(name="form.login").value = self.student_id
2347        self.browser.getControl(name="form.password").value = 'spwd'
2348        self.browser.getControl("Login").click()
2349        self.assertTrue(
2350            'You entered invalid credentials.' in self.browser.contents)
2351        # We set the password again
2352        IUserAccount(
2353            self.app['students'][self.student_id]).setPassword('spwd')
2354        # Students can't login if their account is suspended/deactivated
2355        self.student.suspended = True
2356        self.browser.open(self.login_path)
2357        self.browser.getControl(name="form.login").value = self.student_id
2358        self.browser.getControl(name="form.password").value = 'spwd'
2359        self.browser.getControl("Login").click()
2360        self.assertMatches(
2361            '...<div class="alert alert-warning">'
2362            'Your account has been deactivated.</div>...', self.browser.contents)
2363        # If suspended_comment is set this message will be flashed instead
2364        self.student.suspended_comment = u'Aetsch baetsch!'
2365        self.browser.getControl(name="form.login").value = self.student_id
2366        self.browser.getControl(name="form.password").value = 'spwd'
2367        self.browser.getControl("Login").click()
2368        self.assertMatches(
2369            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2370            self.browser.contents)
2371        self.student.suspended = False
2372        # Students can't login if a temporary password has been set and
2373        # is not expired
2374        self.app['students'][self.student_id].setTempPassword(
2375            'anybody', 'temp_spwd')
2376        self.browser.open(self.login_path)
2377        self.browser.getControl(name="form.login").value = self.student_id
2378        self.browser.getControl(name="form.password").value = 'spwd'
2379        self.browser.getControl("Login").click()
2380        self.assertMatches(
2381            '...Your account has been temporarily deactivated...',
2382            self.browser.contents)
2383        # The student can login with the temporary password
2384        self.browser.open(self.login_path)
2385        self.browser.getControl(name="form.login").value = self.student_id
2386        self.browser.getControl(name="form.password").value = 'temp_spwd'
2387        self.browser.getControl("Login").click()
2388        self.assertMatches(
2389            '...You logged in...', self.browser.contents)
2390        # Student can view the base data
2391        self.browser.open(self.student_path)
2392        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2393        self.assertEqual(self.browser.url, self.student_path)
2394        # When the password expires ...
2395        delta = timedelta(minutes=11)
2396        self.app['students'][self.student_id].temp_password[
2397            'timestamp'] = datetime.utcnow() - delta
2398        self.app['students'][self.student_id]._p_changed = True
2399        # ... the student will be automatically logged out
2400        self.assertRaises(
2401            Unauthorized, self.browser.open, self.student_path)
2402        # Then the student can login with the original password
2403        self.browser.open(self.login_path)
2404        self.browser.getControl(name="form.login").value = self.student_id
2405        self.browser.getControl(name="form.password").value = 'spwd'
2406        self.browser.getControl("Login").click()
2407        self.assertMatches(
2408            '...You logged in...', self.browser.contents)
2409
2410    def test_maintenance_mode(self):
2411        config = grok.getSite()['configuration']
2412        self.browser.open(self.login_path)
2413        self.browser.getControl(name="form.login").value = self.student_id
2414        self.browser.getControl(name="form.password").value = 'spwd'
2415        self.browser.getControl("Login").click()
2416        # Student logged in.
2417        self.assertTrue('You logged in' in self.browser.contents)
2418        self.assertTrue("Anna Tester" in self.browser.contents)
2419        # If maintenance mode is enabled, student is immediately logged out.
2420        config.maintmode_enabled_by = u'any_user'
2421        self.assertRaises(
2422            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
2423        self.browser.open('http://localhost/app/login')
2424        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
2425        # Student really can't login if maintenance mode is enabled.
2426        self.browser.open(self.login_path)
2427        self.browser.getControl(name="form.login").value = self.student_id
2428        self.browser.getControl(name="form.password").value = 'spwd'
2429        self.browser.getControl("Login").click()
2430        # A second warning is raised.
2431        self.assertTrue(
2432            'The portal is in maintenance mode. You can\'t login!'
2433            in self.browser.contents)
2434        return
2435
2436    def test_student_clearance(self):
2437        # Student cant login if their password is not set
2438        IWorkflowInfo(self.student).fireTransition('admit')
2439        self.browser.open(self.login_path)
2440        self.browser.getControl(name="form.login").value = self.student_id
2441        self.browser.getControl(name="form.password").value = 'spwd'
2442        self.browser.getControl("Login").click()
2443        self.assertMatches(
2444            '...You logged in...', self.browser.contents)
2445        # Admitted student can upload a passport picture
2446        self.browser.open(self.student_path + '/change_portrait')
2447        ctrl = self.browser.getControl(name='passportuploadedit')
2448        file_obj = open(SAMPLE_IMAGE, 'rb')
2449        file_ctrl = ctrl.mech_control
2450        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2451        self.browser.getControl(
2452            name='upload_passportuploadedit').click()
2453        self.assertTrue(
2454            'src="http://localhost/app/students/K1000000/passport.jpg"'
2455            in self.browser.contents)
2456        # Students can open admission letter
2457        self.browser.getLink("Base Data").click()
2458        self.browser.getLink("Download admission letter").click()
2459        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2460        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2461        # Student can view the clearance data
2462        self.browser.open(self.student_path)
2463        self.browser.getLink("Clearance Data").click()
2464        # Student can't open clearance edit form before starting clearance
2465        self.browser.open(self.student_path + '/cedit')
2466        self.assertMatches('...The requested form is locked...',
2467                           self.browser.contents)
2468        self.browser.getLink("Clearance Data").click()
2469        self.browser.getLink("Start clearance").click()
2470        self.student.phone = None
2471        # Uups, we forgot to fill the phone fields
2472        self.browser.getControl("Start clearance").click()
2473        self.assertMatches('...Phone number is missing...',
2474                           self.browser.contents)
2475        self.browser.open(self.student_path + '/edit_base')
2476        self.browser.getControl(name="form.phone.ext").value = '12345'
2477        self.browser.getControl("Save").click()
2478        self.browser.open(self.student_path + '/start_clearance')
2479        self.browser.getControl(name="ac_series").value = '3'
2480        self.browser.getControl(name="ac_number").value = '4444444'
2481        self.browser.getControl("Start clearance now").click()
2482        self.assertMatches('...Activation code is invalid...',
2483                           self.browser.contents)
2484        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2485        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2486        # Owner is Hans Wurst, AC can't be invalidated
2487        self.browser.getControl("Start clearance now").click()
2488        self.assertMatches('...You are not the owner of this access code...',
2489                           self.browser.contents)
2490        # Set the correct owner
2491        self.existing_clrac.owner = self.student_id
2492        # clr_code might be set (and thus returns None) due importing
2493        # an empty clr_code column.
2494        self.student.clr_code = None
2495        self.browser.getControl("Start clearance now").click()
2496        self.assertMatches('...Clearance process has been started...',
2497                           self.browser.contents)
2498        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2499        self.browser.getControl("Save", index=0).click()
2500        # Student can view the clearance data
2501        self.browser.getLink("Clearance Data").click()
2502        # and go back to the edit form
2503        self.browser.getLink("Edit").click()
2504        # Students can upload documents
2505        ctrl = self.browser.getControl(name='birthcertificateupload')
2506        file_obj = open(SAMPLE_IMAGE, 'rb')
2507        file_ctrl = ctrl.mech_control
2508        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2509        self.browser.getControl(
2510            name='upload_birthcertificateupload').click()
2511        self.assertTrue(
2512            'href="http://localhost/app/students/K1000000/birth_certificate"'
2513            in self.browser.contents)
2514        # Students can open clearance slip
2515        self.browser.getLink("View").click()
2516        self.browser.getLink("Download clearance slip").click()
2517        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2518        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2519        # Students can request clearance
2520        self.browser.open(self.edit_clearance_path)
2521        self.browser.getControl("Save and request clearance").click()
2522        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2523        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2524        self.browser.getControl("Request clearance now").click()
2525        self.assertMatches('...Clearance has been requested...',
2526                           self.browser.contents)
2527        # Student can't reopen clearance form after requesting clearance
2528        self.browser.open(self.student_path + '/cedit')
2529        self.assertMatches('...The requested form is locked...',
2530                           self.browser.contents)
2531
2532    def test_student_course_registration(self):
2533        # Student cant login if their password is not set
2534        IWorkflowInfo(self.student).fireTransition('admit')
2535        self.browser.open(self.login_path)
2536        self.browser.getControl(name="form.login").value = self.student_id
2537        self.browser.getControl(name="form.password").value = 'spwd'
2538        self.browser.getControl("Login").click()
2539        # Student can't add study level if not in state 'school fee paid'
2540        self.browser.open(self.student_path + '/studycourse/add')
2541        self.assertMatches('...The requested form is locked...',
2542                           self.browser.contents)
2543        # ... and must be transferred first
2544        IWorkflowState(self.student).setState('school fee paid')
2545        # Now students can add the current study level
2546        self.browser.getLink("Study Course").click()
2547        self.student['studycourse'].current_level = None
2548        self.browser.getLink("Add course list").click()
2549        self.assertMatches('...Your data are incomplete...',
2550                           self.browser.contents)
2551        self.student['studycourse'].current_level = 100
2552        self.browser.getLink("Add course list").click()
2553        self.assertMatches('...Add current level 100 (Year 1)...',
2554                           self.browser.contents)
2555        self.browser.getControl("Create course list now").click()
2556        # A level with one course ticket was created
2557        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2558        self.browser.getLink("100").click()
2559        self.browser.getLink("Edit course list").click()
2560        self.browser.getLink("here").click()
2561        self.browser.getControl(name="form.course").value = ['COURSE1']
2562        self.browser.getControl("Add course ticket").click()
2563        self.assertMatches('...The ticket exists...',
2564                           self.browser.contents)
2565        self.student['studycourse'].current_level = 200
2566        self.browser.getLink("Study Course").click()
2567        self.browser.getLink("Add course list").click()
2568        self.assertMatches('...Add current level 200 (Year 2)...',
2569                           self.browser.contents)
2570        self.browser.getControl("Create course list now").click()
2571        self.browser.getLink("200").click()
2572        self.browser.getLink("Edit course list").click()
2573        self.browser.getLink("here").click()
2574        self.browser.getControl(name="form.course").value = ['COURSE1']
2575        self.course.credits = 100
2576        self.browser.getControl("Add course ticket").click()
2577        self.assertMatches(
2578            '...Total credits exceed 50...', self.browser.contents)
2579        self.course.credits = 10
2580        self.browser.getControl("Add course ticket").click()
2581        self.assertMatches('...The ticket exists...',
2582                           self.browser.contents)
2583        # Indeed the ticket exists as carry-over course from level 100
2584        # since its score was 0
2585        self.assertTrue(
2586            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2587        # Students can open the pdf course registration slip
2588        self.browser.open(self.student_path + '/studycourse/200')
2589        self.browser.getLink("Download course registration slip").click()
2590        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2591        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2592        # Students can remove course tickets
2593        self.browser.open(self.student_path + '/studycourse/200/edit')
2594        self.browser.getControl("Remove selected", index=0).click()
2595        self.assertTrue('No ticket selected' in self.browser.contents)
2596        # No ticket can be selected since the carry-over course is a core course
2597        self.assertRaises(
2598            LookupError, self.browser.getControl, name='val_id')
2599        self.student['studycourse']['200']['COURSE1'].mandatory = False
2600        self.browser.open(self.student_path + '/studycourse/200/edit')
2601        # Course list can't be registered if total_credits exceeds max_credits
2602        self.student['studycourse']['200']['COURSE1'].credits = 60
2603        self.browser.getControl("Register course list").click()
2604        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2605        # Student can now remove the ticket
2606        ctrl = self.browser.getControl(name='val_id')
2607        ctrl.getControl(value='COURSE1').selected = True
2608        self.browser.getControl("Remove selected", index=0).click()
2609        self.assertTrue('Successfully removed' in self.browser.contents)
2610        # Removing course tickets is properly logged
2611        logfile = os.path.join(
2612            self.app['datacenter'].storage, 'logs', 'students.log')
2613        logcontent = open(logfile).read()
2614        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2615        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2616        # They can add the same ticket using the edit page directly.
2617        # We can do the same by adding the course on the manage page directly
2618        self.browser.getControl(name="course").value = 'COURSE1'
2619        self.browser.getControl("Add course ticket").click()
2620        # Adding course tickets is logged
2621        logfile = os.path.join(
2622            self.app['datacenter'].storage, 'logs', 'students.log')
2623        logcontent = open(logfile).read()
2624        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2625            'K1000000 - added: COURSE1|200|2004' in logcontent)
2626        # Course list can be registered
2627        self.browser.getControl("Register course list").click()
2628        self.assertTrue('Course list has been registered' in self.browser.contents)
2629        self.assertEqual(self.student.state, 'courses registered')
2630        # Course list can be unregistered
2631        self.browser.getLink("Unregister courses").click()
2632        self.assertEqual(self.student.state, 'school fee paid')
2633        self.assertTrue('Course list has been unregistered' in self.browser.contents)
2634        self.browser.open(self.student_path + '/studycourse/200/unregister_courses')
2635        self.assertTrue('You are in the wrong state' in self.browser.contents)
2636        # Students can view the transcript
2637        #self.browser.open(self.studycourse_path)
2638        #self.browser.getLink("Transcript").click()
2639        #self.browser.getLink("Academic Transcript").click()
2640        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2641        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2642        return
2643
2644    def test_postgraduate_student_access(self):
2645        self.certificate.study_mode = 'pg_ft'
2646        self.certificate.start_level = 999
2647        self.certificate.end_level = 999
2648        self.student['studycourse'].current_level = 999
2649        IWorkflowState(self.student).setState('school fee paid')
2650        self.browser.open(self.login_path)
2651        self.browser.getControl(name="form.login").value = self.student_id
2652        self.browser.getControl(name="form.password").value = 'spwd'
2653        self.browser.getControl("Login").click()
2654        self.assertTrue(
2655            'You logged in.' in self.browser.contents)
2656        # Now students can add the current study level
2657        self.browser.getLink("Study Course").click()
2658        self.browser.getLink("Add course list").click()
2659        self.assertMatches('...Add current level Postgraduate Level...',
2660                           self.browser.contents)
2661        self.browser.getControl("Create course list now").click()
2662        # A level with one course ticket was created
2663        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2664        self.browser.getLink("999").click()
2665        self.browser.getLink("Edit course list").click()
2666        self.browser.getLink("here").click()
2667        self.browser.getControl(name="form.course").value = ['COURSE1']
2668        self.browser.getControl("Add course ticket").click()
2669        self.assertMatches('...Successfully added COURSE1...',
2670                           self.browser.contents)
2671        # Postgraduate students can't register course lists
2672        self.browser.getControl("Register course list").click()
2673        self.assertTrue("your course list can't bee registered"
2674            in self.browser.contents)
2675        self.assertEqual(self.student.state, 'school fee paid')
2676        return
2677
2678    def test_student_clearance_wo_clrcode(self):
2679        IWorkflowState(self.student).setState('clearance started')
2680        self.browser.open(self.login_path)
2681        self.browser.getControl(name="form.login").value = self.student_id
2682        self.browser.getControl(name="form.password").value = 'spwd'
2683        self.browser.getControl("Login").click()
2684        self.browser.open(self.edit_clearance_path)
2685        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2686        self.browser.getControl("Save and request clearance").click()
2687        self.assertMatches('...Clearance has been requested...',
2688                           self.browser.contents)
2689
2690    def test_student_clearance_payment(self):
2691        # Login
2692        self.browser.open(self.login_path)
2693        self.browser.getControl(name="form.login").value = self.student_id
2694        self.browser.getControl(name="form.password").value = 'spwd'
2695        self.browser.getControl("Login").click()
2696
2697        # Students can add online clearance payment tickets
2698        self.browser.open(self.payments_path + '/addop')
2699        self.browser.getControl(name="form.p_category").value = ['clearance']
2700        self.browser.getControl("Create ticket").click()
2701        self.assertMatches('...ticket created...',
2702                           self.browser.contents)
2703
2704        # Students can't approve the payment
2705        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2706        ctrl = self.browser.getControl(name='val_id')
2707        value = ctrl.options[0]
2708        self.browser.getLink(value).click()
2709        payment_url = self.browser.url
2710        self.assertRaises(
2711            Unauthorized, self.browser.open, payment_url + '/approve')
2712        # In the base package they can 'use' a fake approval view.
2713        # XXX: I tried to use
2714        # self.student['payments'][value].approveStudentPayment() instead.
2715        # But this function fails in
2716        # w.k.accesscodes.accesscode.create_accesscode.
2717        # grok.getSite returns None in tests.
2718        self.browser.open(payment_url + '/fake_approve')
2719        self.assertMatches('...Payment approved...',
2720                          self.browser.contents)
2721        expected = '''...
2722        <td>
2723          <span>Paid</span>
2724        </td>...'''
2725        expected = '''...
2726        <td>
2727          <span>Paid</span>
2728        </td>...'''
2729        self.assertMatches(expected,self.browser.contents)
2730        payment_id = self.student['payments'].keys()[0]
2731        payment = self.student['payments'][payment_id]
2732        self.assertEqual(payment.p_state, 'paid')
2733        self.assertEqual(payment.r_amount_approved, 3456.0)
2734        self.assertEqual(payment.r_code, 'AP')
2735        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2736        # The new CLR-0 pin has been created
2737        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2738        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2739        ac = self.app['accesscodes']['CLR-0'][pin]
2740        self.assertEqual(ac.owner, self.student_id)
2741        self.assertEqual(ac.cost, 3456.0)
2742
2743        # Students can open the pdf payment slip
2744        self.browser.open(payment_url + '/payment_slip.pdf')
2745        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2746        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2747
2748        # The new CLR-0 pin can be used for starting clearance
2749        # but they have to upload a passport picture first
2750        # which is only possible in state admitted
2751        self.browser.open(self.student_path + '/change_portrait')
2752        self.assertMatches('...form is locked...',
2753                          self.browser.contents)
2754        IWorkflowInfo(self.student).fireTransition('admit')
2755        self.browser.open(self.student_path + '/change_portrait')
2756        image = open(SAMPLE_IMAGE, 'rb')
2757        ctrl = self.browser.getControl(name='passportuploadedit')
2758        file_ctrl = ctrl.mech_control
2759        file_ctrl.add_file(image, filename='my_photo.jpg')
2760        self.browser.getControl(
2761            name='upload_passportuploadedit').click()
2762        self.browser.open(self.student_path + '/start_clearance')
2763        parts = pin.split('-')[1:]
2764        clrseries, clrnumber = parts
2765        self.browser.getControl(name="ac_series").value = clrseries
2766        self.browser.getControl(name="ac_number").value = clrnumber
2767        self.browser.getControl("Start clearance now").click()
2768        self.assertMatches('...Clearance process has been started...',
2769                           self.browser.contents)
2770
2771    def test_student_schoolfee_payment(self):
2772        configuration = createObject('waeup.SessionConfiguration')
2773        configuration.academic_session = 2005
2774        self.app['configuration'].addSessionConfiguration(configuration)
2775        # Login
2776        self.browser.open(self.login_path)
2777        self.browser.getControl(name="form.login").value = self.student_id
2778        self.browser.getControl(name="form.password").value = 'spwd'
2779        self.browser.getControl("Login").click()
2780
2781        # Students can add online school fee payment tickets.
2782        IWorkflowState(self.student).setState('returning')
2783        self.browser.open(self.payments_path)
2784        self.assertRaises(
2785            LookupError, self.browser.getControl, name='val_id')
2786        self.browser.getLink("Add current session payment ticket").click()
2787        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2788        self.browser.getControl("Create ticket").click()
2789        self.assertMatches('...ticket created...',
2790                           self.browser.contents)
2791        ctrl = self.browser.getControl(name='val_id')
2792        value = ctrl.options[0]
2793        self.browser.getLink(value).click()
2794        self.assertMatches('...Amount Authorized...',
2795                           self.browser.contents)
2796        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2797        # Payment session and will be calculated as defined
2798        # in w.k.students.utils because we set changed the state
2799        # to returning
2800        self.assertEqual(self.student['payments'][value].p_session, 2005)
2801        self.assertEqual(self.student['payments'][value].p_level, 200)
2802
2803        # Student is the payer of the payment ticket.
2804        payer = IPayer(self.student['payments'][value])
2805        self.assertEqual(payer.display_fullname, 'Anna Tester')
2806        self.assertEqual(payer.id, self.student_id)
2807        self.assertEqual(payer.faculty, 'fac1')
2808        self.assertEqual(payer.department, 'dep1')
2809
2810        # We simulate the approval
2811        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2812        self.browser.open(self.browser.url + '/fake_approve')
2813        self.assertMatches('...Payment approved...',
2814                          self.browser.contents)
2815
2816        ## The new SFE-0 pin can be used for starting new session
2817        #self.browser.open(self.studycourse_path)
2818        #self.browser.getLink('Start new session').click()
2819        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2820        #parts = pin.split('-')[1:]
2821        #sfeseries, sfenumber = parts
2822        #self.browser.getControl(name="ac_series").value = sfeseries
2823        #self.browser.getControl(name="ac_number").value = sfenumber
2824        #self.browser.getControl("Start now").click()
2825        #self.assertMatches('...Session started...',
2826        #                   self.browser.contents)
2827
2828        self.assertTrue(self.student.state == 'school fee paid')
2829        return
2830
2831    def test_student_bedallocation_payment(self):
2832        # Login
2833        self.browser.open(self.login_path)
2834        self.browser.getControl(name="form.login").value = self.student_id
2835        self.browser.getControl(name="form.password").value = 'spwd'
2836        self.browser.getControl("Login").click()
2837        self.browser.open(self.payments_path)
2838        self.browser.open(self.payments_path + '/addop')
2839        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2840        self.browser.getControl("Create ticket").click()
2841        self.assertMatches('...ticket created...',
2842                           self.browser.contents)
2843        # Students can remove only online payment tickets which have
2844        # not received a valid callback
2845        self.browser.open(self.payments_path)
2846        ctrl = self.browser.getControl(name='val_id')
2847        value = ctrl.options[0]
2848        ctrl.getControl(value=value).selected = True
2849        self.browser.getControl("Remove selected", index=0).click()
2850        self.assertTrue('Successfully removed' in self.browser.contents)
2851
2852    def test_student_maintenance_payment(self):
2853        # Login
2854        self.browser.open(self.login_path)
2855        self.browser.getControl(name="form.login").value = self.student_id
2856        self.browser.getControl(name="form.password").value = 'spwd'
2857        self.browser.getControl("Login").click()
2858        self.browser.open(self.payments_path)
2859        self.browser.open(self.payments_path + '/addop')
2860        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2861        self.browser.getControl("Create ticket").click()
2862        self.assertMatches('...You have not yet booked accommodation...',
2863                           self.browser.contents)
2864        # We continue this test in test_student_accommodation
2865
2866    def test_student_previous_payments(self):
2867        configuration = createObject('waeup.SessionConfiguration')
2868        configuration.academic_session = 2000
2869        configuration.clearance_fee = 3456.0
2870        configuration.booking_fee = 123.4
2871        self.app['configuration'].addSessionConfiguration(configuration)
2872        configuration2 = createObject('waeup.SessionConfiguration')
2873        configuration2.academic_session = 2003
2874        configuration2.clearance_fee = 3456.0
2875        configuration2.booking_fee = 123.4
2876        self.app['configuration'].addSessionConfiguration(configuration2)
2877        configuration3 = createObject('waeup.SessionConfiguration')
2878        configuration3.academic_session = 2005
2879        configuration3.clearance_fee = 3456.0
2880        configuration3.booking_fee = 123.4
2881        self.app['configuration'].addSessionConfiguration(configuration3)
2882        self.student['studycourse'].entry_session = 2002
2883
2884        # Login
2885        self.browser.open(self.login_path)
2886        self.browser.getControl(name="form.login").value = self.student_id
2887        self.browser.getControl(name="form.password").value = 'spwd'
2888        self.browser.getControl("Login").click()
2889
2890        # Students can add previous school fee payment tickets in any state.
2891        IWorkflowState(self.student).setState('courses registered')
2892        self.browser.open(self.payments_path)
2893        self.browser.getLink("Add previous session payment ticket").click()
2894
2895        # Previous session payment form is provided
2896        self.assertEqual(self.student.current_session, 2004)
2897        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2898        self.browser.getControl(name="form.p_session").value = ['2000']
2899        self.browser.getControl(name="form.p_level").value = ['300']
2900        self.browser.getControl("Create ticket").click()
2901        self.assertMatches('...The previous session must not fall below...',
2902                           self.browser.contents)
2903        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2904        self.browser.getControl(name="form.p_session").value = ['2005']
2905        self.browser.getControl(name="form.p_level").value = ['300']
2906        self.browser.getControl("Create ticket").click()
2907        self.assertMatches('...This is not a previous session...',
2908                           self.browser.contents)
2909        # Students can pay current session school fee.
2910        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2911        self.browser.getControl(name="form.p_session").value = ['2004']
2912        self.browser.getControl(name="form.p_level").value = ['300']
2913        self.browser.getControl("Create ticket").click()
2914        self.assertMatches('...ticket created...',
2915                           self.browser.contents)
2916        ctrl = self.browser.getControl(name='val_id')
2917        value = ctrl.options[0]
2918        self.browser.getLink(value).click()
2919        self.assertMatches('...Amount Authorized...',
2920                           self.browser.contents)
2921        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2922
2923        # Payment session is properly set
2924        self.assertEqual(self.student['payments'][value].p_session, 2004)
2925        self.assertEqual(self.student['payments'][value].p_level, 300)
2926
2927        # We simulate the approval
2928        self.browser.open(self.browser.url + '/fake_approve')
2929        self.assertMatches('...Payment approved...',
2930                          self.browser.contents)
2931
2932        # No AC has been created
2933        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2934        self.assertTrue(self.student['payments'][value].ac is None)
2935
2936        # Current payment flag is set False
2937        self.assertFalse(self.student['payments'][value].p_current)
2938
2939        # Button and form are not available for students who are in
2940        # states up to cleared
2941        self.student['studycourse'].entry_session = 2004
2942        IWorkflowState(self.student).setState('cleared')
2943        self.browser.open(self.payments_path)
2944        self.assertFalse(
2945            "Add previous session payment ticket" in self.browser.contents)
2946        self.browser.open(self.payments_path + '/addpp')
2947        self.assertTrue(
2948            "No previous payment to be made" in self.browser.contents)
2949        return
2950
2951    def test_postgraduate_student_payments(self):
2952        configuration = createObject('waeup.SessionConfiguration')
2953        configuration.academic_session = 2005
2954        self.app['configuration'].addSessionConfiguration(configuration)
2955        self.certificate.study_mode = 'pg_ft'
2956        self.certificate.start_level = 999
2957        self.certificate.end_level = 999
2958        self.student['studycourse'].current_level = 999
2959        # Login
2960        self.browser.open(self.login_path)
2961        self.browser.getControl(name="form.login").value = self.student_id
2962        self.browser.getControl(name="form.password").value = 'spwd'
2963        self.browser.getControl("Login").click()
2964        # Students can add online school fee payment tickets.
2965        IWorkflowState(self.student).setState('cleared')
2966        self.browser.open(self.payments_path)
2967        self.browser.getLink("Add current session payment ticket").click()
2968        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2969        self.browser.getControl("Create ticket").click()
2970        self.assertMatches('...ticket created...',
2971                           self.browser.contents)
2972        ctrl = self.browser.getControl(name='val_id')
2973        value = ctrl.options[0]
2974        self.browser.getLink(value).click()
2975        self.assertMatches('...Amount Authorized...',
2976                           self.browser.contents)
2977        # Payment session and level are current ones.
2978        # Postgrads have to pay school_fee_1.
2979        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2980        self.assertEqual(self.student['payments'][value].p_session, 2004)
2981        self.assertEqual(self.student['payments'][value].p_level, 999)
2982
2983        # We simulate the approval
2984        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2985        self.browser.open(self.browser.url + '/fake_approve')
2986        self.assertMatches('...Payment approved...',
2987                          self.browser.contents)
2988
2989        ## The new SFE-0 pin can be used for starting session
2990        #self.browser.open(self.studycourse_path)
2991        #self.browser.getLink('Start new session').click()
2992        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2993        #parts = pin.split('-')[1:]
2994        #sfeseries, sfenumber = parts
2995        #self.browser.getControl(name="ac_series").value = sfeseries
2996        #self.browser.getControl(name="ac_number").value = sfenumber
2997        #self.browser.getControl("Start now").click()
2998        #self.assertMatches('...Session started...',
2999        #                   self.browser.contents)
3000
3001        self.assertTrue(self.student.state == 'school fee paid')
3002
3003        # Postgrad students do not need to register courses the
3004        # can just pay for the next session.
3005        self.browser.open(self.payments_path)
3006        # Remove first payment to be sure that we access the right ticket
3007        del self.student['payments'][value]
3008        self.browser.getLink("Add current session payment ticket").click()
3009        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3010        self.browser.getControl("Create ticket").click()
3011        ctrl = self.browser.getControl(name='val_id')
3012        value = ctrl.options[0]
3013        self.browser.getLink(value).click()
3014        # Payment session has increased by one, payment level remains the same.
3015        # Returning Postgraduates have to pay school_fee_2.
3016        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3017        self.assertEqual(self.student['payments'][value].p_session, 2005)
3018        self.assertEqual(self.student['payments'][value].p_level, 999)
3019
3020        # Student is still in old session
3021        self.assertEqual(self.student.current_session, 2004)
3022
3023        # We do not need to pay the ticket if any other
3024        # SFE pin is provided
3025        pin_container = self.app['accesscodes']
3026        pin_container.createBatch(
3027            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
3028        pin = pin_container['SFE-1'].values()[0].representation
3029        sfeseries, sfenumber = pin.split('-')[1:]
3030        # The new SFE-1 pin can be used for starting new session
3031        self.browser.open(self.studycourse_path)
3032        self.browser.getLink('Start new session').click()
3033        self.browser.getControl(name="ac_series").value = sfeseries
3034        self.browser.getControl(name="ac_number").value = sfenumber
3035        self.browser.getControl("Start now").click()
3036        self.assertMatches('...Session started...',
3037                           self.browser.contents)
3038        self.assertTrue(self.student.state == 'school fee paid')
3039        # Student is in new session
3040        self.assertEqual(self.student.current_session, 2005)
3041        self.assertEqual(self.student['studycourse'].current_level, 999)
3042        return
3043
3044    def test_student_accommodation(self):
3045        # Create a second hostel with one bed
3046        hostel = Hostel()
3047        hostel.hostel_id = u'hall-2'
3048        hostel.hostel_name = u'Hall 2'
3049        self.app['hostels'].addHostel(hostel)
3050        bed = Bed()
3051        bed.bed_id = u'hall-2_A_101_A'
3052        bed.bed_number = 1
3053        bed.owner = NOT_OCCUPIED
3054        bed.bed_type = u'regular_female_fr'
3055        self.app['hostels'][hostel.hostel_id].addBed(bed)
3056
3057        self.browser.open(self.login_path)
3058        self.browser.getControl(name="form.login").value = self.student_id
3059        self.browser.getControl(name="form.password").value = 'spwd'
3060        self.browser.getControl("Login").click()
3061        # Students can add online booking fee payment tickets and open the
3062        # callback view (see test_manage_payments).
3063        self.browser.getLink("Payments").click()
3064        self.browser.getLink("Add current session payment ticket").click()
3065        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3066        self.browser.getControl("Create ticket").click()
3067        ctrl = self.browser.getControl(name='val_id')
3068        value = ctrl.options[0]
3069        self.browser.getLink(value).click()
3070        self.browser.open(self.browser.url + '/fake_approve')
3071        # The new HOS-0 pin has been created.
3072        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
3073        pin = self.app['accesscodes']['HOS-0'].keys()[0]
3074        ac = self.app['accesscodes']['HOS-0'][pin]
3075        parts = pin.split('-')[1:]
3076        sfeseries, sfenumber = parts
3077        # Students can use HOS code and book a bed space with it ...
3078        self.browser.open(self.acco_path)
3079        # ... but not if booking period has expired ...
3080        self.app['hostels'].enddate = datetime.now(pytz.utc)
3081        self.browser.getControl("Book accommodation").click()
3082        self.assertMatches('...Outside booking period: ...',
3083                           self.browser.contents)
3084        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3085        # ... or student data are incomplete ...
3086        self.student['studycourse'].current_level = None
3087        self.browser.getControl("Book accommodation").click()
3088        self.assertMatches('...Your data are incomplete...',
3089            self.browser.contents)
3090        self.student['studycourse'].current_level = 100
3091        # ... or student is not the an allowed state ...
3092        self.browser.getControl("Book accommodation").click()
3093        self.assertMatches('...You are in the wrong...',
3094                           self.browser.contents)
3095        # Students can still not see the disired hostel selector.
3096        self.assertFalse('desired hostel' in self.browser.contents)
3097        IWorkflowInfo(self.student).fireTransition('admit')
3098        # Students can now see the disired hostel selector.
3099        self.browser.reload()
3100        self.browser.open(self.acco_path)
3101        self.assertTrue('desired hostel' in self.browser.contents)
3102        self.browser.getControl(name="hostel").value = ['hall-2']
3103        self.browser.getControl("Save").click()
3104        self.assertTrue('selection has been saved' in self.browser.contents)
3105        self.assertTrue('<option selected="selected" value="hall-2">'
3106            in self.browser.contents)
3107        self.browser.getControl("Book accommodation").click()
3108        self.assertMatches('...Activation Code:...',
3109                           self.browser.contents)
3110        # Student can't use faked ACs ...
3111        self.browser.getControl(name="ac_series").value = u'nonsense'
3112        self.browser.getControl(name="ac_number").value = sfenumber
3113        self.browser.getControl("Create bed ticket").click()
3114        self.assertMatches('...Activation code is invalid...',
3115                           self.browser.contents)
3116        # ... or ACs owned by somebody else.
3117        ac.owner = u'Anybody'
3118        self.browser.getControl(name="ac_series").value = sfeseries
3119        self.browser.getControl(name="ac_number").value = sfenumber
3120        self.browser.getControl("Create bed ticket").click()
3121        # Hostel 2 has only a bed for women.
3122        self.assertTrue('There is no free bed in your desired hostel'
3123            in self.browser.contents)
3124        self.browser.getControl(name="hostel").value = ['hall-1']
3125        self.browser.getControl("Save").click()
3126        self.browser.getControl("Book accommodation").click()
3127        # Student can't use faked ACs ...
3128        self.browser.getControl(name="ac_series").value = sfeseries
3129        self.browser.getControl(name="ac_number").value = sfenumber
3130        self.browser.getControl("Create bed ticket").click()
3131        self.assertMatches('...You are not the owner of this access code...',
3132                           self.browser.contents)
3133        # The bed remains empty.
3134        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3135        self.assertTrue(bed.owner == NOT_OCCUPIED)
3136        ac.owner = self.student_id
3137        self.browser.getControl(name="ac_series").value = sfeseries
3138        self.browser.getControl(name="ac_number").value = sfenumber
3139        self.browser.getControl("Create bed ticket").click()
3140        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3141                           self.browser.contents)
3142        # Bed has been allocated.
3143        self.assertTrue(bed.owner == self.student_id)
3144        # BedTicketAddPage is now blocked.
3145        self.browser.getControl("Book accommodation").click()
3146        self.assertMatches('...You already booked a bed space...',
3147            self.browser.contents)
3148        # The bed ticket displays the data correctly.
3149        self.browser.open(self.acco_path + '/2004')
3150        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3151                           self.browser.contents)
3152        self.assertMatches('...2004/2005...', self.browser.contents)
3153        self.assertMatches('...regular_male_fr...', self.browser.contents)
3154        self.assertMatches('...%s...' % pin, self.browser.contents)
3155        # Students can open the pdf slip.
3156        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3157        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3158        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3159        # Students can't relocate themselves.
3160        self.assertFalse('Relocate' in self.browser.contents)
3161        relocate_path = self.acco_path + '/2004/relocate'
3162        self.assertRaises(
3163            Unauthorized, self.browser.open, relocate_path)
3164        # Students can't see the Remove button and check boxes.
3165        self.browser.open(self.acco_path)
3166        self.assertFalse('Remove' in self.browser.contents)
3167        self.assertFalse('val_id' in self.browser.contents)
3168        # Students can pay maintenance fee now.
3169        self.browser.open(self.payments_path)
3170        self.browser.open(self.payments_path + '/addop')
3171        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3172        self.browser.getControl("Create ticket").click()
3173        self.assertMatches('...Payment ticket created...',
3174                           self.browser.contents)
3175        ctrl = self.browser.getControl(name='val_id')
3176        value = ctrl.options[0]
3177        # Maintennace fee is taken from the hostel object.
3178        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3179        # If the hostel's maintenance fee isn't set, the fee is
3180        # taken from the session configuration object.
3181        self.app['hostels']['hall-1'].maint_fee = 0.0
3182        self.browser.open(self.payments_path + '/addop')
3183        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3184        self.browser.getControl("Create ticket").click()
3185        ctrl = self.browser.getControl(name='val_id')
3186        value = ctrl.options[1]
3187        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3188        # The bedticket is aware of successfull maintenance fee payment
3189        bedticket = self.student['accommodation']['2004']
3190        self.assertFalse(bedticket.maint_payment_made)
3191        self.student['payments'][value].approve()
3192        self.assertTrue(bedticket.maint_payment_made)
3193        return
3194
3195    def test_change_password_request(self):
3196        self.browser.open('http://localhost/app/changepw')
3197        self.browser.getControl(name="form.identifier").value = '123'
3198        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3199        self.browser.getControl("Send login credentials").click()
3200        self.assertTrue('An email with' in self.browser.contents)
3201
3202    def test_student_expired_personal_data(self):
3203        # Login
3204        IWorkflowState(self.student).setState('school fee paid')
3205        delta = timedelta(days=180)
3206        self.student.personal_updated = datetime.utcnow() - delta
3207        self.browser.open(self.login_path)
3208        self.browser.getControl(name="form.login").value = self.student_id
3209        self.browser.getControl(name="form.password").value = 'spwd'
3210        self.browser.getControl("Login").click()
3211        self.assertEqual(self.browser.url, self.student_path)
3212        self.assertTrue(
3213            'You logged in' in self.browser.contents)
3214        # Students don't see personal_updated field in edit form
3215        self.browser.open(self.edit_personal_path)
3216        self.assertFalse('Updated' in self.browser.contents)
3217        self.browser.open(self.personal_path)
3218        self.assertTrue('Updated' in self.browser.contents)
3219        self.browser.getLink("Logout").click()
3220        delta = timedelta(days=181)
3221        self.student.personal_updated = datetime.utcnow() - delta
3222        self.browser.open(self.login_path)
3223        self.browser.getControl(name="form.login").value = self.student_id
3224        self.browser.getControl(name="form.password").value = 'spwd'
3225        self.browser.getControl("Login").click()
3226        self.assertEqual(self.browser.url, self.edit_personal_path)
3227        self.assertTrue(
3228            'Your personal data record is outdated.' in self.browser.contents)
3229
3230    def test_request_transcript(self):
3231        IWorkflowState(self.student).setState('graduated')
3232        self.browser.open(self.login_path)
3233        self.browser.getControl(name="form.login").value = self.student_id
3234        self.browser.getControl(name="form.password").value = 'spwd'
3235        self.browser.getControl("Login").click()
3236        self.assertMatches(
3237            '...You logged in...', self.browser.contents)
3238        # Create payment ticket
3239        self.browser.open(self.payments_path)
3240        self.browser.open(self.payments_path + '/addop')
3241        self.browser.getControl(name="form.p_category").value = ['transcript']
3242        self.browser.getControl("Create ticket").click()
3243        ctrl = self.browser.getControl(name='val_id')
3244        value = ctrl.options[0]
3245        self.browser.getLink(value).click()
3246        self.assertMatches('...Amount Authorized...',
3247                           self.browser.contents)
3248        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3249        # Student is the payer of the payment ticket.
3250        payer = IPayer(self.student['payments'][value])
3251        self.assertEqual(payer.display_fullname, 'Anna Tester')
3252        self.assertEqual(payer.id, self.student_id)
3253        self.assertEqual(payer.faculty, 'fac1')
3254        self.assertEqual(payer.department, 'dep1')
3255        # We simulate the approval and fetch the pin
3256        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3257        self.browser.open(self.browser.url + '/fake_approve')
3258        self.assertMatches('...Payment approved...',
3259                          self.browser.contents)
3260        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3261        parts = pin.split('-')[1:]
3262        tscseries, tscnumber = parts
3263        # Student can use the pin to send the transcript request
3264        self.browser.open(self.student_path)
3265        self.browser.getLink("Request transcript").click()
3266        self.browser.getControl(name="ac_series").value = tscseries
3267        self.browser.getControl(name="ac_number").value = tscnumber
3268        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3269        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3270        self.browser.getControl("Submit").click()
3271        self.assertMatches('...Transcript processing has been started...',
3272                          self.browser.contents)
3273        self.assertEqual(self.student.state, 'transcript requested')
3274        self.assertMatches(
3275            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3276            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3277            'Address line2\n\n', self.student.transcript_comment)
3278        # The comment has been logged
3279        logfile = os.path.join(
3280            self.app['datacenter'].storage, 'logs', 'students.log')
3281        logcontent = open(logfile).read()
3282        self.assertTrue(
3283            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3284            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3285            in logcontent)
3286
3287    def test_late_registration(self):
3288        # Login
3289        delta = timedelta(days=10)
3290        self.app['configuration'][
3291            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
3292        IWorkflowState(self.student).setState('school fee paid')
3293        self.browser.open(self.login_path)
3294        self.browser.getControl(name="form.login").value = self.student_id
3295        self.browser.getControl(name="form.password").value = 'spwd'
3296        self.browser.getControl("Login").click()
3297        self.browser.open(self.payments_path)
3298        self.browser.open(self.payments_path + '/addop')
3299        self.browser.getControl(name="form.p_category").value = ['late_registration']
3300        self.browser.getControl("Create ticket").click()
3301        self.assertMatches('...ticket created...',
3302                           self.browser.contents)
3303        self.browser.open(self.payments_path)
3304        ctrl = self.browser.getControl(name='val_id')
3305        value = ctrl.options[0]
3306        self.browser.getLink("Study Course").click()
3307        self.browser.getLink("Add course list").click()
3308        self.assertMatches('...Add current level 100 (Year 1)...',
3309                           self.browser.contents)
3310        self.browser.getControl("Create course list now").click()
3311        self.browser.getLink("100").click()
3312        self.browser.getLink("Edit course list").click()
3313        self.browser.getControl("Register course list").click()
3314        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
3315        self.student['payments'][value].approve()
3316        self.browser.getControl("Register course list").click()
3317        self.assertTrue('Course list has been registered' in self.browser.contents)
3318        self.assertEqual(self.student.state, 'courses registered')
3319
3320
3321class StudentRequestPWTests(StudentsFullSetup):
3322    # Tests for student registration
3323
3324    layer = FunctionalLayer
3325
3326    def test_request_pw(self):
3327        # Student with wrong number can't be found.
3328        self.browser.open('http://localhost/app/requestpw')
3329        self.browser.getControl(name="form.lastname").value = 'Tester'
3330        self.browser.getControl(name="form.number").value = 'anynumber'
3331        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3332        self.browser.getControl("Send login credentials").click()
3333        self.assertTrue('No student record found.'
3334            in self.browser.contents)
3335        # Anonymous is not informed that lastname verification failed.
3336        # It seems that the record doesn't exist.
3337        self.browser.open('http://localhost/app/requestpw')
3338        self.browser.getControl(name="form.lastname").value = 'Johnny'
3339        self.browser.getControl(name="form.number").value = '123'
3340        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3341        self.browser.getControl("Send login credentials").click()
3342        self.assertTrue('No student record found.'
3343            in self.browser.contents)
3344        # Even with the correct lastname we can't register if a
3345        # password has been set and used.
3346        self.browser.getControl(name="form.lastname").value = 'Tester'
3347        self.browser.getControl(name="form.number").value = '123'
3348        self.browser.getControl("Send login credentials").click()
3349        self.assertTrue('Your password has already been set and used.'
3350            in self.browser.contents)
3351        self.browser.open('http://localhost/app/requestpw')
3352        self.app['students'][self.student_id].password = None
3353        # The lastname field, used for verification, is not case-sensitive.
3354        self.browser.getControl(name="form.lastname").value = 'tESTer'
3355        self.browser.getControl(name="form.number").value = '123'
3356        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3357        self.browser.getControl("Send login credentials").click()
3358        # Yeah, we succeded ...
3359        self.assertTrue('Your password request was successful.'
3360            in self.browser.contents)
3361        # We can also use the matric_number instead.
3362        self.browser.open('http://localhost/app/requestpw')
3363        self.browser.getControl(name="form.lastname").value = 'tESTer'
3364        self.browser.getControl(name="form.number").value = '234'
3365        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3366        self.browser.getControl("Send login credentials").click()
3367        self.assertTrue('Your password request was successful.'
3368            in self.browser.contents)
3369        # ... and  student can be found in the catalog via the email address
3370        cat = queryUtility(ICatalog, name='students_catalog')
3371        results = list(
3372            cat.searchResults(
3373            email=('new@yy.zz', 'new@yy.zz')))
3374        self.assertEqual(self.student,results[0])
3375        logfile = os.path.join(
3376            self.app['datacenter'].storage, 'logs', 'main.log')
3377        logcontent = open(logfile).read()
3378        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3379                        '234 (K1000000) - new@yy.zz' in logcontent)
3380        return
3381
3382    def test_student_locked_level_forms(self):
3383
3384        # Add two study levels, one current and one previous
3385        studylevel = createObject(u'waeup.StudentStudyLevel')
3386        studylevel.level = 100
3387        self.student['studycourse'].addStudentStudyLevel(
3388            self.certificate, studylevel)
3389        studylevel = createObject(u'waeup.StudentStudyLevel')
3390        studylevel.level = 200
3391        self.student['studycourse'].addStudentStudyLevel(
3392            self.certificate, studylevel)
3393        IWorkflowState(self.student).setState('school fee paid')
3394        self.student['studycourse'].current_level = 200
3395
3396        self.browser.open(self.login_path)
3397        self.browser.getControl(name="form.login").value = self.student_id
3398        self.browser.getControl(name="form.password").value = 'spwd'
3399        self.browser.getControl("Login").click()
3400
3401        self.browser.open(self.student_path + '/studycourse/200/edit')
3402        self.assertFalse('The requested form is locked' in self.browser.contents)
3403        self.browser.open(self.student_path + '/studycourse/100/edit')
3404        self.assertTrue('The requested form is locked' in self.browser.contents)
3405
3406        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3407        self.assertFalse('The requested form is locked' in self.browser.contents)
3408        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3409        self.assertTrue('The requested form is locked' in self.browser.contents)
3410
3411        IWorkflowState(self.student).setState('courses registered')
3412        self.browser.open(self.student_path + '/studycourse/200/edit')
3413        self.assertTrue('The requested form is locked' in self.browser.contents)
3414        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3415        self.assertTrue('The requested form is locked' in self.browser.contents)
3416
3417
3418class PublicPagesTests(StudentsFullSetup):
3419    # Tests for simple webservices
3420
3421    layer = FunctionalLayer
3422
3423    def test_paymentrequest(self):
3424        payment = createObject('waeup.StudentOnlinePayment')
3425        payment.p_category = u'schoolfee'
3426        payment.p_session = self.student.current_session
3427        payment.p_item = u'My Certificate'
3428        payment.p_id = u'anyid'
3429        self.student['payments']['anykey'] = payment
3430        # Request information about unpaid payment ticket
3431        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3432        self.assertEqual(self.browser.contents, '-1')
3433        # Request information about paid payment ticket
3434        payment.p_state = u'paid'
3435        notify(grok.ObjectModifiedEvent(payment))
3436        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3437        self.assertEqual(self.browser.contents,
3438            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3439            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3440            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3441            '&FEE_AMOUNT=0.0')
3442        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3443        self.assertEqual(self.browser.contents, '-1')
3444        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3445        self.assertEqual(self.browser.contents, '-1')
3446
3447class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3448    # Tests for StudentsContainer class views and pages
3449
3450    layer = FunctionalLayer
3451
3452    def wait_for_export_job_completed(self):
3453        # helper function waiting until the current export job is completed
3454        manager = getUtility(IJobManager)
3455        job_id = self.app['datacenter'].running_exports[0][0]
3456        job = manager.get(job_id)
3457        wait_for_result(job)
3458        return job_id
3459
3460    def test_datacenter_export(self):
3461        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3462        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3463        self.browser.getControl(name="exporter").value = ['bursary']
3464        self.browser.getControl(name="session").value = ['2004']
3465        self.browser.getControl(name="level").value = ['100']
3466        self.browser.getControl(name="mode").value = ['ug_ft']
3467        self.browser.getControl(name="payments_start").value = '13/12/2012'
3468        self.browser.getControl(name="payments_end").value = '14/12/2012'
3469        self.browser.getControl("Create CSV file").click()
3470
3471        # When the job is finished and we reload the page...
3472        job_id = self.wait_for_export_job_completed()
3473        # ... the csv file can be downloaded ...
3474        self.browser.open('http://localhost/app/datacenter/@@export')
3475        self.browser.getLink("Download").click()
3476        self.assertEqual(self.browser.headers['content-type'],
3477            'text/csv; charset=UTF-8')
3478        self.assertTrue(
3479            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3480            self.browser.headers['content-disposition'])
3481        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3482        job_id = self.app['datacenter'].running_exports[0][0]
3483        # ... and discarded
3484        self.browser.open('http://localhost/app/datacenter/@@export')
3485        self.browser.getControl("Discard").click()
3486        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3487        # Creation, downloading and discarding is logged
3488        logfile = os.path.join(
3489            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3490        logcontent = open(logfile).read()
3491        self.assertTrue(
3492            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3493            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3494            '13/12/2012, 14/12/2012), job_id=%s'
3495            % job_id in logcontent
3496            )
3497        self.assertTrue(
3498            'zope.mgr - browser.pages.ExportCSVView '
3499            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3500            % (job_id, job_id) in logcontent
3501            )
3502        self.assertTrue(
3503            'zope.mgr - browser.pages.ExportCSVPage '
3504            '- discarded: job_id=%s' % job_id in logcontent
3505            )
3506
3507    def test_datacenter_export_selected(self):
3508        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3509        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3510        self.browser.getControl(name="exporter").value = ['students']
3511        self.browser.getControl(name="students").value = 'K1000000'
3512        self.browser.getControl("Create CSV file").click()
3513        # When the job is finished and we reload the page...
3514        job_id = self.wait_for_export_job_completed()
3515        # ... the csv file can be downloaded ...
3516        self.browser.open('http://localhost/app/datacenter/@@export')
3517        self.browser.getLink("Download").click()
3518        self.assertEqual(self.browser.headers['content-type'],
3519            'text/csv; charset=UTF-8')
3520        self.assertTrue(
3521            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3522            self.browser.headers['content-disposition'])
3523        self.assertTrue(
3524            'adm_code,clr_code,date_of_birth,email,employer,'
3525            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
3526            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3527            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3528            'password,state,history,certcode,is_postgrad,current_level,'
3529            'current_session\r\n'
3530            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
3531            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3532
3533    def test_payment_dates(self):
3534        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3535        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3536        self.browser.getControl(name="exporter").value = ['bursary']
3537        self.browser.getControl(name="session").value = ['2004']
3538        self.browser.getControl(name="level").value = ['100']
3539        self.browser.getControl(name="mode").value = ['ug_ft']
3540        self.browser.getControl(name="payments_start").value = '13/12/2012'
3541        # If one payment date is missing, an error message appears
3542        self.browser.getControl(name="payments_end").value = ''
3543        self.browser.getControl("Create CSV file").click()
3544        self.assertTrue('Payment dates do not match format d/m/Y'
3545            in self.browser.contents)
3546
3547    def test_faculties_export(self):
3548        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3549        facs_path = 'http://localhost/app/faculties'
3550        self.browser.open(facs_path)
3551        self.browser.getLink("Export student data").click()
3552        self.browser.getControl("Configure new export").click()
3553        self.browser.getControl(name="exporter").value = ['bursary']
3554        self.browser.getControl(name="session").value = ['2004']
3555        self.browser.getControl(name="level").value = ['100']
3556        self.browser.getControl(name="mode").value = ['ug_ft']
3557        self.browser.getControl(name="payments_start").value = '13/12/2012'
3558        self.browser.getControl(name="payments_end").value = '14/12/2012'
3559        self.browser.getControl("Create CSV file").click()
3560
3561        # When the job is finished and we reload the page...
3562        job_id = self.wait_for_export_job_completed()
3563        self.browser.open(facs_path + '/exports')
3564        # ... the csv file can be downloaded ...
3565        self.browser.getLink("Download").click()
3566        self.assertEqual(self.browser.headers['content-type'],
3567            'text/csv; charset=UTF-8')
3568        self.assertTrue(
3569            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3570            self.browser.headers['content-disposition'])
3571        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3572        job_id = self.app['datacenter'].running_exports[0][0]
3573        # ... and discarded
3574        self.browser.open(facs_path + '/exports')
3575        self.browser.getControl("Discard").click()
3576        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3577        # Creation, downloading and discarding is logged
3578        logfile = os.path.join(
3579            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3580        logcontent = open(logfile).read()
3581        self.assertTrue(
3582            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3583            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3584            '13/12/2012, 14/12/2012), job_id=%s'
3585            % job_id in logcontent
3586            )
3587        self.assertTrue(
3588            'zope.mgr - students.browser.ExportJobContainerDownload '
3589            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3590            % (job_id, job_id) in logcontent
3591            )
3592        self.assertTrue(
3593            'zope.mgr - students.browser.ExportJobContainerOverview '
3594            '- discarded: job_id=%s' % job_id in logcontent
3595            )
3596
3597    def test_faculty_export(self):
3598        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3599        fac1_path = 'http://localhost/app/faculties/fac1'
3600        self.browser.open(fac1_path)
3601        self.browser.getLink("Export student data").click()
3602        self.browser.getControl("Configure new export").click()
3603        self.browser.getControl(name="exporter").value = ['students']
3604        self.browser.getControl(name="session").value = ['2004']
3605        self.browser.getControl(name="level").value = ['100']
3606        self.browser.getControl(name="mode").value = ['ug_ft']
3607        # The testbrowser does not hide the payment period fields, but
3608        # values are ignored when using the students exporter.
3609        self.browser.getControl(name="payments_start").value = '13/12/2012'
3610        self.browser.getControl(name="payments_end").value = '14/12/2012'
3611        self.browser.getControl("Create CSV file").click()
3612
3613        # When the job is finished and we reload the page...
3614        job_id = self.wait_for_export_job_completed()
3615        self.browser.open(fac1_path + '/exports')
3616        # ... the csv file can be downloaded ...
3617        self.browser.getLink("Download").click()
3618        self.assertEqual(self.browser.headers['content-type'],
3619            'text/csv; charset=UTF-8')
3620        self.assertTrue(
3621            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3622            self.browser.headers['content-disposition'])
3623        self.assertTrue(
3624            'adm_code,clr_code,date_of_birth,email,employer,'
3625            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
3626            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3627            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3628            'password,state,history,certcode,is_postgrad,current_level,'
3629            'current_session\r\n'
3630            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
3631            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3632        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3633        job_id = self.app['datacenter'].running_exports[0][0]
3634        # ... and discarded
3635        self.browser.open(fac1_path + '/exports')
3636        self.browser.getControl("Discard").click()
3637        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3638        # Creation, downloading and discarding is logged
3639        logfile = os.path.join(
3640            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3641        logcontent = open(logfile).read()
3642        self.assertTrue(
3643            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
3644            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
3645            '13/12/2012, 14/12/2012), job_id=%s'
3646            % job_id in logcontent
3647            )
3648        self.assertTrue(
3649            'zope.mgr - students.browser.ExportJobContainerDownload '
3650            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3651            % (job_id, job_id) in logcontent
3652            )
3653        self.assertTrue(
3654            'zope.mgr - students.browser.ExportJobContainerOverview '
3655            '- discarded: job_id=%s' % job_id in logcontent
3656            )
3657
3658    def test_department_export(self):
3659        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3660        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3661        self.browser.open(dep1_path)
3662        self.browser.getLink("Export student data").click()
3663        self.browser.getControl("Configure new export").click()
3664        self.browser.getControl(name="exporter").value = ['students']
3665        self.browser.getControl(name="session").value = ['2004']
3666        self.browser.getControl(name="level").value = ['100']
3667        self.browser.getControl(name="mode").value = ['ug_ft']
3668        # The testbrowser does not hide the payment period fields, but
3669        # values are ignored when using the students exporter.
3670        self.browser.getControl(name="payments_start").value = '13/12/2012'
3671        self.browser.getControl(name="payments_end").value = '14/12/2012'
3672        self.browser.getControl("Create CSV file").click()
3673
3674        # When the job is finished and we reload the page...
3675        job_id = self.wait_for_export_job_completed()
3676        self.browser.open(dep1_path + '/exports')
3677        # ... the csv file can be downloaded ...
3678        self.browser.getLink("Download").click()
3679        self.assertEqual(self.browser.headers['content-type'],
3680            'text/csv; charset=UTF-8')
3681        self.assertTrue(
3682            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3683            self.browser.headers['content-disposition'])
3684        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3685        job_id = self.app['datacenter'].running_exports[0][0]
3686        # ... and discarded
3687        self.browser.open(dep1_path + '/exports')
3688        self.browser.getControl("Discard").click()
3689        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3690        # Creation, downloading and discarding is logged
3691        logfile = os.path.join(
3692            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3693        logcontent = open(logfile).read()
3694        self.assertTrue(
3695            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3696            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
3697            '13/12/2012, 14/12/2012), job_id=%s'
3698            % job_id in logcontent
3699            )
3700        self.assertTrue(
3701            'zope.mgr - students.browser.ExportJobContainerDownload '
3702            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3703            % (job_id, job_id) in logcontent
3704            )
3705        self.assertTrue(
3706            'zope.mgr - students.browser.ExportJobContainerOverview '
3707            '- discarded: job_id=%s' % job_id in logcontent
3708            )
3709
3710    def test_certificate_export(self):
3711        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3712        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3713        self.browser.open(cert1_path)
3714        self.browser.getLink("Export student data").click()
3715        self.browser.getControl("Configure new export").click()
3716        self.browser.getControl(name="exporter").value = ['students']
3717        self.browser.getControl(name="session").value = ['2004']
3718        self.browser.getControl(name="level").value = ['100']
3719        self.browser.getControl("Create CSV file").click()
3720
3721        # When the job is finished and we reload the page...
3722        job_id = self.wait_for_export_job_completed()
3723        self.browser.open(cert1_path + '/exports')
3724        # ... the csv file can be downloaded ...
3725        self.browser.getLink("Download").click()
3726        self.assertEqual(self.browser.headers['content-type'],
3727            'text/csv; charset=UTF-8')
3728        self.assertTrue(
3729            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3730            self.browser.headers['content-disposition'])
3731        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3732        job_id = self.app['datacenter'].running_exports[0][0]
3733        # ... and discarded
3734        self.browser.open(cert1_path + '/exports')
3735        self.browser.getControl("Discard").click()
3736        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3737        # Creation, downloading and discarding is logged
3738        logfile = os.path.join(
3739            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3740        logcontent = open(logfile).read()
3741        self.assertTrue(
3742            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3743            '- exported: students (2004, 100, None, None, None, CERT1, None, None), '
3744            'job_id=%s'
3745            % job_id in logcontent
3746            )
3747        self.assertTrue(
3748            'zope.mgr - students.browser.ExportJobContainerDownload '
3749            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3750            % (job_id, job_id) in logcontent
3751            )
3752        self.assertTrue(
3753            'zope.mgr - students.browser.ExportJobContainerOverview '
3754            '- discarded: job_id=%s' % job_id in logcontent
3755            )
3756
3757    def deprecated_test_course_export_students(self):
3758        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3759        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3760        self.browser.open(course1_path)
3761        self.browser.getLink("Export student data").click()
3762        self.browser.getControl("Configure new export").click()
3763        self.browser.getControl(name="exporter").value = ['students']
3764        self.browser.getControl(name="session").value = ['2004']
3765        self.browser.getControl(name="level").value = ['100']
3766        self.browser.getControl("Create CSV file").click()
3767
3768        # When the job is finished and we reload the page...
3769        job_id = self.wait_for_export_job_completed()
3770        self.browser.open(course1_path + '/exports')
3771        # ... the csv file can be downloaded ...
3772        self.browser.getLink("Download").click()
3773        self.assertEqual(self.browser.headers['content-type'],
3774            'text/csv; charset=UTF-8')
3775        self.assertTrue(
3776            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3777            self.browser.headers['content-disposition'])
3778        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3779        job_id = self.app['datacenter'].running_exports[0][0]
3780        # ... and discarded
3781        self.browser.open(course1_path + '/exports')
3782        self.browser.getControl("Discard").click()
3783        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3784        # Creation, downloading and discarding is logged
3785        logfile = os.path.join(
3786            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3787        logcontent = open(logfile).read()
3788        self.assertTrue(
3789            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3790            '- exported: students (2004, 100, COURSE1), job_id=%s'
3791            % job_id in logcontent
3792            )
3793        self.assertTrue(
3794            'zope.mgr - students.browser.ExportJobContainerDownload '
3795            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3796            % (job_id, job_id) in logcontent
3797            )
3798        self.assertTrue(
3799            'zope.mgr - students.browser.ExportJobContainerOverview '
3800            '- discarded: job_id=%s' % job_id in logcontent
3801            )
3802
3803    def test_course_export_lecturer(self):
3804        # We add study level 100 to the student's studycourse
3805        studylevel = StudentStudyLevel()
3806        studylevel.level = 100
3807        studylevel.level_session = 2004
3808        self.student['studycourse'].addStudentStudyLevel(
3809            self.certificate,studylevel)
3810        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3811        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3812        self.browser.open(course1_path)
3813        self.browser.getLink("Export student data").click()
3814        self.browser.getControl("Configure new export").click()
3815        self.assertTrue(
3816            'Academic session not set. Please contact the administrator.'
3817            in self.browser.contents)
3818        self.app['configuration'].current_academic_session = 2004
3819        self.browser.getControl("Configure new export").click()
3820        self.browser.getControl(name="exporter").value = ['lecturer']
3821        self.browser.getControl(name="session").value = ['2004']
3822        self.browser.getControl(name="level").value = ['100']
3823        self.browser.getControl("Create CSV file").click()
3824        # When the job is finished and we reload the page...
3825        job_id = self.wait_for_export_job_completed()
3826        self.browser.open(course1_path + '/exports')
3827        # ... the csv file can be downloaded ...
3828        self.browser.getLink("Download").click()
3829        self.assertEqual(self.browser.headers['content-type'],
3830            'text/csv; charset=UTF-8')
3831        self.assertTrue(
3832            'filename="WAeUP.Kofa_lecturer_%s.csv' % job_id in
3833            self.browser.headers['content-disposition'])
3834        # ... and contains the course ticket COURSE1
3835        self.assertEqual(self.browser.contents,
3836            'matric_number,reg_number,student_id,display_fullname,level,code,'
3837            'level_session,score\r\n,,K1000000,,100,COURSE1,2004,\r\n')
3838        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3839        job_id = self.app['datacenter'].running_exports[0][0]
3840        # Thew job can be discarded
3841        self.browser.open(course1_path + '/exports')
3842        self.browser.getControl("Discard").click()
3843        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3844        # Creation, downloading and discarding is logged
3845        logfile = os.path.join(
3846            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3847        logcontent = open(logfile).read()
3848        self.assertTrue(
3849            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3850            '- exported: lecturer (2004, 100, COURSE1), job_id=%s'
3851            % job_id in logcontent
3852            )
3853        self.assertTrue(
3854            'zope.mgr - students.browser.ExportJobContainerDownload '
3855            '- downloaded: WAeUP.Kofa_lecturer_%s.csv, job_id=%s'
3856            % (job_id, job_id) in logcontent
3857            )
3858        self.assertTrue(
3859            'zope.mgr - students.browser.ExportJobContainerOverview '
3860            '- discarded: job_id=%s' % job_id in logcontent
3861            )
3862
3863    def test_export_departmet_officers(self):
3864        # Create department officer
3865        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3866        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3867        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3868        # Assign local role
3869        department = self.app['faculties']['fac1']['dep1']
3870        prmlocal = IPrincipalRoleManager(department)
3871        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3872        # Login as department officer
3873        self.browser.open(self.login_path)
3874        self.browser.getControl(name="form.login").value = 'mrdepartment'
3875        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3876        self.browser.getControl("Login").click()
3877        self.assertMatches('...You logged in...', self.browser.contents)
3878        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3879        self.browser.getLink("Export student data").click()
3880        self.browser.getControl("Configure new export").click()
3881        # Only the paymentsoverview exporter is available for department officers
3882        self.assertFalse('<option value="students">' in self.browser.contents)
3883        self.assertTrue(
3884            '<option value="paymentsoverview">' in self.browser.contents)
3885        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3886        self.browser.getControl(name="session").value = ['2004']
3887        self.browser.getControl(name="level").value = ['100']
3888        self.browser.getControl("Create CSV file").click()
3889        self.assertTrue('Export started' in self.browser.contents)
3890        # Thew job can be discarded
3891        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3892        #job_id = self.app['datacenter'].running_exports[0][0]
3893        job_id = self.wait_for_export_job_completed()
3894        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3895        self.browser.getControl("Discard").click()
3896        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3897
3898    def test_export_bursary_officers(self):
3899        # Create bursary officer
3900        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3901        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3902        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3903        prmglobal = IPrincipalRoleManager(self.app)
3904        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3905        # Login as bursary officer
3906        self.browser.open(self.login_path)
3907        self.browser.getControl(name="form.login").value = 'mrbursary'
3908        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3909        self.browser.getControl("Login").click()
3910        self.assertMatches('...You logged in...', self.browser.contents)
3911        self.browser.getLink("Academics").click()
3912        self.browser.getLink("Export student data").click()
3913        self.browser.getControl("Configure new export").click()
3914        # Only the bursary exporter is available for bursary officers
3915        # not only at facultiescontainer level ...
3916        self.assertFalse('<option value="students">' in self.browser.contents)
3917        self.assertTrue('<option value="bursary">' in self.browser.contents)
3918        self.browser.getControl(name="exporter").value = ['bursary']
3919        self.browser.getControl(name="session").value = ['2004']
3920        self.browser.getControl(name="level").value = ['100']
3921        self.browser.getControl("Create CSV file").click()
3922        self.assertTrue('Export started' in self.browser.contents)
3923        # ... but also at other levels
3924        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3925        self.browser.getLink("Export student data").click()
3926        self.browser.getControl("Configure new export").click()
3927        self.assertFalse('<option value="students">' in self.browser.contents)
3928        self.assertTrue('<option value="bursary">' in self.browser.contents)
3929        # Thew job can be discarded
3930        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3931        #job_id = self.app['datacenter'].running_exports[0][0]
3932        job_id = self.wait_for_export_job_completed()
3933        self.browser.open('http://localhost/app/faculties/exports')
3934        self.browser.getControl("Discard").click()
3935        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.