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

Last change on this file since 16281 was 16269, checked in by Henrik Bettermann, 4 years ago

Use the power of the get_payments function.

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