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

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

Make student email field required.

A trick was needed to make test_manage_contact_student work again.

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