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

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

Fix typo.

  • Property svn:keywords set to Id
File size: 254.0 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: test_browser.py 16194 2020-08-11 12:04:02Z 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_temp_clearance_by_co(self):
1682        self.app['configuration']['2004'].clearance_enabled = True
1683        self.init_clearance_officer()
1684        # Hide real name.
1685        self.app['users']['mrclear'].public_name = 'My Public Name'
1686        IWorkflowInfo(self.student).fireTransition('request_clearance')
1687        #notify(LocalRoleSetEvent(
1688        #    self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1689        #    granted=True))
1690        # CO can view the student ...
1691        self.browser.open(self.clearance_path)
1692        self.browser.getLink("Clear student temporarily").click()
1693        self.assertEqual(
1694            self.browser.url, self.student_path + '/temp_clearance')
1695        # Type comment why.
1696        self.browser.getControl(name="form.officer_comment").value = (
1697            'Dear Student,\n'
1698            'Your birth cert is missing.')
1699        self.browser.getControl(
1700            'Save comment and clear student temporarily now').click()
1701        self.assertTrue('Student has been temporarily cleared.'
1702            in self.browser.contents)
1703        url = ('http://localhost/app/students/K1000000/'
1704              'contactstudent?body=Dear+Student%2C%0AYour+birth+cert+is+missing.'
1705              '&subject=You+have+been+temporarily+cleared.')
1706        # CO does now see the prefilled contact form and can send a message.
1707        self.assertEqual(self.browser.url, url)
1708        self.assertTrue('clearance requested' in self.browser.contents)
1709        self.assertTrue('name="form.subject" size="20" type="text" '
1710            'value="You have been temporarily cleared."'
1711            in self.browser.contents)
1712        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1713            in self.browser.contents)
1714        self.browser.getControl("Send message now").click()
1715        self.assertTrue('Your message has been sent' in self.browser.contents)
1716        # The comment has been stored ...
1717        self.assertEqual(self.student.officer_comment,
1718            'Temporarily cleared by My Public Name. Officer\'s comment:\n'
1719            'Dear Student,\nYour birth cert is missing.')
1720        # ... and logged.
1721        logfile = os.path.join(
1722            self.app['datacenter'].storage, 'logs', 'students.log')
1723        logcontent = open(logfile).read()
1724        self.assertTrue(
1725            'INFO - mrclear - students.browser.StudentTempClearancePage - '
1726            'K1000000 - comment: Dear Student,<br>Your birth cert is '
1727            'missing.\n' in logcontent)
1728        # CO can view the student bus no longer see the temp clearance button ...
1729        self.browser.open(self.clearance_path)
1730        self.assertFalse('Clear student temporarily' in self.browser.contents)
1731        return
1732
1733    def test_handle_mass_clearance_by_co(self):
1734        self.init_clearance_officer()
1735        # Additional setups according to test above
1736        notify(LocalRoleSetEvent(
1737            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1738            granted=True))
1739        self.app['configuration']['2004'].clearance_enabled = True
1740        IWorkflowState(self.student).setState('clearance requested')
1741        # Update the catalog
1742        notify(grok.ObjectModifiedEvent(self.student))
1743        # The CO can go to the department and clear all students in department
1744        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1745        self.browser.getLink("Clear all students").click()
1746        self.assertTrue('1 students have been cleared' in self.browser.contents)
1747        self.browser.open(self.history_path)
1748        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1749        logfile = os.path.join(
1750            self.app['datacenter'].storage, 'logs', 'students.log')
1751        logcontent = open(logfile).read()
1752        self.assertTrue(
1753            'INFO - mrclear - K1000000 - Cleared' in logcontent)
1754        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1755        self.browser.getLink("Clear all students").click()
1756        self.assertTrue('0 students have been cleared' in self.browser.contents)
1757        return
1758
1759    def test_handle_courses_by_ca(self):
1760        self.app['users'].addUser('mrsadvise', SECRET)
1761        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1762        self.app['users']['mrsadvise'].title = u'Helen Procter'
1763        # Assign local CourseAdviser100 role for a certificate
1764        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1765        prmlocal = IPrincipalRoleManager(cert)
1766        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1767        IWorkflowState(self.student).setState('school fee paid')
1768        # Login as course adviser.
1769        self.browser.open(self.login_path)
1770        self.browser.getControl(name="form.login").value = 'mrsadvise'
1771        self.browser.getControl(name="form.password").value = SECRET
1772        self.browser.getControl("Login").click()
1773        self.assertMatches('...You logged in...', self.browser.contents)
1774        # CO can see his roles.
1775        self.browser.getLink("My Roles").click()
1776        self.assertMatches(
1777            '...<div>Academics Officer (view only)</div>...',
1778            self.browser.contents)
1779        # But not his local role ...
1780        self.assertFalse('Course Adviser' in self.browser.contents)
1781        # ... because we forgot to notify the certificate that the local role
1782        # has changed.
1783        notify(LocalRoleSetEvent(
1784            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1785        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1786        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1787        self.assertMatches(
1788            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1789            self.browser.contents)
1790        # CA can view the student ...
1791        self.browser.open(self.student_path)
1792        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1793        self.assertEqual(self.browser.url, self.student_path)
1794        # ... but not other students.
1795        other_student = Student()
1796        other_student.firstname = u'Dep2'
1797        other_student.lastname = u'Student'
1798        self.app['students'].addStudent(other_student)
1799        other_student_path = (
1800            'http://localhost/app/students/%s' % other_student.student_id)
1801        self.assertRaises(
1802            Unauthorized, self.browser.open, other_student_path)
1803        # We add study level 110 to the student's studycourse.
1804        studylevel = StudentStudyLevel()
1805        studylevel.level = 110
1806        self.student['studycourse'].addStudentStudyLevel(
1807            cert,studylevel)
1808        L110_student_path = self.studycourse_path + '/110'
1809        # The CA can neither see the Validate nor the Edit button.
1810        self.browser.open(L110_student_path)
1811        self.assertFalse('Validate courses' in self.browser.contents)
1812        self.assertFalse('Edit' in self.browser.contents)
1813        IWorkflowInfo(self.student).fireTransition('register_courses')
1814        self.browser.open(L110_student_path)
1815        self.assertFalse('Validate courses' in self.browser.contents)
1816        self.assertFalse('Edit' in self.browser.contents)
1817        # Only in state courses registered and only if the current level
1818        # corresponds with the name of the study level object
1819        # the 100L CA does see the 'Validate' button but not
1820        # the edit button.
1821        self.student['studycourse'].current_level = 110
1822        self.browser.open(L110_student_path)
1823        self.assertFalse('Edit' in self.browser.contents)
1824        self.assertTrue('Validate courses' in self.browser.contents)
1825        # But a 100L CA does not see the button at other levels.
1826        studylevel2 = StudentStudyLevel()
1827        studylevel2.level = 200
1828        self.student['studycourse'].addStudentStudyLevel(
1829            cert,studylevel2)
1830        L200_student_path = self.studycourse_path + '/200'
1831        self.browser.open(L200_student_path)
1832        self.assertFalse('Edit' in self.browser.contents)
1833        self.assertFalse('Validate courses' in self.browser.contents)
1834        self.browser.open(L110_student_path)
1835        self.browser.getLink("Validate courses").click()
1836        self.assertTrue('Course list has been validated' in self.browser.contents)
1837        self.assertTrue('courses validated' in self.browser.contents)
1838        self.assertEqual(self.student['studycourse']['110'].validated_by,
1839            'Helen Procter')
1840        self.assertMatches(
1841            '<YYYY-MM-DD hh:mm:ss>',
1842            self.student['studycourse']['110'].validation_date.strftime(
1843                "%Y-%m-%d %H:%M:%S"))
1844        self.browser.getLink("Reject courses").click()
1845        self.assertTrue('Course list request has been annulled.'
1846            in self.browser.contents)
1847        urlmessage = 'Course+list+request+has+been+annulled.'
1848        self.assertEqual(self.browser.url, self.student_path +
1849            '/contactstudent?subject=%s' % urlmessage)
1850        self.assertTrue('school fee paid' in self.browser.contents)
1851        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1852        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1853        IWorkflowInfo(self.student).fireTransition('register_courses')
1854        self.browser.open(L110_student_path)
1855        self.browser.getLink("Reject courses").click()
1856        self.assertTrue('Course list has been unregistered'
1857            in self.browser.contents)
1858        self.assertTrue('school fee paid' in self.browser.contents)
1859        # CA does now see the contact form and can send a message.
1860        self.browser.getControl(name="form.subject").value = 'Important subject'
1861        self.browser.getControl(name="form.body").value = 'Course list rejected'
1862        self.browser.getControl("Send message now").click()
1863        self.assertTrue('Your message has been sent' in self.browser.contents)
1864        # The CA does now see the Edit button and can edit
1865        # current study level.
1866        self.browser.open(L110_student_path)
1867        self.browser.getLink("Edit").click()
1868        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1869            in self.browser.contents)
1870        # The CA can't validate courses if not in state
1871        # courses registered.
1872        self.browser.open(L110_student_path + '/validate_courses')
1873        self.assertTrue('Student is in the wrong state'
1874            in self.browser.contents)
1875        # The CA can go to his certificate through the my_roles page ...
1876        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1877        self.browser.getLink(
1878            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1879        # ... and view the list of students.
1880        self.browser.getLink("Show students").click()
1881        self.browser.getControl(name="session").value = ['2004']
1882        self.browser.getControl(name="level").value = ['100']
1883        self.browser.getControl("Show").click()
1884        self.assertTrue(self.student_id in self.browser.contents)
1885
1886    def test_change_current_mode(self):
1887        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1888        self.browser.open(self.clearance_path)
1889        self.assertFalse('Employer' in self.browser.contents)
1890        self.browser.open(self.manage_clearance_path)
1891        self.assertFalse('Employer' in self.browser.contents)
1892        self.browser.open(self.edit_clearance_path)
1893        self.assertFalse('Employer' in self.browser.contents)
1894        # Now we change the study mode of the certificate and a different
1895        # interface is used by clearance views.
1896        self.certificate.study_mode = 'pg_ft'
1897        # Invariants are not being checked here?!
1898        self.certificate.end_level = 100
1899        self.browser.open(self.clearance_path)
1900        self.assertTrue('Employer' in self.browser.contents)
1901        self.browser.open(self.manage_clearance_path)
1902        self.assertTrue('Employer' in self.browser.contents)
1903        IWorkflowState(self.student).setState('clearance started')
1904        self.browser.open(self.edit_clearance_path)
1905        self.assertTrue('Employer' in self.browser.contents)
1906
1907    def test_find_students_in_faculties(self):
1908        # Create local students manager in faculty
1909        self.app['users'].addUser('mrmanager', SECRET)
1910        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1911        self.app['users']['mrmanager'].title = u'Volk Wagen'
1912        # Assign LocalStudentsManager role for faculty
1913        fac = self.app['faculties']['fac1']
1914        prmlocal = IPrincipalRoleManager(fac)
1915        prmlocal.assignRoleToPrincipal(
1916            'waeup.local.LocalStudentsManager', 'mrmanager')
1917        notify(LocalRoleSetEvent(
1918            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1919            granted=True))
1920        # Login as manager
1921        self.browser.open(self.login_path)
1922        self.browser.getControl(name="form.login").value = 'mrmanager'
1923        self.browser.getControl(name="form.password").value = SECRET
1924        self.browser.getControl("Login").click()
1925        self.assertMatches('...You logged in...', self.browser.contents)
1926        # Manager can see his roles
1927        self.browser.getLink("My Roles").click()
1928        self.assertMatches(
1929            '...<span>Students Manager</span>...',
1930            self.browser.contents)
1931        # The manager can go to his faculty
1932        self.browser.getLink(
1933            "http://localhost/app/faculties/fac1").click()
1934        # and find students
1935        self.browser.getLink("Find students").click()
1936        self.browser.getControl("Find student").click()
1937        self.assertTrue('Empty search string' in self.browser.contents)
1938        self.browser.getControl(name="searchtype").value = ['student_id']
1939        self.browser.getControl(name="searchterm").value = self.student_id
1940        self.browser.getControl("Find student").click()
1941        self.assertTrue('Anna Tester' in self.browser.contents)
1942
1943    def test_activate_deactivate_buttons(self):
1944        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1945        self.browser.open(self.student_path)
1946        self.browser.getLink("Deactivate").click()
1947        self.assertTrue(
1948            'Student account has been deactivated.' in self.browser.contents)
1949        self.assertTrue(
1950            'Base Data (account deactivated)' in self.browser.contents)
1951        self.assertTrue(self.student.suspended)
1952        self.browser.getLink("Activate").click()
1953        self.assertTrue(
1954            'Student account has been activated.' in self.browser.contents)
1955        self.assertFalse(
1956            'Base Data (account deactivated)' in self.browser.contents)
1957        self.assertFalse(self.student.suspended)
1958        # History messages have been added ...
1959        self.browser.getLink("History").click()
1960        self.assertTrue(
1961            'Student account deactivated by Manager<br />' in self.browser.contents)
1962        self.assertTrue(
1963            'Student account activated by Manager<br />' in self.browser.contents)
1964        # ... and actions have been logged.
1965        logfile = os.path.join(
1966            self.app['datacenter'].storage, 'logs', 'students.log')
1967        logcontent = open(logfile).read()
1968        self.assertTrue('zope.mgr - students.browser.StudentDeactivateView - '
1969                        'K1000000 - account deactivated' in logcontent)
1970        self.assertTrue('zope.mgr - students.browser.StudentActivateView - '
1971                        'K1000000 - account activated' in logcontent)
1972
1973    def test_manage_student_transfer(self):
1974        # Add second certificate
1975        self.certificate2 = createObject('waeup.Certificate')
1976        self.certificate2.code = u'CERT2'
1977        self.certificate2.study_mode = 'ug_ft'
1978        self.certificate2.start_level = 999
1979        self.certificate2.end_level = 999
1980        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1981            self.certificate2)
1982
1983        # Add study level to old study course
1984        studylevel = createObject(u'waeup.StudentStudyLevel')
1985        studylevel.level = 200
1986        self.student['studycourse'].addStudentStudyLevel(
1987            self.certificate, studylevel)
1988        studylevel = createObject(u'waeup.StudentStudyLevel')
1989        studylevel.level = 999
1990        self.student['studycourse'].addStudentStudyLevel(
1991            self.certificate, studylevel)
1992
1993        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1994        self.browser.open(self.student_path)
1995        self.browser.getLink("Transfer").click()
1996        self.browser.getControl(name="form.certificate").value = ['CERT2']
1997        self.browser.getControl(name="form.current_session").value = ['2011']
1998        self.browser.getControl(name="form.current_level").value = ['200']
1999        self.browser.getControl("Transfer").click()
2000        self.assertTrue(
2001            'Current level does not match certificate levels'
2002            in self.browser.contents)
2003        self.browser.getControl(name="form.current_level").value = ['999']
2004        self.browser.getControl("Transfer").click()
2005        self.assertTrue('Successfully transferred' in self.browser.contents)
2006        # The catalog has been updated
2007        cat = queryUtility(ICatalog, name='students_catalog')
2008        results = list(
2009            cat.searchResults(
2010            certcode=('CERT2', 'CERT2')))
2011        self.assertTrue(results[0] is self.student)
2012        results = list(
2013            cat.searchResults(
2014            current_session=(2011, 2011)))
2015        self.assertTrue(results[0] is self.student)
2016        # Add study level to new study course
2017        studylevel = createObject(u'waeup.StudentStudyLevel')
2018        studylevel.level = 999
2019        self.student['studycourse'].addStudentStudyLevel(
2020            self.certificate, studylevel)
2021
2022        # Edit and add pages are locked for old study courses
2023        self.browser.open(self.student_path + '/studycourse/manage')
2024        self.assertFalse('The requested form is locked' in self.browser.contents)
2025        self.browser.open(self.student_path + '/studycourse_1/manage')
2026        self.assertTrue('The requested form is locked' in self.browser.contents)
2027
2028        self.browser.open(self.student_path + '/studycourse/start_session')
2029        self.assertFalse('The requested form is locked' in self.browser.contents)
2030        self.browser.open(self.student_path + '/studycourse_1/start_session')
2031        self.assertTrue('The requested form is locked' in self.browser.contents)
2032
2033        IWorkflowState(self.student).setState('school fee paid')
2034        self.browser.open(self.student_path + '/studycourse/add')
2035        self.assertFalse('The requested form is locked' in self.browser.contents)
2036        self.browser.open(self.student_path + '/studycourse_1/add')
2037        self.assertTrue('The requested form is locked' in self.browser.contents)
2038
2039        self.browser.open(self.student_path + '/studycourse/999/manage')
2040        self.assertFalse('The requested form is locked' in self.browser.contents)
2041        self.browser.open(self.student_path + '/studycourse_1/999/manage')
2042        self.assertTrue('The requested form is locked' in self.browser.contents)
2043
2044        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
2045        self.assertFalse('The requested form is locked' in self.browser.contents)
2046        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
2047        self.assertTrue('The requested form is locked' in self.browser.contents)
2048
2049        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
2050        self.assertFalse('The requested form is locked' in self.browser.contents)
2051        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
2052        self.assertTrue('The requested form is locked' in self.browser.contents)
2053
2054        self.browser.open(self.student_path + '/studycourse/999/add')
2055        self.assertFalse('The requested form is locked' in self.browser.contents)
2056        self.browser.open(self.student_path + '/studycourse_1/999/add')
2057        self.assertTrue('The requested form is locked' in self.browser.contents)
2058
2059        self.browser.open(self.student_path + '/studycourse/999/edit')
2060        self.assertFalse('The requested form is locked' in self.browser.contents)
2061        self.browser.open(self.student_path + '/studycourse_1/999/edit')
2062        self.assertTrue('The requested form is locked' in self.browser.contents)
2063
2064        # Revert transfer
2065        self.browser.open(self.student_path + '/studycourse_1')
2066        self.browser.getLink("Reactivate").click()
2067        self.browser.getControl("Revert now").click()
2068        self.assertTrue('Previous transfer reverted' in self.browser.contents)
2069        results = list(
2070            cat.searchResults(
2071            certcode=('CERT1', 'CERT1')))
2072        self.assertTrue(results[0] is self.student)
2073        self.assertEqual([i for i in self.student.keys()],
2074            [u'accommodation', u'payments', u'studycourse'])
2075
2076    def test_login_as_student(self):
2077        # StudentImpersonators can login as student
2078        # Create clearance officer
2079        self.app['users'].addUser('mrofficer', SECRET)
2080        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
2081        self.app['users']['mrofficer'].title = 'Harry Actor'
2082        prmglobal = IPrincipalRoleManager(self.app)
2083        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
2084        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
2085        # Login as student impersonator
2086        self.browser.open(self.login_path)
2087        self.browser.getControl(name="form.login").value = 'mrofficer'
2088        self.browser.getControl(name="form.password").value = SECRET
2089        self.browser.getControl("Login").click()
2090        self.assertMatches('...You logged in...', self.browser.contents)
2091        self.browser.open(self.student_path)
2092        self.browser.getLink("Login as").click()
2093        self.browser.getControl("Set password now").click()
2094        temp_password = self.browser.getControl(name='form.password').value
2095        self.browser.getControl("Login now").click()
2096        self.assertMatches(
2097            '...You successfully logged in as...', self.browser.contents)
2098        # We are logged in as student and can see the 'My Data' tab
2099        self.assertMatches(
2100            '...<a href="#" class="dropdown-toggle" data-toggle="dropdown">...',
2101            self.browser.contents)
2102        self.assertMatches(
2103            '...My Data...',
2104            self.browser.contents)
2105        self.browser.getLink("Logout").click()
2106        # The student can't login with the original password ...
2107        self.browser.open(self.login_path)
2108        self.browser.getControl(name="form.login").value = self.student_id
2109        self.browser.getControl(name="form.password").value = 'spwd'
2110        self.browser.getControl("Login").click()
2111        self.assertMatches(
2112            '...Your account has been temporarily deactivated...',
2113            self.browser.contents)
2114        # ... but with the temporary password
2115        self.browser.open(self.login_path)
2116        self.browser.getControl(name="form.login").value = self.student_id
2117        self.browser.getControl(name="form.password").value = temp_password
2118        self.browser.getControl("Login").click()
2119        self.assertMatches('...You logged in...', self.browser.contents)
2120        # Creation of temp_password is properly logged
2121        logfile = os.path.join(
2122            self.app['datacenter'].storage, 'logs', 'students.log')
2123        logcontent = open(logfile).read()
2124        self.assertTrue(
2125            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
2126            'temp_password generated: %s' % temp_password in logcontent)
2127
2128    def test_transcripts(self):
2129        studylevel = createObject(u'waeup.StudentStudyLevel')
2130        studylevel.level = 100
2131        studylevel.level_session = 2005
2132        self.student['studycourse'].entry_mode = 'ug_ft'
2133        self.student['studycourse'].addStudentStudyLevel(
2134            self.certificate, studylevel)
2135        studylevel2 = createObject(u'waeup.StudentStudyLevel')
2136        studylevel2.level = 110
2137        studylevel2.level_session = 2006
2138        self.student['studycourse'].addStudentStudyLevel(
2139            self.certificate, studylevel2)
2140        # Add second course (COURSE has been added automatically)
2141        courseticket = createObject('waeup.CourseTicket')
2142        courseticket.code = 'ANYCODE'
2143        courseticket.title = u'Any TITLE'
2144        courseticket.credits = 13
2145        courseticket.score = 66
2146        courseticket.semester = 1
2147        courseticket.dcode = u'ANYDCODE'
2148        courseticket.fcode = u'ANYFCODE'
2149        self.student['studycourse']['110']['COURSE2'] = courseticket
2150        self.student['studycourse']['100']['COURSE1'].score = 55
2151        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2152        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2153        # Get transcript data
2154        td = self.student['studycourse'].getTranscriptData()
2155        self.assertEqual(td[0][0]['level_key'], '100')
2156        self.assertEqual(td[0][0]['sgpa'], 3.0)
2157        self.assertEqual(td[0][0]['level'].level, 100)
2158        self.assertEqual(td[0][0]['level'].level_session, 2005)
2159        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2160        self.assertEqual(td[0][1]['level_key'], '110')
2161        self.assertEqual(td[0][1]['sgpa'], 4.0)
2162        self.assertEqual(td[0][1]['level'].level, 110)
2163        self.assertEqual(td[0][1]['level'].level_session, 2006)
2164        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2165        self.assertEqual(td[1], 3.5652173913043477)
2166        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2167        self.browser.open(self.student_path + '/studycourse/transcript')
2168        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2169        self.assertTrue('Transcript' in self.browser.contents)
2170        # Officers can open the pdf transcript
2171        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2172        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2173        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2174        path = os.path.join(samples_dir(), 'transcript.pdf')
2175        open(path, 'wb').write(self.browser.contents)
2176        print "Sample PDF transcript.pdf written to %s" % path
2177
2178    def test_process_transcript(self):
2179        IWorkflowState(self.student).setState('graduated')
2180        notify(grok.ObjectModifiedEvent(self.student))
2181        self.student['studycourse'].transcript_comment = (
2182            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2183            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2184            'Address line2\n\n')
2185        # Create officer with both roles
2186        self.app['users'].addUser('mrtranscript', SECRET)
2187        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2188        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2189        prmglobal = IPrincipalRoleManager(self.app)
2190        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2191        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrtranscript')
2192        prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
2193        # Login
2194        self.browser.open(self.login_path)
2195        self.browser.getControl(name="form.login").value = 'mrtranscript'
2196        self.browser.getControl(name="form.password").value = SECRET
2197        self.browser.getControl("Login").click()
2198        self.assertMatches('...You logged in...', self.browser.contents)
2199        # Officer can request transcript for students
2200        self.browser.open(self.student_path)
2201        self.browser.getLink("Request transcript for student").click()
2202        self.browser.getControl("Request now").click()
2203        self.assertEqual(self.student.state, 'transcript requested')
2204        # Officer can see his roles
2205        self.browser.getLink("My Roles").click()
2206        self.assertMatches(
2207            '...<div>Transcript Officer</div>...',
2208            self.browser.contents)
2209        # Officer can search for students in state 'transcript requested'
2210        self.browser.open(self.container_path)
2211        self.browser.getControl(name="searchtype").value = [
2212            'transcript requested']
2213        self.browser.getControl("Find student(s)").click()
2214        self.assertTrue('Anna Tester' in self.browser.contents)
2215        self.browser.getLink("K1000000").click()
2216        self.assertFalse('Release transcript request' in self.browser.contents)
2217        # Officers can still edit studycourse, studylevel and course tickets.
2218        self.browser.open(self.studycourse_path + '/manage')
2219        self.assertTrue('Undergraduate Full-Time</option>'
2220            in self.browser.contents)
2221        self.browser.getControl(name="form.certificate").value = ['CERT1']
2222        self.browser.getControl(name="form.current_session").value = ['2004']
2223        self.browser.getControl(name="form.current_verdict").value = ['A']
2224        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
2225        self.browser.getControl("Save").click()
2226        self.browser.getControl(name="form.current_level").value = ['100']
2227        self.browser.getControl("Save").click()
2228        self.browser.getControl(name="addlevel").value = ['100']
2229        self.browser.getControl(name="level_session").value = ['2004']
2230        self.browser.getControl("Add study level").click()
2231        self.browser.getLink("100").click()
2232        self.browser.getLink("Manage").click()
2233        self.browser.getControl(name="form.level_session").value = ['2002']
2234        self.browser.getControl("Save").click()
2235        self.browser.getLink("COURSE1").click()
2236        self.browser.getLink("Manage").click()
2237        self.browser.getControl("Save").click()
2238        self.assertTrue('Form has been saved' in self.browser.contents)
2239        # Officer can edit transcript remarks and validate the transcript
2240        self.browser.open(self.studycourse_path + '/transcript')
2241        self.browser.getLink("Validate transcript").click()
2242        self.browser.getLink("Edit").click()
2243        self.assertEqual(
2244            self.browser.url, self.studycourse_path + '/100/remark')
2245        self.browser.getControl(
2246            name="form.transcript_remark").value = 'Oh, the student failed'
2247        self.browser.getControl(
2248            "Save remark and go and back to transcript validation page").click()
2249        self.assertEqual(
2250            self.browser.url,
2251            self.studycourse_path + '/validate_transcript#tab4')
2252        self.assertEqual(self.student['studycourse']['100'].transcript_remark,
2253            'Oh, the student failed')
2254        self.browser.getControl("Save comment and validate transcript").click()
2255        # After validation all manage forms are locked.
2256        self.browser.open(self.studycourse_path + '/manage')
2257        self.assertTrue('The requested form is locked' in self.browser.contents)
2258        self.assertFalse('Undergraduate Full-Time</option>'
2259            in self.browser.contents)
2260        self.browser.open(self.studycourse_path + '/100/manage')
2261        self.assertTrue('The requested form is locked' in self.browser.contents)
2262        self.browser.open(self.studycourse_path + '/100/COURSE1/manage')
2263        self.assertTrue('The requested form is locked' in self.browser.contents)
2264        self.browser.open(self.studycourse_path + '/100/remark')
2265        self.assertTrue('The requested form is locked' in self.browser.contents)
2266
2267        # Transcript can be signed if officer has the permission to sign
2268        #self.browser.open(self.studycourse_path + '/transcript')
2269        #self.assertFalse('Sign transcript' in self.browser.contents)
2270        #prmglobal = IPrincipalRoleManager(self.app)
2271        #prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
2272
2273        self.browser.open(self.studycourse_path + '/transcript')
2274        self.browser.getLink("Sign transcript electronically").click()
2275        # Transcript signing has been logged ...
2276        logfile = os.path.join(
2277            self.app['datacenter'].storage, 'logs', 'students.log')
2278        logcontent = open(logfile).read()
2279        self.assertTrue(
2280            'mrtranscript - students.browser.StudentTranscriptSignView - '
2281            'K1000000 - Transcript signed' in logcontent)
2282        # ... appears in the student's history ...
2283        self.browser.open(self.history_path)
2284        self.assertTrue('Transcript signed by Ruth Gordon'
2285            in self.browser.contents)
2286        # ... and is also stored in the transcript_signee attribute.
2287        self.assertTrue(
2288            u'Electronically signed by Ruth Gordon (mrtranscript) on '
2289            in self.student['studycourse'].transcript_signees)
2290        # Officer can release the transcript
2291        self.browser.open(self.studycourse_path + '/transcript')
2292        self.browser.getLink("Release transcript").click()
2293        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2294        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2295        'Address line2<br><br></p>' in self.browser.contents)
2296        self.browser.getControl(name="comment").value = (
2297            'Hello,\nYour transcript has been sent to the address provided.')
2298        self.browser.getControl("Save comment and release transcript").click()
2299        self.assertTrue(
2300            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2301            'been sent to the address provided.\n\n'
2302            in self.student['studycourse'].transcript_comment)
2303        # The comment has been logged
2304        logfile = os.path.join(
2305            self.app['datacenter'].storage, 'logs', 'students.log')
2306        logcontent = open(logfile).read()
2307        self.assertTrue(
2308            'mrtranscript - students.browser.StudentTranscriptReleaseFormPage - '
2309            'K1000000 - comment: Hello,<br>'
2310            'Your transcript has been sent to the address provided'
2311            in logcontent)
2312        # File has been stored in the file system
2313        # Check if transcript exists in the file system and is a PDF file
2314        storage = getUtility(IExtFileStore)
2315        file_id = IFileStoreNameChooser(
2316            self.student).chooseName(attr='final_transcript.pdf')
2317        pdf = storage.getFile(file_id).read()
2318        self.assertTrue(len(pdf) > 0)
2319        self.assertEqual(pdf[:8], '%PDF-1.4')
2320        # Copy the file to samples_dir
2321        path = os.path.join(samples_dir(), 'final_transcript.pdf')
2322        open(path, 'wb').write(pdf)
2323        print "Sample PDF final_transcript.pdf written to %s" % path
2324        # Check if there is an transcript pdf link in UI
2325        self.browser.open(self.student_path)
2326        self.assertTrue('Final Transcript' in self.browser.contents)
2327        self.browser.getLink("Final Transcript").click()
2328        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2329        self.assertEqual(self.browser.headers['Content-Type'],
2330                         'application/pdf')
2331        # Transcript views are no longer accessible
2332        self.browser.open(self.studycourse_path)
2333        self.assertFalse('studycourse/transcript' in self.browser.contents)
2334        self.browser.open(self.studycourse_path + '/transcript')
2335        self.assertTrue('Forbidden!' in self.browser.contents)
2336        self.browser.open(self.studycourse_path + '/transcript.pdf')
2337        self.assertTrue('Forbidden!' in self.browser.contents)
2338        # If we reset the transcript process
2339        # (can't be done by transcript officer), the file will be deleted
2340        IWorkflowInfo(self.student).fireTransition('reset11')
2341        self.browser.open(self.student_path)
2342        self.assertFalse('Final Transcript' in self.browser.contents)
2343        # ... and transcript process information has been removed
2344        self.assertEqual(self.student['studycourse'].transcript_comment, None)
2345        self.assertEqual(self.student['studycourse'].transcript_signees, None)
2346
2347    def test_landingpage_transcript_officer(self):
2348        IWorkflowState(self.student).setState('transcript requested')
2349        notify(grok.ObjectModifiedEvent(self.student))
2350        # Create transcript officer
2351        self.app['users'].addUser('mrtranscript', SECRET)
2352        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2353        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2354        # We assign transcript officer role at faculty level
2355        fac = self.app['faculties']['fac1']
2356        prmlocal = IPrincipalRoleManager(fac)
2357        prmlocal.assignRoleToPrincipal(
2358            'waeup.local.TranscriptOfficer', 'mrtranscript')
2359        notify(LocalRoleSetEvent(
2360            fac, 'waeup.local.TranscriptOfficer', 'mrtranscript', granted=True))
2361        # Login as transcript officer
2362        self.browser.open(self.login_path)
2363        self.browser.getControl(name="form.login").value = 'mrtranscript'
2364        self.browser.getControl(name="form.password").value = SECRET
2365        self.browser.getControl("Login").click()
2366        self.assertMatches('...You logged in...', self.browser.contents)
2367        # Officer is on landing page and does see the transcript link
2368        self.assertTrue(
2369            'http://localhost/app/students/K1000000/studycourse/transcript'
2370            in self.browser.contents)
2371        self.browser.getLink("K1000000").click()
2372        self.assertTrue(
2373            'Anna Tester: Transcript Data' in self.browser.contents)
2374        # Officer is on transcript page and can validate the transcript
2375        self.browser.getLink("Validate transcript").click()
2376        self.browser.getControl("Save comment and validate transcript").click()
2377        self.assertTrue(
2378            '<div class="alert alert-success">Transcript validated.</div>'
2379            in self.browser.contents)
2380        # Officer is still on transcript page and can release the transcript
2381        self.browser.getLink("Release transcript").click()
2382        self.browser.getControl("Save comment and release transcript").click()
2383        self.assertTrue(
2384            '<div class="alert alert-success">'
2385            'Transcript released and final transcript file saved.</div>'
2386            in self.browser.contents)
2387
2388    def test_landingpage_transcript_signee(self):
2389        IWorkflowState(self.student).setState('transcript validated')
2390        notify(grok.ObjectModifiedEvent(self.student))
2391        # Create transcript signee
2392        self.app['users'].addUser('mrtranscript', SECRET)
2393        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2394        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2395        # We assign transcript officer role at faculty level
2396        fac = self.app['faculties']['fac1']
2397        prmlocal = IPrincipalRoleManager(fac)
2398        prmlocal.assignRoleToPrincipal(
2399            'waeup.local.TranscriptSignee', 'mrtranscript')
2400        notify(LocalRoleSetEvent(
2401            fac, 'waeup.local.TranscriptSignee', 'mrtranscript', granted=True))
2402        # Login as transcript officer
2403        self.browser.open(self.login_path)
2404        self.browser.getControl(name="form.login").value = 'mrtranscript'
2405        self.browser.getControl(name="form.password").value = SECRET
2406        self.browser.getControl("Login").click()
2407        self.assertMatches('...You logged in...', self.browser.contents)
2408        # Officer is on landing page and does see the transcript link
2409        self.assertTrue(
2410            'http://localhost/app/students/K1000000/studycourse/transcript'
2411            in self.browser.contents)
2412        self.browser.getLink("K1000000").click()
2413        self.assertTrue(
2414            'Anna Tester: Transcript Data' in self.browser.contents)
2415        # Officer is on transcript page and can sign the transcript
2416        self.browser.getLink("Sign transcript").click()
2417        self.assertTrue(
2418            '<div class="alert alert-success">Transcript signed.</div>'
2419            in self.browser.contents)
2420        # Officer is still on transcript page
2421        self.assertTrue(
2422            'Anna Tester: Transcript Data' in self.browser.contents)
2423        # Officer can sign the transcript only once
2424        self.browser.getLink("Sign transcript").click()
2425        self.assertTrue(
2426            '<div class="alert alert-warning">'
2427            'You have already signed this transcript.</div>'
2428            in self.browser.contents)
2429        # Signature can be seen on transcript page
2430        self.assertTrue(
2431            'Electronically signed by Ruth Gordon (mrtranscript) on'
2432            in self.browser.contents)
2433
2434    def test_update_coursetickets(self):
2435        IWorkflowState(self.student).setState('school fee paid')
2436        studylevel = createObject(u'waeup.StudentStudyLevel')
2437        studylevel.level = 100
2438        studylevel.level_session = 2015
2439        self.student['studycourse'].entry_mode = 'ug_ft'
2440        self.student['studycourse'].addStudentStudyLevel(
2441            self.certificate, studylevel)
2442        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2443        self.browser.open(
2444            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1/')
2445        self.assertFalse(
2446            'Update session 2015/2016 credits' in self.browser.contents)
2447        self.app['configuration'].current_academic_session = 2015
2448        self.browser.open(
2449            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1/')
2450        self.browser.getLink("Update session 2015/2016 credits").click()
2451        self.assertTrue(
2452            'No course ticket found.' in self.browser.contents)
2453        logfile = os.path.join(
2454            self.app['datacenter'].storage, 'logs', 'main.log')
2455        logcontent = open(logfile).read()
2456        self.assertTrue(
2457            'zope.mgr - browser.pages.UpdateCourseTicketsView - '
2458            'course tickets updated: COURSE1' in logcontent)
2459        studylevel['COURSE1'].credits = 12
2460        self.browser.getLink("Update session 2015/2016 credits").click()
2461        self.assertTrue(
2462            'No course ticket found.' in self.browser.contents)
2463        studylevel.level_session = 2015
2464        self.student['studycourse'].current_session = 2015
2465        self.browser.getLink("Update session 2015/2016 credits").click()
2466        self.assertTrue(
2467            '1 course ticket(s) updated.' in self.browser.contents)
2468        logfile = os.path.join(
2469            self.app['datacenter'].storage, 'logs', 'students.log')
2470        logcontent = open(logfile).read()
2471        self.assertTrue(
2472            'zope.mgr - students.utils.StudentsUtils - '
2473            'K1000000 100/COURSE1 credits updated (10->12)' in logcontent)
2474        return
2475
2476
2477class StudentUITests(StudentsFullSetup):
2478    # Tests for Student class views and pages
2479
2480    def test_student_change_password(self):
2481        # Students can change the password
2482        self.student.personal_updated = datetime.utcnow()
2483        self.browser.open(self.login_path)
2484        self.browser.getControl(name="form.login").value = self.student_id
2485        self.browser.getControl(name="form.password").value = 'spwd'
2486        self.browser.getControl("Login").click()
2487        self.assertEqual(self.browser.url, self.student_path)
2488        self.assertTrue('You logged in' in self.browser.contents)
2489        # Change password
2490        self.browser.getLink("Change password").click()
2491        self.browser.getControl(name="change_password").value = 'pw'
2492        self.browser.getControl(
2493            name="change_password_repeat").value = 'pw'
2494        self.browser.getControl("Save").click()
2495        self.assertTrue('Password must have at least' in self.browser.contents)
2496        self.browser.getControl(name="change_password").value = 'new_password'
2497        self.browser.getControl(
2498            name="change_password_repeat").value = 'new_passssword'
2499        self.browser.getControl("Save").click()
2500        self.assertTrue('Passwords do not match' in self.browser.contents)
2501        self.browser.getControl(name="change_password").value = 'new_password'
2502        self.browser.getControl(
2503            name="change_password_repeat").value = 'new_password'
2504        self.browser.getControl("Save").click()
2505        self.assertTrue('Password changed' in self.browser.contents)
2506        # We are still logged in. Changing the password hasn't thrown us out.
2507        self.browser.getLink("Base Data").click()
2508        self.assertEqual(self.browser.url, self.student_path)
2509        # We can logout
2510        self.browser.getLink("Logout").click()
2511        self.assertTrue('You have been logged out' in self.browser.contents)
2512        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2513        # We can login again with the new password
2514        self.browser.getLink("Login").click()
2515        self.browser.open(self.login_path)
2516        self.browser.getControl(name="form.login").value = self.student_id
2517        self.browser.getControl(name="form.password").value = 'new_password'
2518        self.browser.getControl("Login").click()
2519        self.assertEqual(self.browser.url, self.student_path)
2520        self.assertTrue('You logged in' in self.browser.contents)
2521        return
2522
2523    def test_forbidden_name(self):
2524        self.student.lastname = u'<TAG>Tester</TAG>'
2525        self.browser.open(self.login_path)
2526        self.browser.getControl(name="form.login").value = self.student_id
2527        self.browser.getControl(name="form.password").value = 'spwd'
2528        self.browser.getControl("Login").click()
2529        self.assertTrue('XXX: Base Data' in self.browser.contents)
2530        self.assertTrue('&lt;TAG&gt;Tester&lt;/TAG&gt;' in self.browser.contents)
2531        self.assertFalse('<TAG>Tester</TAG>' in self.browser.contents)
2532        return
2533
2534    def test_setpassword(self):
2535        # Set password for first-time access
2536        student = Student()
2537        student.reg_number = u'123456'
2538        student.firstname = u'Klaus'
2539        student.lastname = u'Tester'
2540        self.app['students'].addStudent(student)
2541        setpassword_path = 'http://localhost/app/setpassword'
2542        student_path = 'http://localhost/app/students/%s' % student.student_id
2543        self.browser.open(setpassword_path)
2544        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2545        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2546        self.browser.getControl(name="reg_number").value = '223456'
2547        self.browser.getControl("Set").click()
2548        self.assertMatches('...No student found...',
2549                           self.browser.contents)
2550        self.browser.getControl(name="reg_number").value = '123456'
2551        self.browser.getControl(name="ac_number").value = '999999'
2552        self.browser.getControl("Set").click()
2553        self.assertMatches('...Access code is invalid...',
2554                           self.browser.contents)
2555        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2556        self.browser.getControl("Set").click()
2557        self.assertMatches('...Password has been set. Your Student Id is...',
2558                           self.browser.contents)
2559        self.browser.getControl("Set").click()
2560        self.assertMatches(
2561            '...Password has already been set. Your Student Id is...',
2562            self.browser.contents)
2563        existing_pwdpin = self.pwdpins[1]
2564        parts = existing_pwdpin.split('-')[1:]
2565        existing_pwdseries, existing_pwdnumber = parts
2566        self.browser.getControl(name="ac_series").value = existing_pwdseries
2567        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2568        self.browser.getControl(name="reg_number").value = '123456'
2569        self.browser.getControl("Set").click()
2570        self.assertMatches(
2571            '...You are using the wrong Access Code...',
2572            self.browser.contents)
2573        # The student can login with the new credentials
2574        self.browser.open(self.login_path)
2575        self.browser.getControl(name="form.login").value = student.student_id
2576        self.browser.getControl(
2577            name="form.password").value = self.existing_pwdnumber
2578        self.browser.getControl("Login").click()
2579        self.assertEqual(self.browser.url, student_path)
2580        self.assertTrue('You logged in' in self.browser.contents)
2581        return
2582
2583    def test_student_login(self):
2584        # Student cant login if their password is not set
2585        self.student.password = None
2586        self.browser.open(self.login_path)
2587        self.browser.getControl(name="form.login").value = self.student_id
2588        self.browser.getControl(name="form.password").value = 'spwd'
2589        self.browser.getControl("Login").click()
2590        self.assertTrue(
2591            'You entered invalid credentials.' in self.browser.contents)
2592        # We set the password again
2593        IUserAccount(
2594            self.app['students'][self.student_id]).setPassword('spwd')
2595        # Students can't login if their account is suspended/deactivated
2596        self.student.suspended = True
2597        self.browser.open(self.login_path)
2598        self.browser.getControl(name="form.login").value = self.student_id
2599        self.browser.getControl(name="form.password").value = 'spwd'
2600        self.browser.getControl("Login").click()
2601        self.assertMatches(
2602            '...<div class="alert alert-warning">'
2603            'Your account has been deactivated.</div>...', self.browser.contents)
2604        # If suspended_comment is set this message will be flashed instead
2605        self.student.suspended_comment = u'Aetsch baetsch!'
2606        self.browser.getControl(name="form.login").value = self.student_id
2607        self.browser.getControl(name="form.password").value = 'spwd'
2608        self.browser.getControl("Login").click()
2609        self.assertMatches(
2610            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2611            self.browser.contents)
2612        self.student.suspended = False
2613        # Students can't login if a temporary password has been set and
2614        # is not expired
2615        self.app['students'][self.student_id].setTempPassword(
2616            'anybody', 'temp_spwd')
2617        self.browser.open(self.login_path)
2618        self.browser.getControl(name="form.login").value = self.student_id
2619        self.browser.getControl(name="form.password").value = 'spwd'
2620        self.browser.getControl("Login").click()
2621        self.assertMatches(
2622            '...Your account has been temporarily deactivated...',
2623            self.browser.contents)
2624        # The student can login with the temporary password
2625        self.browser.open(self.login_path)
2626        self.browser.getControl(name="form.login").value = self.student_id
2627        self.browser.getControl(name="form.password").value = 'temp_spwd'
2628        self.browser.getControl("Login").click()
2629        self.assertMatches(
2630            '...You logged in...', self.browser.contents)
2631        # Student can view the base data
2632        self.browser.open(self.student_path)
2633        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2634        self.assertEqual(self.browser.url, self.student_path)
2635        # When the password expires ...
2636        delta = timedelta(minutes=11)
2637        self.app['students'][self.student_id].temp_password[
2638            'timestamp'] = datetime.utcnow() - delta
2639        self.app['students'][self.student_id]._p_changed = True
2640        # ... the student will be automatically logged out
2641        self.assertRaises(
2642            Unauthorized, self.browser.open, self.student_path)
2643        # Then the student can login with the original password
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
2651    def test_maintenance_mode(self):
2652        config = grok.getSite()['configuration']
2653        self.browser.open(self.login_path)
2654        self.browser.getControl(name="form.login").value = self.student_id
2655        self.browser.getControl(name="form.password").value = 'spwd'
2656        self.browser.getControl("Login").click()
2657        # Student logged in.
2658        self.assertTrue('You logged in' in self.browser.contents)
2659        self.assertTrue("Anna Tester" in self.browser.contents)
2660        # If maintenance mode is enabled, student is immediately logged out.
2661        config.maintmode_enabled_by = u'any_user'
2662        self.assertRaises(
2663            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
2664        self.browser.open('http://localhost/app/login')
2665        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
2666        # Student really can't login if maintenance mode is enabled.
2667        self.browser.open(self.login_path)
2668        self.browser.getControl(name="form.login").value = self.student_id
2669        self.browser.getControl(name="form.password").value = 'spwd'
2670        self.browser.getControl("Login").click()
2671        # A second warning is raised.
2672        self.assertTrue(
2673            'The portal is in maintenance mode. You can\'t login!'
2674            in self.browser.contents)
2675        return
2676
2677    def test_student_basedata_slip(self):
2678        IWorkflowState(self.student).setState('school fee paid')
2679        self.browser.open(self.login_path)
2680        self.browser.getControl(name="form.login").value = self.student_id
2681        self.browser.getControl(name="form.password").value = 'spwd'
2682        self.browser.getControl("Login").click()
2683        # Students can open base data slip
2684        # (no button available in base package)
2685        pdf_url = '%s/basedata_slip.pdf' % self.studycourse_path
2686        self.browser.open(pdf_url)
2687        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2688        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2689        path = os.path.join(samples_dir(), 'basedata_slip.pdf')
2690        open(path, 'wb').write(self.browser.contents)
2691        print "Sample PDF basedata_slip.pdf written to %s" % path
2692
2693    def test_student_clearance(self):
2694        # Student cant login if their password is not set
2695        IWorkflowInfo(self.student).fireTransition('admit')
2696        self.browser.open(self.login_path)
2697        self.browser.getControl(name="form.login").value = self.student_id
2698        self.browser.getControl(name="form.password").value = 'spwd'
2699        self.browser.getControl("Login").click()
2700        self.assertMatches(
2701            '...You logged in...', self.browser.contents)
2702        # Admitted student can upload a passport picture
2703        self.browser.open(self.student_path + '/change_portrait')
2704        ctrl = self.browser.getControl(name='passportuploadedit')
2705        file_obj = open(SAMPLE_IMAGE, 'rb')
2706        file_ctrl = ctrl.mech_control
2707        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2708        self.browser.getControl(
2709            name='upload_passportuploadedit').click()
2710        self.assertTrue(
2711            'src="http://localhost/app/students/K1000000/passport.jpg"'
2712            in self.browser.contents)
2713        # Students can open admission letter
2714        self.browser.getLink("Base Data").click()
2715        self.browser.getLink("Download admission letter").click()
2716        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2717        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2718        path = os.path.join(samples_dir(), 'admission_slip.pdf')
2719        open(path, 'wb').write(self.browser.contents)
2720        print "Sample PDF admission_slip.pdf written to %s" % path
2721        # Student can view the clearance data
2722        self.browser.open(self.student_path)
2723        self.browser.getLink("Clearance Data").click()
2724        # Student can't open clearance edit form before starting clearance
2725        self.browser.open(self.student_path + '/cedit')
2726        self.assertMatches('...The requested form is locked...',
2727                           self.browser.contents)
2728        self.browser.getLink("Clearance Data").click()
2729        self.browser.getLink("Start clearance").click()
2730        self.student.phone = None
2731        # Uups, we forgot to fill the phone fields
2732        self.browser.getControl("Start clearance").click()
2733        self.assertMatches('...Phone number is missing...',
2734                           self.browser.contents)
2735        self.browser.open(self.student_path + '/edit_base')
2736        self.browser.getControl(name="form.phone.ext").value = '12345'
2737        self.browser.getControl("Save").click()
2738        self.browser.open(self.student_path + '/start_clearance')
2739        self.browser.getControl(name="ac_series").value = '3'
2740        self.browser.getControl(name="ac_number").value = '4444444'
2741        self.browser.getControl("Start clearance now").click()
2742        self.assertMatches('...Activation code is invalid...',
2743                           self.browser.contents)
2744        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2745        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2746        # Owner is Hans Wurst, AC can't be invalidated
2747        self.browser.getControl("Start clearance now").click()
2748        self.assertMatches('...You are not the owner of this access code...',
2749                           self.browser.contents)
2750        # Set the correct owner
2751        self.existing_clrac.owner = self.student_id
2752        # clr_code might be set (and thus returns None) due importing
2753        # an empty clr_code column.
2754        self.student.clr_code = None
2755        self.browser.getControl("Start clearance now").click()
2756        self.assertMatches('...Clearance process has been started...',
2757                           self.browser.contents)
2758        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2759        self.browser.getControl("Save", index=0).click()
2760        # Student can view the clearance data
2761        self.browser.getLink("Clearance Data").click()
2762        # and go back to the edit form
2763        self.browser.getLink("Edit").click()
2764        # Students can upload documents
2765        ctrl = self.browser.getControl(name='birthcertificateupload')
2766        file_obj = open(SAMPLE_IMAGE, 'rb')
2767        file_ctrl = ctrl.mech_control
2768        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2769        self.browser.getControl(
2770            name='upload_birthcertificateupload').click()
2771        self.assertTrue(
2772            'href="http://localhost/app/students/K1000000/birth_certificate"'
2773            in self.browser.contents)
2774        # Students can open clearance slip
2775        self.browser.getLink("View").click()
2776        self.browser.getLink("Download clearance slip").click()
2777        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2778        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2779        # Students can request clearance
2780        self.browser.open(self.edit_clearance_path)
2781        self.browser.getControl("Save and request clearance").click()
2782        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2783        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2784        self.browser.getControl("Request clearance now").click()
2785        self.assertMatches('...Clearance has been requested...',
2786                           self.browser.contents)
2787        # Student can't reopen clearance form after requesting clearance
2788        self.browser.open(self.student_path + '/cedit')
2789        self.assertMatches('...The requested form is locked...',
2790                           self.browser.contents)
2791
2792    def test_student_course_registration(self):
2793        # Student cant login if their password is not set
2794        IWorkflowInfo(self.student).fireTransition('admit')
2795        self.browser.open(self.login_path)
2796        self.browser.getControl(name="form.login").value = self.student_id
2797        self.browser.getControl(name="form.password").value = 'spwd'
2798        self.browser.getControl("Login").click()
2799        # Student can't add study level if not in state 'school fee paid'
2800        self.browser.open(self.student_path + '/studycourse/add')
2801        self.assertMatches('...The requested form is locked...',
2802                           self.browser.contents)
2803        # ... and must be transferred first
2804        IWorkflowState(self.student).setState('school fee paid')
2805        # Now students can add the current study level
2806        self.browser.getLink("Study Course").click()
2807        self.student['studycourse'].current_level = None
2808        self.browser.getLink("Add course list").click()
2809        self.assertMatches('...Your data are incomplete...',
2810                           self.browser.contents)
2811        self.student['studycourse'].current_level = 100
2812        self.browser.getLink("Add course list").click()
2813        self.assertMatches('...Add current level 100 (Year 1)...',
2814                           self.browser.contents)
2815        self.browser.getControl("Create course list now").click()
2816        # A level with one course ticket was created
2817        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2818        self.browser.getLink("100").click()
2819        self.browser.getLink("Edit course list").click()
2820        self.browser.getLink("here").click()
2821        self.browser.getControl(name="form.course").value = ['COURSE1']
2822        self.browser.getControl("Add course ticket").click()
2823        self.assertMatches('...The ticket exists...',
2824                           self.browser.contents)
2825        self.student['studycourse'].current_level = 200
2826        self.browser.getLink("Study Course").click()
2827        self.browser.getLink("Add course list").click()
2828        self.assertMatches('...Add current level 200 (Year 2)...',
2829                           self.browser.contents)
2830        self.browser.getControl("Create course list now").click()
2831        self.browser.getLink("200").click()
2832        self.browser.getLink("Edit course list").click()
2833        self.browser.getLink("here").click()
2834        self.browser.getControl(name="form.course").value = ['COURSE1']
2835        self.course.credits = 100
2836        self.browser.getControl("Add course ticket").click()
2837        self.assertMatches(
2838            '...Maximum credits exceeded...', self.browser.contents)
2839        self.course.credits = 10
2840        self.browser.getControl("Add course ticket").click()
2841        self.assertMatches('...The ticket exists...',
2842                           self.browser.contents)
2843        # Indeed the ticket exists as carry-over course from level 100
2844        # since its score was 0
2845        self.assertTrue(
2846            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2847        self.assertTrue(
2848            self.student['studycourse']['200']['COURSE1'].course_category is None)
2849        # Students can open the pdf course registration slip
2850        self.browser.open(self.student_path + '/studycourse/200')
2851        self.browser.getLink("Download course registration slip").click()
2852        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2853        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2854        path = os.path.join(samples_dir(), 'course_registration_slip.pdf')
2855        open(path, 'wb').write(self.browser.contents)
2856        print "Sample PDF course_registration_slip.pdf written to %s" % path
2857        # Students can remove course tickets
2858        self.browser.open(self.student_path + '/studycourse/200/edit')
2859        self.browser.getControl("Remove selected", index=0).click()
2860        self.assertTrue('No ticket selected' in self.browser.contents)
2861        # No ticket can be selected since the carry-over course is a core course
2862        self.assertRaises(
2863            LookupError, self.browser.getControl, name='val_id')
2864        self.student['studycourse']['200']['COURSE1'].mandatory = False
2865        self.browser.open(self.student_path + '/studycourse/200/edit')
2866        # Course list can't be registered if total_credits exceeds max_credits
2867        self.student['studycourse']['200']['COURSE1'].credits = 60
2868        self.browser.getControl("Register course list").click()
2869        self.assertTrue('Maximum credits exceeded' in self.browser.contents)
2870        # Student can now remove the ticket
2871        ctrl = self.browser.getControl(name='val_id')
2872        ctrl.getControl(value='COURSE1').selected = True
2873        self.browser.getControl("Remove selected", index=0).click()
2874        self.assertTrue('Successfully removed' in self.browser.contents)
2875        # Removing course tickets is properly logged
2876        logfile = os.path.join(
2877            self.app['datacenter'].storage, 'logs', 'students.log')
2878        logcontent = open(logfile).read()
2879        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2880        '- K1000000 - level 200 - removed: COURSE1' in logcontent)
2881        # They can add the same ticket using the edit page directly.
2882        # We can do the same by adding the course on the manage page directly
2883        self.browser.getControl(name="course").value = 'COURSE1'
2884        self.browser.getControl("Add course ticket").click()
2885        # Adding course tickets is logged
2886        logfile = os.path.join(
2887            self.app['datacenter'].storage, 'logs', 'students.log')
2888        logcontent = open(logfile).read()
2889        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2890            'K1000000 - level 200 - added: COURSE1|200|2004' in logcontent)
2891        # Course list can be registered
2892        self.browser.getControl("Register course list").click()
2893        self.assertTrue('Course list has been registered' in self.browser.contents)
2894        self.assertEqual(self.student.state, 'courses registered')
2895        # Course list can be unregistered
2896        self.browser.getLink("Unregister courses").click()
2897        self.assertEqual(self.student.state, 'school fee paid')
2898        self.assertTrue('Course list has been unregistered' in self.browser.contents)
2899        self.browser.open(self.student_path + '/studycourse/200/unregister_courses')
2900        self.assertTrue('You are in the wrong state' in self.browser.contents)
2901        # Students can view the transcript
2902        #self.browser.open(self.studycourse_path)
2903        #self.browser.getLink("Transcript").click()
2904        #self.browser.getLink("Academic Transcript").click()
2905        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2906        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2907        return
2908
2909    def test_student_ticket_update(self):
2910        IWorkflowState(self.student).setState('school fee paid')
2911        self.student['studycourse'].current_level = 100
2912        self.browser.open(self.login_path)
2913        self.browser.getControl(name="form.login").value = self.student_id
2914        self.browser.getControl(name="form.password").value = 'spwd'
2915        self.browser.getControl("Login").click()
2916        # Now students can add the current study level
2917        self.browser.getLink("Study Course").click()
2918        self.browser.getLink("Add course list").click()
2919        self.assertMatches('...Add current level 100 (Year 1)...',
2920                           self.browser.contents)
2921        self.browser.getControl("Create course list now").click()
2922        # A level with one course ticket was created
2923        self.assertEqual(
2924            self.student['studycourse']['100'].number_of_tickets, 1)
2925        self.browser.getLink("100").click()
2926        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2927        self.browser.getLink("Edit course list").click()
2928        self.browser.getControl("Update all tickets").click()
2929        self.assertTrue('All course tickets updated.' in self.browser.contents)
2930        # ... nothing has changed
2931        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
2932        # We change the title of the course
2933        self.course.title = u'New Title'
2934        self.browser.getControl("Update all tickets").click()
2935        self.assertTrue('<td>New Title</td>' in self.browser.contents)
2936        # We remove the course
2937        del self.app['faculties']['fac1']['dep1'].courses['COURSE1']
2938        self.browser.getControl("Update all tickets").click()
2939        self.assertTrue(' <td>New Title (course cancelled)</td>'
2940            in self.browser.contents)
2941        # Course ticket invalidation has been logged
2942        logfile = os.path.join(
2943            self.app['datacenter'].storage, 'logs', 'students.log')
2944        logcontent = open(logfile).read()
2945        self.assertTrue(
2946            'K1000000 - students.browser.StudyLevelEditFormPage - '
2947            'K1000000 - level 100 - course tickets invalidated: COURSE1'
2948            in logcontent)
2949        return
2950
2951    def test_student_course_already_passed(self):
2952        IWorkflowState(self.student).setState('school fee paid')
2953        self.student['studycourse'].current_level = 100
2954        self.browser.open(self.login_path)
2955        self.browser.getControl(name="form.login").value = self.student_id
2956        self.browser.getControl(name="form.password").value = 'spwd'
2957        self.browser.getControl("Login").click()
2958        # Now students can add the current study level
2959        self.browser.getLink("Study Course").click()
2960        self.browser.getLink("Add course list").click()
2961        self.assertMatches('...Add current level 100 (Year 1)...',
2962                           self.browser.contents)
2963        self.browser.getControl("Create course list now").click()
2964        # A level with one course ticket was created
2965        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2966        # We set the score above the passmark
2967        self.student['studycourse']['100'][
2968            'COURSE1'].score = self.student['studycourse']['100'][
2969                'COURSE1'].passmark + 1
2970        # We add a second level
2971        self.student['studycourse'].current_level = 200
2972        self.browser.getLink("Study Course").click()
2973        self.browser.getLink("Add course list").click()
2974        self.assertMatches('...Add current level 200 (Year 2)...',
2975                           self.browser.contents)
2976        self.browser.getControl("Create course list now").click()
2977        self.browser.getLink("200").click()
2978        self.browser.getLink("Edit course list").click()
2979        self.browser.getLink("here").click()
2980        self.browser.getControl(name="form.course").value = ['COURSE1']
2981        self.browser.getControl("Add course ticket").click()
2982        self.assertTrue(
2983            'Course has already been passed at previous level'
2984            in self.browser.contents)
2985        self.assertEqual(self.student['studycourse']['200'].number_of_tickets, 0)
2986        # We set the score below the passmark
2987        self.student['studycourse']['100'][
2988            'COURSE1'].score = self.student['studycourse']['100'][
2989                'COURSE1'].passmark - 1
2990        self.browser.getControl("Add course ticket").click()
2991        self.assertTrue(
2992            'Successfully added COURSE1' in self.browser.contents)
2993        self.assertEqual(self.student['studycourse']['200'].number_of_tickets, 1)
2994        return
2995
2996    def test_student_course_registration_outstanding(self):
2997        self.course = createObject('waeup.Course')
2998        self.course.code = 'COURSE2'
2999        self.course.semester = 1
3000        self.course.credits = 45
3001        self.course.passmark = 40
3002        self.app['faculties']['fac1']['dep1'].courses.addCourse(
3003            self.course)
3004        IWorkflowState(self.student).setState('school fee paid')
3005        self.browser.open(self.login_path)
3006        self.browser.getControl(name="form.login").value = self.student_id
3007        self.browser.getControl(name="form.password").value = 'spwd'
3008        self.browser.getControl("Login").click()
3009        self.browser.open(self.student_path + '/studycourse/add')
3010        self.browser.getControl("Create course list now").click()
3011        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
3012        self.student['studycourse'].current_level = 200
3013        self.browser.getLink("Study Course").click()
3014        self.browser.getLink("Add course list").click()
3015        self.assertMatches('...Add current level 200 (Year 2)...',
3016                           self.browser.contents)
3017        self.browser.getControl("Create course list now").click()
3018        self.browser.getLink("200").click()
3019        self.browser.getLink("Edit course list").click()
3020        self.browser.getLink("here").click()
3021        self.browser.getControl(name="form.course").value = ['COURSE2']
3022        self.browser.getControl("Add course ticket").click()
3023        # Carryover COURSE1 in level 200 already has 10 credits
3024        self.assertMatches(
3025            '...Maximum credits exceeded...', self.browser.contents)
3026        # If COURSE1 is outstanding, its credits won't be considered
3027        self.student['studycourse']['200']['COURSE1'].outstanding = True
3028        self.browser.getControl("Add course ticket").click()
3029        self.assertMatches(
3030            '...Successfully added COURSE2...', self.browser.contents)
3031        return
3032
3033    def test_postgraduate_student_access(self):
3034        self.certificate.study_mode = 'pg_ft'
3035        self.certificate.start_level = 999
3036        self.certificate.end_level = 999
3037        self.student['studycourse'].current_level = 999
3038        IWorkflowState(self.student).setState('school fee paid')
3039        self.browser.open(self.login_path)
3040        self.browser.getControl(name="form.login").value = self.student_id
3041        self.browser.getControl(name="form.password").value = 'spwd'
3042        self.browser.getControl("Login").click()
3043        self.assertTrue(
3044            'You logged in.' in self.browser.contents)
3045        # Now students can add the current study level
3046        self.browser.getLink("Study Course").click()
3047        self.browser.getLink("Add course list").click()
3048        self.assertMatches('...Add current level Postgraduate Level...',
3049                           self.browser.contents)
3050        self.browser.getControl("Create course list now").click()
3051        self.assertTrue("You successfully created a new course list"
3052            in self.browser.contents)
3053        # A level with one course ticket was created
3054        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
3055        self.browser.getLink("Edit course list").click()
3056        self.browser.getLink("here").click()
3057        self.browser.getControl(name="form.course").value = ['COURSE1']
3058        self.browser.getControl("Add course ticket").click()
3059        self.assertMatches('...Successfully added COURSE1...',
3060                           self.browser.contents)
3061        # Postgraduate students can't register course lists
3062        self.browser.getControl("Register course list").click()
3063        self.assertTrue("your course list can't bee registered"
3064            in self.browser.contents)
3065        self.assertEqual(self.student.state, 'school fee paid')
3066        return
3067
3068    def test_student_clearance_wo_clrcode(self):
3069        IWorkflowState(self.student).setState('clearance started')
3070        self.browser.open(self.login_path)
3071        self.browser.getControl(name="form.login").value = self.student_id
3072        self.browser.getControl(name="form.password").value = 'spwd'
3073        self.browser.getControl("Login").click()
3074        self.browser.open(self.edit_clearance_path)
3075        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
3076        self.browser.getControl("Save and request clearance").click()
3077        self.assertMatches('...Clearance has been requested...',
3078                           self.browser.contents)
3079
3080    def test_student_clearance_payment(self):
3081        # Login
3082        self.browser.open(self.login_path)
3083        self.browser.getControl(name="form.login").value = self.student_id
3084        self.browser.getControl(name="form.password").value = 'spwd'
3085        self.browser.getControl("Login").click()
3086
3087        # Students can add online clearance payment tickets
3088        self.browser.open(self.payments_path + '/addop')
3089        self.browser.getControl(name="form.p_category").value = ['clearance']
3090        self.browser.getControl("Create ticket").click()
3091        self.assertMatches('...ticket created...',
3092                           self.browser.contents)
3093
3094        # Students can't approve the payment
3095        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
3096        ctrl = self.browser.getControl(name='val_id')
3097        value = ctrl.options[0]
3098        self.browser.getLink(value).click()
3099        payment_url = self.browser.url
3100        self.assertRaises(
3101            Unauthorized, self.browser.open, payment_url + '/approve')
3102        # In the base package they can 'use' a fake approval view.
3103        # XXX: I tried to use
3104        # self.student['payments'][value].approveStudentPayment() instead.
3105        # But this function fails in
3106        # w.k.accesscodes.accesscode.create_accesscode.
3107        # grok.getSite returns None in tests.
3108        self.browser.open(payment_url + '/fake_approve')
3109        self.assertMatches('...Payment approved...',
3110                          self.browser.contents)
3111        expected = '''...
3112        <td>
3113          <span>Paid</span>
3114        </td>...'''
3115        expected = '''...
3116        <td>
3117          <span>Paid</span>
3118        </td>...'''
3119        self.assertMatches(expected,self.browser.contents)
3120        payment_id = self.student['payments'].keys()[0]
3121        payment = self.student['payments'][payment_id]
3122        self.assertEqual(payment.p_state, 'paid')
3123        self.assertEqual(payment.r_amount_approved, 3456.0)
3124        self.assertEqual(payment.r_code, 'AP')
3125        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
3126        # The new CLR-0 pin has been created
3127        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
3128        pin = self.app['accesscodes']['CLR-0'].keys()[0]
3129        ac = self.app['accesscodes']['CLR-0'][pin]
3130        self.assertEqual(ac.owner, self.student_id)
3131        self.assertEqual(ac.cost, 3456.0)
3132
3133        # Students can open the pdf payment slip
3134        self.browser.open(payment_url + '/payment_slip.pdf')
3135        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3136        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3137
3138        # The new CLR-0 pin can be used for starting clearance
3139        # but they have to upload a passport picture first
3140        # which is only possible in state admitted
3141        self.browser.open(self.student_path + '/change_portrait')
3142        self.assertMatches('...form is locked...',
3143                          self.browser.contents)
3144        IWorkflowInfo(self.student).fireTransition('admit')
3145        self.browser.open(self.student_path + '/change_portrait')
3146        image = open(SAMPLE_IMAGE, 'rb')
3147        ctrl = self.browser.getControl(name='passportuploadedit')
3148        file_ctrl = ctrl.mech_control
3149        file_ctrl.add_file(image, filename='my_photo.jpg')
3150        self.browser.getControl(
3151            name='upload_passportuploadedit').click()
3152        self.browser.open(self.student_path + '/start_clearance')
3153        parts = pin.split('-')[1:]
3154        clrseries, clrnumber = parts
3155        self.browser.getControl(name="ac_series").value = clrseries
3156        self.browser.getControl(name="ac_number").value = clrnumber
3157        self.browser.getControl("Start clearance now").click()
3158        self.assertMatches('...Clearance process has been started...',
3159                           self.browser.contents)
3160
3161    def test_student_schoolfee_payment(self):
3162        configuration = createObject('waeup.SessionConfiguration')
3163        configuration.academic_session = 2005
3164        self.app['configuration'].addSessionConfiguration(configuration)
3165        # Login
3166        self.browser.open(self.login_path)
3167        self.browser.getControl(name="form.login").value = self.student_id
3168        self.browser.getControl(name="form.password").value = 'spwd'
3169        self.browser.getControl("Login").click()
3170
3171        # Students can add online school fee payment tickets.
3172        IWorkflowState(self.student).setState('returning')
3173        self.browser.open(self.payments_path)
3174        self.assertRaises(
3175            LookupError, self.browser.getControl, name='val_id')
3176        self.browser.getLink("Add current session payment ticket").click()
3177        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3178        self.browser.getControl("Create ticket").click()
3179        self.assertMatches('...ticket created...',
3180                           self.browser.contents)
3181        ctrl = self.browser.getControl(name='val_id')
3182        value = ctrl.options[0]
3183        self.browser.getLink(value).click()
3184        self.assertMatches('...Amount Authorized...',
3185                           self.browser.contents)
3186        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3187        # Payment session and will be calculated as defined
3188        # in w.k.students.utils because we set changed the state
3189        # to returning
3190        self.assertEqual(self.student['payments'][value].p_session, 2005)
3191        self.assertEqual(self.student['payments'][value].p_level, 200)
3192
3193        # Student is the payer of the payment ticket.
3194        payer = IPayer(self.student['payments'][value])
3195        self.assertEqual(payer.display_fullname, 'Anna Tester')
3196        self.assertEqual(payer.id, self.student_id)
3197        self.assertEqual(payer.faculty, 'fac1')
3198        self.assertEqual(payer.department, 'dep1')
3199
3200        # We simulate the approval
3201        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3202        self.browser.open(self.browser.url + '/fake_approve')
3203        self.assertMatches('...Payment approved...',
3204                          self.browser.contents)
3205
3206        ## The new SFE-0 pin can be used for starting new session
3207        #self.browser.open(self.studycourse_path)
3208        #self.browser.getLink('Start new session').click()
3209        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3210        #parts = pin.split('-')[1:]
3211        #sfeseries, sfenumber = parts
3212        #self.browser.getControl(name="ac_series").value = sfeseries
3213        #self.browser.getControl(name="ac_number").value = sfenumber
3214        #self.browser.getControl("Start now").click()
3215        #self.assertMatches('...Session started...',
3216        #                   self.browser.contents)
3217
3218        self.assertTrue(self.student.state == 'school fee paid')
3219        return
3220
3221    def test_student_bedallocation_payment(self):
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        self.browser.open(self.payments_path)
3228        self.browser.open(self.payments_path + '/addop')
3229        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3230        self.browser.getControl("Create ticket").click()
3231        self.assertMatches('...ticket created...',
3232                           self.browser.contents)
3233        # Students can remove only online payment tickets which have
3234        # not received a valid callback
3235        self.browser.open(self.payments_path)
3236        ctrl = self.browser.getControl(name='val_id')
3237        value = ctrl.options[0]
3238        ctrl.getControl(value=value).selected = True
3239        self.browser.getControl("Remove selected", index=0).click()
3240        self.assertTrue('Successfully removed' in self.browser.contents)
3241
3242    def test_student_maintenance_payment(self):
3243        # Login
3244        self.browser.open(self.login_path)
3245        self.browser.getControl(name="form.login").value = self.student_id
3246        self.browser.getControl(name="form.password").value = 'spwd'
3247        self.browser.getControl("Login").click()
3248        self.browser.open(self.payments_path)
3249        self.browser.open(self.payments_path + '/addop')
3250        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3251        self.browser.getControl("Create ticket").click()
3252        self.assertMatches('...You have not yet booked accommodation...',
3253                           self.browser.contents)
3254        # We continue this test in test_student_accommodation
3255
3256    def test_student_previous_payments(self):
3257        configuration = createObject('waeup.SessionConfiguration')
3258        configuration.academic_session = 2000
3259        configuration.clearance_fee = 3456.0
3260        configuration.booking_fee = 123.4
3261        self.app['configuration'].addSessionConfiguration(configuration)
3262        configuration2 = createObject('waeup.SessionConfiguration')
3263        configuration2.academic_session = 2003
3264        configuration2.clearance_fee = 3456.0
3265        configuration2.booking_fee = 123.4
3266        self.app['configuration'].addSessionConfiguration(configuration2)
3267        configuration3 = createObject('waeup.SessionConfiguration')
3268        configuration3.academic_session = 2005
3269        configuration3.clearance_fee = 3456.0
3270        configuration3.booking_fee = 123.4
3271        self.app['configuration'].addSessionConfiguration(configuration3)
3272        self.student['studycourse'].entry_session = 2002
3273
3274        # Login
3275        self.browser.open(self.login_path)
3276        self.browser.getControl(name="form.login").value = self.student_id
3277        self.browser.getControl(name="form.password").value = 'spwd'
3278        self.browser.getControl("Login").click()
3279
3280        # Students can add previous school fee payment tickets in any state.
3281        IWorkflowState(self.student).setState('courses registered')
3282        self.browser.open(self.payments_path)
3283        self.browser.getLink("Add previous session payment ticket").click()
3284
3285        # Previous session payment form is provided
3286        self.assertEqual(self.student.current_session, 2004)
3287        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3288        self.browser.getControl(name="form.p_session").value = ['2000']
3289        self.browser.getControl(name="form.p_level").value = ['300']
3290        self.browser.getControl("Create ticket").click()
3291        self.assertMatches('...The previous session must not fall below...',
3292                           self.browser.contents)
3293        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3294        self.browser.getControl(name="form.p_session").value = ['2005']
3295        self.browser.getControl(name="form.p_level").value = ['300']
3296        self.browser.getControl("Create ticket").click()
3297        self.assertMatches('...This is not a previous session...',
3298                           self.browser.contents)
3299
3300        # Students can pay current session school fee.
3301        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3302        self.browser.getControl(name="form.p_session").value = ['2004']
3303        self.browser.getControl(name="form.p_level").value = ['300']
3304        self.browser.getControl("Create ticket").click()
3305        self.assertMatches('...ticket created...',
3306                           self.browser.contents)
3307        ctrl = self.browser.getControl(name='val_id')
3308        value = ctrl.options[0]
3309        self.browser.getLink(value).click()
3310        self.assertMatches('...Amount Authorized...',
3311                           self.browser.contents)
3312        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3313
3314        # Ticket creation is logged.
3315        logfile = os.path.join(
3316            self.app['datacenter'].storage, 'logs', 'students.log')
3317        logcontent = open(logfile).read()
3318        self.assertTrue(
3319            'K1000000 - students.browser.PreviousPaymentAddFormPage - '
3320            'K1000000 - added: %s' % value
3321            in logcontent)
3322
3323        # Payment session is properly set
3324        self.assertEqual(self.student['payments'][value].p_session, 2004)
3325        self.assertEqual(self.student['payments'][value].p_level, 300)
3326
3327        # We simulate the approval
3328        self.browser.open(self.browser.url + '/fake_approve')
3329        self.assertMatches('...Payment approved...',
3330                          self.browser.contents)
3331
3332        # No AC has been created
3333        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
3334        self.assertTrue(self.student['payments'][value].ac is None)
3335
3336        # Current payment flag is set False
3337        self.assertFalse(self.student['payments'][value].p_current)
3338
3339        # Button and form are not available for students who are in
3340        # states up to cleared
3341        self.student['studycourse'].entry_session = 2004
3342        IWorkflowState(self.student).setState('cleared')
3343        self.browser.open(self.payments_path)
3344        self.assertFalse(
3345            "Add previous session payment ticket" in self.browser.contents)
3346        self.browser.open(self.payments_path + '/addpp')
3347        self.assertTrue(
3348            "No previous payment to be made" in self.browser.contents)
3349        return
3350
3351    def test_postgraduate_student_payments(self):
3352        configuration = createObject('waeup.SessionConfiguration')
3353        configuration.academic_session = 2005
3354        self.app['configuration'].addSessionConfiguration(configuration)
3355        self.certificate.study_mode = 'pg_ft'
3356        self.certificate.start_level = 999
3357        self.certificate.end_level = 999
3358        self.student['studycourse'].current_level = 999
3359        # Login
3360        self.browser.open(self.login_path)
3361        self.browser.getControl(name="form.login").value = self.student_id
3362        self.browser.getControl(name="form.password").value = 'spwd'
3363        self.browser.getControl("Login").click()
3364        # Students can add online school fee payment tickets.
3365        IWorkflowState(self.student).setState('cleared')
3366        self.browser.open(self.payments_path)
3367        self.browser.getLink("Add current session payment ticket").click()
3368        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3369        self.browser.getControl("Create ticket").click()
3370        self.assertMatches('...ticket created...',
3371                           self.browser.contents)
3372        ctrl = self.browser.getControl(name='val_id')
3373        value = ctrl.options[0]
3374        self.browser.getLink(value).click()
3375        self.assertMatches('...Amount Authorized...',
3376                           self.browser.contents)
3377        # Payment session and level are current ones.
3378        # Postgrads have to pay school_fee_1.
3379        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
3380        self.assertEqual(self.student['payments'][value].p_session, 2004)
3381        self.assertEqual(self.student['payments'][value].p_level, 999)
3382
3383        # We simulate the approval
3384        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
3385        self.browser.open(self.browser.url + '/fake_approve')
3386        self.assertMatches('...Payment approved...',
3387                          self.browser.contents)
3388
3389        ## The new SFE-0 pin can be used for starting session
3390        #self.browser.open(self.studycourse_path)
3391        #self.browser.getLink('Start new session').click()
3392        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
3393        #parts = pin.split('-')[1:]
3394        #sfeseries, sfenumber = parts
3395        #self.browser.getControl(name="ac_series").value = sfeseries
3396        #self.browser.getControl(name="ac_number").value = sfenumber
3397        #self.browser.getControl("Start now").click()
3398        #self.assertMatches('...Session started...',
3399        #                   self.browser.contents)
3400
3401        self.assertTrue(self.student.state == 'school fee paid')
3402
3403        # Postgrad students do not need to register courses the
3404        # can just pay for the next session.
3405        self.browser.open(self.payments_path)
3406        # Remove first payment to be sure that we access the right ticket
3407        del self.student['payments'][value]
3408        self.browser.getLink("Add current session payment ticket").click()
3409        self.browser.getControl(name="form.p_category").value = ['schoolfee']
3410        self.browser.getControl("Create ticket").click()
3411        ctrl = self.browser.getControl(name='val_id')
3412        value = ctrl.options[0]
3413        self.browser.getLink(value).click()
3414        # Payment session has increased by one, payment level remains the same.
3415        # Returning Postgraduates have to pay school_fee_2.
3416        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
3417        self.assertEqual(self.student['payments'][value].p_session, 2005)
3418        self.assertEqual(self.student['payments'][value].p_level, 999)
3419
3420        # Student is still in old session
3421        self.assertEqual(self.student.current_session, 2004)
3422
3423        # We do not need to pay the ticket if any other
3424        # SFE pin is provided
3425        pin_container = self.app['accesscodes']
3426        pin_container.createBatch(
3427            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
3428        pin = pin_container['SFE-1'].values()[0].representation
3429        sfeseries, sfenumber = pin.split('-')[1:]
3430        # The new SFE-1 pin can be used for starting new session
3431        self.browser.open(self.studycourse_path)
3432        self.browser.getLink('Start new session').click()
3433        self.browser.getControl(name="ac_series").value = sfeseries
3434        self.browser.getControl(name="ac_number").value = sfenumber
3435        self.browser.getControl("Start now").click()
3436        self.assertMatches('...Session started...',
3437                           self.browser.contents)
3438        self.assertTrue(self.student.state == 'school fee paid')
3439        # Student is in new session
3440        self.assertEqual(self.student.current_session, 2005)
3441        self.assertEqual(self.student['studycourse'].current_level, 999)
3442        return
3443
3444    def test_student_accommodation(self):
3445        # Create a second hostel with one bed
3446        hostel = Hostel()
3447        hostel.hostel_id = u'hall-2'
3448        hostel.hostel_name = u'Hall 2'
3449        self.app['hostels'].addHostel(hostel)
3450        bed = Bed()
3451        bed.bed_id = u'hall-2_A_101_A'
3452        bed.bed_number = 1
3453        bed.owner = NOT_OCCUPIED
3454        bed.bed_type = u'regular_female_fr'
3455        self.app['hostels'][hostel.hostel_id].addBed(bed)
3456        self.app['hostels'].allocation_expiration = 7
3457
3458        self.browser.open(self.login_path)
3459        self.browser.getControl(name="form.login").value = self.student_id
3460        self.browser.getControl(name="form.password").value = 'spwd'
3461        self.browser.getControl("Login").click()
3462        # Students can add online booking fee payment tickets and open the
3463        # callback view (see test_manage_payments).
3464        self.browser.getLink("Payments").click()
3465        self.browser.getLink("Add current session payment ticket").click()
3466        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3467        self.browser.getControl("Create ticket").click()
3468        ctrl = self.browser.getControl(name='val_id')
3469        value = ctrl.options[0]
3470        self.browser.getLink(value).click()
3471        self.browser.open(self.browser.url + '/fake_approve')
3472        # The new HOS-0 pin has been created.
3473        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
3474        pin = self.app['accesscodes']['HOS-0'].keys()[0]
3475        ac = self.app['accesscodes']['HOS-0'][pin]
3476        parts = pin.split('-')[1:]
3477        sfeseries, sfenumber = parts
3478        # Students can use HOS code and book a bed space with it ...
3479        self.browser.open(self.acco_path)
3480        # ... but not if booking period has expired ...
3481        self.app['hostels'].enddate = datetime.now(pytz.utc)
3482        self.browser.getControl("Book accommodation").click()
3483        self.assertMatches('...Outside booking period: ...',
3484                           self.browser.contents)
3485        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3486        # ... or student data are incomplete ...
3487        self.student['studycourse'].current_level = None
3488        self.browser.getControl("Book accommodation").click()
3489        self.assertMatches('...Your data are incomplete...',
3490            self.browser.contents)
3491        self.student['studycourse'].current_level = 100
3492        # ... or student is not the an allowed state ...
3493        self.browser.getControl("Book accommodation").click()
3494        self.assertMatches('...You are in the wrong...',
3495                           self.browser.contents)
3496        # Students can still not see the disired hostel selector.
3497        self.assertFalse('desired hostel' in self.browser.contents)
3498        IWorkflowInfo(self.student).fireTransition('admit')
3499        # Students can now see the disired hostel selector.
3500        self.browser.reload()
3501        self.browser.open(self.acco_path)
3502        self.assertTrue('desired hostel' in self.browser.contents)
3503        self.browser.getControl(name="hostel").value = ['hall-2']
3504        self.browser.getControl("Save").click()
3505        self.assertTrue('selection has been saved' in self.browser.contents)
3506        self.assertTrue('<option selected="selected" value="hall-2">'
3507            in self.browser.contents)
3508        self.browser.getControl("Book accommodation").click()
3509        self.assertMatches('...Activation Code:...',
3510                           self.browser.contents)
3511        # Student can't use faked ACs ...
3512        self.browser.getControl(name="ac_series").value = u'nonsense'
3513        self.browser.getControl(name="ac_number").value = sfenumber
3514        self.browser.getControl("Create bed ticket").click()
3515        self.assertMatches('...Activation code is invalid...',
3516                           self.browser.contents)
3517        # ... or ACs owned by somebody else.
3518        ac.owner = u'Anybody'
3519        self.browser.getControl(name="ac_series").value = sfeseries
3520        self.browser.getControl(name="ac_number").value = sfenumber
3521        # There is no free bed space and the bed selector does not appear
3522        self.assertFalse('<option value="hall-1_A_101_A">'
3523            in self.browser.contents)
3524        self.browser.getControl("Create bed ticket").click()
3525        # Hostel 2 has only a bed for women.
3526        self.assertTrue('There is no free bed in your category regular_male_fr.'
3527            in self.browser.contents)
3528        self.browser.getControl(name="hostel").value = ['hall-1']
3529        self.browser.getControl("Save").click()
3530        self.browser.getControl("Book accommodation").click()
3531        # Student can't use faked ACs ...
3532        self.browser.getControl(name="ac_series").value = sfeseries
3533        self.browser.getControl(name="ac_number").value = sfenumber
3534        self.browser.getControl("Create bed ticket").click()
3535        self.assertMatches('...You are not the owner of this access code...',
3536                           self.browser.contents)
3537        # The bed remains empty.
3538        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3539        self.assertTrue(bed.owner == NOT_OCCUPIED)
3540        ac.owner = self.student_id
3541        self.browser.open(self.acco_path + '/add')
3542        self.browser.getControl(name="ac_series").value = sfeseries
3543        self.browser.getControl(name="ac_number").value = sfenumber
3544        # Bed can be selected
3545        self.browser.getControl(name="bed").value = ['hall-1_A_101_A']
3546        self.browser.getControl("Create bed ticket").click()
3547        self.assertTrue('Bed ticket created and bed booked'
3548            in self.browser.contents)
3549        # Bed has been allocated.
3550        self.assertTrue(bed.owner == self.student_id)
3551        # BedTicketAddPage is now blocked.
3552        self.browser.getControl("Book accommodation").click()
3553        self.assertMatches('...You already booked a bed space...',
3554            self.browser.contents)
3555        # The bed ticket displays the data correctly.
3556        self.browser.open(self.acco_path + '/2004')
3557        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3558                           self.browser.contents)
3559        self.assertMatches('...2004/2005...', self.browser.contents)
3560        self.assertMatches('...regular_male_fr...', self.browser.contents)
3561        self.assertMatches('...%s...' % pin, self.browser.contents)
3562        # Students can open the pdf slip.
3563        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3564        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3565        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3566        path = os.path.join(samples_dir(), 'bed_allocation_slip.pdf')
3567        open(path, 'wb').write(self.browser.contents)
3568        print "Sample PDF bed_allocation_slip.pdf written to %s" % path
3569        # Students can't relocate themselves.
3570        self.assertFalse('Relocate' in self.browser.contents)
3571        relocate_path = self.acco_path + '/2004/relocate'
3572        self.assertRaises(
3573            Unauthorized, self.browser.open, relocate_path)
3574        # Students can't see the Remove button and check boxes.
3575        self.browser.open(self.acco_path)
3576        self.assertFalse('Remove' in self.browser.contents)
3577        self.assertFalse('val_id' in self.browser.contents)
3578        # Students can pay maintenance fee now.
3579        self.browser.open(self.payments_path)
3580        self.browser.open(self.payments_path + '/addop')
3581        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3582        self.browser.getControl("Create ticket").click()
3583        self.assertMatches('...Payment ticket created...',
3584                           self.browser.contents)
3585        ctrl = self.browser.getControl(name='val_id')
3586        value = ctrl.options[0]
3587        # Maintennace fee is taken from the hostel object.
3588        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3589        # If the hostel's maintenance fee isn't set, the fee is
3590        # taken from the session configuration object.
3591        self.app['hostels']['hall-1'].maint_fee = 0.0
3592        self.browser.open(self.payments_path + '/addop')
3593        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3594        self.browser.getControl("Create ticket").click()
3595        ctrl = self.browser.getControl(name='val_id')
3596        value = ctrl.options[1]
3597        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3598        # The bedticket is aware of successfull maintenance fee payment
3599        bedticket = self.student['accommodation']['2004']
3600        self.assertFalse(bedticket.maint_payment_made)
3601        self.student['payments'][value].approve()
3602        self.assertTrue(bedticket.maint_payment_made)
3603        return
3604
3605    def test_change_password_request(self):
3606        self.browser.open('http://localhost/app/changepw')
3607        self.browser.getControl(name="form.identifier").value = '123'
3608        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3609        self.browser.getControl("Send login credentials").click()
3610        self.assertTrue('An email with' in self.browser.contents)
3611
3612    def test_student_expired_personal_data(self):
3613        # Login
3614        IWorkflowState(self.student).setState('school fee paid')
3615        delta = timedelta(days=180)
3616        self.student.personal_updated = datetime.utcnow() - delta
3617        self.browser.open(self.login_path)
3618        self.browser.getControl(name="form.login").value = self.student_id
3619        self.browser.getControl(name="form.password").value = 'spwd'
3620        self.browser.getControl("Login").click()
3621        self.assertEqual(self.browser.url, self.student_path)
3622        self.assertTrue(
3623            'You logged in' in self.browser.contents)
3624        # Students don't see personal_updated field in edit form
3625        self.browser.open(self.edit_personal_path)
3626        self.assertFalse('Updated' in self.browser.contents)
3627        self.browser.open(self.personal_path)
3628        self.assertTrue('Updated' in self.browser.contents)
3629        self.browser.getLink("Logout").click()
3630        delta = timedelta(days=181)
3631        self.student.personal_updated = datetime.utcnow() - delta
3632        self.browser.open(self.login_path)
3633        self.browser.getControl(name="form.login").value = self.student_id
3634        self.browser.getControl(name="form.password").value = 'spwd'
3635        self.browser.getControl("Login").click()
3636        self.assertEqual(self.browser.url, self.edit_personal_path)
3637        self.assertTrue(
3638            'Your personal data record is outdated.' in self.browser.contents)
3639
3640    def test_request_transcript(self):
3641        IWorkflowState(self.student).setState('graduated')
3642        self.browser.open(self.login_path)
3643        self.browser.getControl(name="form.login").value = self.student_id
3644        self.browser.getControl(name="form.password").value = 'spwd'
3645        self.browser.getControl("Login").click()
3646        self.assertMatches(
3647            '...You logged in...', self.browser.contents)
3648        # Create payment ticket
3649        self.browser.open(self.payments_path)
3650        self.browser.open(self.payments_path + '/addop')
3651        self.browser.getControl(name="form.p_category").value = ['transcript']
3652        self.browser.getControl("Create ticket").click()
3653        ctrl = self.browser.getControl(name='val_id')
3654        value = ctrl.options[0]
3655        self.browser.getLink(value).click()
3656        self.assertMatches('...Amount Authorized...',
3657                           self.browser.contents)
3658        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3659        # Student is the payer of the payment ticket.
3660        payer = IPayer(self.student['payments'][value])
3661        self.assertEqual(payer.display_fullname, 'Anna Tester')
3662        self.assertEqual(payer.id, self.student_id)
3663        self.assertEqual(payer.faculty, 'fac1')
3664        self.assertEqual(payer.department, 'dep1')
3665        # We simulate the approval and fetch the pin
3666        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3667        self.browser.open(self.browser.url + '/fake_approve')
3668        self.assertMatches('...Payment approved...',
3669                          self.browser.contents)
3670        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3671        parts = pin.split('-')[1:]
3672        tscseries, tscnumber = parts
3673        # Student can use the pin to send the transcript request
3674        self.browser.open(self.student_path)
3675        self.browser.getLink("Request transcript").click()
3676        self.browser.getControl(name="ac_series").value = tscseries
3677        self.browser.getControl(name="ac_number").value = tscnumber
3678        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3679        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3680        self.browser.getControl("Request now").click()
3681        self.assertMatches('...Transcript processing has been started...',
3682                          self.browser.contents)
3683        self.assertEqual(self.student.state, 'transcript requested')
3684        self.assertMatches(
3685            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3686            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3687            'Address line2\n\n', self.student['studycourse'].transcript_comment)
3688        # The comment has been logged
3689        logfile = os.path.join(
3690            self.app['datacenter'].storage, 'logs', 'students.log')
3691        logcontent = open(logfile).read()
3692        self.assertTrue(
3693            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3694            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3695            in logcontent)
3696
3697    def test_late_registration(self):
3698        # Login
3699        delta = timedelta(days=10)
3700        self.app['configuration'][
3701            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
3702        IWorkflowState(self.student).setState('school fee paid')
3703        self.browser.open(self.login_path)
3704        self.browser.getControl(name="form.login").value = self.student_id
3705        self.browser.getControl(name="form.password").value = 'spwd'
3706        self.browser.getControl("Login").click()
3707        self.browser.open(self.payments_path)
3708        self.browser.open(self.payments_path + '/addop')
3709        self.browser.getControl(name="form.p_category").value = ['late_registration']
3710        self.browser.getControl("Create ticket").click()
3711        self.assertMatches('...ticket created...',
3712                           self.browser.contents)
3713        self.browser.open(self.payments_path)
3714        ctrl = self.browser.getControl(name='val_id')
3715        value = ctrl.options[0]
3716        self.browser.getLink("Study Course").click()
3717        self.browser.getLink("Add course list").click()
3718        self.assertMatches('...Add current level 100 (Year 1)...',
3719                           self.browser.contents)
3720        self.browser.getControl("Create course list now").click()
3721        self.browser.getLink("100").click()
3722        self.browser.getLink("Edit course list").click()
3723        self.browser.getControl("Register course list").click()
3724        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
3725        self.student['payments'][value].approve()
3726        self.browser.getControl("Register course list").click()
3727        self.assertTrue('Course list has been registered' in self.browser.contents)
3728        self.assertEqual(self.student.state, 'courses registered')
3729
3730    def test_former_course(self):
3731        IWorkflowState(self.student).setState('school fee paid')
3732        self.student['studycourse'].current_level = 100
3733        self.browser.open(self.login_path)
3734        self.browser.getControl(name="form.login").value = self.student_id
3735        self.browser.getControl(name="form.password").value = 'spwd'
3736        self.browser.getControl("Login").click()
3737        # Now students can add the current study level
3738        self.browser.getLink("Study Course").click()
3739        self.browser.getLink("Add course list").click()
3740        self.assertMatches('...Add current level 100 (Year 1)...',
3741                           self.browser.contents)
3742        self.browser.getControl("Create course list now").click()
3743        # A level with one course ticket was created
3744        self.assertEqual(
3745            self.student['studycourse']['100'].number_of_tickets, 1)
3746        self.browser.getLink("100").click()
3747        self.assertTrue('<td>Unnamed Course</td>' in self.browser.contents)
3748        self.browser.getLink("Edit course list").click()
3749        del self.student['studycourse']['100']['COURSE1']
3750        # Course can be added again via CourseTicketAddFormPage2
3751        self.browser.getLink("here").click()
3752        self.browser.getControl(name="form.course").value = ['COURSE1']
3753        self.browser.getControl("Add course ticket").click()
3754        self.assertTrue('Successfully added COURSE1' in self.browser.contents)
3755        self.assertEqual(len(self.student['studycourse']['100'].keys()),1)
3756        del self.student['studycourse']['100']['COURSE1']
3757        self.course.former_course = True
3758        self.browser.getLink("here").click()
3759        self.browser.getControl(name="form.course").value = ['COURSE1']
3760        self.browser.getControl("Add course ticket").click()
3761        self.assertTrue('Former courses can\'t be added.' in self.browser.contents)
3762        self.assertEqual(len(self.student['studycourse']['100'].keys()),0)
3763        # Course can be added again via StudyLevelEditFormPage
3764        self.browser.getLink("100").click()
3765        self.browser.getLink("Edit course list").click()
3766        self.browser.getControl(name="course").value = 'COURSE1'
3767        self.browser.getControl("Add course ticket").click()
3768        self.assertTrue('Former courses can\'t be added.' in
3769            self.browser.contents)
3770        self.assertEqual(len(self.student['studycourse']['100'].keys()),0)
3771        # but added if current course
3772        self.course.former_course = False
3773        self.browser.getControl(name="course").value = 'COURSE1'
3774        self.browser.getControl("Add course ticket").click()
3775        self.assertTrue('Successfully added COURSE1' in self.browser.contents)
3776        self.assertEqual(len(self.student['studycourse']['100'].keys()),1)
3777        return
3778
3779class StudentRequestPWTests(StudentsFullSetup):
3780    # Tests for student registration
3781
3782    layer = FunctionalLayer
3783
3784    def test_request_pw(self):
3785        # Student with wrong number can't be found.
3786        self.browser.open('http://localhost/app/requestpw')
3787        self.browser.getControl(name="form.lastname").value = 'Tester'
3788        self.browser.getControl(name="form.number").value = 'anynumber'
3789        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3790        self.browser.getControl("Send login credentials").click()
3791        self.assertTrue('No student record found.'
3792            in self.browser.contents)
3793        # Anonymous is not informed that lastname verification failed.
3794        # It seems that the record doesn't exist.
3795        self.browser.open('http://localhost/app/requestpw')
3796        self.browser.getControl(name="form.lastname").value = 'Johnny'
3797        self.browser.getControl(name="form.number").value = '123'
3798        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3799        self.browser.getControl("Send login credentials").click()
3800        self.assertTrue('No student record found.'
3801            in self.browser.contents)
3802        # Even with the correct lastname we can't register if a
3803        # password has been set and used.
3804        self.browser.getControl(name="form.lastname").value = 'Tester'
3805        self.browser.getControl(name="form.number").value = '123'
3806        self.browser.getControl("Send login credentials").click()
3807        self.assertTrue('Your password has already been set and used.'
3808            in self.browser.contents)
3809        self.browser.open('http://localhost/app/requestpw')
3810        self.app['students'][self.student_id].password = None
3811        # The lastname field, used for verification, is not case-sensitive.
3812        self.browser.getControl(name="form.lastname").value = 'tESTer'
3813        self.browser.getControl(name="form.number").value = '123'
3814        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3815        self.browser.getControl("Send login credentials").click()
3816        # Yeah, we succeded ...
3817        self.assertTrue('Your password request was successful.'
3818            in self.browser.contents)
3819        # We can also use the matric_number instead.
3820        self.browser.open('http://localhost/app/requestpw')
3821        self.browser.getControl(name="form.lastname").value = 'tESTer'
3822        self.browser.getControl(name="form.number").value = '234'
3823        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3824        self.browser.getControl("Send login credentials").click()
3825        self.assertTrue('Your password request was successful.'
3826            in self.browser.contents)
3827        # ... and  student can be found in the catalog via the email address
3828        cat = queryUtility(ICatalog, name='students_catalog')
3829        results = list(
3830            cat.searchResults(
3831            email=('new@yy.zz', 'new@yy.zz')))
3832        self.assertEqual(self.student,results[0])
3833        logfile = os.path.join(
3834            self.app['datacenter'].storage, 'logs', 'main.log')
3835        logcontent = open(logfile).read()
3836        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3837                        '234 (K1000000) - new@yy.zz' in logcontent)
3838        return
3839
3840    def test_student_locked_level_forms(self):
3841
3842        # Add two study levels, one current and one previous
3843        studylevel = createObject(u'waeup.StudentStudyLevel')
3844        studylevel.level = 100
3845        self.student['studycourse'].addStudentStudyLevel(
3846            self.certificate, studylevel)
3847        studylevel = createObject(u'waeup.StudentStudyLevel')
3848        studylevel.level = 200
3849        self.student['studycourse'].addStudentStudyLevel(
3850            self.certificate, studylevel)
3851        IWorkflowState(self.student).setState('school fee paid')
3852        self.student['studycourse'].current_level = 200
3853
3854        self.browser.open(self.login_path)
3855        self.browser.getControl(name="form.login").value = self.student_id
3856        self.browser.getControl(name="form.password").value = 'spwd'
3857        self.browser.getControl("Login").click()
3858
3859        self.browser.open(self.student_path + '/studycourse/200/edit')
3860        self.assertFalse('The requested form is locked' in self.browser.contents)
3861        self.browser.open(self.student_path + '/studycourse/100/edit')
3862        self.assertTrue('The requested form is locked' in self.browser.contents)
3863
3864        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3865        self.assertFalse('The requested form is locked' in self.browser.contents)
3866        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3867        self.assertTrue('The requested form is locked' in self.browser.contents)
3868
3869        IWorkflowState(self.student).setState('courses registered')
3870        self.browser.open(self.student_path + '/studycourse/200/edit')
3871        self.assertTrue('The requested form is locked' in self.browser.contents)
3872        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3873        self.assertTrue('The requested form is locked' in self.browser.contents)
3874
3875
3876class PublicPagesTests(StudentsFullSetup):
3877    # Tests for simple webservices
3878
3879    layer = FunctionalLayer
3880
3881    def test_paymentrequest(self):
3882        payment = createObject('waeup.StudentOnlinePayment')
3883        payment.p_category = u'schoolfee'
3884        payment.p_session = self.student.current_session
3885        payment.p_item = u'My Certificate'
3886        payment.p_id = u'anyid'
3887        self.student['payments']['anykey'] = payment
3888        # Request information about unpaid payment ticket
3889        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3890        self.assertEqual(self.browser.contents, '-1')
3891        # Request information about paid payment ticket
3892        payment.p_state = u'paid'
3893        notify(grok.ObjectModifiedEvent(payment))
3894        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3895        self.assertEqual(self.browser.contents,
3896            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3897            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3898            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3899            '&FEE_AMOUNT=0.0')
3900        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3901        self.assertEqual(self.browser.contents, '-1')
3902        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3903        self.assertEqual(self.browser.contents, '-1')
3904
3905class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3906    # Tests for StudentsContainer class views and pages
3907
3908    layer = FunctionalLayer
3909
3910    def wait_for_export_job_completed(self):
3911        # helper function waiting until the current export job is completed
3912        manager = getUtility(IJobManager)
3913        job_id = self.app['datacenter'].running_exports[0][0]
3914        job = manager.get(job_id)
3915        wait_for_result(job)
3916        return job_id
3917
3918    def add_payment(self, student):
3919        # get a payment with all fields set
3920        payment = StudentOnlinePayment()
3921        payment.creation_date = datetime(2012, 12, 13)
3922        payment.p_id = 'my-id'
3923        payment.p_category = u'schoolfee'
3924        payment.p_state = 'paid'
3925        payment.ac = u'666'
3926        payment.p_item = u'p-item'
3927        payment.p_level = 100
3928        payment.p_session = curr_year - 6
3929        payment.payment_date = datetime(2012, 12, 13)
3930        payment.amount_auth = 12.12
3931        payment.r_amount_approved = 12.12
3932        payment.r_code = u'r-code'
3933        # XXX: there is no addPayment method to give predictable names
3934        self.payment = student['payments']['my-payment'] = payment
3935        return payment
3936
3937    def test_datacenter_export(self):
3938        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3939        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3940        self.browser.getControl(name="exporter").value = ['bursary']
3941        self.browser.getControl(name="session").value = ['2004']
3942        self.browser.getControl(name="level").value = ['100']
3943        self.browser.getControl(name="mode").value = ['ug_ft']
3944        self.browser.getControl(name="payments_start").value = '13/12/2012'
3945        self.browser.getControl(name="payments_end").value = '14/12/2012'
3946        self.browser.getControl("Create CSV file").click()
3947
3948        # When the job is finished and we reload the page...
3949        job_id = self.wait_for_export_job_completed()
3950        # ... the csv file can be downloaded ...
3951        self.browser.open('http://localhost/app/datacenter/@@export')
3952        self.browser.getLink("Download").click()
3953        self.assertEqual(self.browser.headers['content-type'],
3954            'text/csv; charset=UTF-8')
3955        self.assertTrue(
3956            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3957            self.browser.headers['content-disposition'])
3958        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3959        job_id = self.app['datacenter'].running_exports[0][0]
3960        # ... and discarded
3961        self.browser.open('http://localhost/app/datacenter/@@export')
3962        self.browser.getControl("Discard").click()
3963        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3964        # Creation, downloading and discarding is logged
3965        logfile = os.path.join(
3966            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3967        logcontent = open(logfile).read()
3968        self.assertTrue(
3969            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3970            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3971            '13/12/2012, 14/12/2012, all, all, all, all, all), job_id=%s'
3972            % job_id in logcontent
3973            )
3974        self.assertTrue(
3975            'zope.mgr - browser.pages.ExportCSVView '
3976            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3977            % (job_id, job_id) in logcontent
3978            )
3979        self.assertTrue(
3980            'zope.mgr - browser.pages.ExportCSVPage '
3981            '- discarded: job_id=%s' % job_id in logcontent
3982            )
3983
3984    def test_datacenter_export_selected(self):
3985        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3986        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3987        self.browser.getControl(name="exporter").value = ['students']
3988        self.browser.getControl(name="students").value = 'K1000000'
3989        self.browser.getControl("Create CSV file").click()
3990        # When the job is finished and we reload the page...
3991        job_id = self.wait_for_export_job_completed()
3992        # ... the csv file can be downloaded ...
3993        self.browser.open('http://localhost/app/datacenter/@@export')
3994        self.browser.getLink("Download").click()
3995        self.assertEqual(self.browser.headers['content-type'],
3996            'text/csv; charset=UTF-8')
3997        self.assertTrue(
3998            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3999            self.browser.headers['content-disposition'])
4000        self.assertTrue(
4001            'adm_code,clr_code,date_of_birth,email,employer,'
4002            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
4003            'officer_comment,parents_email,perm_address,'
4004            'personal_updated,phone,reg_number,'
4005            'sex,student_id,suspended,suspended_comment,'
4006            'password,state,history,certcode,is_postgrad,'
4007            'current_level,current_session,entry_session\r\n'
4008            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,,'
4009            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
4010        self.browser.open('http://localhost/app/datacenter/@@export')
4011        self.browser.getControl("Discard").click()
4012
4013    def test_payment_dates(self):
4014        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4015        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
4016        self.browser.getControl(name="exporter").value = ['bursary']
4017        self.browser.getControl(name="session").value = ['2004']
4018        self.browser.getControl(name="level").value = ['100']
4019        self.browser.getControl(name="mode").value = ['ug_ft']
4020        self.browser.getControl(name="payments_start").value = '13/12/2012'
4021        # If one payment date is missing, an error message appears
4022        self.browser.getControl(name="payments_end").value = ''
4023        self.browser.getControl("Create CSV file").click()
4024        self.assertTrue('Payment dates do not match format d/m/Y'
4025            in self.browser.contents)
4026
4027    def test_faculties_export(self):
4028        self.add_payment(self.student)
4029        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4030        facs_path = 'http://localhost/app/faculties'
4031        self.browser.open(facs_path)
4032        self.browser.getLink("Export student data").click()
4033        self.browser.getControl("Set export parameters").click()
4034        self.browser.getControl(name="exporter").value = ['bursary']
4035        self.browser.getControl(name="session").value = ['2004']
4036        self.browser.getControl(name="level").value = ['100']
4037        self.browser.getControl(name="mode").value = ['ug_ft']
4038        self.browser.getControl(name="payments_start").value = '13/12/2012'
4039        self.browser.getControl(name="payments_end").value = '14/12/2012'
4040        self.browser.getControl(name="paycat").value = ['schoolfee']
4041        self.browser.getControl("Create CSV file").click()
4042
4043        # When the job is finished and we reload the page...
4044        job_id = self.wait_for_export_job_completed()
4045        self.browser.open(facs_path + '/exports')
4046        # ... the csv file can be downloaded ...
4047        self.browser.getLink("Download").click()
4048        self.assertEqual(self.browser.headers['content-type'],
4049            'text/csv; charset=UTF-8')
4050        self.assertTrue(
4051            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4052            self.browser.headers['content-disposition'])
4053        self.assertTrue(
4054            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4055            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4056            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4057            %(curr_year-6) in self.browser.contents)
4058        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4059        job_id = self.app['datacenter'].running_exports[0][0]
4060        # ... and discarded
4061        self.browser.open(facs_path + '/exports')
4062        self.browser.getControl("Discard").click()
4063        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4064        # Creation, downloading and discarding is logged
4065        logfile = os.path.join(
4066            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4067        logcontent = open(logfile).read()
4068        self.assertTrue(
4069            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
4070            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
4071            '13/12/2012, 14/12/2012, all, all, schoolfee, all, all), job_id=%s'
4072            % job_id in logcontent
4073            )
4074        self.assertTrue(
4075            'zope.mgr - students.browser.ExportJobContainerDownload '
4076            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
4077            % (job_id, job_id) in logcontent
4078            )
4079        self.assertTrue(
4080            'zope.mgr - students.browser.ExportJobContainerOverview '
4081            '- discarded: job_id=%s' % job_id in logcontent
4082            )
4083        # Officer can also enter student id and gets the same export file
4084        self.browser.open(facs_path)
4085        self.browser.getLink("Export student data").click()
4086        self.browser.getControl("Enter student ids or matric numbers").click()
4087        self.browser.getControl(name="exporter").value = ['bursary']
4088        self.browser.getControl(name="students").value = 'K1000000'
4089        self.browser.getControl("Create CSV file").click()
4090        # When the job is finished and we reload the page...
4091        job_id = self.wait_for_export_job_completed()
4092        # ... the csv file can be downloaded ...
4093        self.browser.open('http://localhost/app/faculties/exports')
4094        self.browser.getLink("Download").click()
4095        self.assertEqual(self.browser.headers['content-type'],
4096            'text/csv; charset=UTF-8')
4097        self.assertTrue(
4098            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4099            self.browser.headers['content-disposition'])
4100        self.assertTrue(
4101            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4102            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4103            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4104            %(curr_year-6) in self.browser.contents)
4105
4106    def test_faculty_export(self):
4107        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4108        fac1_path = 'http://localhost/app/faculties/fac1'
4109        self.browser.open(fac1_path)
4110        self.browser.getLink("Export student data").click()
4111        self.browser.getControl("Set export parameters").click()
4112        self.browser.getControl(name="exporter").value = ['students']
4113        self.browser.getControl(name="session").value = ['2004']
4114        self.browser.getControl(name="level").value = ['100']
4115        self.browser.getControl(name="mode").value = ['ug_ft']
4116        # The testbrowser does not hide the payment period fields, but
4117        # values are ignored when using the students exporter.
4118        self.browser.getControl(name="payments_start").value = '13/12/2012'
4119        self.browser.getControl(name="payments_end").value = '14/12/2012'
4120        self.browser.getControl("Create CSV file").click()
4121        # When the job is finished and we reload the page...
4122        job_id = self.wait_for_export_job_completed()
4123        self.browser.open(fac1_path + '/exports')
4124        # ... the csv file can be downloaded ...
4125        self.browser.getLink("Download").click()
4126        self.assertEqual(self.browser.headers['content-type'],
4127            'text/csv; charset=UTF-8')
4128        self.assertTrue(
4129            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4130            self.browser.headers['content-disposition'])
4131        self.assertTrue(
4132            'adm_code,clr_code,date_of_birth,email,employer,'
4133            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
4134            'officer_comment,parents_email,perm_address,'
4135            'personal_updated,phone,reg_number,'
4136            'sex,student_id,suspended,suspended_comment,'
4137            'password,state,history,certcode,is_postgrad,'
4138            'current_level,current_session,entry_session\r\n'
4139            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,,'
4140            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
4141        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4142        job_id = self.app['datacenter'].running_exports[0][0]
4143        # ... and discarded
4144        self.browser.open(fac1_path + '/exports')
4145        self.browser.getControl("Discard").click()
4146        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4147        # Creation, downloading and discarding is logged
4148        logfile = os.path.join(
4149            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4150        logcontent = open(logfile).read()
4151        self.assertTrue(
4152            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
4153            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
4154            '13/12/2012, 14/12/2012, all, all, all, all, all), job_id=%s'
4155            % job_id in logcontent
4156            )
4157        self.assertTrue(
4158            'zope.mgr - students.browser.ExportJobContainerDownload '
4159            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4160            % (job_id, job_id) in logcontent
4161            )
4162        self.assertTrue(
4163            'zope.mgr - students.browser.ExportJobContainerOverview '
4164            '- discarded: job_id=%s' % job_id in logcontent
4165            )
4166        # Officer can set export parameters but cannot enter student id
4167        # at faculty level
4168        self.browser.open(fac1_path + '/exports')
4169        self.assertTrue("Set export parameters"
4170                         in self.browser.contents)
4171        self.assertFalse("Enter student ids or matric numbers"
4172                         in self.browser.contents)
4173
4174    def test_department_export(self):
4175        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4176        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
4177        self.browser.open(dep1_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(name="mode").value = ['ug_ft']
4184        # The testbrowser does not hide the payment period fields, but
4185        # values are ignored when using the students exporter.
4186        self.browser.getControl(name="payments_start").value = '13/12/2012'
4187        self.browser.getControl(name="payments_end").value = '14/12/2012'
4188        self.browser.getControl("Create CSV file").click()
4189
4190        # When the job is finished and we reload the page...
4191        job_id = self.wait_for_export_job_completed()
4192        self.browser.open(dep1_path + '/exports')
4193        # ... the csv file can be downloaded ...
4194        self.browser.getLink("Download").click()
4195        self.assertEqual(self.browser.headers['content-type'],
4196            'text/csv; charset=UTF-8')
4197        self.assertTrue(
4198            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4199            self.browser.headers['content-disposition'])
4200        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4201        job_id = self.app['datacenter'].running_exports[0][0]
4202        # ... and discarded
4203        self.browser.open(dep1_path + '/exports')
4204        self.browser.getControl("Discard").click()
4205        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4206        # Creation, downloading and discarding is logged
4207        logfile = os.path.join(
4208            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4209        logcontent = open(logfile).read()
4210        self.assertTrue(
4211            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
4212            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
4213            '13/12/2012, 14/12/2012, all, all, all, all, all), job_id=%s'
4214            % job_id in logcontent
4215            )
4216        self.assertTrue(
4217            'zope.mgr - students.browser.ExportJobContainerDownload '
4218            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4219            % (job_id, job_id) in logcontent
4220            )
4221        self.assertTrue(
4222            'zope.mgr - students.browser.ExportJobContainerOverview '
4223            '- discarded: job_id=%s' % job_id in logcontent
4224            )
4225
4226    def test_certificate_export(self):
4227        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4228        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
4229        self.browser.open(cert1_path)
4230        self.browser.getLink("Export student data").click()
4231        self.browser.getControl("Set export parameters").click()
4232        self.browser.getControl(name="exporter").value = ['students']
4233        self.browser.getControl(name="session").value = ['2004']
4234        self.browser.getControl(name="level").value = ['100']
4235        self.browser.getControl("Create CSV file").click()
4236
4237        # When the job is finished and we reload the page...
4238        job_id = self.wait_for_export_job_completed()
4239        self.browser.open(cert1_path + '/exports')
4240        # ... the csv file can be downloaded ...
4241        self.browser.getLink("Download").click()
4242        self.assertEqual(self.browser.headers['content-type'],
4243            'text/csv; charset=UTF-8')
4244        self.assertTrue(
4245            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4246            self.browser.headers['content-disposition'])
4247        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4248        job_id = self.app['datacenter'].running_exports[0][0]
4249        # ... and discarded
4250        self.browser.open(cert1_path + '/exports')
4251        self.browser.getControl("Discard").click()
4252        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4253        # Creation, downloading and discarding is logged
4254        logfile = os.path.join(
4255            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4256        logcontent = open(logfile).read()
4257        self.assertTrue(
4258            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
4259            '- exported: students '
4260            '(2004, 100, None, None, None, CERT1, , , None, None, '
4261            'None, None, None), '
4262            'job_id=%s'
4263            % job_id in logcontent
4264            )
4265        self.assertTrue(
4266            'zope.mgr - students.browser.ExportJobContainerDownload '
4267            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4268            % (job_id, job_id) in logcontent
4269            )
4270        self.assertTrue(
4271            'zope.mgr - students.browser.ExportJobContainerOverview '
4272            '- discarded: job_id=%s' % job_id in logcontent
4273            )
4274
4275    def deprecated_test_course_export_students(self):
4276        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4277        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4278        self.browser.open(course1_path)
4279        self.browser.getLink("Export student data").click()
4280        self.browser.getControl("Set export parameters").click()
4281        self.browser.getControl(name="exporter").value = ['students']
4282        self.browser.getControl(name="session").value = ['2004']
4283        self.browser.getControl(name="level").value = ['100']
4284        self.browser.getControl("Create CSV file").click()
4285
4286        # When the job is finished and we reload the page...
4287        job_id = self.wait_for_export_job_completed()
4288        self.browser.open(course1_path + '/exports')
4289        # ... the csv file can be downloaded ...
4290        self.browser.getLink("Download").click()
4291        self.assertEqual(self.browser.headers['content-type'],
4292            'text/csv; charset=UTF-8')
4293        self.assertTrue(
4294            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
4295            self.browser.headers['content-disposition'])
4296        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4297        job_id = self.app['datacenter'].running_exports[0][0]
4298        # ... and discarded
4299        self.browser.open(course1_path + '/exports')
4300        self.browser.getControl("Discard").click()
4301        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4302        # Creation, downloading and discarding is logged
4303        logfile = os.path.join(
4304            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4305        logcontent = open(logfile).read()
4306        self.assertTrue(
4307            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4308            '- exported: students (2004, 100, COURSE1), job_id=%s'
4309            % job_id in logcontent
4310            )
4311        self.assertTrue(
4312            'zope.mgr - students.browser.ExportJobContainerDownload '
4313            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
4314            % (job_id, job_id) in logcontent
4315            )
4316        self.assertTrue(
4317            'zope.mgr - students.browser.ExportJobContainerOverview '
4318            '- discarded: job_id=%s' % job_id in logcontent
4319            )
4320
4321    def test_course_export_lecturer(self):
4322        # We add study level 100 to the student's studycourse
4323        studylevel = StudentStudyLevel()
4324        studylevel.level = 100
4325        studylevel.level_session = 2004
4326        IWorkflowState(self.student).setState('courses validated')
4327        self.student['studycourse'].addStudentStudyLevel(
4328            self.certificate,studylevel)
4329        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
4330        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
4331        self.browser.open(course1_path)
4332        self.browser.getLink("Export student data").click()
4333        self.browser.getControl("Set export parameters").click()
4334        self.assertTrue(
4335            'Academic session not set. Please contact the administrator.'
4336            in self.browser.contents)
4337        self.app['configuration'].current_academic_session = 2004
4338        self.browser.getControl("Set export parameters").click()
4339        self.browser.getControl(name="exporter").value = ['lecturer']
4340        self.browser.getControl(name="session").value = ['2004']
4341        self.browser.getControl(name="level").value = ['100']
4342        self.browser.getControl("Create CSV file").click()
4343        # When the job is finished and we reload the page...
4344        job_id = self.wait_for_export_job_completed()
4345        self.browser.open(course1_path + '/exports')
4346        # ... the csv file can be downloaded ...
4347        self.browser.getLink("Download").click()
4348        self.assertEqual(self.browser.headers['content-type'],
4349            'text/csv; charset=UTF-8')
4350        self.assertTrue(
4351            'filename="WAeUP.Kofa_lecturer_%s.csv' % job_id in
4352            self.browser.headers['content-disposition'])
4353        # ... and contains the course ticket COURSE1
4354        self.assertEqual(self.browser.contents,
4355            'matric_number,student_id,display_fullname,level,code,'
4356            'level_session,score\r\n234,K1000000,Anna Tester,'
4357            '100,COURSE1,2004,\r\n')
4358        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4359        job_id = self.app['datacenter'].running_exports[0][0]
4360        # Thew job can be discarded
4361        self.browser.open(course1_path + '/exports')
4362        self.browser.getControl("Discard").click()
4363        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4364        # Creation, downloading and discarding is logged
4365        logfile = os.path.join(
4366            self.app['datacenter'].storage, 'logs', 'datacenter.log')
4367        logcontent = open(logfile).read()
4368        self.assertTrue(
4369            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
4370            '- exported: lecturer (2004, 100, COURSE1), job_id=%s'
4371            % job_id in logcontent
4372            )
4373        self.assertTrue(
4374            'zope.mgr - students.browser.ExportJobContainerDownload '
4375            '- downloaded: WAeUP.Kofa_lecturer_%s.csv, job_id=%s'
4376            % (job_id, job_id) in logcontent
4377            )
4378        self.assertTrue(
4379            'zope.mgr - students.browser.ExportJobContainerOverview '
4380            '- discarded: job_id=%s' % job_id in logcontent
4381            )
4382
4383    def test_export_departmet_officers(self):
4384        # Create department officer
4385        self.app['users'].addUser('mrdepartment', SECRET)
4386        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
4387        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
4388        # Assign local role
4389        department = self.app['faculties']['fac1']['dep1']
4390        prmlocal = IPrincipalRoleManager(department)
4391        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
4392        # Login as department officer
4393        self.browser.open(self.login_path)
4394        self.browser.getControl(name="form.login").value = 'mrdepartment'
4395        self.browser.getControl(name="form.password").value = SECRET
4396        self.browser.getControl("Login").click()
4397        self.assertMatches('...You logged in...', self.browser.contents)
4398        self.browser.open("http://localhost/app/faculties/fac1/dep1")
4399        self.browser.getLink("Export student data").click()
4400        self.browser.getControl("Set export parameters").click()
4401        # Only the sfpaymentsoverview exporter is available for department officers
4402        self.assertFalse('<option value="students">' in self.browser.contents)
4403        self.assertTrue(
4404            '<option value="sfpaymentsoverview">' in self.browser.contents)
4405        self.browser.getControl(name="exporter").value = ['sfpaymentsoverview']
4406        self.browser.getControl(name="session").value = ['2004']
4407        self.browser.getControl(name="level").value = ['100']
4408        self.browser.getControl("Create CSV file").click()
4409        self.assertTrue('Export started' in self.browser.contents)
4410        # Thew job can be discarded
4411        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4412        self.wait_for_export_job_completed()
4413        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
4414        self.browser.getControl("Discard").click()
4415        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4416
4417    def test_export_bursary_officers(self):
4418        self.add_payment(self.student)
4419        # Create bursary officer
4420        self.app['users'].addUser('mrbursary', SECRET)
4421        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
4422        self.app['users']['mrbursary'].title = 'Carlo Pitter'
4423        prmglobal = IPrincipalRoleManager(self.app)
4424        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
4425        # Login as bursary officer
4426        self.browser.open(self.login_path)
4427        self.browser.getControl(name="form.login").value = 'mrbursary'
4428        self.browser.getControl(name="form.password").value = SECRET
4429        self.browser.getControl("Login").click()
4430        self.assertMatches('...You logged in...', self.browser.contents)
4431        self.browser.getLink("Academics").click()
4432        self.browser.getLink("Export student data").click()
4433        self.browser.getControl("Set export parameters").click()
4434        # Only the bursary exporter is available for bursary officers
4435        # not only at facultiescontainer level ...
4436        self.assertFalse('<option value="students">' in self.browser.contents)
4437        self.assertTrue('<option value="bursary">' in self.browser.contents)
4438        self.browser.getControl(name="exporter").value = ['bursary']
4439        self.browser.getControl(name="session").value = ['2004']
4440        self.browser.getControl(name="level").value = ['100']
4441        self.browser.getControl("Create CSV file").click()
4442        self.assertTrue('Export started' in self.browser.contents)
4443        # ... but also at other levels
4444        self.browser.open('http://localhost/app/faculties/fac1/dep1')
4445        self.browser.getLink("Export student data").click()
4446        self.browser.getControl("Set export parameters").click()
4447        self.assertFalse('<option value="students">' in self.browser.contents)
4448        self.assertTrue('<option value="bursary">' in self.browser.contents)
4449        # Thew job can be downloaded
4450        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4451        job_id = self.wait_for_export_job_completed()
4452        self.browser.open('http://localhost/app/faculties/exports')
4453        self.browser.getLink("Download").click()
4454        self.assertEqual(self.browser.headers['content-type'],
4455            'text/csv; charset=UTF-8')
4456        self.assertTrue(
4457            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4458            self.browser.headers['content-disposition'])
4459        self.assertTrue(
4460            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4461            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4462            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4463            %(curr_year-6) in self.browser.contents)
4464        # ... and discarded
4465        self.browser.open('http://localhost/app/faculties/exports')
4466        self.browser.getControl("Discard").click()
4467        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4468        # At Academics level bursary officers can also enter student ids
4469        self.browser.getLink("Academics").click()
4470        self.browser.getLink("Export student data").click()
4471        self.browser.getControl("Enter student ids or matric numbers").click()
4472        self.assertFalse('<option value="students">' in self.browser.contents)
4473        self.browser.getControl(name="exporter").value = ['bursary']
4474        self.browser.getControl(name="students").value = 'K1000000'
4475        self.browser.getControl("Create CSV file").click()
4476        # When the job is finished and we reload the page...
4477        job_id = self.wait_for_export_job_completed()
4478        # ... the csv file can be downloaded ...
4479        self.browser.open('http://localhost/app/faculties/exports')
4480        self.browser.getLink("Download").click()
4481        self.assertEqual(self.browser.headers['content-type'],
4482            'text/csv; charset=UTF-8')
4483        self.assertTrue(
4484            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
4485            self.browser.headers['content-disposition'])
4486        self.assertTrue(
4487            '666,12.12,2012-12-13 00:00:00#,schoolfee,[],1,my-id,p-item,100,%s,'
4488            'paid,2012-12-13 00:00:00#,12.12,r-code,,K1000000,234,123,Anna,,'
4489            'Tester,created,2004,2004,,fac1,dep1,CERT1'
4490            %(curr_year-6) in self.browser.contents)
4491
4492    def test_export_accommodation_officers(self):
4493        # Create bursary officer
4494        self.app['users'].addUser('mracco', SECRET)
4495        self.app['users']['mracco'].email = 'mracco@foo.ng'
4496        self.app['users']['mracco'].title = 'Carlo Pitter'
4497        prmglobal = IPrincipalRoleManager(self.app)
4498        prmglobal.assignRoleToPrincipal('waeup.AccommodationOfficer', 'mracco')
4499        # Login as bursary officer
4500        self.browser.open(self.login_path)
4501        self.browser.getControl(name="form.login").value = 'mracco'
4502        self.browser.getControl(name="form.password").value = SECRET
4503        self.browser.getControl("Login").click()
4504        self.assertMatches('...You logged in...', self.browser.contents)
4505        self.browser.getLink("Academics").click()
4506        self.browser.getLink("Export student data").click()
4507        self.browser.getControl("Set export parameters").click()
4508        # accommodationpayments and beds exporters are available
4509        # not only at facultiescontainer level ...
4510        self.assertFalse('<option value="students">' in self.browser.contents)
4511        self.assertTrue('<option value="accommodationpayments">'
4512            in self.browser.contents)
4513        self.assertTrue('<option value="bedtickets">' in self.browser.contents)
4514        self.browser.getControl(
4515            name="exporter").value = ['accommodationpayments']
4516        self.browser.getControl(name="session").value = ['2004']
4517        self.browser.getControl(name="level").value = ['100']
4518        self.browser.getControl("Create CSV file").click()
4519        self.assertTrue('Export started' in self.browser.contents)
4520        # ... but also at other levels
4521        self.browser.open('http://localhost/app/faculties/fac1/dep1')
4522        self.browser.getLink("Export student data").click()
4523        self.browser.getControl("Set export parameters").click()
4524        self.assertFalse('<option value="students">' in self.browser.contents)
4525        self.assertTrue('<option value="accommodationpayments">'
4526            in self.browser.contents)
4527        self.assertTrue('<option value="bedtickets">' in self.browser.contents)
4528        # Thew job can be discarded
4529        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
4530        self.wait_for_export_job_completed()
4531        self.browser.open('http://localhost/app/faculties/exports')
4532        self.browser.getControl("Discard").click()
4533        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
4534
4535
4536UPLOAD_CSV_TEMPLATE = (
4537    'matric_number,student_id,display_fullname,level,code,level_session,'
4538    'score\r\n'
4539    '234,K1000000,Anna Tester,100,COURSE1,2004,%s\r\n')
4540
4541class LecturerUITests(StudentsFullSetup):
4542    # Tests for UI actions when acting as lecturer.
4543
4544    def login_as_lecturer(self):
4545        self.app['users'].addUser('mrslecturer', SECRET)
4546        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
4547        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
4548        # Add course ticket
4549        self.studylevel = createObject(u'waeup.StudentStudyLevel')
4550        self.studylevel.level = 100
4551        self.studylevel.level_session = 2004
4552        self.student['studycourse'].addStudentStudyLevel(
4553            self.certificate, self.studylevel)
4554        # Assign local Lecturer role for a course.
4555        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4556        prmlocal = IPrincipalRoleManager(course)
4557        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4558        notify(LocalRoleSetEvent(
4559            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4560        # Login as lecturer.
4561        self.browser.open(self.login_path)
4562        self.browser.getControl(name="form.login").value = 'mrslecturer'
4563        self.browser.getControl(
4564            name="form.password").value = SECRET
4565        self.browser.getControl("Login").click()
4566        # Store reused urls/paths
4567        self.course_url = (
4568            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1')
4569        self.edit_scores_url = '%s/edit_scores' % self.course_url
4570        # Set standard parameters
4571        self.app['configuration'].current_academic_session = 2004
4572        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4573        IWorkflowState(self.student).setState(VALIDATED)
4574
4575    @property
4576    def stud_log_path(self):
4577        return os.path.join(
4578            self.app['datacenter'].storage, 'logs', 'students.log')
4579
4580    def test_lecturer_lands_on_landing_page(self):
4581        # lecturers can login and will be led to landing page.
4582        self.login_as_lecturer()
4583        self.assertMatches('...You logged in...', self.browser.contents)
4584        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4585        self.assertTrue(
4586            "<span>Unnamed Course (COURSE1)</span>"
4587            in self.browser.contents)
4588
4589    def test_lecturer_department_role(self):
4590        # lecturers can login and will be led to landing page also if
4591        # role is assigned at department level.
4592        self.login_as_lecturer()
4593        # we remove the role granted above
4594        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
4595        prmlocal = IPrincipalRoleManager(course)
4596        prmlocal.removeRoleFromPrincipal('waeup.local.Lecturer', 'mrslecturer')
4597        notify(LocalRoleSetEvent(
4598            course, 'waeup.local.Lecturer', 'mrslecturer', granted=False))
4599        self.browser.open(URL_LECTURER_LANDING)
4600        # no course appears
4601        self.assertFalse(
4602            "<span>Unnamed Course (COURSE1)</span>"
4603            in self.browser.contents)
4604        # we assign lecturer at department level
4605        dep = self.app['faculties']['fac1']['dep1']
4606        prmlocal = IPrincipalRoleManager(dep)
4607        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
4608        notify(LocalRoleSetEvent(
4609            dep, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
4610        self.browser.open(URL_LECTURER_LANDING)
4611        # course appears again
4612        self.assertTrue(
4613            "<span>Unnamed Course (COURSE1)</span>"
4614            in self.browser.contents)
4615
4616    def test_my_roles_link_works(self):
4617        # lecturers can see their roles
4618        self.login_as_lecturer()
4619        self.browser.getLink("My Roles").click()
4620        self.assertTrue(
4621            "<div>Academics Officer (view only)</div>"
4622            in self.browser.contents)
4623        self.assertTrue(
4624            '<a href="%s">' % self.course_url in self.browser.contents)
4625
4626    def test_my_roles_page_contains_backlink(self):
4627        # we can get back from 'My Roles' view to landing page
4628        self.login_as_lecturer()
4629        self.browser.getLink("My Roles").click()
4630        self.browser.getLink("My Courses").click()
4631        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
4632
4633    def test_lecturers_can_reach_their_courses(self):
4634        # lecturers get links to their courses on the landing page
4635        self.login_as_lecturer()
4636        self.browser.getLink("COURSE1").click()
4637        self.assertEqual(self.browser.url, self.course_url)
4638
4639    def test_lecturers_student_access_is_restricted(self):
4640        # lecturers are not able to change other student data
4641        self.login_as_lecturer()
4642        # Lecturers can neither filter students ...
4643        self.assertRaises(
4644            Unauthorized, self.browser.open, '%s/students' % self.course_url)
4645        # ... nor access the student ...
4646        self.assertRaises(
4647            Unauthorized, self.browser.open, self.student_path)
4648        # ... nor the respective course ticket since editing course
4649        # tickets by lecturers is not feasible.
4650        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
4651        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
4652        self.assertRaises(
4653            Unauthorized, self.browser.open, course_ticket_path)
4654
4655    def test_score_editing_requires_department_permit(self):
4656        # we get a warning if we try to update score while we are not allowed
4657        self.login_as_lecturer()
4658        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
4659        self.browser.open(self.course_url)
4660        self.browser.getLink("Update session 2004/2005 scores").click()
4661        self.assertTrue('Score editing disabled' in self.browser.contents)
4662        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
4663        self.browser.open(self.course_url)
4664        self.browser.getLink("Update session 2004/2005 scores").click()
4665        self.assertFalse('Score editing disabled' in self.browser.contents)
4666
4667    def test_score_editing_requires_validated_students(self):
4668        # we can edit only scores of students whose courses have been
4669        # validated.
4670        self.login_as_lecturer()
4671        # set invalid student state
4672        IWorkflowState(self.student).setState(CREATED)
4673        self.browser.open(self.edit_scores_url)
4674        self.assertRaises(
4675            LookupError, self.browser.getControl, name="scores")
4676        # set valid student state
4677        IWorkflowState(self.student).setState(VALIDATED)
4678        self.browser.open(self.edit_scores_url)
4679        self.assertTrue(
4680            self.browser.getControl(name="scores:list") is not None)
4681
4682    def test_score_editing_offers_only_current_scores(self):
4683        # only scores from current academic session can be edited
4684        self.login_as_lecturer()
4685        IWorkflowState(self.student).setState('courses validated')
4686        # with no academic session set
4687        self.app['configuration'].current_academic_session = None
4688        self.browser.open(self.edit_scores_url)
4689        self.assertRaises(
4690            LookupError, self.browser.getControl, name="scores")
4691        # with wrong academic session set
4692        self.app['configuration'].current_academic_session = 1999
4693        self.browser.open(self.edit_scores_url)
4694        self.assertRaises(
4695            LookupError, self.browser.getControl, name="scores")
4696        # with right academic session set
4697        self.app['configuration'].current_academic_session = 2004
4698        self.browser.reload()
4699        self.assertTrue(
4700            self.browser.getControl(name="scores:list") is not None)
4701        # if level_session of studycourse changes, catalog has been updated
4702        # and student disappears
4703        self.studylevel.level_session = 2005
4704        self.browser.reload()
4705        self.assertRaises(
4706            LookupError, self.browser.getControl, name="scores")
4707
4708    def test_score_editing_can_change_scores(self):
4709        # we can really change scores via edit_scores view
4710        self.login_as_lecturer()
4711        self.assertEqual(
4712            self.student['studycourse']['100']['COURSE1'].score, None)
4713        self.browser.open(self.edit_scores_url)
4714        self.browser.getControl(name="scores:list", index=0).value = '55'
4715        self.browser.getControl("Update scores").click()
4716        # the new value is stored in data
4717        self.assertEqual(
4718            self.student['studycourse']['100']['COURSE1'].score, 55)
4719        # the new value is displayed on page/prefilled in form
4720        self.assertEqual(
4721            self.browser.getControl(name="scores:list", index=0).value, '55')
4722        # The change has been logged
4723        with open(self.stud_log_path, 'r') as fd:
4724            self.assertTrue(
4725                'mrslecturer - students.browser.EditScoresPage - '
4726                'K1000000 100/COURSE1 score updated (55)' in fd.read())
4727
4728    def test_scores_editing_scores_must_be_integers(self):
4729        # Non-integer scores won't be accepted.
4730        self.login_as_lecturer()
4731        self.browser.open(self.edit_scores_url)
4732        self.browser.getControl(name="scores:list", index=0).value = 'abc'
4733        self.browser.getControl("Update scores").click()
4734        self.assertTrue(
4735            'Error: Score(s) of following students have not been updated '
4736            '(only integers are allowed): Anna Tester.'
4737            in self.browser.contents)
4738
4739    def test_scores_editing_allows_score_removal(self):
4740        # we can remove scores, once they were set
4741        self.login_as_lecturer()
4742        # without a prior value, we cannot remove
4743        self.student['studycourse']['100']['COURSE1'].score = None
4744        self.browser.open(self.edit_scores_url)
4745        self.browser.getControl(name="scores:list", index=0).value = ''
4746        self.browser.getControl("Update scores").click()
4747        logcontent = open(self.stud_log_path, 'r').read()
4748        self.assertFalse('COURSE1 score updated (None)' in logcontent)
4749        # now retry with some value set
4750        self.student['studycourse']['100']['COURSE1'].score = 55
4751        self.browser.getControl(name="scores:list", index=0).value = ''
4752        self.browser.getControl("Update scores").click()
4753        logcontent = open(self.stud_log_path, 'r').read()
4754        self.assertTrue('COURSE1 score updated (None)' in logcontent)
4755
4756    def test_lecturer_can_validate_courses(self):
4757        # the form is locked after validation
4758        self.login_as_lecturer()
4759        self.student['studycourse']['100']['COURSE1'].score = None
4760        self.browser.open(self.edit_scores_url)
4761        self.browser.getControl(name="scores:list", index=0).value = ''
4762        self.browser.getControl("Update scores").click()
4763        self.browser.getControl("Validate").click()
4764        self.assertTrue(
4765            'No score has been entered.' in self.browser.contents)
4766        self.browser.open(self.edit_scores_url)
4767        self.browser.getControl(name="scores:list", index=0).value = '66'
4768        self.browser.getControl("Update scores").click()
4769        self.browser.getControl("Validate").click()
4770        self.assertTrue(
4771            'You successfully validated the course results'
4772            in self.browser.contents)
4773        self.assertEqual(self.course.results_validation_session, 2004)
4774        self.assertEqual(self.course.results_validated_by, 'Mercedes Benz')
4775        self.assertEqual(self.browser.url, self.course_url)
4776        # Lecturer can't open edit_scores again
4777        self.browser.getLink("Update session 2004/2005 scores").click()
4778        self.assertEqual(self.browser.url, self.course_url)
4779        self.assertTrue(
4780            'Course results have already been validated'
4781            ' and can no longer be changed.'
4782            in self.browser.contents)
4783        # Also DownloadScoresView is blocked
4784        self.browser.open(self.browser.url + '/download_scores')
4785        self.assertEqual(self.browser.url, self.course_url)
4786        self.assertTrue(
4787            'Course results have already been validated'
4788            ' and can no longer be changed.'
4789            in self.browser.contents)
4790        # Students Manager can open page ...
4791        prmlocal = IPrincipalRoleManager(self.course)
4792        prmlocal.assignRoleToPrincipal(
4793            'waeup.local.LocalStudentsManager', 'mrslecturer')
4794        self.browser.getLink("Update session 2004/2005 scores").click()
4795        self.assertEqual(self.browser.url, self.edit_scores_url)
4796        self.browser.getLink("Download csv file").click()
4797        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4798        self.assertEqual(self.browser.headers['Content-Type'],
4799                         'text/csv; charset=UTF-8')
4800        # ... but can't validate courses a second time
4801        self.browser.open(self.edit_scores_url)
4802        self.browser.getControl("Validate").click()
4803        self.assertTrue(
4804            'Course results have already been validated.'
4805            in self.browser.contents)
4806
4807    def test_lecturers_can_download_course_tickets(self):
4808        # A course ticket slip can be downloaded
4809        self.course.title = (u'Lorem ipsum     dolor sit amet, consectetur     adipisici, '
4810                             u'sed         eiusmod tempor    incidunt ut  labore et dolore')
4811        self.login_as_lecturer()
4812        pdf_url = '%s/coursetickets.pdf' % self.course_url
4813        self.browser.open(pdf_url)
4814        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4815        self.assertEqual(
4816            self.browser.headers['Content-Type'], 'application/pdf')
4817        path = os.path.join(samples_dir(), 'coursetickets.pdf')
4818        open(path, 'wb').write(self.browser.contents)
4819        print "Sample PDF coursetickets.pdf written to %s" % path
4820
4821    def test_lecturers_can_download_attendance_sheet(self):
4822        # A course ticket slip can be downloaded
4823        self.course.title = (u'Lorem ipsum     dolor sit amet, consectetur     adipisici, '
4824                             u'sed         eiusmod tempor    incidunt ut  labore et dolore')
4825        self.student.firstname = u'Emmanuella Woyengifigha Mercy Onosemudiana'
4826        self.student.lastname = u'OYAKEMIEGBEGHA'
4827        self.student.matric_number = u'hdk7gd62i872z27zt27ge'
4828        self.login_as_lecturer()
4829        pdf_url = '%s/attendance.pdf' % self.course_url
4830        self.browser.open(pdf_url)
4831        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4832        self.assertEqual(
4833            self.browser.headers['Content-Type'], 'application/pdf')
4834        path = os.path.join(samples_dir(), 'attendance.pdf')
4835        open(path, 'wb').write(self.browser.contents)
4836        print "Sample PDF attendance.pdf written to %s" % path
4837
4838
4839    def test_lecturers_can_download_scores_as_csv(self):
4840        # Lecturers can download course scores as CSV.
4841        self.login_as_lecturer()
4842        self.browser.open(self.edit_scores_url)
4843        self.browser.getLink("Download csv file").click()
4844        self.assertEqual(self.browser.headers['Status'], '200 Ok')
4845        self.assertEqual(self.browser.headers['Content-Type'],
4846                         'text/csv; charset=UTF-8')
4847        self.assertEqual(self.browser.contents, 'matric_number,student_id,'
4848            'display_fullname,level,code,level_session,score\r\n234,'
4849            'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
4850
4851    def test_scores_csv_upload_available(self):
4852        # lecturers can upload a CSV file to set values.
4853        self.login_as_lecturer()
4854        # set value to change from
4855        self.student['studycourse']['100']['COURSE1'].score = 55
4856        self.browser.open(self.edit_scores_url)
4857        upload_ctrl = self.browser.getControl(name='uploadfile:file')
4858        upload_file = StringIO(UPLOAD_CSV_TEMPLATE % '65')
4859        upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv')
4860        self.browser.getControl("Update editable scores from").click()
4861        # value changed
4862        self.assertEqual(
4863            self.student['studycourse']['100']['COURSE1'].score, 65)
4864
4865    def test_scores_csv_upload_ignored(self):
4866        # for many type of file contents we simply ignore uploaded data
4867        self.login_as_lecturer()
4868        self.student['studycourse']['100']['COURSE1'].score = 55
4869        self.browser.open(self.edit_scores_url)
4870        for content, mimetype, name in (
4871                # empty file
4872                ('', 'text/foo', 'my.foo'),
4873                # plain ASCII text, w/o comma
4874                ('abcdef' * 200, 'text/plain', 'my.txt'),
4875                # plain UTF-8 text, with umlauts
4876                ('umlauts: äöü', 'text/plain', 'my.txt'),
4877                # csv file with only a header row
4878                ('student_id,score', 'text/csv', 'my.csv'),
4879                # csv with student_id column missing
4880                ('foo,score\r\nbar,66\r\n', 'text/csv', 'my.csv'),
4881                # csv with score column missing
4882                ('student_id,foo\r\nK1000000,bar\r\n', 'text/csv', 'my.csv'),
4883                # csv with non number as score value
4884                (UPLOAD_CSV_TEMPLATE % 'not-a-number', 'text/csv', 'my.csv'),
4885                ):
4886            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4887            upload_ctrl.add_file(StringIO(content), mimetype, name)
4888            self.browser.getControl("Update scores").click()
4889            self.assertEqual(
4890                self.student['studycourse']['100']['COURSE1'].score, 55)
4891            self.assertFalse(
4892                'Uploaded file contains illegal data' in self.browser.contents)
4893
4894    def test_scores_csv_upload_warn_illegal_chars(self):
4895        # for some types of files we issue a warning if upload data
4896        # contains illegal chars (and ignore the data)
4897        self.login_as_lecturer()
4898        self.student['studycourse']['100']['COURSE1'].score = 55
4899        self.browser.open(self.edit_scores_url)
4900        for content, mimetype, name in (
4901                # plain ASCII text, commas, control chars
4902                ('abv,qwe\n\r\r\t\b\n' * 20, 'text/plain', 'my.txt'),
4903                # image data (like a JPEG image)
4904                (open(SAMPLE_IMAGE, 'rb').read(), 'image/jpg', 'my.jpg'),
4905                ):
4906            upload_ctrl = self.browser.getControl(name='uploadfile:file')
4907            upload_ctrl.add_file(StringIO(content), mimetype, name)
4908            self.browser.getControl("Update editable scores").click()
4909            self.assertEqual(
4910                self.student['studycourse']['100']['COURSE1'].score, 55)
4911            self.assertTrue(
4912                'Uploaded file contains illegal data' in self.browser.contents)
4913
4914class ParentsUITests(StudentsFullSetup):
4915    # Tests for UI actions when acting as parents.
4916
4917    def test_request_ppw(self):
4918        self.app['students'][self.student_id].parents_email = 'par@yy.zz'
4919        self.browser.open('http://localhost/app/requestppw')
4920        self.browser.getControl(name="form.lastname").value = 'tESTer'
4921        self.browser.getControl(name="form.number").value = '123'
4922        self.browser.getControl(name="form.email").value = 'par@yy.zz'
4923        self.browser.getControl("Send temporary login credentials").click()
4924        self.assertTrue('Your password request was successful.'
4925            in self.browser.contents)
4926        logfile = os.path.join(
4927            self.app['datacenter'].storage, 'logs', 'main.log')
4928        logcontent = open(logfile).read()
4929        self.assertTrue('zope.anybody - students.browser.RequestParentsPasswordPage - '
4930                        '123 (K1000000) - par@yy.zz' in logcontent)
4931        return
4932
4933    def test_login_as_parents(self):
4934        # Student login still works after all the changes made
4935        self.browser.open(self.login_path)
4936        self.browser.getControl(name="form.login").value = self.student_id
4937        self.browser.getControl(name="form.password").value = 'spwd'
4938        self.browser.getControl("Login").click()
4939        self.assertTrue('You logged in' in self.browser.contents)
4940        self.browser.open(self.edit_personal_path)
4941        self.browser.getLink("Logout").click()
4942        self.assertTrue('You have been logged out' in self.browser.contents)
4943        # We set parents password
4944        self.app['students'][self.student_id].setParentsPassword('ppwd')
4945        self.browser.open(self.login_path)
4946        # Student can't login with original password
4947        self.browser.getControl(name="form.login").value = self.student_id
4948        self.browser.getControl(name="form.password").value = 'spwd'
4949        self.browser.getControl("Login").click()
4950        self.assertEqual(self.browser.url, self.login_path)
4951        self.assertTrue('Your account has been temporarily deactivated '
4952            'because your parents have logged in.' in self.browser.contents)
4953        # Parents can login with their password
4954        self.browser.open(self.login_path)
4955        self.browser.getControl(name="form.login").value = self.student_id
4956        self.browser.getControl(name="form.password").value = 'ppwd'
4957        self.browser.getControl("Login").click()
4958        self.assertTrue(
4959            'You logged in.' in self.browser.contents)
4960        self.assertTrue(
4961            '<a href="http://localhost/app/students/K1000000">Base Data</a>'
4962            in self.browser.contents)
4963        # They do not see all links ...
4964        self.assertFalse(
4965            '<a href="http://localhost/app/students/K1000000/history">History</a>'
4966            in self.browser.contents)
4967        # ... and can't change anything
4968        self.assertRaises(
4969            Unauthorized, self.browser.open, self.edit_personal_path)
4970        # If the password has expired, parents are logged out and the
4971        # student can login again with the original password
4972        delta = timedelta(minutes=11)
4973        self.app['students'][self.student_id].parents_password[
4974            'timestamp'] = datetime.utcnow() - delta
4975        self.app['students'][self.student_id]._p_changed = True
4976        self.assertRaises(
4977            Unauthorized, self.browser.open, self.student_path)
4978        # Parents login is written to log file
4979        logfile = os.path.join(
4980            self.app['datacenter'].storage, 'logs', 'students.log')
4981        logcontent = open(logfile).read()
4982        self.assertTrue(
4983            'K1000000 - browser.pages.LoginPage - K1000000 - Parents logged in'
4984            in logcontent)
Note: See TracBrowser for help on using the repository browser.