Ignore:
Timestamp:
14 Jun 2016, 01:38:12 (9 years ago)
Author:
uli
Message:

Merge changes from uli-scores-upload back into trunk.

Location:
main/waeup.kofa/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/trunk

  • main/waeup.kofa/trunk/src/waeup/kofa

  • main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py

    r13908 r13935  
     1# -*- coding: utf-8 -*-
    12## $Id$
    23##
     
    4546from waeup.kofa.university.faculty import Faculty
    4647from waeup.kofa.university.department import Department
    47 from waeup.kofa.interfaces import IUserAccount, IJobManager
     48from waeup.kofa.interfaces import IUserAccount, IJobManager, VALIDATED, CREATED
    4849from waeup.kofa.authentication import LocalRoleSetEvent
    4950from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
     
    5556SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
    5657SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
     58URL_LECTURER_LANDING = 'http://localhost/app/my_courses'
    5759
    5860
     
    18071809        self.assertTrue(self.student_id in self.browser.contents)
    18081810
    1809     def test_handle_courses_by_lecturer(self):
    1810         self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
    1811         self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
    1812         self.app['users']['mrslecturer'].title = u'Mercedes Benz'
    1813         # Add course ticket
    1814         studylevel = createObject(u'waeup.StudentStudyLevel')
    1815         studylevel.level = 100
    1816         studylevel.level_session = 2004
    1817         self.student['studycourse'].addStudentStudyLevel(
    1818             self.certificate, studylevel)
    1819         # Assign local Lecturer role for a certificate.
    1820         course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
    1821         prmlocal = IPrincipalRoleManager(course)
    1822         prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
    1823         notify(LocalRoleSetEvent(
    1824             course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
    1825         # Login as lecturer.
    1826         self.browser.open(self.login_path)
    1827         self.browser.getControl(name="form.login").value = 'mrslecturer'
    1828         self.browser.getControl(
    1829             name="form.password").value = 'mrslecturersecret'
    1830         self.browser.getControl("Login").click()
    1831         self.assertMatches('...You logged in...', self.browser.contents)
    1832         # Lecturer is on landing page
    1833         self.assertTrue('(<span>COURSE1</span>)' in self.browser.contents)
    1834         # Lecturer can click the 'My Roles' link.
    1835         self.browser.getLink("My Roles").click()
    1836         self.assertMatches(
    1837             '...<div>Academics Officer (view only)</div>...',
    1838             self.browser.contents)
    1839         self.assertMatches(
    1840             '...<a href="http://localhost/app/'
    1841             'faculties/fac1/dep1/courses/COURSE1">...',
    1842             self.browser.contents)
    1843         self.assertFalse('(<span>COURSE1</span>)' in self.browser.contents)
    1844         # Lecturer can click the 'My Courses' link ...
    1845         self.browser.getLink("My Courses").click()
    1846         # ... and is is back on landing page
    1847         self.assertTrue('(<span>COURSE1</span>)' in self.browser.contents)
    1848         # The lecturer can go to her course ...
    1849         self.browser.getLink("COURSE1").click()
    1850         # Lecturers can neither filter students ...
    1851         self.assertRaises(
    1852             Unauthorized, self.browser.open,
    1853             "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
    1854         # ... nor access the student ...
    1855         self.assertRaises(
    1856             Unauthorized, self.browser.open, self.student_path)
    1857         # ... nor the respective course ticket since
    1858         # editing course tickets by lecturers is not feasible.
    1859         self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
    1860         course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
    1861         self.assertRaises(
    1862             Unauthorized, self.browser.open, course_ticket_path)
    1863         # Course results can be batch edited via the edit_courses view.
    1864         self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
    1865         self.browser.open(
    1866             "http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
    1867         self.browser.getLink("Update scores").click()
    1868         self.assertTrue('Score editing disabled' in self.browser.contents)
    1869         self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
    1870         self.browser.getLink("Update scores").click()
    1871         self.assertTrue(
    1872             'Current academic session not set' in self.browser.contents)
    1873         self.app['configuration'].current_academic_session = 2004
    1874         self.browser.getLink("Update scores").click()
    1875         self.assertFalse(
    1876             '<input type="text" name="scores" class="span1" />'
    1877             in self.browser.contents)
    1878         IWorkflowState(self.student).setState('courses validated')
    1879         # Student must be in state 'courses validated'.
    1880         self.browser.open(
    1881             'http://localhost/app/faculties/fac1/'
    1882             'dep1/courses/COURSE1/edit_scores')
    1883         self.assertTrue(
    1884             'input type="text" name="scores"' in self.browser.contents)
    1885         self.browser.getControl(name="scores", index=0).value = '55'
    1886         self.browser.getControl("Update scores").click()
    1887         # New score has been set.
    1888         self.assertEqual(
    1889             self.student['studycourse']['100']['COURSE1'].score, 55)
    1890         # Score editing has been logged.
    1891         logfile = os.path.join(
    1892             self.app['datacenter'].storage, 'logs', 'students.log')
    1893         logcontent = open(logfile).read()
    1894         self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
    1895             'K1000000 100/COURSE1 score updated (55)' in logcontent)
    1896         # Non-integer scores won't be accepted.
    1897         self.browser.open(
    1898             'http://localhost/app/faculties/fac1/'
    1899             'dep1/courses/COURSE1/edit_scores')
    1900         self.assertTrue('value="55" />' in self.browser.contents)
    1901         self.browser.getControl(name="scores", index=0).value = 'abc'
    1902         self.browser.getControl("Update scores").click()
    1903         self.assertTrue('Error: Score(s) of Anna Tester have not be updated'
    1904             in self.browser.contents)
    1905         # Scores can be removed.
    1906         self.browser.open(
    1907             'http://localhost/app/faculties/fac1/'
    1908             'dep1/courses/COURSE1/edit_scores')
    1909         self.browser.getControl(name="scores", index=0).value = ''
    1910         self.browser.getControl("Update scores").click()
    1911         self.assertEqual(
    1912             self.student['studycourse']['100']['COURSE1'].score, None)
    1913         logcontent = open(logfile).read()
    1914         self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
    1915             'K1000000 100/COURSE1 score updated (None)' in logcontent)
    1916         # A course ticket slip can be downloaded
    1917         self.browser.open(
    1918             'http://localhost/app/faculties/fac1/'
    1919             'dep1/courses/COURSE1/coursetickets.pdf')
    1920         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    1921         self.assertEqual(self.browser.headers['Content-Type'],
    1922                          'application/pdf')
    1923         path = os.path.join(samples_dir(), 'coursetickets.pdf')
    1924         open(path, 'wb').write(self.browser.contents)
    1925         print "Sample PDF coursetickets.pdf written to %s" % path
    1926 
    19271811    def test_change_current_mode(self):
    19281812        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     
    38983782            )
    38993783
    3900     def test_course_download_lecturer(self):
    3901         # We add study level 100 to the student's studycourse
    3902         studylevel = StudentStudyLevel()
    3903         studylevel.level = 100
    3904         studylevel.level_session = 2004
    3905         IWorkflowState(self.student).setState('courses validated')
    3906         self.student['studycourse'].addStudentStudyLevel(
    3907             self.certificate,studylevel)
    3908         course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
    3909         # Create lecturer
    3910         self.app['users'].addUser('mrlecturer', 'mrlecturersecret')
    3911         self.app['users']['mrlecturer'].email = 'mrlecturer@foo.ng'
    3912         self.app['users']['mrlecturer'].title = 'Carlo Intelligent'
    3913         prmglobal = IPrincipalRoleManager(self.course)
    3914         prmglobal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrlecturer')
    3915         # Login as lecturer
    3916         self.browser.open(self.login_path)
    3917         self.browser.getControl(name="form.login").value = 'mrlecturer'
    3918         self.browser.getControl(name="form.password").value = 'mrlecturersecret'
    3919         self.browser.getControl("Login").click()
    3920         self.assertMatches('...You logged in...', self.browser.contents)
    3921         self.browser.open(course1_path)
    3922         self.assertFalse('Export' in self.browser.contents)
    3923         self.browser.getLink("Update scores").click()
    3924         self.assertTrue('Current academic session not set' in self.browser.contents)
    3925         self.app['configuration'].current_academic_session = 2004
    3926         self.browser.getLink("Update scores").click()
    3927         self.browser.getLink("Download editable tickets").click()
    3928         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    3929         self.assertEqual(self.browser.headers['Content-Type'],
    3930                          'text/csv; charset=UTF-8')
    3931         self.assertEqual(self.browser.contents, 'matric_number,student_id,'
    3932             'display_fullname,level,code,level_session,score\r\n234,'
    3933             'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
    3934 
    39353784    def test_export_departmet_officers(self):
    39363785        # Create department officer
     
    40043853        self.browser.getControl("Discard").click()
    40053854        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
     3855
     3856
     3857UPLOAD_CSV_TEMPLATE = (
     3858    'matric_number,student_id,display_fullname,level,code,level_session,'
     3859    'score\r\n'
     3860    '234,K1000000,Anna Tester,100,COURSE1,2004,%s\r\n')
     3861
     3862class LecturerUITests(StudentsFullSetup):
     3863    # Tests for UI actions when acting as lecturer.
     3864
     3865    def login_as_lecturer(self):
     3866        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
     3867        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
     3868        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
     3869        # Add course ticket
     3870        studylevel = createObject(u'waeup.StudentStudyLevel')
     3871        studylevel.level = 100
     3872        studylevel.level_session = 2004
     3873        self.student['studycourse'].addStudentStudyLevel(
     3874            self.certificate, studylevel)
     3875        # Assign local Lecturer role for a certificate.
     3876        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
     3877        prmlocal = IPrincipalRoleManager(course)
     3878        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
     3879        notify(LocalRoleSetEvent(
     3880            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
     3881        # Login as lecturer.
     3882        self.browser.open(self.login_path)
     3883        self.browser.getControl(name="form.login").value = 'mrslecturer'
     3884        self.browser.getControl(
     3885            name="form.password").value = 'mrslecturersecret'
     3886        self.browser.getControl("Login").click()
     3887        # Store reused urls/paths
     3888        self.course_url = (
     3889            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1')
     3890        self.edit_scores_url = '%s/edit_scores' % self.course_url
     3891        # Set standard parameters
     3892        self.app['configuration'].current_academic_session = 2004
     3893        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
     3894        IWorkflowState(self.student).setState(VALIDATED)
     3895
     3896    @property
     3897    def stud_log_path(self):
     3898        return os.path.join(
     3899            self.app['datacenter'].storage, 'logs', 'students.log')
     3900
     3901    def test_lecturer_lands_on_landing_page(self):
     3902        # lecturers can login and will be led to landing page.
     3903        self.login_as_lecturer()
     3904        self.assertMatches('...You logged in...', self.browser.contents)
     3905        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
     3906
     3907    def test_my_roles_link_works(self):
     3908        # lecturers can see their roles
     3909        self.login_as_lecturer()
     3910        self.browser.getLink("My Roles").click()
     3911        self.assertTrue(
     3912            "<div>Academics Officer (view only)</div>"
     3913            in self.browser.contents)
     3914        self.assertTrue(
     3915            '<a href="%s">' % self.course_url in self.browser.contents)
     3916
     3917    def test_my_roles_page_contains_backlink(self):
     3918        # we can get back from 'My Roles' view to landing page
     3919        self.login_as_lecturer()
     3920        self.browser.getLink("My Roles").click()
     3921        self.browser.getLink("My Courses").click()
     3922        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
     3923
     3924    def test_lecturers_can_reach_their_courses(self):
     3925        # lecturers get links to their courses on the landing page
     3926        self.login_as_lecturer()
     3927        self.browser.getLink("COURSE1").click()
     3928        self.assertEqual(self.browser.url, self.course_url)
     3929
     3930    def test_lecturers_student_access_is_restricted(self):
     3931        # lecturers are not able to change other student data
     3932        self.login_as_lecturer()
     3933        # Lecturers can neither filter students ...
     3934        self.assertRaises(
     3935            Unauthorized, self.browser.open, '%s/students' % self.course_url)
     3936        # ... nor access the student ...
     3937        self.assertRaises(
     3938            Unauthorized, self.browser.open, self.student_path)
     3939        # ... nor the respective course ticket since editing course
     3940        # tickets by lecturers is not feasible.
     3941        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
     3942        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
     3943        self.assertRaises(
     3944            Unauthorized, self.browser.open, course_ticket_path)
     3945
     3946    def test_score_editing_requires_department_permit(self):
     3947        # we get a warning if we try to update score while we are not allowed
     3948        self.login_as_lecturer()
     3949        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
     3950        self.browser.open(self.course_url)
     3951        self.browser.getLink("Update scores").click()
     3952        self.assertTrue('Score editing disabled' in self.browser.contents)
     3953        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
     3954        self.browser.open(self.course_url)
     3955        self.browser.getLink("Update scores").click()
     3956        self.assertFalse('Score editing disabled' in self.browser.contents)
     3957
     3958    def test_score_editing_requires_validated_students(self):
     3959        # we can edit only scores of students whose courses have been
     3960        # validated.
     3961        self.login_as_lecturer()
     3962        # set invalid student state
     3963        IWorkflowState(self.student).setState(CREATED)
     3964        self.browser.open(self.edit_scores_url)
     3965        self.assertRaises(
     3966            LookupError, self.browser.getControl, name="scores")
     3967        # set valid student state
     3968        IWorkflowState(self.student).setState(VALIDATED)
     3969        self.browser.open(self.edit_scores_url)
     3970        self.assertTrue(
     3971            self.browser.getControl(name="scores:list") is not None)
     3972
     3973    def test_score_editing_offers_only_current_scores(self):
     3974        # only scores from current academic session can be edited
     3975        self.login_as_lecturer()
     3976        IWorkflowState(self.student).setState('courses validated')
     3977        # with no academic session set
     3978        self.app['configuration'].current_academic_session = None
     3979        self.browser.open(self.edit_scores_url)
     3980        self.assertRaises(
     3981            LookupError, self.browser.getControl, name="scores")
     3982        # with wrong academic session set
     3983        self.app['configuration'].current_academic_session = 1999
     3984        self.browser.open(self.edit_scores_url)
     3985        self.assertRaises(
     3986            LookupError, self.browser.getControl, name="scores")
     3987        # with right academic session set
     3988        self.app['configuration'].current_academic_session = 2004
     3989        self.browser.reload()
     3990        self.assertTrue(
     3991            self.browser.getControl(name="scores:list") is not None)
     3992
     3993    def test_score_editing_can_change_scores(self):
     3994        # we can really change scores via edit_scores view
     3995        self.login_as_lecturer()
     3996        self.assertEqual(
     3997            self.student['studycourse']['100']['COURSE1'].score, None)
     3998        self.browser.open(self.edit_scores_url)
     3999        self.browser.getControl(name="scores:list", index=0).value = '55'
     4000        self.browser.getControl("Update scores").click()
     4001        # the new value is stored in data
     4002        self.assertEqual(
     4003            self.student['studycourse']['100']['COURSE1'].score, 55)
     4004        # the new value is displayed on page/prefilled in form
     4005        self.assertEqual(
     4006            self.browser.getControl(name="scores:list", index=0).value, '55')
     4007        # The change has been logged
     4008        with open(self.stud_log_path, 'r') as fd:
     4009            self.assertTrue(
     4010                'mrslecturer - students.browser.EditScoresPage - '
     4011                'K1000000 100/COURSE1 score updated (55)' in fd.read())
     4012
     4013    def test_scores_editing_scores_must_be_integers(self):
     4014        # Non-integer scores won't be accepted.
     4015        self.login_as_lecturer()
     4016        self.browser.open(self.edit_scores_url)
     4017        self.browser.getControl(name="scores:list", index=0).value = 'abc'
     4018        self.browser.getControl("Update scores").click()
     4019        self.assertTrue(
     4020            'Error: Score(s) of following students have not been updated '
     4021            '(only integers are allowed): Anna Tester.'
     4022            in self.browser.contents)
     4023
     4024    def test_scores_editing_allows_score_removal(self):
     4025        # we can remove scores, once they were set
     4026        self.login_as_lecturer()
     4027        # without a prior value, we cannot remove
     4028        self.student['studycourse']['100']['COURSE1'].score = None
     4029        self.browser.open(self.edit_scores_url)
     4030        self.browser.getControl(name="scores:list", index=0).value = ''
     4031        self.browser.getControl("Update scores").click()
     4032        logcontent = open(self.stud_log_path, 'r').read()
     4033        self.assertFalse('COURSE1 score updated (None)' in logcontent)
     4034        # now retry with some value set
     4035        self.student['studycourse']['100']['COURSE1'].score = 55
     4036        self.browser.getControl(name="scores:list", index=0).value = ''
     4037        self.browser.getControl("Update scores").click()
     4038        logcontent = open(self.stud_log_path, 'r').read()
     4039        self.assertTrue('COURSE1 score updated (None)' in logcontent)
     4040
     4041    def test_lecturers_can_download_course_tickets(self):
     4042        # A course ticket slip can be downloaded
     4043        self.login_as_lecturer()
     4044        pdf_url = '%s/coursetickets.pdf' % self.course_url
     4045        self.browser.open(pdf_url)
     4046        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     4047        self.assertEqual(
     4048            self.browser.headers['Content-Type'], 'application/pdf')
     4049        path = os.path.join(samples_dir(), 'coursetickets.pdf')
     4050        open(path, 'wb').write(self.browser.contents)
     4051        print "Sample PDF coursetickets.pdf written to %s" % path
     4052
     4053    def test_lecturers_can_download_scores_as_csv(self):
     4054        # Lecturers can download course scores as CSV.
     4055        self.login_as_lecturer()
     4056        self.browser.open(self.edit_scores_url)
     4057        self.browser.getLink("Download editable tickets").click()
     4058        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     4059        self.assertEqual(self.browser.headers['Content-Type'],
     4060                         'text/csv; charset=UTF-8')
     4061        self.assertEqual(self.browser.contents, 'matric_number,student_id,'
     4062            'display_fullname,level,code,level_session,score\r\n234,'
     4063            'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
     4064
     4065    def test_scores_csv_upload_available(self):
     4066        # lecturers can upload a CSV file to set values.
     4067        self.login_as_lecturer()
     4068        # set value to change from
     4069        self.student['studycourse']['100']['COURSE1'].score = 55
     4070        self.browser.open(self.edit_scores_url)
     4071        upload_ctrl = self.browser.getControl(name='uploadfile:file')
     4072        upload_file = StringIO(UPLOAD_CSV_TEMPLATE % '65')
     4073        upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv')
     4074        self.browser.getControl("Update scores").click()
     4075        # value changed
     4076        self.assertEqual(
     4077            self.student['studycourse']['100']['COURSE1'].score, 65)
     4078
     4079    def test_scores_csv_upload_ignored(self):
     4080        # for many type of file contents we simply ignore uploaded data
     4081        self.login_as_lecturer()
     4082        self.student['studycourse']['100']['COURSE1'].score = 55
     4083        self.browser.open(self.edit_scores_url)
     4084        for content, mimetype, name in (
     4085                # empty file
     4086                ('', 'text/foo', 'my.foo'),
     4087                # plain ASCII text, w/o comma
     4088                ('abcdef' * 200, 'text/plain', 'my.txt'),
     4089                # plain UTF-8 text, with umlauts
     4090                ('umlauts: äöü', 'text/plain', 'my.txt'),
     4091                # csv file with only a header row
     4092                ('student_id,score', 'text/csv', 'my.csv'),
     4093                # csv with student_id column missing
     4094                ('foo,score\r\nbar,66\r\n', 'text/csv', 'my.csv'),
     4095                # csv with score column missing
     4096                ('student_id,foo\r\nK1000000,bar\r\n', 'text/csv', 'my.csv'),
     4097                # csv with non number as score value
     4098                (UPLOAD_CSV_TEMPLATE % 'not-a-number', 'text/csv', 'my.csv'),
     4099                ):
     4100            upload_ctrl = self.browser.getControl(name='uploadfile:file')
     4101            upload_ctrl.add_file(StringIO(content), mimetype, name)
     4102            self.browser.getControl("Update scores").click()
     4103            self.assertEqual(
     4104                self.student['studycourse']['100']['COURSE1'].score, 55)
     4105            self.assertFalse(
     4106                'Uploaded file contains illegal data' in self.browser.contents)
     4107
     4108    def test_scores_csv_upload_warn_illegal_chars(self):
     4109        # for some types of files we issue a warning if upload data
     4110        # contains illegal chars (and ignore the data)
     4111        self.login_as_lecturer()
     4112        self.student['studycourse']['100']['COURSE1'].score = 55
     4113        self.browser.open(self.edit_scores_url)
     4114        for content, mimetype, name in (
     4115                # plain ASCII text, commas, control chars
     4116                ('abv,qwe\n\r\r\t\b\n' * 20, 'text/plain', 'my.txt'),
     4117                # image data (like a JPEG image)
     4118                (open(SAMPLE_IMAGE, 'rb').read(), 'image/jpg', 'my.jpg'),
     4119                ):
     4120            upload_ctrl = self.browser.getControl(name='uploadfile:file')
     4121            upload_ctrl.add_file(StringIO(content), mimetype, name)
     4122            self.browser.getControl("Update scores").click()
     4123            self.assertEqual(
     4124                self.student['studycourse']['100']['COURSE1'].score, 55)
     4125            self.assertTrue(
     4126                'Uploaded file contains illegal data' in self.browser.contents)
Note: See TracChangeset for help on using the changeset viewer.