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

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

Configure transfer payments and let students enter their desired
study course. Save entered text in p_item attribute.

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