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

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

Add option which allows students to select a desired hostel before booking accommodation.

  • Property svn:keywords set to Id
File size: 199.8 KB
Line 
1## $Id: test_browser.py 13457 2015-11-16 09:05:30Z 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").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").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.getControl("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.getControl("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.getControl("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.getControl("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.accommodation.BedTicket '
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        self.browser.getLink("Switch maintenance mode").click()
1391        button = lookup_submit_value(
1392            'select', 'students_zope.mgr.csv', self.browser)
1393        button.click()
1394        importerselect = self.browser.getControl(name='importer')
1395        modeselect = self.browser.getControl(name='mode')
1396        importerselect.getControl('Student Processor').selected = True
1397        modeselect.getControl(value='create').selected = True
1398        self.browser.getControl('Proceed to step 3').click()
1399        self.assertTrue('Header fields OK' in self.browser.contents)
1400        self.browser.getControl('Perform import').click()
1401        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1402        self.assertTrue('Successfully processed 2 rows' in self.browser.contents)
1403        self.assertTrue('Batch processing finished' in self.browser.contents)
1404        open('studycourses.csv', 'wb').write(
1405"""reg_number,matric_number,certificate,current_session,current_level
14061,,CERT1,2008,100
1407,100001,CERT1,2008,100
1408,100002,CERT1,2008,100
1409""")
1410        self.browser.open(datacenter_path)
1411        self.browser.getLink('Upload data').click()
1412        filecontents = StringIO(open('studycourses.csv', 'rb').read())
1413        filewidget = self.browser.getControl(name='uploadfile:file')
1414        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
1415        self.browser.getControl(name='SUBMIT').click()
1416        self.browser.getLink('Process data').click()
1417        # Meanwhile maintenance mode is disabled again.
1418        self.browser.getLink("Switch maintenance mode").click()
1419        button = lookup_submit_value(
1420            'select', 'studycourses_zope.mgr.csv', self.browser)
1421        button.click()
1422        importerselect = self.browser.getControl(name='importer')
1423        modeselect = self.browser.getControl(name='mode')
1424        importerselect.getControl(
1425            'StudentStudyCourse Processor (update only)').selected = True
1426        modeselect.getControl(value='create').selected = True
1427        self.browser.getControl('Proceed to step 3').click()
1428        self.assertTrue('Update mode only' in self.browser.contents)
1429        self.browser.getControl('Proceed to step 3').click()
1430        self.assertTrue('Header fields OK' in self.browser.contents)
1431        self.browser.getControl('Perform import').click()
1432        self.assertTrue('Processing of 1 rows failed' in self.browser.contents)
1433        self.assertTrue('Successfully processed 2 rows'
1434                        in self.browser.contents)
1435        # The students are properly indexed and we can
1436        # thus find a student in  the department
1437        self.browser.open(self.manage_container_path)
1438        self.browser.getControl(name="searchtype").value = ['depcode']
1439        self.browser.getControl(name="searchterm").value = 'dep1'
1440        self.browser.getControl("Find student(s)").click()
1441        self.assertTrue('Aaren Pieri' in self.browser.contents)
1442        # We can search for a new student by name ...
1443        self.browser.getControl(name="searchtype").value = ['fullname']
1444        self.browser.getControl(name="searchterm").value = 'Claus'
1445        self.browser.getControl("Find student(s)").click()
1446        self.assertTrue('Claus Finau' in self.browser.contents)
1447        # ... and check if the imported password has been properly set
1448        ctrl = self.browser.getControl(name='entries')
1449        value = ctrl.options[0]
1450        claus = self.app['students'][value]
1451        self.assertTrue(IUserAccount(claus).checkPassword('mypwd1'))
1452        return
1453
1454    def init_clearance_officer(self):
1455        # Create clearance officer
1456        self.app['users'].addUser('mrclear', 'mrclearsecret')
1457        self.app['users']['mrclear'].email = 'mrclear@foo.ng'
1458        self.app['users']['mrclear'].title = 'Carlo Pitter'
1459        # Clearance officers need not necessarily to get
1460        # the StudentsOfficer site role
1461        #prmglobal = IPrincipalRoleManager(self.app)
1462        #prmglobal.assignRoleToPrincipal('waeup.StudentsOfficer', 'mrclear')
1463        # Assign local ClearanceOfficer role
1464        self.department = self.app['faculties']['fac1']['dep1']
1465        prmlocal = IPrincipalRoleManager(self.department)
1466        prmlocal.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'mrclear')
1467        IWorkflowState(self.student).setState('clearance started')
1468        # Add another student for testing
1469        other_student = Student()
1470        other_student.firstname = u'Dep2'
1471        other_student.lastname = u'Student'
1472        self.app['students'].addStudent(other_student)
1473        self.other_student_path = (
1474            'http://localhost/app/students/%s' % other_student.student_id)
1475        # Login as clearance officer
1476        self.browser.open(self.login_path)
1477        self.browser.getControl(name="form.login").value = 'mrclear'
1478        self.browser.getControl(name="form.password").value = 'mrclearsecret'
1479        self.browser.getControl("Login").click()
1480
1481    def test_handle_clearance_by_co(self):
1482        self.init_clearance_officer()
1483        self.assertMatches('...You logged in...', self.browser.contents)
1484        # CO is landing on index page.
1485        self.assertEqual(self.browser.url, 'http://localhost/app/index')
1486        # CO can see his roles
1487        self.browser.getLink("My Roles").click()
1488        self.assertMatches(
1489            '...<div>Academics Officer (view only)</div>...',
1490            self.browser.contents)
1491        # But not his local role ...
1492        self.assertFalse('Clearance Officer' in self.browser.contents)
1493        # ... because we forgot to notify the department that the local role
1494        # has changed.
1495        notify(LocalRoleSetEvent(
1496            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1497            granted=True))
1498        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1499        self.assertTrue('Clearance Officer' in self.browser.contents)
1500        self.assertMatches(
1501            '...<a href="http://localhost/app/faculties/fac1/dep1">...',
1502            self.browser.contents)
1503        # CO can view the student ...
1504        self.browser.open(self.clearance_path)
1505        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1506        self.assertEqual(self.browser.url, self.clearance_path)
1507        # ... but not other students.
1508        self.assertRaises(
1509            Unauthorized, self.browser.open, self.other_student_path)
1510        # Clearance is disabled for this session.
1511        self.browser.open(self.clearance_path)
1512        self.assertFalse('Clear student' in self.browser.contents)
1513        self.browser.open(self.student_path + '/clear')
1514        self.assertTrue('Clearance is disabled for this session'
1515            in self.browser.contents)
1516        self.app['configuration']['2004'].clearance_enabled = True
1517        # Only in state clearance requested the CO does see the 'Clear' button.
1518        self.browser.open(self.clearance_path)
1519        self.assertFalse('Clear student' in self.browser.contents)
1520        self.browser.open(self.student_path + '/clear')
1521        self.assertTrue('Student is in wrong state.'
1522            in self.browser.contents)
1523        IWorkflowInfo(self.student).fireTransition('request_clearance')
1524        self.browser.open(self.clearance_path)
1525        self.assertTrue('Clear student' in self.browser.contents)
1526        self.browser.getLink("Clear student").click()
1527        self.assertTrue('Student has been cleared' in self.browser.contents)
1528        self.assertTrue('cleared' in self.browser.contents)
1529        self.browser.open(self.history_path)
1530        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1531        # Hide real name.
1532        self.app['users']['mrclear'].public_name = 'My Public Name'
1533        self.browser.open(self.clearance_path)
1534        self.browser.getLink("Reject clearance").click()
1535        self.assertEqual(
1536            self.browser.url, self.student_path + '/reject_clearance')
1537        # Type comment why.
1538        self.browser.getControl(name="form.officer_comment").value = (
1539            'Dear Student,\n'
1540            'You did not fill properly.')
1541        self.browser.getControl("Save comment").click()
1542        self.assertTrue('Clearance has been annulled' in self.browser.contents)
1543        url = ('http://localhost/app/students/K1000000/'
1544              'contactstudent?body=Dear+Student%2C%0AYou+did+not+fill+properly.'
1545              '&subject=Clearance+has+been+annulled.')
1546        # CO does now see the prefilled contact form and can send a message.
1547        self.assertEqual(self.browser.url, url)
1548        self.assertTrue('clearance started' in self.browser.contents)
1549        self.assertTrue('name="form.subject" size="20" type="text" '
1550            'value="Clearance has been annulled."'
1551            in self.browser.contents)
1552        self.assertTrue('name="form.body" rows="10" >Dear Student,'
1553            in self.browser.contents)
1554        self.browser.getControl("Send message now").click()
1555        self.assertTrue('Your message has been sent' in self.browser.contents)
1556        # The comment has been stored ...
1557        self.assertEqual(self.student.officer_comment,
1558            u'Dear Student,\nYou did not fill properly.')
1559        # ... and logged.
1560        logfile = os.path.join(
1561            self.app['datacenter'].storage, 'logs', 'students.log')
1562        logcontent = open(logfile).read()
1563        self.assertTrue(
1564            'INFO - mrclear - students.browser.StudentRejectClearancePage - '
1565            'K1000000 - comment: Dear Student,<br>You did not fill '
1566            'properly.\n' in logcontent)
1567        self.browser.open(self.history_path)
1568        self.assertTrue("Reset to 'clearance started' by My Public Name" in
1569            self.browser.contents)
1570        IWorkflowInfo(self.student).fireTransition('request_clearance')
1571        self.browser.open(self.clearance_path)
1572        self.browser.getLink("Reject clearance").click()
1573        self.browser.getControl("Save comment").click()
1574        self.assertTrue('Clearance request has been rejected'
1575            in self.browser.contents)
1576        self.assertTrue('clearance started' in self.browser.contents)
1577        # The CO can't clear students if not in state
1578        # clearance requested.
1579        self.browser.open(self.student_path + '/clear')
1580        self.assertTrue('Student is in wrong state'
1581            in self.browser.contents)
1582        # The CO can go to his department throug the my_roles page ...
1583        self.browser.open('http://localhost/app/users/mrclear/my_roles')
1584        self.browser.getLink("http://localhost/app/faculties/fac1/dep1").click()
1585        # ... and view the list of students.
1586        self.browser.getLink("Show students").click()
1587        self.browser.getControl(name="session").value = ['2004']
1588        self.browser.getControl(name="level").value = ['200']
1589        self.browser.getControl("Show").click()
1590        self.assertFalse(self.student_id in self.browser.contents)
1591        self.browser.getControl(name="session").value = ['2004']
1592        self.browser.getControl(name="level").value = ['100']
1593        self.browser.getControl("Show").click()
1594        self.assertTrue(self.student_id in self.browser.contents)
1595        # The comment is indicated by 'yes'.
1596        self.assertTrue('<td><span>yes</span></td>' in self.browser.contents)
1597        # Check if the enquiries form is not pre-filled with officer_comment
1598        # (regression test).
1599        self.browser.getLink("Logout").click()
1600        self.browser.open('http://localhost/app/enquiries')
1601        self.assertFalse(
1602            'You did not fill properly'
1603            in self.browser.contents)
1604        # When a student is cleared the comment is automatically deleted
1605        IWorkflowInfo(self.student).fireTransition('request_clearance')
1606        IWorkflowInfo(self.student).fireTransition('clear')
1607        self.assertEqual(self.student.officer_comment, None)
1608        return
1609
1610    def test_handle_mass_clearance_by_co(self):
1611        self.init_clearance_officer()
1612        # Additional setups according to test above
1613        notify(LocalRoleSetEvent(
1614            self.department, 'waeup.local.ClearanceOfficer', 'mrclear',
1615            granted=True))
1616        self.app['configuration']['2004'].clearance_enabled = True
1617        IWorkflowState(self.student).setState('clearance requested')
1618        # Update the catalog
1619        notify(grok.ObjectModifiedEvent(self.student))
1620        # The CO can go to the department and clear all students in department
1621        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1622        self.browser.getLink("Clear all students").click()
1623        self.assertTrue('1 students have been cleared' in self.browser.contents)
1624        self.browser.open(self.history_path)
1625        self.assertTrue('Cleared by Carlo Pitter' in self.browser.contents)
1626        logfile = os.path.join(
1627            self.app['datacenter'].storage, 'logs', 'students.log')
1628        logcontent = open(logfile).read()
1629        self.assertTrue(
1630            'INFO - mrclear - K1000000 - Cleared' in logcontent)
1631        self.browser.open('http://localhost/app/faculties/fac1/dep1')
1632        self.browser.getLink("Clear all students").click()
1633        self.assertTrue('0 students have been cleared' in self.browser.contents)
1634        return
1635
1636    def test_handle_courses_by_ca(self):
1637        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
1638        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
1639        self.app['users']['mrsadvise'].title = u'Helen Procter'
1640        # Assign local CourseAdviser100 role for a certificate
1641        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1642        prmlocal = IPrincipalRoleManager(cert)
1643        prmlocal.assignRoleToPrincipal('waeup.local.CourseAdviser100', 'mrsadvise')
1644        IWorkflowState(self.student).setState('school fee paid')
1645        # Login as course adviser.
1646        self.browser.open(self.login_path)
1647        self.browser.getControl(name="form.login").value = 'mrsadvise'
1648        self.browser.getControl(name="form.password").value = 'mrsadvisesecret'
1649        self.browser.getControl("Login").click()
1650        self.assertMatches('...You logged in...', self.browser.contents)
1651        # CO can see his roles.
1652        self.browser.getLink("My Roles").click()
1653        self.assertMatches(
1654            '...<div>Academics Officer (view only)</div>...',
1655            self.browser.contents)
1656        # But not his local role ...
1657        self.assertFalse('Course Adviser' in self.browser.contents)
1658        # ... because we forgot to notify the certificate that the local role
1659        # has changed.
1660        notify(LocalRoleSetEvent(
1661            cert, 'waeup.local.CourseAdviser100', 'mrsadvise', granted=True))
1662        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1663        self.assertTrue('Course Adviser 100L' in self.browser.contents)
1664        self.assertMatches(
1665            '...<a href="http://localhost/app/faculties/fac1/dep1/certificates/CERT1">...',
1666            self.browser.contents)
1667        # CA can view the student ...
1668        self.browser.open(self.student_path)
1669        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1670        self.assertEqual(self.browser.url, self.student_path)
1671        # ... but not other students.
1672        other_student = Student()
1673        other_student.firstname = u'Dep2'
1674        other_student.lastname = u'Student'
1675        self.app['students'].addStudent(other_student)
1676        other_student_path = (
1677            'http://localhost/app/students/%s' % other_student.student_id)
1678        self.assertRaises(
1679            Unauthorized, self.browser.open, other_student_path)
1680        # We add study level 110 to the student's studycourse.
1681        studylevel = StudentStudyLevel()
1682        studylevel.level = 110
1683        self.student['studycourse'].addStudentStudyLevel(
1684            cert,studylevel)
1685        L110_student_path = self.studycourse_path + '/110'
1686        # The CA can neither see the Validate nor the Edit button.
1687        self.browser.open(L110_student_path)
1688        self.assertFalse('Validate courses' in self.browser.contents)
1689        self.assertFalse('Edit' in self.browser.contents)
1690        IWorkflowInfo(self.student).fireTransition('register_courses')
1691        self.browser.open(L110_student_path)
1692        self.assertFalse('Validate courses' in self.browser.contents)
1693        self.assertFalse('Edit' in self.browser.contents)
1694        # Only in state courses registered and only if the current level
1695        # corresponds with the name of the study level object
1696        # the 100L CA does see the 'Validate' button but not
1697        # the edit button.
1698        self.student['studycourse'].current_level = 110
1699        self.browser.open(L110_student_path)
1700        self.assertFalse('Edit' in self.browser.contents)
1701        self.assertTrue('Validate courses' in self.browser.contents)
1702        # But a 100L CA does not see the button at other levels.
1703        studylevel2 = StudentStudyLevel()
1704        studylevel2.level = 200
1705        self.student['studycourse'].addStudentStudyLevel(
1706            cert,studylevel2)
1707        L200_student_path = self.studycourse_path + '/200'
1708        self.browser.open(L200_student_path)
1709        self.assertFalse('Edit' in self.browser.contents)
1710        self.assertFalse('Validate courses' in self.browser.contents)
1711        self.browser.open(L110_student_path)
1712        self.browser.getLink("Validate courses").click()
1713        self.assertTrue('Course list has been validated' in self.browser.contents)
1714        self.assertTrue('courses validated' in self.browser.contents)
1715        self.assertEqual(self.student['studycourse']['110'].validated_by,
1716            'Helen Procter')
1717        self.assertMatches(
1718            '<YYYY-MM-DD hh:mm:ss>',
1719            self.student['studycourse']['110'].validation_date.strftime(
1720                "%Y-%m-%d %H:%M:%S"))
1721        self.browser.getLink("Reject courses").click()
1722        self.assertTrue('Course list request has been annulled.'
1723            in self.browser.contents)
1724        urlmessage = 'Course+list+request+has+been+annulled.'
1725        self.assertEqual(self.browser.url, self.student_path +
1726            '/contactstudent?subject=%s' % urlmessage)
1727        self.assertTrue('school fee paid' in self.browser.contents)
1728        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
1729        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
1730        IWorkflowInfo(self.student).fireTransition('register_courses')
1731        self.browser.open(L110_student_path)
1732        self.browser.getLink("Reject courses").click()
1733        self.assertTrue('Course list request has been rejected'
1734            in self.browser.contents)
1735        self.assertTrue('school fee paid' in self.browser.contents)
1736        # CA does now see the contact form and can send a message.
1737        self.browser.getControl(name="form.subject").value = 'Important subject'
1738        self.browser.getControl(name="form.body").value = 'Course list rejected'
1739        self.browser.getControl("Send message now").click()
1740        self.assertTrue('Your message has been sent' in self.browser.contents)
1741        # The CA does now see the Edit button and can edit
1742        # current study level.
1743        self.browser.open(L110_student_path)
1744        self.browser.getLink("Edit").click()
1745        self.assertTrue('Edit course list of 100 (Year 1) on 1st probation'
1746            in self.browser.contents)
1747        # The CA can't validate courses if not in state
1748        # courses registered.
1749        self.browser.open(L110_student_path + '/validate_courses')
1750        self.assertTrue('Student is in the wrong state'
1751            in self.browser.contents)
1752        # The CA can go to his certificate through the my_roles page ...
1753        self.browser.open('http://localhost/app/users/mrsadvise/my_roles')
1754        self.browser.getLink(
1755            "http://localhost/app/faculties/fac1/dep1/certificates/CERT1").click()
1756        # ... and view the list of students.
1757        self.browser.getLink("Show students").click()
1758        self.browser.getControl(name="session").value = ['2004']
1759        self.browser.getControl(name="level").value = ['100']
1760        self.browser.getControl("Show").click()
1761        self.assertTrue(self.student_id in self.browser.contents)
1762
1763    def test_handle_courses_by_lecturer(self):
1764        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
1765        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
1766        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
1767        # Assign local Lecturer role for a certificate.
1768        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
1769        prmlocal = IPrincipalRoleManager(course)
1770        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
1771        # Login as lecturer.
1772        self.browser.open(self.login_path)
1773        self.browser.getControl(name="form.login").value = 'mrslecturer'
1774        self.browser.getControl(name="form.password").value = 'mrslecturersecret'
1775        self.browser.getControl("Login").click()
1776        self.assertMatches('...You logged in...', self.browser.contents)
1777        # CO can see her roles.
1778        self.browser.getLink("My Roles").click()
1779        self.assertMatches(
1780            '...<div>Academics Officer (view only)</div>...',
1781            self.browser.contents)
1782        # But not her local role ...
1783        self.assertFalse('Lecturer' in self.browser.contents)
1784        # ... because we forgot to notify the course that the local role
1785        # has changed.
1786        notify(LocalRoleSetEvent(
1787            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
1788        self.browser.open('http://localhost/app/users/mrslecturer/my_roles')
1789        self.assertTrue('Lecturer' in self.browser.contents)
1790        self.assertMatches(
1791            '...<a href="http://localhost/app/faculties/fac1/dep1/courses/COURSE1">...',
1792            self.browser.contents)
1793        # The lecturer can go to her course ...
1794        self.browser.getLink(
1795            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1").click()
1796        # ... and view the list of students
1797        self.browser.getLink("Show students").click()
1798        self.browser.getControl(name="session").value = ['2004']
1799        self.browser.getControl(name="level").value = ['100']
1800        self.browser.getControl("Show").click()
1801        self.assertTrue('No student found.' in self.browser.contents)
1802        # No student in course so far.
1803        self.assertFalse(self.student_id in self.browser.contents)
1804        studylevel = createObject(u'waeup.StudentStudyLevel')
1805        studylevel.level = 100
1806        studylevel.level_session = 2004
1807        self.student['studycourse'].addStudentStudyLevel(
1808            self.certificate, studylevel)
1809        # Now the student has registered the course and can
1810        # be seen by the lecturer.
1811        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
1812        self.browser.getControl(name="session").value = ['2004']
1813        self.browser.getControl(name="level").value = ['100']
1814        self.browser.getControl("Show").click()
1815        self.assertTrue(self.student_id in self.browser.contents)
1816        # Lecturer can neither access the student ...
1817        self.assertRaises(
1818            Unauthorized, self.browser.open, self.student_path)
1819        # ... nor the respective course ticket since
1820        # editing course tickets by lecturers is not feasible.
1821        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
1822        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
1823        self.assertRaises(
1824            Unauthorized, self.browser.open, course_ticket_path)
1825        # Course results can be batch edited via the edit_courses view.
1826        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
1827        self.browser.open("http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
1828        self.browser.getLink("Update scores").click()
1829        self.assertTrue('Score editing disabled' in self.browser.contents)
1830        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
1831        self.browser.getLink("Update scores").click()
1832        self.assertTrue('Current academic session not set' in self.browser.contents)
1833        self.app['configuration'].current_academic_session = 2004
1834        self.browser.getLink("Update scores").click()
1835        self.assertFalse(
1836            '<input type="text" name="scores" class="span1" />'
1837            in self.browser.contents)
1838        IWorkflowState(self.student).setState('courses validated')
1839        # Student must be in state 'courses validated'.
1840        self.browser.open(
1841            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1842        self.assertTrue(
1843            '<input type="text" name="scores" class="span1" />'
1844            in self.browser.contents)
1845        self.browser.getControl(name="scores", index=0).value = '55'
1846        self.browser.getControl("Update scores").click()
1847        # New score has been set.
1848        self.assertEqual(
1849            self.student['studycourse']['100']['COURSE1'].score, 55)
1850        # Score editing has been logged.
1851        logfile = os.path.join(
1852            self.app['datacenter'].storage, 'logs', 'students.log')
1853        logcontent = open(logfile).read()
1854        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1855                        'K1000000 100/COURSE1 score updated (55)' in logcontent)
1856        # Non-integer scores won't be accepted.
1857        self.browser.open(
1858            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1859        self.assertTrue('value="55" />' in self.browser.contents)
1860        self.browser.getControl(name="scores", index=0).value = 'abc'
1861        self.browser.getControl("Update scores").click()
1862        self.assertTrue('Error: Score(s) of Anna Tester have not be updated'
1863            in self.browser.contents)
1864        # Scores can be removed.
1865        self.browser.open(
1866            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
1867        self.browser.getControl(name="scores", index=0).value = ''
1868        self.browser.getControl("Update scores").click()
1869        self.assertEqual(
1870            self.student['studycourse']['100']['COURSE1'].score, None)
1871        logcontent = open(logfile).read()
1872        self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
1873                        'K1000000 100/COURSE1 score updated (None)' in logcontent)
1874
1875    def test_change_current_mode(self):
1876        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1877        self.browser.open(self.clearance_path)
1878        self.assertFalse('Employer' in self.browser.contents)
1879        self.browser.open(self.manage_clearance_path)
1880        self.assertFalse('Employer' in self.browser.contents)
1881        self.browser.open(self.edit_clearance_path)
1882        self.assertFalse('Employer' in self.browser.contents)
1883        # Now we change the study mode of the certificate and a different
1884        # interface is used by clearance views.
1885        self.certificate.study_mode = 'pg_ft'
1886        # Invariants are not being checked here?!
1887        self.certificate.end_level = 100
1888        self.browser.open(self.clearance_path)
1889        self.assertTrue('Employer' in self.browser.contents)
1890        self.browser.open(self.manage_clearance_path)
1891        self.assertTrue('Employer' in self.browser.contents)
1892        IWorkflowState(self.student).setState('clearance started')
1893        self.browser.open(self.edit_clearance_path)
1894        self.assertTrue('Employer' in self.browser.contents)
1895
1896    def test_find_students_in_faculties(self):
1897        # Create local students manager in faculty
1898        self.app['users'].addUser('mrmanager', 'mrmanagersecret')
1899        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
1900        self.app['users']['mrmanager'].title = u'Volk Wagen'
1901        # Assign LocalStudentsManager role for faculty
1902        fac = self.app['faculties']['fac1']
1903        prmlocal = IPrincipalRoleManager(fac)
1904        prmlocal.assignRoleToPrincipal(
1905            'waeup.local.LocalStudentsManager', 'mrmanager')
1906        notify(LocalRoleSetEvent(
1907            fac, 'waeup.local.LocalStudentsManager', 'mrmanager',
1908            granted=True))
1909        # Login as manager
1910        self.browser.open(self.login_path)
1911        self.browser.getControl(name="form.login").value = 'mrmanager'
1912        self.browser.getControl(name="form.password").value = 'mrmanagersecret'
1913        self.browser.getControl("Login").click()
1914        self.assertMatches('...You logged in...', self.browser.contents)
1915        # Manager can see his roles
1916        self.browser.getLink("My Roles").click()
1917        self.assertMatches(
1918            '...<span>Students Manager</span>...',
1919            self.browser.contents)
1920        # The manager can go to his faculty
1921        self.browser.getLink(
1922            "http://localhost/app/faculties/fac1").click()
1923        # and find students
1924        self.browser.getLink("Find students").click()
1925        self.browser.getControl("Find student").click()
1926        self.assertTrue('Empty search string' in self.browser.contents)
1927        self.browser.getControl(name="searchtype").value = ['student_id']
1928        self.browser.getControl(name="searchterm").value = self.student_id
1929        self.browser.getControl("Find student").click()
1930        self.assertTrue('Anna Tester' in self.browser.contents)
1931
1932    def test_activate_deactivate_buttons(self):
1933        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1934        self.browser.open(self.student_path)
1935        self.browser.getLink("Deactivate").click()
1936        self.assertTrue(
1937            'Student account has been deactivated.' in self.browser.contents)
1938        self.assertTrue(
1939            'Base Data (account deactivated)' in self.browser.contents)
1940        self.assertTrue(self.student.suspended)
1941        self.browser.getLink("Activate").click()
1942        self.assertTrue(
1943            'Student account has been activated.' in self.browser.contents)
1944        self.assertFalse(
1945            'Base Data (account deactivated)' in self.browser.contents)
1946        self.assertFalse(self.student.suspended)
1947        # History messages have been added ...
1948        self.browser.getLink("History").click()
1949        self.assertTrue(
1950            'Student account deactivated by Manager<br />' in self.browser.contents)
1951        self.assertTrue(
1952            'Student account activated by Manager<br />' in self.browser.contents)
1953        # ... and actions have been logged.
1954        logfile = os.path.join(
1955            self.app['datacenter'].storage, 'logs', 'students.log')
1956        logcontent = open(logfile).read()
1957        self.assertTrue('zope.mgr - students.browser.StudentDeactivateView - '
1958                        'K1000000 - account deactivated' in logcontent)
1959        self.assertTrue('zope.mgr - students.browser.StudentActivateView - '
1960                        'K1000000 - account activated' in logcontent)
1961
1962    def test_manage_student_transfer(self):
1963        # Add second certificate
1964        self.certificate2 = createObject('waeup.Certificate')
1965        self.certificate2.code = u'CERT2'
1966        self.certificate2.study_mode = 'ug_ft'
1967        self.certificate2.start_level = 999
1968        self.certificate2.end_level = 999
1969        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1970            self.certificate2)
1971
1972        # Add study level to old study course
1973        studylevel = createObject(u'waeup.StudentStudyLevel')
1974        studylevel.level = 200
1975        self.student['studycourse'].addStudentStudyLevel(
1976            self.certificate, studylevel)
1977        studylevel = createObject(u'waeup.StudentStudyLevel')
1978        studylevel.level = 999
1979        self.student['studycourse'].addStudentStudyLevel(
1980            self.certificate, studylevel)
1981
1982        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1983        self.browser.open(self.student_path)
1984        self.browser.getLink("Transfer").click()
1985        self.browser.getControl(name="form.certificate").value = ['CERT2']
1986        self.browser.getControl(name="form.current_session").value = ['2011']
1987        self.browser.getControl(name="form.current_level").value = ['200']
1988        self.browser.getControl("Transfer").click()
1989        self.assertTrue(
1990            'Current level does not match certificate levels'
1991            in self.browser.contents)
1992        self.browser.getControl(name="form.current_level").value = ['999']
1993        self.browser.getControl("Transfer").click()
1994        self.assertTrue('Successfully transferred' in self.browser.contents)
1995        # The catalog has been updated
1996        cat = queryUtility(ICatalog, name='students_catalog')
1997        results = list(
1998            cat.searchResults(
1999            certcode=('CERT2', 'CERT2')))
2000        self.assertTrue(results[0] is self.student)
2001        results = list(
2002            cat.searchResults(
2003            current_session=(2011, 2011)))
2004        self.assertTrue(results[0] is self.student)
2005        # Add study level to new study course
2006        studylevel = createObject(u'waeup.StudentStudyLevel')
2007        studylevel.level = 999
2008        self.student['studycourse'].addStudentStudyLevel(
2009            self.certificate, studylevel)
2010
2011        # Edit and add pages are locked for old study courses
2012        self.browser.open(self.student_path + '/studycourse/manage')
2013        self.assertFalse('The requested form is locked' in self.browser.contents)
2014        self.browser.open(self.student_path + '/studycourse_1/manage')
2015        self.assertTrue('The requested form is locked' in self.browser.contents)
2016
2017        self.browser.open(self.student_path + '/studycourse/start_session')
2018        self.assertFalse('The requested form is locked' in self.browser.contents)
2019        self.browser.open(self.student_path + '/studycourse_1/start_session')
2020        self.assertTrue('The requested form is locked' in self.browser.contents)
2021
2022        IWorkflowState(self.student).setState('school fee paid')
2023        self.browser.open(self.student_path + '/studycourse/add')
2024        self.assertFalse('The requested form is locked' in self.browser.contents)
2025        self.browser.open(self.student_path + '/studycourse_1/add')
2026        self.assertTrue('The requested form is locked' in self.browser.contents)
2027
2028        self.browser.open(self.student_path + '/studycourse/999/manage')
2029        self.assertFalse('The requested form is locked' in self.browser.contents)
2030        self.browser.open(self.student_path + '/studycourse_1/999/manage')
2031        self.assertTrue('The requested form is locked' in self.browser.contents)
2032
2033        self.browser.open(self.student_path + '/studycourse/999/validate_courses')
2034        self.assertFalse('The requested form is locked' in self.browser.contents)
2035        self.browser.open(self.student_path + '/studycourse_1/999/validate_courses')
2036        self.assertTrue('The requested form is locked' in self.browser.contents)
2037
2038        self.browser.open(self.student_path + '/studycourse/999/reject_courses')
2039        self.assertFalse('The requested form is locked' in self.browser.contents)
2040        self.browser.open(self.student_path + '/studycourse_1/999/reject_courses')
2041        self.assertTrue('The requested form is locked' in self.browser.contents)
2042
2043        self.browser.open(self.student_path + '/studycourse/999/add')
2044        self.assertFalse('The requested form is locked' in self.browser.contents)
2045        self.browser.open(self.student_path + '/studycourse_1/999/add')
2046        self.assertTrue('The requested form is locked' in self.browser.contents)
2047
2048        self.browser.open(self.student_path + '/studycourse/999/edit')
2049        self.assertFalse('The requested form is locked' in self.browser.contents)
2050        self.browser.open(self.student_path + '/studycourse_1/999/edit')
2051        self.assertTrue('The requested form is locked' in self.browser.contents)
2052
2053        # Revert transfer
2054        self.browser.open(self.student_path + '/studycourse_1')
2055        self.browser.getLink("Reactivate").click()
2056        self.browser.getControl("Revert now").click()
2057        self.assertTrue('Previous transfer reverted' in self.browser.contents)
2058        results = list(
2059            cat.searchResults(
2060            certcode=('CERT1', 'CERT1')))
2061        self.assertTrue(results[0] is self.student)
2062        self.assertEqual([i for i in self.student.keys()],
2063            [u'accommodation', u'payments', u'studycourse'])
2064
2065    def test_login_as_student(self):
2066        # StudentImpersonators can login as student
2067        # Create clearance officer
2068        self.app['users'].addUser('mrofficer', 'mrofficersecret')
2069        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
2070        self.app['users']['mrofficer'].title = 'Harry Actor'
2071        prmglobal = IPrincipalRoleManager(self.app)
2072        prmglobal.assignRoleToPrincipal('waeup.StudentImpersonator', 'mrofficer')
2073        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrofficer')
2074        # Login as student impersonator
2075        self.browser.open(self.login_path)
2076        self.browser.getControl(name="form.login").value = 'mrofficer'
2077        self.browser.getControl(name="form.password").value = 'mrofficersecret'
2078        self.browser.getControl("Login").click()
2079        self.assertMatches('...You logged in...', self.browser.contents)
2080        self.browser.open(self.student_path)
2081        self.browser.getLink("Login as").click()
2082        self.browser.getControl("Set password now").click()
2083        temp_password = self.browser.getControl(name='form.password').value
2084        self.browser.getControl("Login now").click()
2085        self.assertMatches(
2086            '...You successfully logged in as...', self.browser.contents)
2087        # We are logged in as student and can see the 'My Data' tab
2088        self.assertMatches(
2089            '...<a href="#" class="dropdown-toggle" data-toggle="dropdown">...',
2090            self.browser.contents)
2091        self.assertMatches(
2092            '...My Data...',
2093            self.browser.contents)
2094        self.browser.getLink("Logout").click()
2095        # The student can't login with the original password ...
2096        self.browser.open(self.login_path)
2097        self.browser.getControl(name="form.login").value = self.student_id
2098        self.browser.getControl(name="form.password").value = 'spwd'
2099        self.browser.getControl("Login").click()
2100        self.assertMatches(
2101            '...Your account has been temporarily deactivated...',
2102            self.browser.contents)
2103        # ... but with the temporary password
2104        self.browser.open(self.login_path)
2105        self.browser.getControl(name="form.login").value = self.student_id
2106        self.browser.getControl(name="form.password").value = temp_password
2107        self.browser.getControl("Login").click()
2108        self.assertMatches('...You logged in...', self.browser.contents)
2109        # Creation of temp_password is properly logged
2110        logfile = os.path.join(
2111            self.app['datacenter'].storage, 'logs', 'students.log')
2112        logcontent = open(logfile).read()
2113        self.assertTrue(
2114            'mrofficer - students.browser.LoginAsStudentStep1 - K1000000 - '
2115            'temp_password generated: %s' % temp_password in logcontent)
2116
2117    def test_transcripts(self):
2118        studylevel = createObject(u'waeup.StudentStudyLevel')
2119        studylevel.level = 100
2120        studylevel.level_session = 2005
2121        self.student['studycourse'].entry_mode = 'ug_ft'
2122        self.student['studycourse'].addStudentStudyLevel(
2123            self.certificate, studylevel)
2124        studylevel2 = createObject(u'waeup.StudentStudyLevel')
2125        studylevel2.level = 110
2126        studylevel2.level_session = 2006
2127        self.student['studycourse'].addStudentStudyLevel(
2128            self.certificate, studylevel2)
2129        # Add second course (COURSE has been added automatically)
2130        courseticket = createObject('waeup.CourseTicket')
2131        courseticket.code = 'ANYCODE'
2132        courseticket.title = u'Any TITLE'
2133        courseticket.credits = 13
2134        courseticket.score = 66
2135        courseticket.semester = 1
2136        courseticket.dcode = u'ANYDCODE'
2137        courseticket.fcode = u'ANYFCODE'
2138        self.student['studycourse']['110']['COURSE2'] = courseticket
2139        self.student['studycourse']['100']['COURSE1'].score = 55
2140        self.assertEqual(self.student['studycourse']['100'].gpa_params_rectified[0], 3.0)
2141        self.assertEqual(self.student['studycourse']['110'].gpa_params_rectified[0], 4.0)
2142        # Get transcript data
2143        td = self.student['studycourse'].getTranscriptData()
2144        self.assertEqual(td[0][0]['level_key'], '100')
2145        self.assertEqual(td[0][0]['sgpa'], 3.0)
2146        self.assertEqual(td[0][0]['level'].level, 100)
2147        self.assertEqual(td[0][0]['level'].level_session, 2005)
2148        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
2149        self.assertEqual(td[0][1]['level_key'], '110')
2150        self.assertEqual(td[0][1]['sgpa'], 4.0)
2151        self.assertEqual(td[0][1]['level'].level, 110)
2152        self.assertEqual(td[0][1]['level'].level_session, 2006)
2153        self.assertEqual(td[0][1]['tickets_1'][0].code, 'ANYCODE')
2154        self.assertEqual(td[1], 3.57)
2155        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2156        self.browser.open(self.student_path + '/studycourse/transcript')
2157        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2158        self.assertTrue('Transcript' in self.browser.contents)
2159        # Officers can open the pdf transcript
2160        self.browser.open(self.student_path + '/studycourse/transcript.pdf')
2161        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2162        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2163        path = os.path.join(samples_dir(), 'transcript.pdf')
2164        open(path, 'wb').write(self.browser.contents)
2165        print "Sample PDF transcript.pdf written to %s" % path
2166
2167    def test_process_transcript_request(self):
2168        IWorkflowState(self.student).setState('transcript requested')
2169        notify(grok.ObjectModifiedEvent(self.student))
2170        self.student.transcript_comment = (
2171            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
2172            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
2173            'Address line2\n\n')
2174        # Create transcript officer
2175        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
2176        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
2177        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
2178        prmglobal = IPrincipalRoleManager(self.app)
2179        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
2180        # Login as transcript officer
2181        self.browser.open(self.login_path)
2182        self.browser.getControl(name="form.login").value = 'mrtranscript'
2183        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
2184        self.browser.getControl("Login").click()
2185        self.assertMatches('...You logged in...', self.browser.contents)
2186        # Officer can see his roles
2187        self.browser.getLink("My Roles").click()
2188        self.assertMatches(
2189            '...<div>Transcript Officer</div>...',
2190            self.browser.contents)
2191        # Officer can search for students in state 'transcripr requested'
2192        self.browser.open(self.container_path)
2193        self.browser.getControl(name="searchtype").value = ['transcript']
2194        self.browser.getControl("Find student(s)").click()
2195        self.assertTrue('Anna Tester' in self.browser.contents)
2196        self.browser.getLink("K1000000").click()
2197        self.browser.getLink("Manage transcript request").click()
2198        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
2199        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
2200        'Address line2<br><br></p>' in self.browser.contents)
2201        self.browser.getControl(name="comment").value = (
2202            'Hello,\nYour transcript has been sent to the address provided.')
2203        self.browser.getControl("Save comment and mark as processed").click()
2204        self.assertTrue(
2205            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
2206            'been sent to the address provided.\n\n'
2207            in self.student.transcript_comment)
2208        # The comment has been logged
2209        logfile = os.path.join(
2210            self.app['datacenter'].storage, 'logs', 'students.log')
2211        logcontent = open(logfile).read()
2212        self.assertTrue(
2213            'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
2214            'K1000000 - comment: Hello,<br>'
2215            'Your transcript has been sent to the address provided'
2216            in logcontent)
2217
2218class StudentUITests(StudentsFullSetup):
2219    # Tests for Student class views and pages
2220
2221    def test_student_change_password(self):
2222        # Students can change the password
2223        self.student.personal_updated = datetime.utcnow()
2224        self.browser.open(self.login_path)
2225        self.browser.getControl(name="form.login").value = self.student_id
2226        self.browser.getControl(name="form.password").value = 'spwd'
2227        self.browser.getControl("Login").click()
2228        self.assertEqual(self.browser.url, self.student_path)
2229        self.assertTrue('You logged in' in self.browser.contents)
2230        # Change password
2231        self.browser.getLink("Change password").click()
2232        self.browser.getControl(name="change_password").value = 'pw'
2233        self.browser.getControl(
2234            name="change_password_repeat").value = 'pw'
2235        self.browser.getControl("Save").click()
2236        self.assertTrue('Password must have at least' in self.browser.contents)
2237        self.browser.getControl(name="change_password").value = 'new_password'
2238        self.browser.getControl(
2239            name="change_password_repeat").value = 'new_passssword'
2240        self.browser.getControl("Save").click()
2241        self.assertTrue('Passwords do not match' in self.browser.contents)
2242        self.browser.getControl(name="change_password").value = 'new_password'
2243        self.browser.getControl(
2244            name="change_password_repeat").value = 'new_password'
2245        self.browser.getControl("Save").click()
2246        self.assertTrue('Password changed' in self.browser.contents)
2247        # We are still logged in. Changing the password hasn't thrown us out.
2248        self.browser.getLink("Base Data").click()
2249        self.assertEqual(self.browser.url, self.student_path)
2250        # We can logout
2251        self.browser.getLink("Logout").click()
2252        self.assertTrue('You have been logged out' in self.browser.contents)
2253        self.assertEqual(self.browser.url, 'http://localhost/app/index')
2254        # We can login again with the new password
2255        self.browser.getLink("Login").click()
2256        self.browser.open(self.login_path)
2257        self.browser.getControl(name="form.login").value = self.student_id
2258        self.browser.getControl(name="form.password").value = 'new_password'
2259        self.browser.getControl("Login").click()
2260        self.assertEqual(self.browser.url, self.student_path)
2261        self.assertTrue('You logged in' in self.browser.contents)
2262        return
2263
2264    def test_setpassword(self):
2265        # Set password for first-time access
2266        student = Student()
2267        student.reg_number = u'123456'
2268        student.firstname = u'Klaus'
2269        student.lastname = u'Tester'
2270        self.app['students'].addStudent(student)
2271        setpassword_path = 'http://localhost/app/setpassword'
2272        student_path = 'http://localhost/app/students/%s' % student.student_id
2273        self.browser.open(setpassword_path)
2274        self.browser.getControl(name="ac_series").value = self.existing_pwdseries
2275        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2276        self.browser.getControl(name="reg_number").value = '223456'
2277        self.browser.getControl("Set").click()
2278        self.assertMatches('...No student found...',
2279                           self.browser.contents)
2280        self.browser.getControl(name="reg_number").value = '123456'
2281        self.browser.getControl(name="ac_number").value = '999999'
2282        self.browser.getControl("Set").click()
2283        self.assertMatches('...Access code is invalid...',
2284                           self.browser.contents)
2285        self.browser.getControl(name="ac_number").value = self.existing_pwdnumber
2286        self.browser.getControl("Set").click()
2287        self.assertMatches('...Password has been set. Your Student Id is...',
2288                           self.browser.contents)
2289        self.browser.getControl("Set").click()
2290        self.assertMatches(
2291            '...Password has already been set. Your Student Id is...',
2292            self.browser.contents)
2293        existing_pwdpin = self.pwdpins[1]
2294        parts = existing_pwdpin.split('-')[1:]
2295        existing_pwdseries, existing_pwdnumber = parts
2296        self.browser.getControl(name="ac_series").value = existing_pwdseries
2297        self.browser.getControl(name="ac_number").value = existing_pwdnumber
2298        self.browser.getControl(name="reg_number").value = '123456'
2299        self.browser.getControl("Set").click()
2300        self.assertMatches(
2301            '...You are using the wrong Access Code...',
2302            self.browser.contents)
2303        # The student can login with the new credentials
2304        self.browser.open(self.login_path)
2305        self.browser.getControl(name="form.login").value = student.student_id
2306        self.browser.getControl(
2307            name="form.password").value = self.existing_pwdnumber
2308        self.browser.getControl("Login").click()
2309        self.assertEqual(self.browser.url, student_path)
2310        self.assertTrue('You logged in' in self.browser.contents)
2311        return
2312
2313    def test_student_login(self):
2314        # Student cant login if their password is not set
2315        self.student.password = None
2316        self.browser.open(self.login_path)
2317        self.browser.getControl(name="form.login").value = self.student_id
2318        self.browser.getControl(name="form.password").value = 'spwd'
2319        self.browser.getControl("Login").click()
2320        self.assertTrue(
2321            'You entered invalid credentials.' in self.browser.contents)
2322        # We set the password again
2323        IUserAccount(
2324            self.app['students'][self.student_id]).setPassword('spwd')
2325        # Students can't login if their account is suspended/deactivated
2326        self.student.suspended = True
2327        self.browser.open(self.login_path)
2328        self.browser.getControl(name="form.login").value = self.student_id
2329        self.browser.getControl(name="form.password").value = 'spwd'
2330        self.browser.getControl("Login").click()
2331        self.assertMatches(
2332            '...<div class="alert alert-warning">'
2333            'Your account has been deactivated.</div>...', self.browser.contents)
2334        # If suspended_comment is set this message will be flashed instead
2335        self.student.suspended_comment = u'Aetsch baetsch!'
2336        self.browser.getControl(name="form.login").value = self.student_id
2337        self.browser.getControl(name="form.password").value = 'spwd'
2338        self.browser.getControl("Login").click()
2339        self.assertMatches(
2340            '...<div class="alert alert-warning">Aetsch baetsch!</div>...',
2341            self.browser.contents)
2342        self.student.suspended = False
2343        # Students can't login if a temporary password has been set and
2344        # is not expired
2345        self.app['students'][self.student_id].setTempPassword(
2346            'anybody', 'temp_spwd')
2347        self.browser.open(self.login_path)
2348        self.browser.getControl(name="form.login").value = self.student_id
2349        self.browser.getControl(name="form.password").value = 'spwd'
2350        self.browser.getControl("Login").click()
2351        self.assertMatches(
2352            '...Your account has been temporarily deactivated...',
2353            self.browser.contents)
2354        # The student can login with the temporary password
2355        self.browser.open(self.login_path)
2356        self.browser.getControl(name="form.login").value = self.student_id
2357        self.browser.getControl(name="form.password").value = 'temp_spwd'
2358        self.browser.getControl("Login").click()
2359        self.assertMatches(
2360            '...You logged in...', self.browser.contents)
2361        # Student can view the base data
2362        self.browser.open(self.student_path)
2363        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2364        self.assertEqual(self.browser.url, self.student_path)
2365        # When the password expires ...
2366        delta = timedelta(minutes=11)
2367        self.app['students'][self.student_id].temp_password[
2368            'timestamp'] = datetime.utcnow() - delta
2369        self.app['students'][self.student_id]._p_changed = True
2370        # ... the student will be automatically logged out
2371        self.assertRaises(
2372            Unauthorized, self.browser.open, self.student_path)
2373        # Then the student can login with the original password
2374        self.browser.open(self.login_path)
2375        self.browser.getControl(name="form.login").value = self.student_id
2376        self.browser.getControl(name="form.password").value = 'spwd'
2377        self.browser.getControl("Login").click()
2378        self.assertMatches(
2379            '...You logged in...', self.browser.contents)
2380
2381    def test_maintenance_mode(self):
2382        config = grok.getSite()['configuration']
2383        self.browser.open(self.login_path)
2384        self.browser.getControl(name="form.login").value = self.student_id
2385        self.browser.getControl(name="form.password").value = 'spwd'
2386        self.browser.getControl("Login").click()
2387        # Student logged in.
2388        self.assertTrue('You logged in' in self.browser.contents)
2389        self.assertTrue("Anna Tester" in self.browser.contents)
2390        # If maintenance mode is enabled, student is immediately logged out.
2391        config.maintmode_enabled_by = u'any_user'
2392        self.assertRaises(
2393            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
2394        self.browser.open('http://localhost/app/login')
2395        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
2396        # Student really can't login if maintenance mode is enabled.
2397        self.browser.open(self.login_path)
2398        self.browser.getControl(name="form.login").value = self.student_id
2399        self.browser.getControl(name="form.password").value = 'spwd'
2400        self.browser.getControl("Login").click()
2401        # A second warning is raised.
2402        self.assertTrue(
2403            'The portal is in maintenance mode. You can\'t login!'
2404            in self.browser.contents)
2405        return
2406
2407    def test_student_clearance(self):
2408        # Student cant login if their password is not set
2409        IWorkflowInfo(self.student).fireTransition('admit')
2410        self.browser.open(self.login_path)
2411        self.browser.getControl(name="form.login").value = self.student_id
2412        self.browser.getControl(name="form.password").value = 'spwd'
2413        self.browser.getControl("Login").click()
2414        self.assertMatches(
2415            '...You logged in...', self.browser.contents)
2416        # Admitted student can upload a passport picture
2417        self.browser.open(self.student_path + '/change_portrait')
2418        ctrl = self.browser.getControl(name='passportuploadedit')
2419        file_obj = open(SAMPLE_IMAGE, 'rb')
2420        file_ctrl = ctrl.mech_control
2421        file_ctrl.add_file(file_obj, filename='my_photo.jpg')
2422        self.browser.getControl(
2423            name='upload_passportuploadedit').click()
2424        self.assertTrue(
2425            'src="http://localhost/app/students/K1000000/passport.jpg"'
2426            in self.browser.contents)
2427        # Students can open admission letter
2428        self.browser.getLink("Base Data").click()
2429        self.browser.getLink("Download admission letter").click()
2430        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2431        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2432        # Student can view the clearance data
2433        self.browser.open(self.student_path)
2434        self.browser.getLink("Clearance Data").click()
2435        # Student can't open clearance edit form before starting clearance
2436        self.browser.open(self.student_path + '/cedit')
2437        self.assertMatches('...The requested form is locked...',
2438                           self.browser.contents)
2439        self.browser.getLink("Clearance Data").click()
2440        self.browser.getLink("Start clearance").click()
2441        self.student.phone = None
2442        # Uups, we forgot to fill the phone fields
2443        self.browser.getControl("Start clearance").click()
2444        self.assertMatches('...Phone number is missing...',
2445                           self.browser.contents)
2446        self.browser.open(self.student_path + '/edit_base')
2447        self.browser.getControl(name="form.phone.ext").value = '12345'
2448        self.browser.getControl("Save").click()
2449        self.browser.open(self.student_path + '/start_clearance')
2450        self.browser.getControl(name="ac_series").value = '3'
2451        self.browser.getControl(name="ac_number").value = '4444444'
2452        self.browser.getControl("Start clearance now").click()
2453        self.assertMatches('...Activation code is invalid...',
2454                           self.browser.contents)
2455        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2456        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2457        # Owner is Hans Wurst, AC can't be invalidated
2458        self.browser.getControl("Start clearance now").click()
2459        self.assertMatches('...You are not the owner of this access code...',
2460                           self.browser.contents)
2461        # Set the correct owner
2462        self.existing_clrac.owner = self.student_id
2463        # clr_code might be set (and thus returns None) due importing
2464        # an empty clr_code column.
2465        self.student.clr_code = None
2466        self.browser.getControl("Start clearance now").click()
2467        self.assertMatches('...Clearance process has been started...',
2468                           self.browser.contents)
2469        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2470        self.browser.getControl("Save", index=0).click()
2471        # Student can view the clearance data
2472        self.browser.getLink("Clearance Data").click()
2473        # and go back to the edit form
2474        self.browser.getLink("Edit").click()
2475        # Students can upload documents
2476        ctrl = self.browser.getControl(name='birthcertificateupload')
2477        file_obj = open(SAMPLE_IMAGE, 'rb')
2478        file_ctrl = ctrl.mech_control
2479        file_ctrl.add_file(file_obj, filename='my_birth_certificate.jpg')
2480        self.browser.getControl(
2481            name='upload_birthcertificateupload').click()
2482        self.assertTrue(
2483            'href="http://localhost/app/students/K1000000/birth_certificate"'
2484            in self.browser.contents)
2485        # Students can open clearance slip
2486        self.browser.getLink("View").click()
2487        self.browser.getLink("Download clearance slip").click()
2488        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2489        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2490        # Students can request clearance
2491        self.browser.open(self.edit_clearance_path)
2492        self.browser.getControl("Save and request clearance").click()
2493        self.browser.getControl(name="ac_series").value = self.existing_clrseries
2494        self.browser.getControl(name="ac_number").value = self.existing_clrnumber
2495        self.browser.getControl("Request clearance now").click()
2496        self.assertMatches('...Clearance has been requested...',
2497                           self.browser.contents)
2498        # Student can't reopen clearance form after requesting clearance
2499        self.browser.open(self.student_path + '/cedit')
2500        self.assertMatches('...The requested form is locked...',
2501                           self.browser.contents)
2502
2503    def test_student_course_registration(self):
2504        # Student cant login if their password is not set
2505        IWorkflowInfo(self.student).fireTransition('admit')
2506        self.browser.open(self.login_path)
2507        self.browser.getControl(name="form.login").value = self.student_id
2508        self.browser.getControl(name="form.password").value = 'spwd'
2509        self.browser.getControl("Login").click()
2510        # Student can't add study level if not in state 'school fee paid'
2511        self.browser.open(self.student_path + '/studycourse/add')
2512        self.assertMatches('...The requested form is locked...',
2513                           self.browser.contents)
2514        # ... and must be transferred first
2515        IWorkflowState(self.student).setState('school fee paid')
2516        # Now students can add the current study level
2517        self.browser.getLink("Study Course").click()
2518        self.student['studycourse'].current_level = None
2519        self.browser.getLink("Add course list").click()
2520        self.assertMatches('...Your data are incomplete...',
2521                           self.browser.contents)
2522        self.student['studycourse'].current_level = 100
2523        self.browser.getLink("Add course list").click()
2524        self.assertMatches('...Add current level 100 (Year 1)...',
2525                           self.browser.contents)
2526        self.browser.getControl("Create course list now").click()
2527        # A level with one course ticket was created
2528        self.assertEqual(self.student['studycourse']['100'].number_of_tickets, 1)
2529        self.browser.getLink("100").click()
2530        self.browser.getLink("Edit course list").click()
2531        self.browser.getLink("here").click()
2532        self.browser.getControl(name="form.course").value = ['COURSE1']
2533        self.browser.getControl("Add course ticket").click()
2534        self.assertMatches('...The ticket exists...',
2535                           self.browser.contents)
2536        self.student['studycourse'].current_level = 200
2537        self.browser.getLink("Study Course").click()
2538        self.browser.getLink("Add course list").click()
2539        self.assertMatches('...Add current level 200 (Year 2)...',
2540                           self.browser.contents)
2541        self.browser.getControl("Create course list now").click()
2542        self.browser.getLink("200").click()
2543        self.browser.getLink("Edit course list").click()
2544        self.browser.getLink("here").click()
2545        self.browser.getControl(name="form.course").value = ['COURSE1']
2546        self.course.credits = 100
2547        self.browser.getControl("Add course ticket").click()
2548        self.assertMatches(
2549            '...Total credits exceed 50...', self.browser.contents)
2550        self.course.credits = 10
2551        self.browser.getControl("Add course ticket").click()
2552        self.assertMatches('...The ticket exists...',
2553                           self.browser.contents)
2554        # Indeed the ticket exists as carry-over course from level 100
2555        # since its score was 0
2556        self.assertTrue(
2557            self.student['studycourse']['200']['COURSE1'].carry_over is True)
2558        # Students can open the pdf course registration slip
2559        self.browser.open(self.student_path + '/studycourse/200')
2560        self.browser.getLink("Download course registration slip").click()
2561        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2562        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2563        # Students can remove course tickets
2564        self.browser.open(self.student_path + '/studycourse/200/edit')
2565        self.browser.getControl("Remove selected", index=0).click()
2566        self.assertTrue('No ticket selected' in self.browser.contents)
2567        # No ticket can be selected since the carry-over course is a core course
2568        self.assertRaises(
2569            LookupError, self.browser.getControl, name='val_id')
2570        self.student['studycourse']['200']['COURSE1'].mandatory = False
2571        self.browser.open(self.student_path + '/studycourse/200/edit')
2572        # Course list can't be registered if total_credits exceeds max_credits
2573        self.student['studycourse']['200']['COURSE1'].credits = 60
2574        self.browser.getControl("Register course list").click()
2575        self.assertTrue('Maximum credits of 50 exceeded' in self.browser.contents)
2576        # Student can now remove the ticket
2577        ctrl = self.browser.getControl(name='val_id')
2578        ctrl.getControl(value='COURSE1').selected = True
2579        self.browser.getControl("Remove selected", index=0).click()
2580        self.assertTrue('Successfully removed' in self.browser.contents)
2581        # Removing course tickets is properly logged
2582        logfile = os.path.join(
2583            self.app['datacenter'].storage, 'logs', 'students.log')
2584        logcontent = open(logfile).read()
2585        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage '
2586        '- K1000000 - removed: COURSE1 at 200' in logcontent)
2587        # They can add the same ticket using the edit page directly.
2588        # We can do the same by adding the course on the manage page directly
2589        self.browser.getControl(name="course").value = 'COURSE1'
2590        self.browser.getControl("Add course ticket").click()
2591        # Adding course tickets is logged
2592        logfile = os.path.join(
2593            self.app['datacenter'].storage, 'logs', 'students.log')
2594        logcontent = open(logfile).read()
2595        self.assertTrue('K1000000 - students.browser.StudyLevelEditFormPage - '
2596            'K1000000 - added: COURSE1|200|2004' in logcontent)
2597        # Course list can be registered
2598        self.browser.getControl("Register course list").click()
2599        self.assertTrue('Course list has been registered' in self.browser.contents)
2600        self.assertEqual(self.student.state, 'courses registered')
2601        # Students can view the transcript
2602        #self.browser.open(self.studycourse_path)
2603        #self.browser.getLink("Transcript").click()
2604        #self.browser.getLink("Academic Transcript").click()
2605        #self.assertEqual(self.browser.headers['Status'], '200 Ok')
2606        #self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2607        return
2608
2609    def test_postgraduate_student_access(self):
2610        self.certificate.study_mode = 'pg_ft'
2611        self.certificate.start_level = 999
2612        self.certificate.end_level = 999
2613        self.student['studycourse'].current_level = 999
2614        IWorkflowState(self.student).setState('school fee paid')
2615        self.browser.open(self.login_path)
2616        self.browser.getControl(name="form.login").value = self.student_id
2617        self.browser.getControl(name="form.password").value = 'spwd'
2618        self.browser.getControl("Login").click()
2619        self.assertTrue(
2620            'You logged in.' in self.browser.contents)
2621        # Now students can add the current study level
2622        self.browser.getLink("Study Course").click()
2623        self.browser.getLink("Add course list").click()
2624        self.assertMatches('...Add current level Postgraduate Level...',
2625                           self.browser.contents)
2626        self.browser.getControl("Create course list now").click()
2627        # A level with one course ticket was created
2628        self.assertEqual(self.student['studycourse']['999'].number_of_tickets, 0)
2629        self.browser.getLink("999").click()
2630        self.browser.getLink("Edit course list").click()
2631        self.browser.getLink("here").click()
2632        self.browser.getControl(name="form.course").value = ['COURSE1']
2633        self.browser.getControl("Add course ticket").click()
2634        self.assertMatches('...Successfully added COURSE1...',
2635                           self.browser.contents)
2636        # Postgraduate students can't register course lists
2637        self.browser.getControl("Register course list").click()
2638        self.assertTrue("your course list can't bee registered"
2639            in self.browser.contents)
2640        self.assertEqual(self.student.state, 'school fee paid')
2641        return
2642
2643    def test_student_clearance_wo_clrcode(self):
2644        IWorkflowState(self.student).setState('clearance started')
2645        self.browser.open(self.login_path)
2646        self.browser.getControl(name="form.login").value = self.student_id
2647        self.browser.getControl(name="form.password").value = 'spwd'
2648        self.browser.getControl("Login").click()
2649        self.browser.open(self.edit_clearance_path)
2650        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
2651        self.browser.getControl("Save and request clearance").click()
2652        self.assertMatches('...Clearance has been requested...',
2653                           self.browser.contents)
2654
2655    def test_student_clearance_payment(self):
2656        # Login
2657        self.browser.open(self.login_path)
2658        self.browser.getControl(name="form.login").value = self.student_id
2659        self.browser.getControl(name="form.password").value = 'spwd'
2660        self.browser.getControl("Login").click()
2661
2662        # Students can add online clearance payment tickets
2663        self.browser.open(self.payments_path + '/addop')
2664        self.browser.getControl(name="form.p_category").value = ['clearance']
2665        self.browser.getControl("Create ticket").click()
2666        self.assertMatches('...ticket created...',
2667                           self.browser.contents)
2668
2669        # Students can't approve the payment
2670        self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
2671        ctrl = self.browser.getControl(name='val_id')
2672        value = ctrl.options[0]
2673        self.browser.getLink(value).click()
2674        payment_url = self.browser.url
2675        self.assertRaises(
2676            Unauthorized, self.browser.open, payment_url + '/approve')
2677        # In the base package they can 'use' a fake approval view.
2678        # XXX: I tried to use
2679        # self.student['payments'][value].approveStudentPayment() instead.
2680        # But this function fails in
2681        # w.k.accesscodes.accesscode.create_accesscode.
2682        # grok.getSite returns None in tests.
2683        self.browser.open(payment_url + '/fake_approve')
2684        self.assertMatches('...Payment approved...',
2685                          self.browser.contents)
2686        expected = '''...
2687        <td>
2688          <span>Paid</span>
2689        </td>...'''
2690        expected = '''...
2691        <td>
2692          <span>Paid</span>
2693        </td>...'''
2694        self.assertMatches(expected,self.browser.contents)
2695        payment_id = self.student['payments'].keys()[0]
2696        payment = self.student['payments'][payment_id]
2697        self.assertEqual(payment.p_state, 'paid')
2698        self.assertEqual(payment.r_amount_approved, 3456.0)
2699        self.assertEqual(payment.r_code, 'AP')
2700        self.assertEqual(payment.r_desc, u'Payment approved by Anna Tester')
2701        # The new CLR-0 pin has been created
2702        self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
2703        pin = self.app['accesscodes']['CLR-0'].keys()[0]
2704        ac = self.app['accesscodes']['CLR-0'][pin]
2705        self.assertEqual(ac.owner, self.student_id)
2706        self.assertEqual(ac.cost, 3456.0)
2707
2708        # Students can open the pdf payment slip
2709        self.browser.open(payment_url + '/payment_slip.pdf')
2710        self.assertEqual(self.browser.headers['Status'], '200 Ok')
2711        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
2712
2713        # The new CLR-0 pin can be used for starting clearance
2714        # but they have to upload a passport picture first
2715        # which is only possible in state admitted
2716        self.browser.open(self.student_path + '/change_portrait')
2717        self.assertMatches('...form is locked...',
2718                          self.browser.contents)
2719        IWorkflowInfo(self.student).fireTransition('admit')
2720        self.browser.open(self.student_path + '/change_portrait')
2721        image = open(SAMPLE_IMAGE, 'rb')
2722        ctrl = self.browser.getControl(name='passportuploadedit')
2723        file_ctrl = ctrl.mech_control
2724        file_ctrl.add_file(image, filename='my_photo.jpg')
2725        self.browser.getControl(
2726            name='upload_passportuploadedit').click()
2727        self.browser.open(self.student_path + '/start_clearance')
2728        parts = pin.split('-')[1:]
2729        clrseries, clrnumber = parts
2730        self.browser.getControl(name="ac_series").value = clrseries
2731        self.browser.getControl(name="ac_number").value = clrnumber
2732        self.browser.getControl("Start clearance now").click()
2733        self.assertMatches('...Clearance process has been started...',
2734                           self.browser.contents)
2735
2736    def test_student_schoolfee_payment(self):
2737        configuration = createObject('waeup.SessionConfiguration')
2738        configuration.academic_session = 2005
2739        self.app['configuration'].addSessionConfiguration(configuration)
2740        # Login
2741        self.browser.open(self.login_path)
2742        self.browser.getControl(name="form.login").value = self.student_id
2743        self.browser.getControl(name="form.password").value = 'spwd'
2744        self.browser.getControl("Login").click()
2745
2746        # Students can add online school fee payment tickets.
2747        IWorkflowState(self.student).setState('returning')
2748        self.browser.open(self.payments_path)
2749        self.assertRaises(
2750            LookupError, self.browser.getControl, name='val_id')
2751        self.browser.getLink("Add current session payment ticket").click()
2752        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2753        self.browser.getControl("Create ticket").click()
2754        self.assertMatches('...ticket created...',
2755                           self.browser.contents)
2756        ctrl = self.browser.getControl(name='val_id')
2757        value = ctrl.options[0]
2758        self.browser.getLink(value).click()
2759        self.assertMatches('...Amount Authorized...',
2760                           self.browser.contents)
2761        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2762        # Payment session and will be calculated as defined
2763        # in w.k.students.utils because we set changed the state
2764        # to returning
2765        self.assertEqual(self.student['payments'][value].p_session, 2005)
2766        self.assertEqual(self.student['payments'][value].p_level, 200)
2767
2768        # Student is the payer of the payment ticket.
2769        payer = IPayer(self.student['payments'][value])
2770        self.assertEqual(payer.display_fullname, 'Anna Tester')
2771        self.assertEqual(payer.id, self.student_id)
2772        self.assertEqual(payer.faculty, 'fac1')
2773        self.assertEqual(payer.department, 'dep1')
2774
2775        # We simulate the approval
2776        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2777        self.browser.open(self.browser.url + '/fake_approve')
2778        self.assertMatches('...Payment approved...',
2779                          self.browser.contents)
2780
2781        ## The new SFE-0 pin can be used for starting new session
2782        #self.browser.open(self.studycourse_path)
2783        #self.browser.getLink('Start new session').click()
2784        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2785        #parts = pin.split('-')[1:]
2786        #sfeseries, sfenumber = parts
2787        #self.browser.getControl(name="ac_series").value = sfeseries
2788        #self.browser.getControl(name="ac_number").value = sfenumber
2789        #self.browser.getControl("Start now").click()
2790        #self.assertMatches('...Session started...',
2791        #                   self.browser.contents)
2792
2793        self.assertTrue(self.student.state == 'school fee paid')
2794        return
2795
2796    def test_student_bedallocation_payment(self):
2797        # Login
2798        self.browser.open(self.login_path)
2799        self.browser.getControl(name="form.login").value = self.student_id
2800        self.browser.getControl(name="form.password").value = 'spwd'
2801        self.browser.getControl("Login").click()
2802        self.browser.open(self.payments_path)
2803        self.browser.open(self.payments_path + '/addop')
2804        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
2805        self.browser.getControl("Create ticket").click()
2806        self.assertMatches('...ticket created...',
2807                           self.browser.contents)
2808        # Students can remove only online payment tickets which have
2809        # not received a valid callback
2810        self.browser.open(self.payments_path)
2811        ctrl = self.browser.getControl(name='val_id')
2812        value = ctrl.options[0]
2813        ctrl.getControl(value=value).selected = True
2814        self.browser.getControl("Remove selected", index=0).click()
2815        self.assertTrue('Successfully removed' in self.browser.contents)
2816
2817    def test_student_maintenance_payment(self):
2818        # Login
2819        self.browser.open(self.login_path)
2820        self.browser.getControl(name="form.login").value = self.student_id
2821        self.browser.getControl(name="form.password").value = 'spwd'
2822        self.browser.getControl("Login").click()
2823        self.browser.open(self.payments_path)
2824        self.browser.open(self.payments_path + '/addop')
2825        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
2826        self.browser.getControl("Create ticket").click()
2827        self.assertMatches('...You have not yet booked accommodation...',
2828                           self.browser.contents)
2829        # We continue this test in test_student_accommodation
2830
2831    def test_student_previous_payments(self):
2832        configuration = createObject('waeup.SessionConfiguration')
2833        configuration.academic_session = 2000
2834        configuration.clearance_fee = 3456.0
2835        configuration.booking_fee = 123.4
2836        self.app['configuration'].addSessionConfiguration(configuration)
2837        configuration2 = createObject('waeup.SessionConfiguration')
2838        configuration2.academic_session = 2003
2839        configuration2.clearance_fee = 3456.0
2840        configuration2.booking_fee = 123.4
2841        self.app['configuration'].addSessionConfiguration(configuration2)
2842        configuration3 = createObject('waeup.SessionConfiguration')
2843        configuration3.academic_session = 2005
2844        configuration3.clearance_fee = 3456.0
2845        configuration3.booking_fee = 123.4
2846        self.app['configuration'].addSessionConfiguration(configuration3)
2847        self.student['studycourse'].entry_session = 2002
2848
2849        # Login
2850        self.browser.open(self.login_path)
2851        self.browser.getControl(name="form.login").value = self.student_id
2852        self.browser.getControl(name="form.password").value = 'spwd'
2853        self.browser.getControl("Login").click()
2854
2855        # Students can add previous school fee payment tickets in any state.
2856        IWorkflowState(self.student).setState('courses registered')
2857        self.browser.open(self.payments_path)
2858        self.browser.getLink("Add previous session payment ticket").click()
2859
2860        # Previous session payment form is provided
2861        self.assertEqual(self.student.current_session, 2004)
2862        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2863        self.browser.getControl(name="form.p_session").value = ['2000']
2864        self.browser.getControl(name="form.p_level").value = ['300']
2865        self.browser.getControl("Create ticket").click()
2866        self.assertMatches('...The previous session must not fall below...',
2867                           self.browser.contents)
2868        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2869        self.browser.getControl(name="form.p_session").value = ['2005']
2870        self.browser.getControl(name="form.p_level").value = ['300']
2871        self.browser.getControl("Create ticket").click()
2872        self.assertMatches('...This is not a previous session...',
2873                           self.browser.contents)
2874        # Students can pay current session school fee.
2875        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2876        self.browser.getControl(name="form.p_session").value = ['2004']
2877        self.browser.getControl(name="form.p_level").value = ['300']
2878        self.browser.getControl("Create ticket").click()
2879        self.assertMatches('...ticket created...',
2880                           self.browser.contents)
2881        ctrl = self.browser.getControl(name='val_id')
2882        value = ctrl.options[0]
2883        self.browser.getLink(value).click()
2884        self.assertMatches('...Amount Authorized...',
2885                           self.browser.contents)
2886        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2887
2888        # Payment session is properly set
2889        self.assertEqual(self.student['payments'][value].p_session, 2004)
2890        self.assertEqual(self.student['payments'][value].p_level, 300)
2891
2892        # We simulate the approval
2893        self.browser.open(self.browser.url + '/fake_approve')
2894        self.assertMatches('...Payment approved...',
2895                          self.browser.contents)
2896
2897        # No AC has been created
2898        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
2899        self.assertTrue(self.student['payments'][value].ac is None)
2900
2901        # Current payment flag is set False
2902        self.assertFalse(self.student['payments'][value].p_current)
2903
2904        # Button and form are not available for students who are in
2905        # states up to cleared
2906        self.student['studycourse'].entry_session = 2004
2907        IWorkflowState(self.student).setState('cleared')
2908        self.browser.open(self.payments_path)
2909        self.assertFalse(
2910            "Add previous session payment ticket" in self.browser.contents)
2911        self.browser.open(self.payments_path + '/addpp')
2912        self.assertTrue(
2913            "No previous payment to be made" in self.browser.contents)
2914        return
2915
2916    def test_postgraduate_student_payments(self):
2917        configuration = createObject('waeup.SessionConfiguration')
2918        configuration.academic_session = 2005
2919        self.app['configuration'].addSessionConfiguration(configuration)
2920        self.certificate.study_mode = 'pg_ft'
2921        self.certificate.start_level = 999
2922        self.certificate.end_level = 999
2923        self.student['studycourse'].current_level = 999
2924        # Login
2925        self.browser.open(self.login_path)
2926        self.browser.getControl(name="form.login").value = self.student_id
2927        self.browser.getControl(name="form.password").value = 'spwd'
2928        self.browser.getControl("Login").click()
2929        # Students can add online school fee payment tickets.
2930        IWorkflowState(self.student).setState('cleared')
2931        self.browser.open(self.payments_path)
2932        self.browser.getLink("Add current session payment ticket").click()
2933        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2934        self.browser.getControl("Create ticket").click()
2935        self.assertMatches('...ticket created...',
2936                           self.browser.contents)
2937        ctrl = self.browser.getControl(name='val_id')
2938        value = ctrl.options[0]
2939        self.browser.getLink(value).click()
2940        self.assertMatches('...Amount Authorized...',
2941                           self.browser.contents)
2942        # Payment session and level are current ones.
2943        # Postgrads have to pay school_fee_1.
2944        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
2945        self.assertEqual(self.student['payments'][value].p_session, 2004)
2946        self.assertEqual(self.student['payments'][value].p_level, 999)
2947
2948        # We simulate the approval
2949        self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
2950        self.browser.open(self.browser.url + '/fake_approve')
2951        self.assertMatches('...Payment approved...',
2952                          self.browser.contents)
2953
2954        ## The new SFE-0 pin can be used for starting session
2955        #self.browser.open(self.studycourse_path)
2956        #self.browser.getLink('Start new session').click()
2957        #pin = self.app['accesscodes']['SFE-0'].keys()[0]
2958        #parts = pin.split('-')[1:]
2959        #sfeseries, sfenumber = parts
2960        #self.browser.getControl(name="ac_series").value = sfeseries
2961        #self.browser.getControl(name="ac_number").value = sfenumber
2962        #self.browser.getControl("Start now").click()
2963        #self.assertMatches('...Session started...',
2964        #                   self.browser.contents)
2965
2966        self.assertTrue(self.student.state == 'school fee paid')
2967
2968        # Postgrad students do not need to register courses the
2969        # can just pay for the next session.
2970        self.browser.open(self.payments_path)
2971        # Remove first payment to be sure that we access the right ticket
2972        del self.student['payments'][value]
2973        self.browser.getLink("Add current session payment ticket").click()
2974        self.browser.getControl(name="form.p_category").value = ['schoolfee']
2975        self.browser.getControl("Create ticket").click()
2976        ctrl = self.browser.getControl(name='val_id')
2977        value = ctrl.options[0]
2978        self.browser.getLink(value).click()
2979        # Payment session has increased by one, payment level remains the same.
2980        # Returning Postgraduates have to pay school_fee_2.
2981        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
2982        self.assertEqual(self.student['payments'][value].p_session, 2005)
2983        self.assertEqual(self.student['payments'][value].p_level, 999)
2984
2985        # Student is still in old session
2986        self.assertEqual(self.student.current_session, 2004)
2987
2988        # We do not need to pay the ticket if any other
2989        # SFE pin is provided
2990        pin_container = self.app['accesscodes']
2991        pin_container.createBatch(
2992            datetime.utcnow(), 'some_userid', 'SFE', 9.99, 1)
2993        pin = pin_container['SFE-1'].values()[0].representation
2994        sfeseries, sfenumber = pin.split('-')[1:]
2995        # The new SFE-1 pin can be used for starting new session
2996        self.browser.open(self.studycourse_path)
2997        self.browser.getLink('Start new session').click()
2998        self.browser.getControl(name="ac_series").value = sfeseries
2999        self.browser.getControl(name="ac_number").value = sfenumber
3000        self.browser.getControl("Start now").click()
3001        self.assertMatches('...Session started...',
3002                           self.browser.contents)
3003        self.assertTrue(self.student.state == 'school fee paid')
3004        # Student is in new session
3005        self.assertEqual(self.student.current_session, 2005)
3006        self.assertEqual(self.student['studycourse'].current_level, 999)
3007        return
3008
3009    def test_student_accommodation(self):
3010        # Create a second hostel with one bed
3011        hostel = Hostel()
3012        hostel.hostel_id = u'hall-2'
3013        hostel.hostel_name = u'Hall 2'
3014        self.app['hostels'].addHostel(hostel)
3015        bed = Bed()
3016        bed.bed_id = u'hall-2_A_101_A'
3017        bed.bed_number = 1
3018        bed.owner = NOT_OCCUPIED
3019        bed.bed_type = u'regular_female_fr'
3020        self.app['hostels'][hostel.hostel_id].addBed(bed)
3021
3022        self.browser.open(self.login_path)
3023        self.browser.getControl(name="form.login").value = self.student_id
3024        self.browser.getControl(name="form.password").value = 'spwd'
3025        self.browser.getControl("Login").click()
3026        # Students can add online booking fee payment tickets and open the
3027        # callback view (see test_manage_payments).
3028        self.browser.getLink("Payments").click()
3029        self.browser.getLink("Add current session payment ticket").click()
3030        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
3031        self.browser.getControl("Create ticket").click()
3032        ctrl = self.browser.getControl(name='val_id')
3033        value = ctrl.options[0]
3034        self.browser.getLink(value).click()
3035        self.browser.open(self.browser.url + '/fake_approve')
3036        # The new HOS-0 pin has been created.
3037        self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
3038        pin = self.app['accesscodes']['HOS-0'].keys()[0]
3039        ac = self.app['accesscodes']['HOS-0'][pin]
3040        parts = pin.split('-')[1:]
3041        sfeseries, sfenumber = parts
3042        # Students can use HOS code and book a bed space with it ...
3043        self.browser.open(self.acco_path)
3044        # ... but not if booking period has expired ...
3045        self.app['hostels'].enddate = datetime.now(pytz.utc)
3046        self.browser.getControl("Book accommodation").click()
3047        self.assertMatches('...Outside booking period: ...',
3048                           self.browser.contents)
3049        self.app['hostels'].enddate = datetime.now(pytz.utc) + timedelta(days=10)
3050        # ... or student data are incomplete ...
3051        self.student['studycourse'].current_level = None
3052        self.browser.getControl("Book accommodation").click()
3053        self.assertMatches('...Your data are incomplete...',
3054            self.browser.contents)
3055        self.student['studycourse'].current_level = 100
3056        # ... or student is not the an allowed state ...
3057        self.browser.getControl("Book accommodation").click()
3058        self.assertMatches('...You are in the wrong...',
3059                           self.browser.contents)
3060        # Students can still not see the disired hostel selector.
3061        self.assertFalse('desired hostel' in self.browser.contents)
3062        IWorkflowInfo(self.student).fireTransition('admit')
3063        # Students can now see the disired hostel selector.
3064        self.browser.reload()
3065        self.browser.open(self.acco_path)
3066        self.assertTrue('desired hostel' in self.browser.contents)
3067        self.browser.getControl(name="hostel").value = ['hall-2']
3068        self.browser.getControl("Save").click()
3069        self.assertTrue('selection has been saved' in self.browser.contents)
3070        self.assertTrue('<option selected="selected" value="hall-2">'
3071            in self.browser.contents)
3072        self.browser.getControl("Book accommodation").click()
3073        self.assertMatches('...Activation Code:...',
3074                           self.browser.contents)
3075        # Student can't use faked ACs ...
3076        self.browser.getControl(name="ac_series").value = u'nonsense'
3077        self.browser.getControl(name="ac_number").value = sfenumber
3078        self.browser.getControl("Create bed ticket").click()
3079        self.assertMatches('...Activation code is invalid...',
3080                           self.browser.contents)
3081        # ... or ACs owned by somebody else.
3082        ac.owner = u'Anybody'
3083        self.browser.getControl(name="ac_series").value = sfeseries
3084        self.browser.getControl(name="ac_number").value = sfenumber
3085        self.browser.getControl("Create bed ticket").click()
3086        # Hostel 2 has only a bed for women.
3087        self.assertTrue('There is no free bed in your desired hostel'
3088            in self.browser.contents)
3089        self.browser.getControl(name="hostel").value = ['hall-1']
3090        self.browser.getControl("Save").click()
3091        self.browser.getControl("Book accommodation").click()
3092        # Student can't use faked ACs ...
3093        self.browser.getControl(name="ac_series").value = sfeseries
3094        self.browser.getControl(name="ac_number").value = sfenumber
3095        self.browser.getControl("Create bed ticket").click()
3096        self.assertMatches('...You are not the owner of this access code...',
3097                           self.browser.contents)
3098        # The bed remains empty.
3099        bed = self.app['hostels']['hall-1']['hall-1_A_101_A']
3100        self.assertTrue(bed.owner == NOT_OCCUPIED)
3101        ac.owner = self.student_id
3102        self.browser.getControl(name="ac_series").value = sfeseries
3103        self.browser.getControl(name="ac_number").value = sfenumber
3104        self.browser.getControl("Create bed ticket").click()
3105        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3106                           self.browser.contents)
3107        # Bed has been allocated.
3108        self.assertTrue(bed.owner == self.student_id)
3109        # BedTicketAddPage is now blocked.
3110        self.browser.getControl("Book accommodation").click()
3111        self.assertMatches('...You already booked a bed space...',
3112            self.browser.contents)
3113        # The bed ticket displays the data correctly.
3114        self.browser.open(self.acco_path + '/2004')
3115        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
3116                           self.browser.contents)
3117        self.assertMatches('...2004/2005...', self.browser.contents)
3118        self.assertMatches('...regular_male_fr...', self.browser.contents)
3119        self.assertMatches('...%s...' % pin, self.browser.contents)
3120        # Students can open the pdf slip.
3121        self.browser.open(self.browser.url + '/bed_allocation_slip.pdf')
3122        self.assertEqual(self.browser.headers['Status'], '200 Ok')
3123        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
3124        # Students can't relocate themselves.
3125        self.assertFalse('Relocate' in self.browser.contents)
3126        relocate_path = self.acco_path + '/2004/relocate'
3127        self.assertRaises(
3128            Unauthorized, self.browser.open, relocate_path)
3129        # Students can't see the Remove button and check boxes.
3130        self.browser.open(self.acco_path)
3131        self.assertFalse('Remove' in self.browser.contents)
3132        self.assertFalse('val_id' in self.browser.contents)
3133        # Students can pay maintenance fee now.
3134        self.browser.open(self.payments_path)
3135        self.browser.open(self.payments_path + '/addop')
3136        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3137        self.browser.getControl("Create ticket").click()
3138        self.assertMatches('...Payment ticket created...',
3139                           self.browser.contents)
3140        ctrl = self.browser.getControl(name='val_id')
3141        value = ctrl.options[0]
3142        # Maintennace fee is taken from the hostel object.
3143        self.assertEqual(self.student['payments'][value].amount_auth, 876.0)
3144        # If the hostel's maintenance fee isn't set, the fee is
3145        # taken from the session configuration object.
3146        self.app['hostels']['hall-1'].maint_fee = 0.0
3147        self.browser.open(self.payments_path + '/addop')
3148        self.browser.getControl(name="form.p_category").value = ['hostel_maintenance']
3149        self.browser.getControl("Create ticket").click()
3150        ctrl = self.browser.getControl(name='val_id')
3151        value = ctrl.options[1]
3152        self.assertEqual(self.student['payments'][value].amount_auth, 987.0)
3153        # The bedticket is aware of successfull maintenance fee payment
3154        bedticket = self.student['accommodation']['2004']
3155        self.assertFalse(bedticket.maint_payment_made)
3156        self.student['payments'][value].approve()
3157        self.assertTrue(bedticket.maint_payment_made)
3158        return
3159
3160    def test_change_password_request(self):
3161        self.browser.open('http://localhost/app/changepw')
3162        self.browser.getControl(name="form.identifier").value = '123'
3163        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
3164        self.browser.getControl("Send login credentials").click()
3165        self.assertTrue('An email with' in self.browser.contents)
3166
3167    def test_student_expired_personal_data(self):
3168        # Login
3169        IWorkflowState(self.student).setState('school fee paid')
3170        delta = timedelta(days=180)
3171        self.student.personal_updated = datetime.utcnow() - delta
3172        self.browser.open(self.login_path)
3173        self.browser.getControl(name="form.login").value = self.student_id
3174        self.browser.getControl(name="form.password").value = 'spwd'
3175        self.browser.getControl("Login").click()
3176        self.assertEqual(self.browser.url, self.student_path)
3177        self.assertTrue(
3178            'You logged in' in self.browser.contents)
3179        # Students don't see personal_updated field in edit form
3180        self.browser.open(self.edit_personal_path)
3181        self.assertFalse('Updated' in self.browser.contents)
3182        self.browser.open(self.personal_path)
3183        self.assertTrue('Updated' in self.browser.contents)
3184        self.browser.getLink("Logout").click()
3185        delta = timedelta(days=181)
3186        self.student.personal_updated = datetime.utcnow() - delta
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.assertEqual(self.browser.url, self.edit_personal_path)
3192        self.assertTrue(
3193            'Your personal data record is outdated.' in self.browser.contents)
3194
3195    def test_request_transcript(self):
3196        IWorkflowState(self.student).setState('graduated')
3197        self.browser.open(self.login_path)
3198        self.browser.getControl(name="form.login").value = self.student_id
3199        self.browser.getControl(name="form.password").value = 'spwd'
3200        self.browser.getControl("Login").click()
3201        self.assertMatches(
3202            '...You logged in...', self.browser.contents)
3203        # Create payment ticket
3204        self.browser.open(self.payments_path)
3205        self.browser.open(self.payments_path + '/addop')
3206        self.browser.getControl(name="form.p_category").value = ['transcript']
3207        self.browser.getControl("Create ticket").click()
3208        ctrl = self.browser.getControl(name='val_id')
3209        value = ctrl.options[0]
3210        self.browser.getLink(value).click()
3211        self.assertMatches('...Amount Authorized...',
3212                           self.browser.contents)
3213        self.assertEqual(self.student['payments'][value].amount_auth, 4567.0)
3214        # Student is the payer of the payment ticket.
3215        payer = IPayer(self.student['payments'][value])
3216        self.assertEqual(payer.display_fullname, 'Anna Tester')
3217        self.assertEqual(payer.id, self.student_id)
3218        self.assertEqual(payer.faculty, 'fac1')
3219        self.assertEqual(payer.department, 'dep1')
3220        # We simulate the approval and fetch the pin
3221        self.assertEqual(len(self.app['accesscodes']['TSC-0']),0)
3222        self.browser.open(self.browser.url + '/fake_approve')
3223        self.assertMatches('...Payment approved...',
3224                          self.browser.contents)
3225        pin = self.app['accesscodes']['TSC-0'].keys()[0]
3226        parts = pin.split('-')[1:]
3227        tscseries, tscnumber = parts
3228        # Student can use the pin to send the transcript request
3229        self.browser.open(self.student_path)
3230        self.browser.getLink("Request transcript").click()
3231        self.browser.getControl(name="ac_series").value = tscseries
3232        self.browser.getControl(name="ac_number").value = tscnumber
3233        self.browser.getControl(name="comment").value = 'Comment line 1 \nComment line2'
3234        self.browser.getControl(name="address").value = 'Address line 1 \nAddress line2'
3235        self.browser.getControl("Submit").click()
3236        self.assertMatches('...Transcript processing has been started...',
3237                          self.browser.contents)
3238        self.assertEqual(self.student.state, 'transcript requested')
3239        self.assertMatches(
3240            '... UTC K1000000 wrote:\n\nComment line 1 \n'
3241            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
3242            'Address line2\n\n', self.student.transcript_comment)
3243        # The comment has been logged
3244        logfile = os.path.join(
3245            self.app['datacenter'].storage, 'logs', 'students.log')
3246        logcontent = open(logfile).read()
3247        self.assertTrue(
3248            'K1000000 - students.browser.StudentTranscriptRequestPage - '
3249            'K1000000 - comment: Comment line 1 <br>Comment line2\n'
3250            in logcontent)
3251
3252    def test_late_registration(self):
3253        # Login
3254        delta = timedelta(days=10)
3255        self.app['configuration'][
3256            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
3257        IWorkflowState(self.student).setState('school fee paid')
3258        self.browser.open(self.login_path)
3259        self.browser.getControl(name="form.login").value = self.student_id
3260        self.browser.getControl(name="form.password").value = 'spwd'
3261        self.browser.getControl("Login").click()
3262        self.browser.open(self.payments_path)
3263        self.browser.open(self.payments_path + '/addop')
3264        self.browser.getControl(name="form.p_category").value = ['late_registration']
3265        self.browser.getControl("Create ticket").click()
3266        self.assertMatches('...ticket created...',
3267                           self.browser.contents)
3268        self.browser.open(self.payments_path)
3269        ctrl = self.browser.getControl(name='val_id')
3270        value = ctrl.options[0]
3271        self.browser.getLink("Study Course").click()
3272        self.browser.getLink("Add course list").click()
3273        self.assertMatches('...Add current level 100 (Year 1)...',
3274                           self.browser.contents)
3275        self.browser.getControl("Create course list now").click()
3276        self.browser.getLink("100").click()
3277        self.browser.getLink("Edit course list").click()
3278        self.browser.getControl("Register course list").click()
3279        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
3280        self.student['payments'][value].approve()
3281        self.browser.getControl("Register course list").click()
3282        self.assertTrue('Course list has been registered' in self.browser.contents)
3283        self.assertEqual(self.student.state, 'courses registered')
3284
3285
3286class StudentRequestPWTests(StudentsFullSetup):
3287    # Tests for student registration
3288
3289    layer = FunctionalLayer
3290
3291    def test_request_pw(self):
3292        # Student with wrong number can't be found.
3293        self.browser.open('http://localhost/app/requestpw')
3294        self.browser.getControl(name="form.lastname").value = 'Tester'
3295        self.browser.getControl(name="form.number").value = 'anynumber'
3296        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3297        self.browser.getControl("Send login credentials").click()
3298        self.assertTrue('No student record found.'
3299            in self.browser.contents)
3300        # Anonymous is not informed that lastname verification failed.
3301        # It seems that the record doesn't exist.
3302        self.browser.open('http://localhost/app/requestpw')
3303        self.browser.getControl(name="form.lastname").value = 'Johnny'
3304        self.browser.getControl(name="form.number").value = '123'
3305        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
3306        self.browser.getControl("Send login credentials").click()
3307        self.assertTrue('No student record found.'
3308            in self.browser.contents)
3309        # Even with the correct lastname we can't register if a
3310        # password has been set and used.
3311        self.browser.getControl(name="form.lastname").value = 'Tester'
3312        self.browser.getControl(name="form.number").value = '123'
3313        self.browser.getControl("Send login credentials").click()
3314        self.assertTrue('Your password has already been set and used.'
3315            in self.browser.contents)
3316        self.browser.open('http://localhost/app/requestpw')
3317        self.app['students'][self.student_id].password = None
3318        # The lastname field, used for verification, is not case-sensitive.
3319        self.browser.getControl(name="form.lastname").value = 'tESTer'
3320        self.browser.getControl(name="form.number").value = '123'
3321        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3322        self.browser.getControl("Send login credentials").click()
3323        # Yeah, we succeded ...
3324        self.assertTrue('Your password request was successful.'
3325            in self.browser.contents)
3326        # We can also use the matric_number instead.
3327        self.browser.open('http://localhost/app/requestpw')
3328        self.browser.getControl(name="form.lastname").value = 'tESTer'
3329        self.browser.getControl(name="form.number").value = '234'
3330        self.browser.getControl(name="form.email").value = 'new@yy.zz'
3331        self.browser.getControl("Send login credentials").click()
3332        self.assertTrue('Your password request was successful.'
3333            in self.browser.contents)
3334        # ... and  student can be found in the catalog via the email address
3335        cat = queryUtility(ICatalog, name='students_catalog')
3336        results = list(
3337            cat.searchResults(
3338            email=('new@yy.zz', 'new@yy.zz')))
3339        self.assertEqual(self.student,results[0])
3340        logfile = os.path.join(
3341            self.app['datacenter'].storage, 'logs', 'main.log')
3342        logcontent = open(logfile).read()
3343        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
3344                        '234 (K1000000) - new@yy.zz' in logcontent)
3345        return
3346
3347    def test_student_locked_level_forms(self):
3348
3349        # Add two study levels, one current and one previous
3350        studylevel = createObject(u'waeup.StudentStudyLevel')
3351        studylevel.level = 100
3352        self.student['studycourse'].addStudentStudyLevel(
3353            self.certificate, studylevel)
3354        studylevel = createObject(u'waeup.StudentStudyLevel')
3355        studylevel.level = 200
3356        self.student['studycourse'].addStudentStudyLevel(
3357            self.certificate, studylevel)
3358        IWorkflowState(self.student).setState('school fee paid')
3359        self.student['studycourse'].current_level = 200
3360
3361        self.browser.open(self.login_path)
3362        self.browser.getControl(name="form.login").value = self.student_id
3363        self.browser.getControl(name="form.password").value = 'spwd'
3364        self.browser.getControl("Login").click()
3365
3366        self.browser.open(self.student_path + '/studycourse/200/edit')
3367        self.assertFalse('The requested form is locked' in self.browser.contents)
3368        self.browser.open(self.student_path + '/studycourse/100/edit')
3369        self.assertTrue('The requested form is locked' in self.browser.contents)
3370
3371        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3372        self.assertFalse('The requested form is locked' in self.browser.contents)
3373        self.browser.open(self.student_path + '/studycourse/100/ctadd')
3374        self.assertTrue('The requested form is locked' in self.browser.contents)
3375
3376        IWorkflowState(self.student).setState('courses registered')
3377        self.browser.open(self.student_path + '/studycourse/200/edit')
3378        self.assertTrue('The requested form is locked' in self.browser.contents)
3379        self.browser.open(self.student_path + '/studycourse/200/ctadd')
3380        self.assertTrue('The requested form is locked' in self.browser.contents)
3381
3382
3383class PublicPagesTests(StudentsFullSetup):
3384    # Tests for simple webservices
3385
3386    layer = FunctionalLayer
3387
3388    def test_paymentrequest(self):
3389        payment = createObject('waeup.StudentOnlinePayment')
3390        payment.p_category = u'schoolfee'
3391        payment.p_session = self.student.current_session
3392        payment.p_item = u'My Certificate'
3393        payment.p_id = u'anyid'
3394        self.student['payments']['anykey'] = payment
3395        # Request information about unpaid payment ticket
3396        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3397        self.assertEqual(self.browser.contents, '-1')
3398        # Request information about paid payment ticket
3399        payment.p_state = u'paid'
3400        notify(grok.ObjectModifiedEvent(payment))
3401        self.browser.open('http://localhost/app/paymentrequest?P_ID=anyid')
3402        self.assertEqual(self.browser.contents,
3403            'FULL_NAME=Anna Tester&FACULTY=fac1&DEPARTMENT=dep1'
3404            '&PAYMENT_ITEM=My Certificate&PAYMENT_CATEGORY=School Fee'
3405            '&ACADEMIC_SESSION=2004/2005&MATRIC_NUMBER=234&REG_NUMBER=123'
3406            '&FEE_AMOUNT=0.0')
3407        self.browser.open('http://localhost/app/paymentrequest?NONSENSE=nonsense')
3408        self.assertEqual(self.browser.contents, '-1')
3409        self.browser.open('http://localhost/app/paymentrequest?P_ID=nonsense')
3410        self.assertEqual(self.browser.contents, '-1')
3411
3412class StudentDataExportTests(StudentsFullSetup, FunctionalAsyncTestCase):
3413    # Tests for StudentsContainer class views and pages
3414
3415    layer = FunctionalLayer
3416
3417    def wait_for_export_job_completed(self):
3418        # helper function waiting until the current export job is completed
3419        manager = getUtility(IJobManager)
3420        job_id = self.app['datacenter'].running_exports[0][0]
3421        job = manager.get(job_id)
3422        wait_for_result(job)
3423        return job_id
3424
3425    def test_datacenter_export(self):
3426        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3427        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3428        self.browser.getControl(name="exporter").value = ['bursary']
3429        self.browser.getControl(name="session").value = ['2004']
3430        self.browser.getControl(name="level").value = ['100']
3431        self.browser.getControl(name="mode").value = ['ug_ft']
3432        self.browser.getControl(name="payments_start").value = '13/12/2012'
3433        self.browser.getControl(name="payments_end").value = '14/12/2012'
3434        self.browser.getControl("Create CSV file").click()
3435
3436        # When the job is finished and we reload the page...
3437        job_id = self.wait_for_export_job_completed()
3438        # ... the csv file can be downloaded ...
3439        self.browser.open('http://localhost/app/datacenter/@@export')
3440        self.browser.getLink("Download").click()
3441        self.assertEqual(self.browser.headers['content-type'],
3442            'text/csv; charset=UTF-8')
3443        self.assertTrue(
3444            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3445            self.browser.headers['content-disposition'])
3446        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3447        job_id = self.app['datacenter'].running_exports[0][0]
3448        # ... and discarded
3449        self.browser.open('http://localhost/app/datacenter/@@export')
3450        self.browser.getControl("Discard").click()
3451        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3452        # Creation, downloading and discarding is logged
3453        logfile = os.path.join(
3454            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3455        logcontent = open(logfile).read()
3456        self.assertTrue(
3457            'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
3458            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3459            '13/12/2012, 14/12/2012), job_id=%s'
3460            % job_id in logcontent
3461            )
3462        self.assertTrue(
3463            'zope.mgr - browser.pages.ExportCSVView '
3464            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3465            % (job_id, job_id) in logcontent
3466            )
3467        self.assertTrue(
3468            'zope.mgr - browser.pages.ExportCSVPage '
3469            '- discarded: job_id=%s' % job_id in logcontent
3470            )
3471
3472    def test_datacenter_export_selected(self):
3473        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3474        self.browser.open('http://localhost/app/datacenter/@@exportselected')
3475        self.browser.getControl(name="exporter").value = ['students']
3476        self.browser.getControl(name="students").value = 'K1000000'
3477        self.browser.getControl("Create CSV file").click()
3478        # When the job is finished and we reload the page...
3479        job_id = self.wait_for_export_job_completed()
3480        # ... the csv file can be downloaded ...
3481        self.browser.open('http://localhost/app/datacenter/@@export')
3482        self.browser.getLink("Download").click()
3483        self.assertEqual(self.browser.headers['content-type'],
3484            'text/csv; charset=UTF-8')
3485        self.assertTrue(
3486            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3487            self.browser.headers['content-disposition'])
3488        self.assertTrue(
3489            'adm_code,clr_code,date_of_birth,email,employer,'
3490            'firstname,lastname,matric_number,middlename,nationality,'
3491            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3492            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3493            'password,state,history,certcode,is_postgrad,current_level,'
3494            'current_session\r\n'
3495            ',,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3496            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3497
3498    def test_payment_dates(self):
3499        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3500        self.browser.open('http://localhost/app/datacenter/@@exportconfig')
3501        self.browser.getControl(name="exporter").value = ['bursary']
3502        self.browser.getControl(name="session").value = ['2004']
3503        self.browser.getControl(name="level").value = ['100']
3504        self.browser.getControl(name="mode").value = ['ug_ft']
3505        self.browser.getControl(name="payments_start").value = '13/12/2012'
3506        # If one payment date is missing, an error message appears
3507        self.browser.getControl(name="payments_end").value = ''
3508        self.browser.getControl("Create CSV file").click()
3509        self.assertTrue('Payment dates do not match format d/m/Y'
3510            in self.browser.contents)
3511
3512    def test_faculties_export(self):
3513        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3514        facs_path = 'http://localhost/app/faculties'
3515        self.browser.open(facs_path)
3516        self.browser.getLink("Export student data").click()
3517        self.browser.getControl("Configure new export").click()
3518        self.browser.getControl(name="exporter").value = ['bursary']
3519        self.browser.getControl(name="session").value = ['2004']
3520        self.browser.getControl(name="level").value = ['100']
3521        self.browser.getControl(name="mode").value = ['ug_ft']
3522        self.browser.getControl(name="payments_start").value = '13/12/2012'
3523        self.browser.getControl(name="payments_end").value = '14/12/2012'
3524        self.browser.getControl("Create CSV file").click()
3525
3526        # When the job is finished and we reload the page...
3527        job_id = self.wait_for_export_job_completed()
3528        self.browser.open(facs_path + '/exports')
3529        # ... the csv file can be downloaded ...
3530        self.browser.getLink("Download").click()
3531        self.assertEqual(self.browser.headers['content-type'],
3532            'text/csv; charset=UTF-8')
3533        self.assertTrue(
3534            'filename="WAeUP.Kofa_bursary_%s.csv' % job_id in
3535            self.browser.headers['content-disposition'])
3536        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3537        job_id = self.app['datacenter'].running_exports[0][0]
3538        # ... and discarded
3539        self.browser.open(facs_path + '/exports')
3540        self.browser.getControl("Discard").click()
3541        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3542        # Creation, downloading and discarding is logged
3543        logfile = os.path.join(
3544            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3545        logcontent = open(logfile).read()
3546        self.assertTrue(
3547            'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
3548            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
3549            '13/12/2012, 14/12/2012), job_id=%s'
3550            % job_id in logcontent
3551            )
3552        self.assertTrue(
3553            'zope.mgr - students.browser.ExportJobContainerDownload '
3554            '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
3555            % (job_id, job_id) in logcontent
3556            )
3557        self.assertTrue(
3558            'zope.mgr - students.browser.ExportJobContainerOverview '
3559            '- discarded: job_id=%s' % job_id in logcontent
3560            )
3561
3562    def test_faculty_export(self):
3563        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3564        fac1_path = 'http://localhost/app/faculties/fac1'
3565        self.browser.open(fac1_path)
3566        self.browser.getLink("Export student data").click()
3567        self.browser.getControl("Configure new export").click()
3568        self.browser.getControl(name="exporter").value = ['students']
3569        self.browser.getControl(name="session").value = ['2004']
3570        self.browser.getControl(name="level").value = ['100']
3571        self.browser.getControl(name="mode").value = ['ug_ft']
3572        # The testbrowser does not hide the payment period fields, but
3573        # values are ignored when using the students exporter.
3574        self.browser.getControl(name="payments_start").value = '13/12/2012'
3575        self.browser.getControl(name="payments_end").value = '14/12/2012'
3576        self.browser.getControl("Create CSV file").click()
3577
3578        # When the job is finished and we reload the page...
3579        job_id = self.wait_for_export_job_completed()
3580        self.browser.open(fac1_path + '/exports')
3581        # ... the csv file can be downloaded ...
3582        self.browser.getLink("Download").click()
3583        self.assertEqual(self.browser.headers['content-type'],
3584            'text/csv; charset=UTF-8')
3585        self.assertTrue(
3586            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3587            self.browser.headers['content-disposition'])
3588        self.assertTrue(
3589            'adm_code,clr_code,date_of_birth,email,employer,'
3590            'firstname,lastname,matric_number,middlename,nationality,'
3591            'officer_comment,perm_address,personal_updated,phone,reg_number,'
3592            'sex,student_id,suspended,suspended_comment,transcript_comment,'
3593            'password,state,history,certcode,is_postgrad,current_level,'
3594            'current_session\r\n'
3595            ',,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
3596            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
3597        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3598        job_id = self.app['datacenter'].running_exports[0][0]
3599        # ... and discarded
3600        self.browser.open(fac1_path + '/exports')
3601        self.browser.getControl("Discard").click()
3602        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3603        # Creation, downloading and discarding is logged
3604        logfile = os.path.join(
3605            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3606        logcontent = open(logfile).read()
3607        self.assertTrue(
3608            'zope.mgr - students.browser.FacultyExportJobContainerJobConfig '
3609            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
3610            '13/12/2012, 14/12/2012), job_id=%s'
3611            % job_id in logcontent
3612            )
3613        self.assertTrue(
3614            'zope.mgr - students.browser.ExportJobContainerDownload '
3615            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3616            % (job_id, job_id) in logcontent
3617            )
3618        self.assertTrue(
3619            'zope.mgr - students.browser.ExportJobContainerOverview '
3620            '- discarded: job_id=%s' % job_id in logcontent
3621            )
3622
3623    def test_department_export(self):
3624        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3625        dep1_path = 'http://localhost/app/faculties/fac1/dep1'
3626        self.browser.open(dep1_path)
3627        self.browser.getLink("Export student data").click()
3628        self.browser.getControl("Configure new export").click()
3629        self.browser.getControl(name="exporter").value = ['students']
3630        self.browser.getControl(name="session").value = ['2004']
3631        self.browser.getControl(name="level").value = ['100']
3632        self.browser.getControl(name="mode").value = ['ug_ft']
3633        # The testbrowser does not hide the payment period fields, but
3634        # values are ignored when using the students exporter.
3635        self.browser.getControl(name="payments_start").value = '13/12/2012'
3636        self.browser.getControl(name="payments_end").value = '14/12/2012'
3637        self.browser.getControl("Create CSV file").click()
3638
3639        # When the job is finished and we reload the page...
3640        job_id = self.wait_for_export_job_completed()
3641        self.browser.open(dep1_path + '/exports')
3642        # ... the csv file can be downloaded ...
3643        self.browser.getLink("Download").click()
3644        self.assertEqual(self.browser.headers['content-type'],
3645            'text/csv; charset=UTF-8')
3646        self.assertTrue(
3647            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3648            self.browser.headers['content-disposition'])
3649        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3650        job_id = self.app['datacenter'].running_exports[0][0]
3651        # ... and discarded
3652        self.browser.open(dep1_path + '/exports')
3653        self.browser.getControl("Discard").click()
3654        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3655        # Creation, downloading and discarding is logged
3656        logfile = os.path.join(
3657            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3658        logcontent = open(logfile).read()
3659        self.assertTrue(
3660            'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
3661            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
3662            '13/12/2012, 14/12/2012), job_id=%s'
3663            % job_id in logcontent
3664            )
3665        self.assertTrue(
3666            'zope.mgr - students.browser.ExportJobContainerDownload '
3667            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3668            % (job_id, job_id) in logcontent
3669            )
3670        self.assertTrue(
3671            'zope.mgr - students.browser.ExportJobContainerOverview '
3672            '- discarded: job_id=%s' % job_id in logcontent
3673            )
3674
3675    def test_certificate_export(self):
3676        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3677        cert1_path = 'http://localhost/app/faculties/fac1/dep1/certificates/CERT1'
3678        self.browser.open(cert1_path)
3679        self.browser.getLink("Export student data").click()
3680        self.browser.getControl("Configure new export").click()
3681        self.browser.getControl(name="exporter").value = ['students']
3682        self.browser.getControl(name="session").value = ['2004']
3683        self.browser.getControl(name="level").value = ['100']
3684        self.browser.getControl("Create CSV file").click()
3685
3686        # When the job is finished and we reload the page...
3687        job_id = self.wait_for_export_job_completed()
3688        self.browser.open(cert1_path + '/exports')
3689        # ... the csv file can be downloaded ...
3690        self.browser.getLink("Download").click()
3691        self.assertEqual(self.browser.headers['content-type'],
3692            'text/csv; charset=UTF-8')
3693        self.assertTrue(
3694            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3695            self.browser.headers['content-disposition'])
3696        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3697        job_id = self.app['datacenter'].running_exports[0][0]
3698        # ... and discarded
3699        self.browser.open(cert1_path + '/exports')
3700        self.browser.getControl("Discard").click()
3701        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3702        # Creation, downloading and discarding is logged
3703        logfile = os.path.join(
3704            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3705        logcontent = open(logfile).read()
3706        self.assertTrue(
3707            'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
3708            '- exported: students (2004, 100, None, None, None, CERT1, None, None), '
3709            'job_id=%s'
3710            % job_id in logcontent
3711            )
3712        self.assertTrue(
3713            'zope.mgr - students.browser.ExportJobContainerDownload '
3714            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3715            % (job_id, job_id) in logcontent
3716            )
3717        self.assertTrue(
3718            'zope.mgr - students.browser.ExportJobContainerOverview '
3719            '- discarded: job_id=%s' % job_id in logcontent
3720            )
3721
3722    def test_course_export_students(self):
3723        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3724        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3725        self.browser.open(course1_path)
3726        self.browser.getLink("Export student data").click()
3727        self.browser.getControl("Configure new export").click()
3728        self.browser.getControl(name="exporter").value = ['students']
3729        self.browser.getControl(name="session").value = ['2004']
3730        self.browser.getControl(name="level").value = ['100']
3731        self.browser.getControl("Create CSV file").click()
3732
3733        # When the job is finished and we reload the page...
3734        job_id = self.wait_for_export_job_completed()
3735        self.browser.open(course1_path + '/exports')
3736        # ... the csv file can be downloaded ...
3737        self.browser.getLink("Download").click()
3738        self.assertEqual(self.browser.headers['content-type'],
3739            'text/csv; charset=UTF-8')
3740        self.assertTrue(
3741            'filename="WAeUP.Kofa_students_%s.csv' % job_id in
3742            self.browser.headers['content-disposition'])
3743        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3744        job_id = self.app['datacenter'].running_exports[0][0]
3745        # ... and discarded
3746        self.browser.open(course1_path + '/exports')
3747        self.browser.getControl("Discard").click()
3748        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3749        # Creation, downloading and discarding is logged
3750        logfile = os.path.join(
3751            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3752        logcontent = open(logfile).read()
3753        self.assertTrue(
3754            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3755            '- exported: students (2004, 100, COURSE1), job_id=%s'
3756            % job_id in logcontent
3757            )
3758        self.assertTrue(
3759            'zope.mgr - students.browser.ExportJobContainerDownload '
3760            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
3761            % (job_id, job_id) in logcontent
3762            )
3763        self.assertTrue(
3764            'zope.mgr - students.browser.ExportJobContainerOverview '
3765            '- discarded: job_id=%s' % job_id in logcontent
3766            )
3767
3768    def test_course_export_coursetickets(self):
3769        # We add study level 100 to the student's studycourse
3770        studylevel = StudentStudyLevel()
3771        studylevel.level = 100
3772        studylevel.level_session = 2004
3773        self.student['studycourse'].addStudentStudyLevel(
3774            self.certificate,studylevel)
3775        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
3776        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
3777        self.browser.open(course1_path)
3778        self.browser.getLink("Export student data").click()
3779        self.browser.getControl("Configure new export").click()
3780        self.browser.getControl(name="exporter").value = ['coursetickets']
3781        self.browser.getControl(name="session").value = ['2004']
3782        self.browser.getControl(name="level").value = ['100']
3783        self.browser.getControl("Create CSV file").click()
3784        # When the job is finished and we reload the page...
3785        job_id = self.wait_for_export_job_completed()
3786        self.browser.open(course1_path + '/exports')
3787        # ... the csv file can be downloaded ...
3788        self.browser.getLink("Download").click()
3789        self.assertEqual(self.browser.headers['content-type'],
3790            'text/csv; charset=UTF-8')
3791        self.assertTrue(
3792            'filename="WAeUP.Kofa_coursetickets_%s.csv' % job_id in
3793            self.browser.headers['content-disposition'])
3794        # ... and contains the course ticket COURSE1
3795        self.assertEqual(self.browser.contents,
3796            'automatic,carry_over,code,credits,dcode,fcode,level,'
3797            'level_session,mandatory,passmark,score,semester,title,'
3798            'student_id,certcode,display_fullname\r\n1,0,COURSE1,10,'
3799            'dep1,fac1,100,2004,1,40,,1,'
3800            'Unnamed Course,K1000000,CERT1,Anna Tester\r\n')
3801
3802        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3803        job_id = self.app['datacenter'].running_exports[0][0]
3804        # Thew job can be discarded
3805        self.browser.open(course1_path + '/exports')
3806        self.browser.getControl("Discard").click()
3807        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3808        # Creation, downloading and discarding is logged
3809        logfile = os.path.join(
3810            self.app['datacenter'].storage, 'logs', 'datacenter.log')
3811        logcontent = open(logfile).read()
3812        self.assertTrue(
3813            'zope.mgr - students.browser.CourseExportJobContainerJobConfig '
3814            '- exported: coursetickets (2004, 100, COURSE1), job_id=%s'
3815            % job_id in logcontent
3816            )
3817        self.assertTrue(
3818            'zope.mgr - students.browser.ExportJobContainerDownload '
3819            '- downloaded: WAeUP.Kofa_coursetickets_%s.csv, job_id=%s'
3820            % (job_id, job_id) in logcontent
3821            )
3822        self.assertTrue(
3823            'zope.mgr - students.browser.ExportJobContainerOverview '
3824            '- discarded: job_id=%s' % job_id in logcontent
3825            )
3826
3827    def test_export_departmet_officers(self):
3828        # Create department officer
3829        self.app['users'].addUser('mrdepartment', 'mrdepartmentsecret')
3830        self.app['users']['mrdepartment'].email = 'mrdepartment@foo.ng'
3831        self.app['users']['mrdepartment'].title = 'Carlo Pitter'
3832        # Assign local role
3833        department = self.app['faculties']['fac1']['dep1']
3834        prmlocal = IPrincipalRoleManager(department)
3835        prmlocal.assignRoleToPrincipal('waeup.local.DepartmentOfficer', 'mrdepartment')
3836        # Login as department officer
3837        self.browser.open(self.login_path)
3838        self.browser.getControl(name="form.login").value = 'mrdepartment'
3839        self.browser.getControl(name="form.password").value = 'mrdepartmentsecret'
3840        self.browser.getControl("Login").click()
3841        self.assertMatches('...You logged in...', self.browser.contents)
3842        self.browser.open("http://localhost/app/faculties/fac1/dep1")
3843        self.browser.getLink("Export student data").click()
3844        self.browser.getControl("Configure new export").click()
3845        # Only the paymentsoverview exporter is available for department officers
3846        self.assertFalse('<option value="students">' in self.browser.contents)
3847        self.assertTrue(
3848            '<option value="paymentsoverview">' in self.browser.contents)
3849        self.browser.getControl(name="exporter").value = ['paymentsoverview']
3850        self.browser.getControl(name="session").value = ['2004']
3851        self.browser.getControl(name="level").value = ['100']
3852        self.browser.getControl("Create CSV file").click()
3853        self.assertTrue('Export started' in self.browser.contents)
3854        # Thew job can be discarded
3855        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3856        #job_id = self.app['datacenter'].running_exports[0][0]
3857        job_id = self.wait_for_export_job_completed()
3858        self.browser.open("http://localhost/app/faculties/fac1/dep1/exports")
3859        self.browser.getControl("Discard").click()
3860        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
3861
3862    def test_export_bursary_officers(self):
3863        # Create bursary officer
3864        self.app['users'].addUser('mrbursary', 'mrbursarysecret')
3865        self.app['users']['mrbursary'].email = 'mrbursary@foo.ng'
3866        self.app['users']['mrbursary'].title = 'Carlo Pitter'
3867        prmglobal = IPrincipalRoleManager(self.app)
3868        prmglobal.assignRoleToPrincipal('waeup.BursaryOfficer', 'mrbursary')
3869        # Login as bursary officer
3870        self.browser.open(self.login_path)
3871        self.browser.getControl(name="form.login").value = 'mrbursary'
3872        self.browser.getControl(name="form.password").value = 'mrbursarysecret'
3873        self.browser.getControl("Login").click()
3874        self.assertMatches('...You logged in...', self.browser.contents)
3875        self.browser.getLink("Academics").click()
3876        self.browser.getLink("Export student data").click()
3877        self.browser.getControl("Configure new export").click()
3878        # Only the bursary exporter is available for bursary officers
3879        # not only at facultiescontainer level ...
3880        self.assertFalse('<option value="students">' in self.browser.contents)
3881        self.assertTrue('<option value="bursary">' in self.browser.contents)
3882        self.browser.getControl(name="exporter").value = ['bursary']
3883        self.browser.getControl(name="session").value = ['2004']
3884        self.browser.getControl(name="level").value = ['100']
3885        self.browser.getControl("Create CSV file").click()
3886        self.assertTrue('Export started' in self.browser.contents)
3887        # ... but also at other levels
3888        self.browser.open('http://localhost/app/faculties/fac1/dep1')
3889        self.browser.getLink("Export student data").click()
3890        self.browser.getControl("Configure new export").click()
3891        self.assertFalse('<option value="students">' in self.browser.contents)
3892        self.assertTrue('<option value="bursary">' in self.browser.contents)
3893        # Thew job can be discarded
3894        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
3895        #job_id = self.app['datacenter'].running_exports[0][0]
3896        job_id = self.wait_for_export_job_completed()
3897        self.browser.open('http://localhost/app/faculties/exports')
3898        self.browser.getControl("Discard").click()
3899        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
Note: See TracBrowser for help on using the repository browser.