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

Last change on this file since 16153 was 16120, checked in by Henrik Bettermann, 4 years ago

Finetune transcript processing. Allow transcript officers to request a transcript.

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