source: main/waeup.kofa/branches/uli-stud-utils-cleanup/src/waeup/kofa/students/tests/test_browser.py @ 13621

Last change on this file since 13621 was 11862, checked in by Henrik Bettermann, 10 years ago

Add permission, page and button to mass-clear all students in a department.

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