- Timestamp:
- 14 Jun 2016, 01:38:12 (9 years ago)
- Location:
- main/waeup.kofa/trunk
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.kofa/trunk
- Property svn:mergeinfo changed
/main/waeup.kofa/branches/uli-scores-upload merged: 13913-13934
- Property svn:mergeinfo changed
-
main/waeup.kofa/trunk/src/waeup/kofa
- Property svn:mergeinfo changed
/main/waeup.kofa/branches/uli-scores-upload/src/waeup/kofa merged: 13913-13934
- Property svn:mergeinfo changed
-
main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py
r13908 r13935 18 18 import grok 19 19 from urllib import urlencode 20 from zope.component import getMultiAdapter, queryAdapter, getUtility 20 21 from zope.i18n import translate 21 from zope.component import getMultiAdapter, queryAdapter, getUtility22 22 from zope.interface import Interface 23 23 from zope.location.interfaces import ISite 24 24 from zope.traversing.browser import absoluteURL 25 from waeup.kofa.browser.interfaces import ( 26 IFacultiesContainer, IFaculty, IDepartment, ICourse, ICertificate, 27 ICertificateCourse, IBreadcrumbContainer, IUniversity, IUsersContainer) 28 from waeup.kofa.browser.layout import KofaPage, default_primary_nav_template 25 29 from waeup.kofa.browser.pages import ( 26 30 UniversityPage, FacultiesContainerPage, DatacenterPage, FacultyPage, 27 31 DepartmentPage, CoursePage, CertificatePage, CertificateCoursePage, 28 32 UsersContainerPage, UserManageFormPage, DatacenterImportStep1) 29 from waeup.kofa.students.browser import EditScoresPage 30 from waeup.kofa.browser.interfaces import ( 31 IFacultiesContainer, IFaculty, IDepartment, ICourse, ICertificate, 32 ICertificateCourse, IBreadcrumbContainer, IUniversity, IUsersContainer) 33 from waeup.kofa.interfaces import MessageFactory as _ 33 34 from waeup.kofa.interfaces import ( 34 35 IKofaUtils, IKofaObject, 35 36 IDataCenter, IUserAccount) 36 from waeup.kofa.browser.layout import KofaPage, default_primary_nav_template37 37 from waeup.kofa.utils.helpers import get_user_account 38 38 39 from waeup.kofa.interfaces import MessageFactory as _40 39 41 40 grok.templatedir('templates') 42 41 grok.context(IKofaObject) # Make IKofaObject the default context 43 42 43 44 44 class ManageSidebar(grok.ViewletManager): 45 45 grok.name('left_manage') 46 46 47 47 48 class BreadCrumbManager(grok.ViewletManager): 48 49 grok.name('breadcrumbs') 49 50 51 50 52 class LanguageManager(grok.ViewletManager): 51 53 grok.name('languages') 52 54 55 53 56 class ActionBar(grok.ViewletManager): 54 57 grok.name('actionbar') 55 58 59 56 60 class AdministrationTasks(grok.ViewletManager): 57 61 grok.name('admintasks') 58 62 63 59 64 class WidgetsSlot(grok.ViewletManager): 60 65 grok.name('widgets') 61 66 67 62 68 class SourcesLeft(grok.ViewletManager): 63 69 grok.name('sources_left') 70 64 71 65 72 class SourcesRight(grok.ViewletManager): … … 555 562 grok.order(4) 556 563 557 class DownloadCSVFileActionButton(ManageActionButton):558 """ 'Download csv file' button for courses.559 """560 grok.context(ICourse)561 grok.view(EditScoresPage)562 grok.name('downloadcsv')563 grok.require('waeup.editScores')564 icon = 'actionicon_down.png'565 text = _('Download editable tickets')566 target = 'download_scores'567 grok.order(1)568 569 class DownloadTicketOverviewActionButton(ManageActionButton):570 """ 'Download ticket overview' button for courses.571 """572 grok.context(ICourse)573 grok.view(EditScoresPage)574 grok.name('coursetickets')575 grok.require('waeup.editScores')576 icon = 'actionicon_pdf.png'577 text = _('Download pdf file')578 target = 'coursetickets.pdf'579 grok.order(2)580 564 581 565 class ManageCertificateActionButton(ManageActionButton): -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
r13908 r13935 18 18 """UI components for students and related components. 19 19 """ 20 import sys20 import csv 21 21 import grok 22 22 import pytz 23 import sys 24 from cStringIO import StringIO 25 from datetime import datetime 26 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState 23 27 from urllib import urlencode 24 from datetime import datetime25 from zope.event import notify26 from zope.i18n import translate27 28 from zope.catalog.interfaces import ICatalog 28 29 from zope.component import queryUtility, getUtility, createObject 30 from zope.event import notify 31 from zope.formlib.textwidgets import BytesDisplayWidget 32 from zope.i18n import translate 29 33 from zope.schema.interfaces import ConstraintNotSatisfied, RequiredMissing 30 from zope.formlib.textwidgets import BytesDisplayWidget31 34 from zope.security import checkPermission 32 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState 33 from waeup.kofa.accesscodes import ( 34 invalidate_accesscode, get_access_code) 35 from waeup.kofa.accesscodes import invalidate_accesscode, get_access_code 35 36 from waeup.kofa.accesscodes.workflow import USED 37 from waeup.kofa.browser.breadcrumbs import Breadcrumb 38 from waeup.kofa.browser.interfaces import ICaptchaManager 36 39 from waeup.kofa.browser.layout import ( 37 40 KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage, 38 41 NullValidator, jsaction, action, UtilityView) 39 from waeup.kofa.browser.breadcrumbs import Breadcrumb40 42 from waeup.kofa.browser.pages import ( 41 43 ContactAdminFormPage, ExportCSVView, doll_up, exports_not_allowed, 42 44 LocalRoleAssignmentUtilityView) 43 from waeup.kofa.browser.interfaces import ICaptchaManager44 45 from waeup.kofa.hostels.hostel import NOT_OCCUPIED 45 46 from waeup.kofa.interfaces import ( 46 47 IKofaObject, IUserAccount, IExtFileStore, IPasswordValidator, IContactForm, 47 IKofaUtils, I University, IObjectHistory, academic_sessions, ICSVExporter,48 academic_sessions_vocab, I JobManager, IDataCenter, DOCLINK)48 IKofaUtils, IObjectHistory, academic_sessions, ICSVExporter, 49 academic_sessions_vocab, IDataCenter, DOCLINK) 49 50 from waeup.kofa.interfaces import MessageFactory as _ 50 from waeup.kofa.widgets.datewidget import (51 FriendlyDateWidget, FriendlyDateDisplayWidget,52 FriendlyDatetimeDisplayWidget)53 51 from waeup.kofa.mandates.mandate import PasswordMandate 54 52 from waeup.kofa.university.interfaces import ( 55 53 IDepartment, ICertificate, ICourse) 54 from waeup.kofa.university.certificate import ( 55 VirtualCertificateExportJobContainer) 56 from waeup.kofa.university.department import ( 57 VirtualDepartmentExportJobContainer) 56 58 from waeup.kofa.university.faculty import VirtualFacultyExportJobContainer 57 from waeup.kofa.university.department import VirtualDepartmentExportJobContainer58 59 from waeup.kofa.university.facultiescontainer import ( 59 VirtualFacultiesExportJobContainer, FacultiesContainer) 60 from waeup.kofa.university.certificate import ( 61 VirtualCertificateExportJobContainer,) 60 VirtualFacultiesExportJobContainer) 62 61 from waeup.kofa.university.course import ( 63 62 VirtualCourseExportJobContainer,) 64 63 from waeup.kofa.university.vocabularies import course_levels 65 64 from waeup.kofa.utils.batching import VirtualExportJobContainer 66 from waeup.kofa.utils.helpers import get_current_principal, to_timezone, now 65 from waeup.kofa.utils.helpers import get_current_principal, now 66 from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget 67 67 from waeup.kofa.students.interfaces import ( 68 IStudentsContainer, IStudent, 69 IUGStudentClearance,IPGStudentClearance, 68 IStudentsContainer, IStudent, IUGStudentClearance, IPGStudentClearance, 70 69 IStudentPersonal, IStudentPersonalEdit, IStudentBase, IStudentStudyCourse, 71 70 IStudentStudyCourseTransfer, IStudentStudyCourseTranscript, 72 IStudentAccommodation, IStudentStudyLevel, 73 I CourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,74 IStudent OnlinePayment, IStudentPreviousPayment, IStudentBalancePayment,75 I BedTicket, IStudentsUtils, IStudentRequestPW, IStudentTranscript71 IStudentAccommodation, IStudentStudyLevel, ICourseTicket, ICourseTicketAdd, 72 IStudentPaymentsContainer, IStudentOnlinePayment, IStudentPreviousPayment, 73 IStudentBalancePayment, IBedTicket, IStudentsUtils, IStudentRequestPW, 74 IStudentTranscript 76 75 ) 77 76 from waeup.kofa.students.catalog import search, StudentQueryResultItem 78 from waeup.kofa.students.studylevel import StudentStudyLevel, CourseTicket79 77 from waeup.kofa.students.vocabularies import StudyLevelSource 80 from waeup.kofa.students.workflow import (CREATED, ADMITTED, PAID, 81 CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED, 82 GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS) 83 84 85 grok.context(IKofaObject) # Make IKofaObject the default context 78 from waeup.kofa.students.workflow import ( 79 ADMITTED, PAID, CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, 80 VALIDATED, GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS 81 ) 82 83 84 grok.context(IKofaObject) # Make IKofaObject the default context 85 86 86 87 87 # Save function used for save methods in pages … … 90 90 # Turn list of lists into single list 91 91 if changed_fields: 92 changed_fields = reduce(lambda x, y: x+y, changed_fields.values())92 changed_fields = reduce(lambda x, y: x+y, changed_fields.values()) 93 93 # Inform catalog if certificate has changed 94 94 # (applyData does this only for the context) … … 1199 1199 def label(self): 1200 1200 # Here we know that the cookie has been set 1201 lang = self.request.cookies.get('kofa.language')1202 1201 return _('${a}: Transcript Data', mapping = { 1203 1202 'a':self.context.student.display_fullname}) … … 1397 1396 def render(self): 1398 1397 portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE 1399 Term = translate(_('Term'), 'waeup.kofa', target_language=portal_language)1400 1398 Code = translate(_('Code'), 'waeup.kofa', target_language=portal_language) 1401 1399 Title = translate(_('Title'), 'waeup.kofa', target_language=portal_language) … … 3104 3102 return list(coursetickets) 3105 3103 3104 def _extract_uploadfile(self, uploadfile): 3105 """Get a mapping of student-ids to scores. 3106 3107 The mapping is constructed by reading contents from `uploadfile`. 3108 3109 We expect uploadfile to be a regular CSV file with columns 3110 ``student_id`` and ``score`` (other cols are ignored). 3111 """ 3112 result = dict() 3113 data = StringIO(uploadfile.read()) # ensure we have something seekable 3114 reader = csv.DictReader(data) 3115 for row in reader: 3116 if not 'student_id' in row or not 'score' in row: 3117 continue 3118 result[row['student_id']] = row['score'] 3119 return result 3120 3106 3121 def update(self, *args, **kw): 3107 3122 form = self.request.form 3108 ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')3123 ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '') 3109 3124 self.current_academic_session = grok.getSite()[ 3110 3125 'configuration'].current_academic_session … … 3124 3139 self.redirect(self.url(self.context)) 3125 3140 return 3126 if 'UPDATE' in form: 3127 error = '' 3128 if not editable_tickets: 3129 return 3130 scores = form['scores'] 3131 sids = form['sids'] 3132 if isinstance(scores, basestring): 3133 scores = [scores] 3134 if isinstance(sids, basestring): 3135 sids = [sids] 3136 formvals = dict([(sids[i], scores[i]) for i in range(len(sids))]) 3137 for ticket in editable_tickets: 3138 score = ticket.score 3139 sid = ticket.student.student_id 3140 if formvals[sid] == '': 3141 score = None 3142 else: 3143 try: 3144 score = int(formvals[sid]) 3145 except ValueError: 3146 error += '%s, ' % ticket.student.display_fullname 3147 if ticket.score != score: 3148 ticket.score = score 3149 ticket.student.__parent__.logger.info( 3150 '%s - %s %s/%s score updated (%s)' % 3151 (ob_class, ticket.student.student_id, 3152 ticket.level, ticket.code, score)) 3153 #notify(grok.ObjectModifiedEvent(ticket)) 3154 if error: 3155 self.flash(_('Error: Score(s) of %s have not be updated. ' 3156 'Only integers are allowed.' % error.strip(', ')), 3157 type="danger") 3158 return 3141 if not 'UPDATE' in form: 3142 return 3143 error = '' 3144 if not editable_tickets: 3145 return 3146 formvals = dict(zip(form['sids'], form['scores'])) 3147 if form['uploadfile']: 3148 try: 3149 formvals = self._extract_uploadfile(form['uploadfile']) 3150 except: 3151 self.flash( 3152 _('Uploaded file contains illegal data. Ignored'), 3153 type="danger") 3154 for ticket in editable_tickets: 3155 score = ticket.score 3156 sid = ticket.student.student_id 3157 if sid not in formvals: 3158 continue 3159 if formvals[sid] == '': 3160 score = None 3161 else: 3162 try: 3163 score = int(formvals[sid]) 3164 except ValueError: 3165 error += '%s, ' % ticket.student.display_fullname 3166 if ticket.score != score: 3167 ticket.score = score 3168 ticket.student.__parent__.logger.info( 3169 '%s - %s %s/%s score updated (%s)' % ( 3170 ob_class, ticket.student.student_id, 3171 ticket.level, ticket.code, score) 3172 ) 3173 if error: 3174 self.flash( 3175 _('Error: Score(s) of following students have not been ' 3176 'updated (only integers are allowed): %s.' % error.strip(', ')), 3177 type="danger") 3178 return 3179 3159 3180 3160 3181 class DownloadScoresView(UtilityView, grok.View): … … 3166 3187 3167 3188 def update(self): 3168 ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')3169 3189 self.current_academic_session = grok.getSite()[ 3170 3190 'configuration'].current_academic_session … … 3350 3370 date_format = '%d/%m/%Y' 3351 3371 try: 3352 d ummy = datetime.strptime(payments_start, date_format)3353 d ummy = datetime.strptime(payments_end, date_format)3372 datetime.strptime(payments_start, date_format) 3373 datetime.strptime(payments_end, date_format) 3354 3374 except ValueError: 3355 3375 self.flash(_('Payment dates do not match format d/m/Y.'), -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/editscorespage.pt
r13896 r13935 1 <form i18n:domain="waeup.kofa" method="POST" >1 <form i18n:domain="waeup.kofa" method="POST" enctype="multipart/form-data"> 2 2 <br /> 3 3 … … 15 15 </thead> 16 16 <tbody> 17 17 <tr tal:repeat="ticket view/tickets"> 18 18 <td tal:content="ticket/student/matric_number">MATRIC_NUMBER</td> 19 19 <td tal:content="ticket/student/student_id">STUDENT ID</td> … … 24 24 25 25 <td tal:condition="ticket/editable_by_lecturer" style="width: 65px;"> 26 <input type="text" name="scores " class="form-control"26 <input type="text" name="scores:list" class="form-control" 27 27 tal:attributes="value ticket/score" /> 28 <input type="hidden" name="sids "28 <input type="hidden" name="sids:list" 29 29 tal:attributes="value ticket/student/student_id" /> 30 30 </td> … … 32 32 tal:content="ticket/score">SCORE</td> 33 33 </tr> 34 35 <tr> 36 <td i18n:translate=""> 37 Set scores from CSV file: 38 </td> 39 <td> 40 <div class="input-group half"> 41 <div class="input-group-btn"> 42 <div class="btn btn-default btn-file"> 43 Select… 44 <input type="file" name="uploadfile:file" /> 45 </div> 46 </div> 47 <input type="text" class="form-control" readonly /> 48 </div> 49 </td> 50 </tr> 51 34 52 </tbody> 35 53 </table> -
main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py
r13908 r13935 1 # -*- coding: utf-8 -*- 1 2 ## $Id$ 2 3 ## … … 45 46 from waeup.kofa.university.faculty import Faculty 46 47 from waeup.kofa.university.department import Department 47 from waeup.kofa.interfaces import IUserAccount, IJobManager 48 from waeup.kofa.interfaces import IUserAccount, IJobManager, VALIDATED, CREATED 48 49 from waeup.kofa.authentication import LocalRoleSetEvent 49 50 from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED … … 55 56 SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg') 56 57 SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp') 58 URL_LECTURER_LANDING = 'http://localhost/app/my_courses' 57 59 58 60 … … 1807 1809 self.assertTrue(self.student_id in self.browser.contents) 1808 1810 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 ticket1814 studylevel = createObject(u'waeup.StudentStudyLevel')1815 studylevel.level = 1001816 studylevel.level_session = 20041817 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 page1833 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 page1847 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 since1858 # 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 = True1865 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 = False1870 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 = 20041874 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 downloaded1917 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" % path1926 1927 1811 def test_change_current_mode(self): 1928 1812 self.browser.addHeader('Authorization', 'Basic mgr:mgrpw') … … 3898 3782 ) 3899 3783 3900 def test_course_download_lecturer(self):3901 # We add study level 100 to the student's studycourse3902 studylevel = StudentStudyLevel()3903 studylevel.level = 1003904 studylevel.level_session = 20043905 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 lecturer3910 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 lecturer3916 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 = 20043926 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 3935 3784 def test_export_departmet_officers(self): 3936 3785 # Create department officer … … 4004 3853 self.browser.getControl("Discard").click() 4005 3854 self.assertEqual(len(self.app['datacenter'].running_exports), 0) 3855 3856 3857 UPLOAD_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 3862 class 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) -
main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py
r13610 r13935 18 18 import grok 19 19 from zope.component import getUtility 20 from zope.i18n import translate 20 21 from zope.interface import Interface 21 from zope.i18n import translate 22 from waeup.kofa.interfaces import IExtFileStore, IKofaObject 23 from waeup.kofa.interfaces import MessageFactory as _ 22 from waeup.kofa.browser.layout import default_primary_nav_template 24 23 from waeup.kofa.browser.viewlets import ( 25 24 PrimaryNavTab, ManageActionButton, AddActionButton) 26 from waeup.kofa.browser.layout import default_primary_nav_template 27 from waeup.kofa.students.workflow import ( 28 ADMITTED, PAID, REQUESTED, RETURNING, CLEARED, REGISTERED, 29 VALIDATED, GRADUATED, TRANSCRIPT) 25 from waeup.kofa.interfaces import MessageFactory as _ 26 from waeup.kofa.interfaces import IExtFileStore, IKofaObject 30 27 from waeup.kofa.students.browser import ( 31 StudentsContainerPage, 32 StudentsContainerManagePage, StudentBaseDisplayFormPage, 33 StudentClearanceDisplayFormPage, StudentPersonalDisplayFormPage, 34 StudyCourseDisplayFormPage, StudyLevelDisplayFormPage, 35 CourseTicketDisplayFormPage, OnlinePaymentDisplayFormPage, 36 AccommodationManageFormPage, BedTicketDisplayFormPage, 28 StudentsContainerPage, StudentsContainerManagePage, 29 StudentBaseDisplayFormPage, StudentClearanceDisplayFormPage, 30 StudentPersonalDisplayFormPage, StudyCourseDisplayFormPage, 31 StudyLevelDisplayFormPage, CourseTicketDisplayFormPage, 32 OnlinePaymentDisplayFormPage, BedTicketDisplayFormPage, 37 33 StudentClearanceEditFormPage, StudentPersonalEditFormPage, 38 PaymentsManageFormPage, StudyCourseTranscriptPage )34 PaymentsManageFormPage, StudyCourseTranscriptPage, EditScoresPage) 39 35 from waeup.kofa.students.interfaces import ( 40 IStudentsContainer, IStudent, IStudentStudyCourse, IStudent Accommodation,41 I StudentStudyLevel, ICourseTicket, IStudentOnlinePayment, IBedTicket,36 IStudentsContainer, IStudent, IStudentStudyCourse, IStudentStudyLevel, 37 ICourseTicket, IStudentOnlinePayment, IBedTicket, 42 38 IStudentPaymentsContainer, IStudentsUtils 43 39 ) 44 45 grok.context(IKofaObject) # Make IKofaObject the default context 40 from waeup.kofa.students.workflow import ( 41 ADMITTED, PAID, REQUESTED, CLEARED, REGISTERED, VALIDATED, GRADUATED, 42 TRANSCRIPT) 43 from waeup.kofa.university.interfaces import ICourse 44 45 46 grok.context(IKofaObject) # Make IKofaObject the default context 46 47 grok.templatedir('browser_templates') 47 48 … … 49 50 class StudentManageSidebar(grok.ViewletManager): 50 51 grok.name('left_studentmanage') 52 51 53 52 54 class StudentManageLink(grok.Viewlet): … … 69 71 # Here we know that the cookie has been set 70 72 lang = self.request.cookies.get('kofa.language') 71 text = translate( self.text, 'waeup.kofa',72 target_language=lang)73 text = translate( 74 self.text, 'waeup.kofa', target_language=lang) 73 75 if not self.link: 74 76 return '' 75 77 return u'<li><a href="%s">%s</a></li>' % ( 76 url, text) 78 url, text) 79 77 80 78 81 class StudentManageApplicationLink(StudentManageLink): … … 86 89 if slip: 87 90 lang = self.request.cookies.get('kofa.language') 88 text = translate( self.text, 'waeup.kofa',89 target_language=lang)90 url = self.view.url(self.context.student, self.link)91 text = translate( 92 self.text, 'waeup.kofa', target_language=lang) 93 url = self.view.url(self.context.student, self.link) 91 94 return u'<li><a href="%s">%s</a></li>' % ( 92 95 url, text) 93 96 return '' 97 94 98 95 99 class StudentManageBaseLink(StudentManageLink): … … 97 101 link = 'index' 98 102 text = _(u'Base Data') 103 99 104 100 105 class StudentManageClearanceLink(StudentManageLink): … … 104 109 text = _(u'Clearance Data') 105 110 111 106 112 class StudentManagePersonalLink(StudentManageLink): 107 113 grok.order(4) … … 110 116 text = _(u'Personal Data') 111 117 118 112 119 class StudentManageStudyCourseLink(StudentManageLink): 113 120 grok.order(5) 114 121 link = 'studycourse' 115 122 text = _(u'Study Course') 123 116 124 117 125 class StudentManagePaymentsLink(StudentManageLink): … … 120 128 link = 'payments' 121 129 text = _(u'Payments') 130 122 131 123 132 class StudentManageAccommodationLink(StudentManageLink): … … 128 137 text = _(u'Accommodation') 129 138 139 130 140 class StudentManageHistoryLink(StudentManageLink): 131 141 grok.order(8) … … 141 151 text = _('Manage students section') 142 152 153 143 154 class StudentsContainerAddActionButton(AddActionButton): 144 155 grok.order(1) … … 148 159 text = _('Add student') 149 160 target = 'addstudent' 161 150 162 151 163 class ContactActionButton(ManageActionButton): … … 158 170 target = 'contactstudent' 159 171 172 160 173 class StudentBaseManageActionButton(ManageActionButton): 161 174 grok.order(1) … … 165 178 text = _('Manage') 166 179 target = 'manage_base' 180 167 181 168 182 class StudentTrigTransActionButton(ManageActionButton): … … 175 189 target = 'trigtrans' 176 190 191 177 192 class StudentLoginAsActionButton(ManageActionButton): 178 193 grok.order(3) … … 184 199 target = 'loginasstep1' 185 200 201 186 202 class AdmissionSlipActionButton(ManageActionButton): 187 203 grok.order(4) … … 193 209 target = 'admission_slip.pdf' 194 210 211 195 212 class StudentTransferButton(ManageActionButton): 196 213 grok.order(6) … … 202 219 icon = 'actionicon_redo.png' 203 220 221 204 222 class StudentDeactivateActionButton(ManageActionButton): 205 223 grok.order(7) … … 222 240 "'A history message will be added. Are you sure?'") 223 241 242 224 243 class StudentActivateActionButton(ManageActionButton): 225 244 grok.order(7) … … 242 261 "'A history message will be added. Are you sure?'") 243 262 263 244 264 class StudentClearanceManageActionButton(ManageActionButton): 245 265 grok.order(1) … … 249 269 text = _('Manage') 250 270 target = 'manage_clearance' 271 251 272 252 273 class StudentClearActionButton(ManageActionButton): … … 261 282 @property 262 283 def target_url(self): 263 cdm = getUtility(IStudentsUtils).clearance_disabled_message(self.context) 284 cdm = getUtility( 285 IStudentsUtils).clearance_disabled_message(self.context) 264 286 if cdm: 265 287 return '' … … 267 289 return '' 268 290 return self.view.url(self.view.context, self.target) 291 269 292 270 293 class StudentRejectClearanceActionButton(ManageActionButton): … … 279 302 @property 280 303 def target_url(self): 281 cdm = getUtility(IStudentsUtils).clearance_disabled_message(self.context) 304 cdm = getUtility( 305 IStudentsUtils).clearance_disabled_message(self.context) 282 306 if cdm: 283 307 return '' … … 285 309 return '' 286 310 return self.view.url(self.view.context, self.target) 311 287 312 288 313 class ClearanceSlipActionButton(ManageActionButton): … … 295 320 target = 'clearance_slip.pdf' 296 321 322 297 323 class ClearanceViewActionButton(ManageActionButton): 298 324 grok.order(1) … … 304 330 target = 'view_clearance' 305 331 332 306 333 class PersonalViewActionButton(ManageActionButton): 307 334 grok.order(1) … … 313 340 target = 'view_personal' 314 341 342 315 343 class StudentPersonalManageActionButton(ManageActionButton): 316 344 grok.order(1) … … 321 349 target = 'manage_personal' 322 350 351 323 352 class StudentPersonalEditActionButton(ManageActionButton): 324 353 grok.order(2) … … 328 357 text = _('Edit') 329 358 target = 'edit_personal' 359 330 360 331 361 class StudyCourseManageActionButton(ManageActionButton): … … 342 372 return self.view.url(self.view.context, self.target) 343 373 return False 374 344 375 345 376 class StudyCourseTranscriptActionButton(ManageActionButton): … … 358 389 return False 359 390 391 360 392 class TranscriptSlipActionButton(ManageActionButton): 361 393 grok.order(1) … … 373 405 return False 374 406 407 375 408 class RevertTransferActionButton(ManageActionButton): 376 409 grok.order(1) … … 388 421 return False 389 422 423 390 424 class StudyLevelManageActionButton(ManageActionButton): 391 425 grok.order(1) … … 402 436 return '' 403 437 return self.view.url(self.view.context, self.target) 438 404 439 405 440 class StudentValidateCoursesActionButton(ManageActionButton): … … 414 449 @property 415 450 def target_url(self): 416 is_current = self.context.__parent__.is_current 417 if self.context.student.state != REGISTERED or \ 418 str(self.context.__parent__.current_level) != self.context.__name__ or\ 419 not is_current: 420 return '' 421 return self.view.url(self.view.context, self.target) 451 if not self.context.__parent__.is_current: 452 return '' 453 if self.context.student.state != REGISTERED: 454 return '' 455 if str(self.context.__parent__.current_level) != self.context.__name__: 456 return '' 457 return self.view.url(self.view.context, self.target) 458 422 459 423 460 class StudentRejectCoursesActionButton(ManageActionButton): … … 432 469 @property 433 470 def target_url(self): 434 is_current = self.context.__parent__.is_current 435 if self.context.student.state not in (VALIDATED, REGISTERED) or \ 436 str(self.context.__parent__.current_level) != self.context.__name__ or\ 437 not is_current: 438 return '' 439 return self.view.url(self.view.context, self.target) 471 if not self.context.__parent__.is_current: 472 return '' 473 if self.context.student.state not in (VALIDATED, REGISTERED): 474 return '' 475 if str(self.context.__parent__.current_level) != self.context.__name__: 476 return '' 477 return self.view.url(self.view.context, self.target) 478 440 479 441 480 class StudentUnregisterCoursesActionButton(ManageActionButton): … … 450 489 @property 451 490 def target_url(self): 452 is_current = self.context.__parent__.is_current 453 if self.context.student.state != REGISTERED or \ 454 str(self.context.__parent__.current_level) != self.context.__name__ or\ 455 not is_current: 456 return '' 457 return self.view.url(self.view.context, self.target) 491 if not self.context.__parent__.is_current: 492 return '' 493 if self.context.student.state != REGISTERED: 494 return '' 495 if str(self.context.__parent__.current_level) != self.context.__name__: 496 return '' 497 return self.view.url(self.view.context, self.target) 498 458 499 459 500 class CourseRegistrationSlipActionButton(ManageActionButton): … … 473 514 return self.view.url(self.view.context, self.target) 474 515 516 475 517 class CourseTicketManageActionButton(ManageActionButton): 476 518 grok.order(1) … … 481 523 target = 'manage' 482 524 483 #class OnlinePaymentManageActionButton(ManageActionButton):484 # grok.order(1)485 # grok.context(IStudentPaymentsContainer)486 # grok.view(PaymentsDisplayFormPage)487 # grok.require('waeup.manageStudent')488 # text = 'Manage payments'489 # target = 'manage'490 525 491 526 class PaymentReceiptActionButton(ManageActionButton): 492 grok.order(9) # This button should always be the last one.527 grok.order(9) # This button should always be the last one. 493 528 grok.context(IStudentOnlinePayment) 494 529 grok.view(OnlinePaymentDisplayFormPage) … … 503 538 # return '' 504 539 return self.view.url(self.view.context, self.target) 540 505 541 506 542 class ApprovePaymentActionButton(ManageActionButton): … … 519 555 return self.view.url(self.view.context, self.target) 520 556 557 521 558 class BedTicketSlipActionButton(ManageActionButton): 522 559 grok.order(1) … … 528 565 target = 'bed_allocation_slip.pdf' 529 566 567 530 568 class RelocateStudentActionButton(ManageActionButton): 531 569 grok.order(2) … … 537 575 target = 'relocate' 538 576 577 539 578 class StudentBaseActionButton(ManageActionButton): 540 579 grok.order(1) … … 544 583 text = _('Edit') 545 584 target = 'edit_base' 585 546 586 547 587 class StudentPasswordActionButton(ManageActionButton): … … 554 594 target = 'change_password' 555 595 596 556 597 class StudentPassportActionButton(ManageActionButton): 557 598 grok.order(3) … … 565 606 @property 566 607 def target_url(self): 567 PORTRAIT_CHANGE_STATES = getUtility(IStudentsUtils).PORTRAIT_CHANGE_STATES 608 PORTRAIT_CHANGE_STATES = getUtility( 609 IStudentsUtils).PORTRAIT_CHANGE_STATES 568 610 if self.context.state not in PORTRAIT_CHANGE_STATES: 569 611 return '' 570 612 return self.view.url(self.view.context, self.target) 613 571 614 572 615 class StudentClearanceStartActionButton(ManageActionButton): … … 585 628 return self.view.url(self.view.context, self.target) 586 629 630 587 631 class StudentClearanceEditActionButton(ManageActionButton): 588 632 grok.order(1) … … 598 642 return '' 599 643 return self.view.url(self.view.context, self.target) 644 600 645 601 646 class StartSessionActionButton(ManageActionButton): … … 613 658 return self.view.url(self.view.context, self.target) 614 659 return False 660 615 661 616 662 class AddStudyLevelActionButton(AddActionButton): … … 632 678 return '' 633 679 return self.view.url(self.view.context, self.target) 680 634 681 635 682 class StudyLevelEditActionButton(ManageActionButton): … … 651 698 return '' 652 699 700 653 701 class AddPaymentActionButton(AddActionButton): 654 702 grok.order(1) … … 658 706 text = _('Add current session payment ticket') 659 707 target = 'addop' 708 660 709 661 710 class AddPreviousPaymentActionButton(AddActionButton): … … 675 724 return self.view.url(self.view.context, self.target) 676 725 726 677 727 class AddBalancePaymentActionButton(AddActionButton): 678 728 grok.order(3) … … 690 740 return self.view.url(self.view.context, self.target) 691 741 742 692 743 class RequestTranscriptActionButton(ManageActionButton): 693 744 grok.order(8) … … 704 755 return '' 705 756 return self.view.url(self.view.context, self.target) 757 706 758 707 759 class ProcessTranscriptRequestActionButton(ManageActionButton): … … 720 772 return self.view.url(self.view.context, self.target) 721 773 774 722 775 class StudentsTab(PrimaryNavTab): 723 776 """Students tab in primary navigation. 724 777 """ 725 726 778 grok.context(IKofaObject) 727 779 grok.order(4) … … 736 788 return self.view.application_url('students') 737 789 790 738 791 class PrimaryStudentNavManager(grok.ViewletManager): 739 792 """Viewlet manager for the primary navigation tab. 740 793 """ 741 794 grok.name('primary_nav_student') 795 742 796 743 797 class PrimaryStudentNavTab(grok.Viewlet): … … 763 817 return 'active' 764 818 return '' 819 765 820 766 821 class MyStudentDataTab(PrimaryStudentNavTab): … … 789 844 targets = [] 790 845 if app_slip: 791 targets = [{'url': student_url + '/application_slip',792 'title': _('Application Slip')},]846 targets = [{'url': student_url + '/application_slip', 847 'title': _('Application Slip')}, ] 793 848 targets += [ 794 {'url':student_url, 'title':'Base Data'}, 795 {'url':student_url + '/view_clearance', 796 'title':_('Clearance Data')}, 797 {'url':student_url + '/view_personal', 'title':_('Personal Data')}, 798 {'url':student_url + '/studycourse', 'title':_('Study Course')}, 799 {'url':student_url + '/payments', 'title':_('Payments')}, 800 {'url':student_url + '/accommodation', 801 'title':_('Accommodation Data')}, 802 {'url':student_url + '/history', 'title':_('History')}, 849 {'url': student_url, 'title': 'Base Data'}, 850 {'url': student_url + '/view_clearance', 851 'title': _('Clearance Data')}, 852 {'url': student_url + '/view_personal', 853 'title': _('Personal Data')}, 854 {'url': student_url + '/studycourse', 'title': _('Study Course')}, 855 {'url': student_url + '/payments', 'title': _('Payments')}, 856 {'url': student_url + '/accommodation', 857 'title': _('Accommodation Data')}, 858 {'url': student_url + '/history', 'title': _('History')}, 803 859 ] 804 860 return targets 861 862 863 class DownloadCSVFileActionButton(ManageActionButton): 864 """ 'Download csv file' button for courses. 865 """ 866 grok.context(ICourse) 867 grok.view(EditScoresPage) 868 grok.name('downloadcsv') 869 grok.require('waeup.editScores') 870 icon = 'actionicon_down.png' 871 text = _('Download editable tickets') 872 target = 'download_scores' 873 grok.order(1) 874 875 876 class DownloadTicketOverviewActionButton(ManageActionButton): 877 """ 'Download ticket overview' button for courses. 878 """ 879 grok.context(ICourse) 880 grok.view(EditScoresPage) 881 grok.name('coursetickets') 882 grok.require('waeup.editScores') 883 icon = 'actionicon_pdf.png' 884 text = _('Download pdf file') 885 target = 'coursetickets.pdf' 886 grok.order(2)
Note: See TracChangeset for help on using the changeset viewer.