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

Last change on this file since 14716 was 14698, checked in by Henrik Bettermann, 7 years ago

Add score_editing_enabled field which is not used in base package but can be used in custom packages enable/disable score editing for subgroups of students.

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