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

Last change on this file since 14466 was 11674, checked in by uli, 10 years ago

Provide xmlrpc service for uploading .fpm files.

  • Property svn:keywords set to Id
File size: 12.2 KB
RevLine 
[10033]1## $Id: webservices.py 11674 2014-05-28 13:19:03Z uli $
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 (
25    IUniversity, IExtFileStore, IFileStoreNameChooser,
26    )
27from waeup.kofa.utils.helpers import get_fileformat
[10033]28
[10502]29
[10041]30def get_student(students, identifier):
31    if identifier is None:
32        return None
33    student = students.get(identifier, None)
34    if student is None:
35        cat = queryUtility(ICatalog, name='students_catalog')
36        results = list(
37            cat.searchResults(matric_number=(identifier, identifier)))
38        if len(results) == 1:
39            student = results[0]
40        else:
41            results = list(
42                cat.searchResults(reg_number=(identifier, identifier)))
43            if len(results) == 1:
44                student = results[0]
45    return student
[10033]46
47#class XMLRPCPermission(grok.Permission):
48#    """Permission for using XMLRPC functions.
49#    """
50#    grok.name('waeup.xmlrpc')
51
52#class XMLRPCUsers2(grok.Role):
53#    """Usergroup 2
54#    """
55#    grok.name('waeup.xmlrpcusers2')
56#    grok.title('XMLRPC Users Group 2')
57#    grok.permissions('waeup.xmlrpc',)
58
[10502]59
[10033]60class StudentsXMLRPC(grok.XMLRPC):
61    """Student related XMLRPC webservices.
62
63    Please note, that XMLRPC does not support real keyword arguments
64    but positional arguments only.
65    """
66
67    grok.context(IUniversity)
68
69    @grok.require('waeup.xmlrpc')
70    def get_student_id(self, reg_number=None):
71        """Get the id of a student with registration number `reg_number`.
72
73        Returns the student id as string if successful, ``None`` else.
74        """
75        if reg_number is not None:
76            cat = getUtility(ICatalog, name='students_catalog')
77            result = list(
78                cat.searchResults(reg_number=(reg_number, reg_number),
79                                  _limit=1))
80            if not len(result):
81                return None
82            return result[0].student_id
83        return None
[10036]84
85    @grok.require('waeup.xmlrpc')
[10041]86    def get_courses_by_session(self, identifier=None, session=None):
[10043]87        """1. What COURSES are registered by student X in session Y?
[10036]88
89        """
[10041]90        students = self.context['students']
91        student = get_student(students, identifier)
92        if student is None:
[10036]93            return None
94        try:
95            session = int(session)
96        except (TypeError, ValueError):
97            pass
98        sessionsearch = True
99        if session in (None, ''):
100            sessionsearch = False
101        studycourse = student['studycourse']
102        coursetickets = {}
103        for level in studycourse.values():
104            if sessionsearch and level.level_session != session:
105                continue
106            for ct in level.values():
107                coursetickets.update(
108                    {"%s|%s" % (level.level, ct.code): ct.title})
109        if not coursetickets:
110            return None
111        return coursetickets
[10038]112
113    @grok.require('waeup.xmlrpc')
114    def get_students_by_course(self, course=None, session=None):
[10043]115        """2. What STUDENTS registered (student id / matric no)
[10044]116        for course Z in session Y and did they pay school fee?
[10038]117
118        """
119        try:
120            session = int(session)
121        except (TypeError, ValueError):
122            pass
123        sessionsearch = True
124        if session in (None, ''):
125            sessionsearch = False
126        cat = queryUtility(ICatalog, name='coursetickets_catalog')
127        if sessionsearch:
128            coursetickets = cat.searchResults(
129                session=(session, session),
130                code=(course, course))
131        else:
132            coursetickets = cat.searchResults(
133                code=(course, course))
134        if not coursetickets:
135            return None
136        hitlist = []
[10044]137        for c_ticket in coursetickets:
138            amount = 0
139            for p_ticket in c_ticket.student['payments'].values():
140                if p_ticket.p_state == 'paid' and \
141                    p_ticket.p_category == 'schoolfee' and \
142                    p_ticket.p_session == c_ticket.__parent__.level_session:
143                    amount = p_ticket.amount_auth
[10040]144            hitlist.append((
[10044]145                c_ticket.student.student_id,
146                c_ticket.student.matric_number,
147                c_ticket.__parent__.validated_by,
148                amount
149                ))
[10038]150        return list(set(hitlist))
[10042]151
152    @grok.require('waeup.xmlrpc')
153    def get_student_info(self, identifier=None):
[10043]154        """3a. Who is the student with matriculation number / student id
[10042]155
156        """
157        students = self.context['students']
158        student = get_student(students, identifier)
159        if student is None:
160            return None
161        return [student.display_fullname, student.certcode,
162            student.phone, student.email]
163
164    @grok.require('waeup.xmlrpc')
165    def get_student_passport(self, identifier=None):
[10043]166        """3b. Get passport picture of student with
167        matriculation number / student id.
[10042]168
169        """
170        students = self.context['students']
171        student = get_student(students, identifier)
172        if student is None:
173            return None
174        img = getUtility(IExtFileStore).getFileByContext(
175            student, attr='passport.jpg')
176        return xmlrpclib.Binary(img.read())
[10043]177
178    @grok.require('waeup.xmlrpc')
179    def get_paid_sessions(self, identifier=None):
180        """6. Get list of SESSIONS school fees paid by student X.
181
182        """
183        students = self.context['students']
184        student = get_student(students, identifier)
185        if student is None:
186            return None
187        payments_dict = {}
188        for ticket in student['payments'].values():
189            if ticket.p_state == 'paid' and \
190                ticket.p_category == 'schoolfee':
191                payments_dict[str(ticket.p_session)] = ticket.amount_auth
192        if not payments_dict:
193            return None
194        return payments_dict
[10477]195
196    @grok.require('waeup.xmlrpc')
[10505]197    def check_student_credentials(self, username, password):
[10477]198        """Returns student data if username and password are valid,
199        None else.
200
201        We only query the students authenticator plugin in order not
202        to mix up with other credentials (admins, staff, etc.).
203
204        All additional checks performed by usual student
205        authentication apply. For instance for suspended students we
206        won't get a successful response but `None`.
207
208        This method can be used by CAS to authentify students for
209        external systems like moodle.
210        """
211        from zope.pluggableauth.interfaces import IAuthenticatorPlugin
212        auth = getUtility(IAuthenticatorPlugin, name='students')
213        creds = dict(login=username, password=password)
214        principal = auth.authenticateCredentials(creds)
215        if principal is None:
216            return None
217        return dict(email=principal.email, id=principal.id,
218                    type=principal.user_type,
219                    description=principal.description)
[10508]220
221    @grok.require('waeup.xmlrpc')
[10516]222    def get_student_moodle_data(self, identifier=None):
[10521]223        """Returns student data to update user data and enroll user
224        in Moodle courses.
[10508]225
226        """
227        students = self.context['students']
228        student = get_student(students, identifier)
229        if student is None:
230            return None
231        return dict(email=student.email,
232                    firstname=student.firstname,
233                    lastname=student.lastname,
234                    )
[11667]235
[11674]236    @grok.require('waeup.putBiometricData')
237    def put_student_fingerprints(self, identifier=None, fingerprints={}):
238        """Store fingerprint files for student identified by `identifier`.
239
240        `fingerprints` is expected to be a dict with strings
241        ``1``..``10`` as keys and binary data as values.
242
243        The keys ``1``..``10`` represent respective fingers: ``1`` is
244        the left thumb, ``10`` the little finger of right hand.
245
246        The binary data values are expected to be fingerprint minutiae
247        files as created by the libfprint library. With the std python
248        `xmlrpclib` client you can create such values with
249        `xmlrpclib.Binary(<BINARY_DATA_HERE>)`.
250
251        The following problems will raise errors:
252
253        - Invalid student identifiers (student does not exist or
254          unknown format of identifier)
255
256        - Fingerprint files that are not put into a dict.
257
258        - Fingerprint dicts that contain non-FPM files (or otherwise
259          invalid .fpm data).
260
261        Returns `True` in case of successful operation (at least one
262        fingerprint was stored), `False` otherwise.
263        """
264        result = False
265        students = self.context['students']
266        student = get_student(students, identifier)
267        if student is None:
268            raise xmlrpclib.Fault(
269                xmlrpclib.INVALID_METHOD_PARAMS,
270                "No such student: '%s'" % identifier)
271        if not isinstance(fingerprints, dict):
272            raise xmlrpclib.Fault(
273                xmlrpclib.INVALID_METHOD_PARAMS,
274                "Invalid fingerprint data: must be in dict")
275        for str_key, val in fingerprints.items():
276            num = 0
277            try:
278                num = int(str_key)
279            except ValueError:
280                pass
281            if num < 1 or num > 10:
282                continue
283            if not isinstance(val, xmlrpclib.Binary):
284                raise xmlrpclib.Fault(
285                    xmlrpclib.INVALID_METHOD_PARAMS,
286                    "Invalid data for finger %s" % num)
287            fmt = get_fileformat(None, val.data)
288            if fmt != 'fpm':
289                raise xmlrpclib.Fault(
290                    xmlrpclib.INVALID_METHOD_PARAMS,
291                    "Invalid file format for finger %s" % num)
292            file_store = getUtility(IExtFileStore)
293            file_id = IFileStoreNameChooser(student).chooseName(
294                attr='%s.fpm' % num)
295            file_store.createFile(file_id, StringIO(val.data))
296            result = True
297        return result
298
[11667]299    @grok.require('waeup.getBiometricData')
300    def get_student_fingerprints(self, identifier=None):
301        """Returns student fingerprint data if available.
[11671]302
303        Result set is a dictionary with entries for ``email``,
304        ``firstname``, ``lastname``, ``img``, ``img_name``, and
305        ``fingerprints``.
306
307        Here ``img`` and ``img_name`` represent a photograph of the
308        student (binary image data and filename
309        respectively).
310
311        ``fingerprints`` is a dictionary itself with possible entries
312        ``1`` to ``10``, containing binary minutiae data
313        (i.e. fingerprint scans).
[11667]314        """
315        students = self.context['students']
316        student = get_student(students, identifier)
317        if student is None:
318            return dict()
319        result = dict(
320            email=student.email,
321            firstname=student.firstname,
322            lastname=student.lastname,
[11668]323            fingerprints={},
324            img_name=None,
[11667]325            img=None,
326            )
327        file_store = getUtility(IExtFileStore)
[11668]328        img = file_store.getFileByContext(student, attr='passport.jpg')
329        if img is not None:
330            result.update(
331                img=xmlrpclib.Binary(img.read()),
332                img_name=os.path.basename(img.name))
333
334        for num in [str(x + 1) for x in range(10)]:
[11671]335            fp_file = file_store.getFileByContext(
[11667]336                student, attr='%s.fpm' % num)
337            if fp_file is not None:
[11671]338                result['fingerprints'][num] = xmlrpclib.Binary(fp_file.read())
[11667]339        return result
Note: See TracBrowser for help on using the repository browser.