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

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

acc_details are never None.

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