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

Last change on this file since 11808 was 11774, checked in by Henrik Bettermann, 11 years ago

Extend browser test slightly.

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