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

Last change on this file since 14310 was 14293, checked in by Henrik Bettermann, 8 years ago

Add control button to create graduated students.

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