source: main/waeup.kofa/trunk/src/waeup/kofa/students/webservices.py @ 16159

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

Extend webservice.

  • Property svn:keywords set to Id
File size: 15.4 KB
RevLine 
[10033]1## $Id: webservices.py 16112 2020-06-09 06:40:21Z henrik $
2##
3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
[11668]19import os
[10042]20import xmlrpclib
[11674]21from cStringIO import StringIO
[10038]22from zope.component import getUtility, queryUtility
[10033]23from zope.catalog.interfaces import ICatalog
[11674]24from waeup.kofa.interfaces import (
[16110]25    IUniversity, IExtFileStore, IFileStoreNameChooser, IKofaUtils,
[16112]26    GRADUATED, TRANSREL)
[15798]27from waeup.kofa.utils.helpers import get_fileformat, to_timezone
28from waeup.kofa.students.catalog import StudentsQuery
29from waeup.kofa.students.export import get_payments
[10033]30
[10502]31
[10041]32def get_student(students, identifier):
33    if identifier is None:
34        return None
35    student = students.get(identifier, None)
36    if student is None:
37        cat = queryUtility(ICatalog, name='students_catalog')
38        results = list(
39            cat.searchResults(matric_number=(identifier, identifier)))
40        if len(results) == 1:
41            student = results[0]
42        else:
43            results = list(
44                cat.searchResults(reg_number=(identifier, identifier)))
45            if len(results) == 1:
46                student = results[0]
47    return student
[10033]48
49#class XMLRPCPermission(grok.Permission):
50#    """Permission for using XMLRPC functions.
51#    """
52#    grok.name('waeup.xmlrpc')
53
54#class XMLRPCUsers2(grok.Role):
55#    """Usergroup 2
56#    """
57#    grok.name('waeup.xmlrpcusers2')
58#    grok.title('XMLRPC Users Group 2')
59#    grok.permissions('waeup.xmlrpc',)
60
[10502]61
[10033]62class StudentsXMLRPC(grok.XMLRPC):
63    """Student related XMLRPC webservices.
64
65    Please note, that XMLRPC does not support real keyword arguments
66    but positional arguments only.
67    """
68
69    grok.context(IUniversity)
70
71    @grok.require('waeup.xmlrpc')
72    def get_student_id(self, reg_number=None):
73        """Get the id of a student with registration number `reg_number`.
74
75        Returns the student id as string if successful, ``None`` else.
76        """
77        if reg_number is not None:
78            cat = getUtility(ICatalog, name='students_catalog')
79            result = list(
80                cat.searchResults(reg_number=(reg_number, reg_number),
81                                  _limit=1))
82            if not len(result):
83                return None
84            return result[0].student_id
85        return None
[10036]86
87    @grok.require('waeup.xmlrpc')
[10041]88    def get_courses_by_session(self, identifier=None, session=None):
[10043]89        """1. What COURSES are registered by student X in session Y?
[10036]90
91        """
[10041]92        students = self.context['students']
93        student = get_student(students, identifier)
94        if student is None:
[10036]95            return None
96        try:
97            session = int(session)
98        except (TypeError, ValueError):
99            pass
100        sessionsearch = True
101        if session in (None, ''):
102            sessionsearch = False
103        studycourse = student['studycourse']
104        coursetickets = {}
105        for level in studycourse.values():
106            if sessionsearch and level.level_session != session:
107                continue
108            for ct in level.values():
109                coursetickets.update(
110                    {"%s|%s" % (level.level, ct.code): ct.title})
111        if not coursetickets:
112            return None
113        return coursetickets
[10038]114
115    @grok.require('waeup.xmlrpc')
116    def get_students_by_course(self, course=None, session=None):
[10043]117        """2. What STUDENTS registered (student id / matric no)
[10044]118        for course Z in session Y and did they pay school fee?
[10038]119
120        """
121        try:
122            session = int(session)
123        except (TypeError, ValueError):
124            pass
125        sessionsearch = True
126        if session in (None, ''):
127            sessionsearch = False
128        cat = queryUtility(ICatalog, name='coursetickets_catalog')
129        if sessionsearch:
130            coursetickets = cat.searchResults(
131                session=(session, session),
132                code=(course, course))
133        else:
134            coursetickets = cat.searchResults(
135                code=(course, course))
136        if not coursetickets:
137            return None
138        hitlist = []
[10044]139        for c_ticket in coursetickets:
140            amount = 0
141            for p_ticket in c_ticket.student['payments'].values():
142                if p_ticket.p_state == 'paid' and \
143                    p_ticket.p_category == 'schoolfee' and \
144                    p_ticket.p_session == c_ticket.__parent__.level_session:
145                    amount = p_ticket.amount_auth
[10040]146            hitlist.append((
[10044]147                c_ticket.student.student_id,
148                c_ticket.student.matric_number,
149                c_ticket.__parent__.validated_by,
150                amount
151                ))
[10038]152        return list(set(hitlist))
[10042]153
154    @grok.require('waeup.xmlrpc')
155    def get_student_info(self, identifier=None):
[10043]156        """3a. Who is the student with matriculation number / student id
[10042]157
158        """
159        students = self.context['students']
160        student = get_student(students, identifier)
161        if student is None:
162            return None
163        return [student.display_fullname, student.certcode,
164            student.phone, student.email]
165
[16110]166    @grok.require('waeup.Public')
167    def get_grad_student(self, identifier=None, email=None):
[16112]168        """Check if student record exist, check email address and
169        retrieve registration state.
[16110]170        """
171        students = self.context['students']
172        student = get_student(students, identifier)
173        if student is None:
174            return None
175        correct_email = False
176        has_graduated = False
[16112]177        transcript_released = False
[16110]178        if student.email == email:
179            correct_email = True
180        if student.state == GRADUATED:
181            has_graduated = True
[16112]182        if student.state == TRANSREL:
183            transcript_released = True
184        return [correct_email, has_graduated, transcript_released]
[16110]185
[10042]186    @grok.require('waeup.xmlrpc')
187    def get_student_passport(self, identifier=None):
[10043]188        """3b. Get passport picture of student with
189        matriculation number / student id.
[10042]190
191        """
192        students = self.context['students']
193        student = get_student(students, identifier)
194        if student is None:
195            return None
196        img = getUtility(IExtFileStore).getFileByContext(
197            student, attr='passport.jpg')
198        return xmlrpclib.Binary(img.read())
[10043]199
200    @grok.require('waeup.xmlrpc')
201    def get_paid_sessions(self, identifier=None):
202        """6. Get list of SESSIONS school fees paid by student X.
203
204        """
205        students = self.context['students']
206        student = get_student(students, identifier)
207        if student is None:
208            return None
209        payments_dict = {}
210        for ticket in student['payments'].values():
211            if ticket.p_state == 'paid' and \
212                ticket.p_category == 'schoolfee':
213                payments_dict[str(ticket.p_session)] = ticket.amount_auth
214        if not payments_dict:
215            return None
216        return payments_dict
[10477]217
218    @grok.require('waeup.xmlrpc')
[10505]219    def check_student_credentials(self, username, password):
[10477]220        """Returns student data if username and password are valid,
221        None else.
222
223        We only query the students authenticator plugin in order not
224        to mix up with other credentials (admins, staff, etc.).
225
226        All additional checks performed by usual student
227        authentication apply. For instance for suspended students we
228        won't get a successful response but `None`.
229
230        This method can be used by CAS to authentify students for
231        external systems like moodle.
232        """
233        from zope.pluggableauth.interfaces import IAuthenticatorPlugin
234        auth = getUtility(IAuthenticatorPlugin, name='students')
235        creds = dict(login=username, password=password)
236        principal = auth.authenticateCredentials(creds)
237        if principal is None:
238            return None
239        return dict(email=principal.email, id=principal.id,
240                    type=principal.user_type,
241                    description=principal.description)
[10508]242
243    @grok.require('waeup.xmlrpc')
[10516]244    def get_student_moodle_data(self, identifier=None):
[10521]245        """Returns student data to update user data and enroll user
246        in Moodle courses.
[10508]247
248        """
249        students = self.context['students']
250        student = get_student(students, identifier)
251        if student is None:
252            return None
253        return dict(email=student.email,
254                    firstname=student.firstname,
255                    lastname=student.lastname,
256                    )
[11667]257
[11674]258    @grok.require('waeup.putBiometricData')
259    def put_student_fingerprints(self, identifier=None, fingerprints={}):
260        """Store fingerprint files for student identified by `identifier`.
261
262        `fingerprints` is expected to be a dict with strings
263        ``1``..``10`` as keys and binary data as values.
264
265        The keys ``1``..``10`` represent respective fingers: ``1`` is
266        the left thumb, ``10`` the little finger of right hand.
267
268        The binary data values are expected to be fingerprint minutiae
269        files as created by the libfprint library. With the std python
270        `xmlrpclib` client you can create such values with
271        `xmlrpclib.Binary(<BINARY_DATA_HERE>)`.
272
273        The following problems will raise errors:
274
275        - Invalid student identifiers (student does not exist or
276          unknown format of identifier)
277
278        - Fingerprint files that are not put into a dict.
279
280        - Fingerprint dicts that contain non-FPM files (or otherwise
281          invalid .fpm data).
282
283        Returns `True` in case of successful operation (at least one
284        fingerprint was stored), `False` otherwise.
285        """
286        result = False
287        students = self.context['students']
288        student = get_student(students, identifier)
289        if student is None:
290            raise xmlrpclib.Fault(
291                xmlrpclib.INVALID_METHOD_PARAMS,
292                "No such student: '%s'" % identifier)
293        if not isinstance(fingerprints, dict):
294            raise xmlrpclib.Fault(
295                xmlrpclib.INVALID_METHOD_PARAMS,
296                "Invalid fingerprint data: must be in dict")
297        for str_key, val in fingerprints.items():
298            num = 0
299            try:
300                num = int(str_key)
301            except ValueError:
302                pass
303            if num < 1 or num > 10:
304                continue
305            if not isinstance(val, xmlrpclib.Binary):
306                raise xmlrpclib.Fault(
307                    xmlrpclib.INVALID_METHOD_PARAMS,
308                    "Invalid data for finger %s" % num)
309            fmt = get_fileformat(None, val.data)
310            if fmt != 'fpm':
311                raise xmlrpclib.Fault(
312                    xmlrpclib.INVALID_METHOD_PARAMS,
313                    "Invalid file format for finger %s" % num)
314            file_store = getUtility(IExtFileStore)
315            file_id = IFileStoreNameChooser(student).chooseName(
[14637]316                attr='finger%s.fpm' % num)
[11674]317            file_store.createFile(file_id, StringIO(val.data))
[14681]318            student.writeLogMessage(self, 'fingerprint stored')
[11674]319            result = True
320        return result
321
[11667]322    @grok.require('waeup.getBiometricData')
323    def get_student_fingerprints(self, identifier=None):
324        """Returns student fingerprint data if available.
[11671]325
326        Result set is a dictionary with entries for ``email``,
327        ``firstname``, ``lastname``, ``img``, ``img_name``, and
328        ``fingerprints``.
329
330        Here ``img`` and ``img_name`` represent a photograph of the
331        student (binary image data and filename
332        respectively).
333
334        ``fingerprints`` is a dictionary itself with possible entries
335        ``1`` to ``10``, containing binary minutiae data
336        (i.e. fingerprint scans).
[11667]337        """
338        students = self.context['students']
339        student = get_student(students, identifier)
340        if student is None:
341            return dict()
342        result = dict(
343            email=student.email,
344            firstname=student.firstname,
345            lastname=student.lastname,
[11668]346            fingerprints={},
347            img_name=None,
[11667]348            img=None,
349            )
350        file_store = getUtility(IExtFileStore)
[11668]351        img = file_store.getFileByContext(student, attr='passport.jpg')
352        if img is not None:
353            result.update(
354                img=xmlrpclib.Binary(img.read()),
355                img_name=os.path.basename(img.name))
356
357        for num in [str(x + 1) for x in range(10)]:
[11671]358            fp_file = file_store.getFileByContext(
[14637]359                student, attr='finger%s.fpm' % num)
[11667]360            if fp_file is not None:
[11671]361                result['fingerprints'][num] = xmlrpclib.Binary(fp_file.read())
[11667]362        return result
[15798]363
364    @grok.require('waeup.xmlrpc')
365    def get_bursary_data(self,
366            current_session=None, current_level=None, certcode=None,
367            current_mode=None, depcode=None):
368        """Returns bursary data of a subset of students.
369        """
370        if not current_session:
371            current_session = None
372        if not current_level:
373            current_level = None
374        if not depcode:
375            depcode = None
376        if not certcode:
377            certcode = None
378        if not current_mode:
379            current_mode = None
380        hitlist = []
381        cat = queryUtility(ICatalog, name='students_catalog')
382        results = list(
383            cat.searchResults(
384                current_session=(current_session, current_session),
385                current_level=(current_level, current_level),
386                certcode=(certcode, certcode),
387                current_mode=(current_mode, current_mode),
388                depcode=(depcode, depcode),
389                ))
390        payments = get_payments(results)
391        tz = getUtility(IKofaUtils).tzinfo
392        for payment in payments:
[15811]393            hitlist.append(dict(
394                student_id=payment.student.student_id,
395                matric_number=payment.student.matric_number,
396                reg_number=payment.student.reg_number,
397                firstname=payment.student.firstname,
398                middlename=payment.student.middlename,
399                lastname=payment.student.lastname,
400                state=payment.student.state,
401                current_session=payment.student.current_session,
402                entry_session=payment.student.entry_session,
403                entry_mode=payment.student.entry_mode,
404                faccode=payment.student.faccode,
405                depcode=payment.student.depcode,
406                certcode=payment.student.certcode,
407                p_id=payment.p_id,
408                amount_auth=payment.amount_auth,
409                p_category=payment.p_category,
410                display_item=payment.display_item,
411                p_session=payment.p_session,
412                p_state=payment.p_state,
413                creation_date=str('%s#' % to_timezone(payment.creation_date, tz)),
414                payment_date=str('%s#' % to_timezone(payment.payment_date, tz)),
[15798]415                )
[15811]416              )
[15798]417        return hitlist
Note: See TracBrowser for help on using the repository browser.