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

Last change on this file since 15943 was 15918, checked in by Henrik Bettermann, 5 years ago

Add session_levelfilter to StudentStudyLevelExporter.

Adjust tests.

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