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

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

Return array of structs and not array of arrays.

  • Property svn:keywords set to Id
File size: 14.7 KB
Line 
1## $Id: webservices.py 15811 2019-11-17 08:18:54Z 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
19import os
20import xmlrpclib
21from cStringIO import StringIO
22from zope.component import getUtility, queryUtility
23from zope.catalog.interfaces import ICatalog
24from waeup.kofa.interfaces import (
25    IUniversity, IExtFileStore, IFileStoreNameChooser, IKofaUtils
26    )
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
30
31
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
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
61
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
86
87    @grok.require('waeup.xmlrpc')
88    def get_courses_by_session(self, identifier=None, session=None):
89        """1. What COURSES are registered by student X in session Y?
90
91        """
92        students = self.context['students']
93        student = get_student(students, identifier)
94        if student is None:
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
114
115    @grok.require('waeup.xmlrpc')
116    def get_students_by_course(self, course=None, session=None):
117        """2. What STUDENTS registered (student id / matric no)
118        for course Z in session Y and did they pay school fee?
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 = []
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
146            hitlist.append((
147                c_ticket.student.student_id,
148                c_ticket.student.matric_number,
149                c_ticket.__parent__.validated_by,
150                amount
151                ))
152        return list(set(hitlist))
153
154    @grok.require('waeup.xmlrpc')
155    def get_student_info(self, identifier=None):
156        """3a. Who is the student with matriculation number / student id
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
166    @grok.require('waeup.xmlrpc')
167    def get_student_passport(self, identifier=None):
168        """3b. Get passport picture of student with
169        matriculation number / student id.
170
171        """
172        students = self.context['students']
173        student = get_student(students, identifier)
174        if student is None:
175            return None
176        img = getUtility(IExtFileStore).getFileByContext(
177            student, attr='passport.jpg')
178        return xmlrpclib.Binary(img.read())
179
180    @grok.require('waeup.xmlrpc')
181    def get_paid_sessions(self, identifier=None):
182        """6. Get list of SESSIONS school fees paid by student X.
183
184        """
185        students = self.context['students']
186        student = get_student(students, identifier)
187        if student is None:
188            return None
189        payments_dict = {}
190        for ticket in student['payments'].values():
191            if ticket.p_state == 'paid' and \
192                ticket.p_category == 'schoolfee':
193                payments_dict[str(ticket.p_session)] = ticket.amount_auth
194        if not payments_dict:
195            return None
196        return payments_dict
197
198    @grok.require('waeup.xmlrpc')
199    def check_student_credentials(self, username, password):
200        """Returns student data if username and password are valid,
201        None else.
202
203        We only query the students authenticator plugin in order not
204        to mix up with other credentials (admins, staff, etc.).
205
206        All additional checks performed by usual student
207        authentication apply. For instance for suspended students we
208        won't get a successful response but `None`.
209
210        This method can be used by CAS to authentify students for
211        external systems like moodle.
212        """
213        from zope.pluggableauth.interfaces import IAuthenticatorPlugin
214        auth = getUtility(IAuthenticatorPlugin, name='students')
215        creds = dict(login=username, password=password)
216        principal = auth.authenticateCredentials(creds)
217        if principal is None:
218            return None
219        return dict(email=principal.email, id=principal.id,
220                    type=principal.user_type,
221                    description=principal.description)
222
223    @grok.require('waeup.xmlrpc')
224    def get_student_moodle_data(self, identifier=None):
225        """Returns student data to update user data and enroll user
226        in Moodle courses.
227
228        """
229        students = self.context['students']
230        student = get_student(students, identifier)
231        if student is None:
232            return None
233        return dict(email=student.email,
234                    firstname=student.firstname,
235                    lastname=student.lastname,
236                    )
237
238    @grok.require('waeup.putBiometricData')
239    def put_student_fingerprints(self, identifier=None, fingerprints={}):
240        """Store fingerprint files for student identified by `identifier`.
241
242        `fingerprints` is expected to be a dict with strings
243        ``1``..``10`` as keys and binary data as values.
244
245        The keys ``1``..``10`` represent respective fingers: ``1`` is
246        the left thumb, ``10`` the little finger of right hand.
247
248        The binary data values are expected to be fingerprint minutiae
249        files as created by the libfprint library. With the std python
250        `xmlrpclib` client you can create such values with
251        `xmlrpclib.Binary(<BINARY_DATA_HERE>)`.
252
253        The following problems will raise errors:
254
255        - Invalid student identifiers (student does not exist or
256          unknown format of identifier)
257
258        - Fingerprint files that are not put into a dict.
259
260        - Fingerprint dicts that contain non-FPM files (or otherwise
261          invalid .fpm data).
262
263        Returns `True` in case of successful operation (at least one
264        fingerprint was stored), `False` otherwise.
265        """
266        result = False
267        students = self.context['students']
268        student = get_student(students, identifier)
269        if student is None:
270            raise xmlrpclib.Fault(
271                xmlrpclib.INVALID_METHOD_PARAMS,
272                "No such student: '%s'" % identifier)
273        if not isinstance(fingerprints, dict):
274            raise xmlrpclib.Fault(
275                xmlrpclib.INVALID_METHOD_PARAMS,
276                "Invalid fingerprint data: must be in dict")
277        for str_key, val in fingerprints.items():
278            num = 0
279            try:
280                num = int(str_key)
281            except ValueError:
282                pass
283            if num < 1 or num > 10:
284                continue
285            if not isinstance(val, xmlrpclib.Binary):
286                raise xmlrpclib.Fault(
287                    xmlrpclib.INVALID_METHOD_PARAMS,
288                    "Invalid data for finger %s" % num)
289            fmt = get_fileformat(None, val.data)
290            if fmt != 'fpm':
291                raise xmlrpclib.Fault(
292                    xmlrpclib.INVALID_METHOD_PARAMS,
293                    "Invalid file format for finger %s" % num)
294            file_store = getUtility(IExtFileStore)
295            file_id = IFileStoreNameChooser(student).chooseName(
296                attr='finger%s.fpm' % num)
297            file_store.createFile(file_id, StringIO(val.data))
298            student.writeLogMessage(self, 'fingerprint stored')
299            result = True
300        return result
301
302    @grok.require('waeup.getBiometricData')
303    def get_student_fingerprints(self, identifier=None):
304        """Returns student fingerprint data if available.
305
306        Result set is a dictionary with entries for ``email``,
307        ``firstname``, ``lastname``, ``img``, ``img_name``, and
308        ``fingerprints``.
309
310        Here ``img`` and ``img_name`` represent a photograph of the
311        student (binary image data and filename
312        respectively).
313
314        ``fingerprints`` is a dictionary itself with possible entries
315        ``1`` to ``10``, containing binary minutiae data
316        (i.e. fingerprint scans).
317        """
318        students = self.context['students']
319        student = get_student(students, identifier)
320        if student is None:
321            return dict()
322        result = dict(
323            email=student.email,
324            firstname=student.firstname,
325            lastname=student.lastname,
326            fingerprints={},
327            img_name=None,
328            img=None,
329            )
330        file_store = getUtility(IExtFileStore)
331        img = file_store.getFileByContext(student, attr='passport.jpg')
332        if img is not None:
333            result.update(
334                img=xmlrpclib.Binary(img.read()),
335                img_name=os.path.basename(img.name))
336
337        for num in [str(x + 1) for x in range(10)]:
338            fp_file = file_store.getFileByContext(
339                student, attr='finger%s.fpm' % num)
340            if fp_file is not None:
341                result['fingerprints'][num] = xmlrpclib.Binary(fp_file.read())
342        return result
343
344    @grok.require('waeup.xmlrpc')
345    def get_bursary_data(self,
346            current_session=None, current_level=None, certcode=None,
347            current_mode=None, depcode=None):
348        """Returns bursary data of a subset of students.
349        """
350        if not current_session:
351            current_session = None
352        if not current_level:
353            current_level = None
354        if not depcode:
355            depcode = None
356        if not certcode:
357            certcode = None
358        if not current_mode:
359            current_mode = None
360        hitlist = []
361        cat = queryUtility(ICatalog, name='students_catalog')
362        results = list(
363            cat.searchResults(
364                current_session=(current_session, current_session),
365                current_level=(current_level, current_level),
366                certcode=(certcode, certcode),
367                current_mode=(current_mode, current_mode),
368                depcode=(depcode, depcode),
369                ))
370        payments = get_payments(results)
371        tz = getUtility(IKofaUtils).tzinfo
372        for payment in payments:
373            hitlist.append(dict(
374                student_id=payment.student.student_id,
375                matric_number=payment.student.matric_number,
376                reg_number=payment.student.reg_number,
377                firstname=payment.student.firstname,
378                middlename=payment.student.middlename,
379                lastname=payment.student.lastname,
380                state=payment.student.state,
381                current_session=payment.student.current_session,
382                entry_session=payment.student.entry_session,
383                entry_mode=payment.student.entry_mode,
384                faccode=payment.student.faccode,
385                depcode=payment.student.depcode,
386                certcode=payment.student.certcode,
387                p_id=payment.p_id,
388                amount_auth=payment.amount_auth,
389                p_category=payment.p_category,
390                display_item=payment.display_item,
391                p_session=payment.p_session,
392                p_state=payment.p_state,
393                creation_date=str('%s#' % to_timezone(payment.creation_date, tz)),
394                payment_date=str('%s#' % to_timezone(payment.payment_date, tz)),
395                )
396              )
397        return hitlist
Note: See TracBrowser for help on using the repository browser.