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

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

Add webservice to check if graduated student exists.

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