Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

Activities in Lino Voga

This document specifies how the lino_xl.lib.courses plugin is being used in Lino Voga.

See also The courses plugin.

Side note: Code snippets (lines starting with >>>) in this document get tested as part of our development workflow. The following initialization snippet tells you which demo project is being used in this document.

>>> from lino import startup
>>> startup('lino_book.projects.voga2.settings')
>>> from lino.api.doctest import *

Implementation

>>> dd.plugins.courses
<lino_voga.lib.roger.courses.Plugin lino_voga.lib.roger.courses(needs ['lino_xl.lib.cal'])>
>>> dd.plugins.courses.__class__.__bases__
(<class 'lino_voga.lib.courses.Plugin'>,)
>>> settings.SITE.default_ui
'lino_react.react'
>>> pprint(settings.SITE.kernel.web_front_ends)
[<lino_react.react.Plugin lino_react.react(needs ['lino.modlib.jinja'])>]

Activity layouts

The ActivityLayouts choicelist in Lino Voga defines the following areas:

>>> rt.show(courses.ActivityLayouts)
======= ========== ========== ==================
 value   name       text       Table
------- ---------- ---------- ------------------
 C       default    Courses    courses.Courses
 H       hikes      Hikes      courses.Hikes
 J       journeys   Journeys   courses.Journeys
======= ========== ========== ==================

The lino_xl.lib.courses plugin has two settings teacher_model and pupil_model:

>>> dd.plugins.courses.teacher_model
<class 'lino_voga.lib.courses.models.Teacher'>
>>> dd.plugins.courses.pupil_model
<class 'lino_voga.lib.roger.courses.models.Pupil'>
class lino_voga.lib.courses.Pupil

Django model used to represent a pupil.

It defines an additional field pupil_type.

pupil_type

Pointer to PupilType.

class lino_voga.lib.courses.Teacher

Django model used to represent a teacher.

It has an additional field teacher_type.

teacher_type

Pointer to TeacherType.

The demo database has 35 pupils and 9 teachers:

>>> rt.models.courses.Pupil.objects.count()
35
>>> rt.models.courses.Teacher.objects.count()
9
>>> rt.show('courses.Teachers')
==================== =============================== =================
 Name                 Address                         Instructor type
-------------------- ------------------------------- -----------------
 Hans Altenberg       Aachener Straße, 4700 Eupen
 Charlotte Collard    Auf dem Spitzberg, 4700 Eupen
 Daniel Emonts        Bellmerin, 4700 Eupen
 Germaine Gernegroß   Buchenweg, 4700 Eupen
 Josef Jonas          Gülcherstraße, 4700 Eupen
 Marc Malmendier      Heidhöhe, 4700 Eupen
 Edgard Radermacher   4730 Raeren
 Tom Thess            4700 Eupen
 David da Vinci       4730 Raeren
==================== =============================== =================
>>> ses = rt.login('robin')
class lino_voga.lib.courses.PupilType

Django model used to represent a pupil type.

>>> ses.show(rt.models.courses.PupilTypes)
==== =========== ============= ================== ==================
 ID   Reference   Designation   Designation (de)   Designation (fr)
---- ----------- ------------- ------------------ ------------------
 1    M           Member        Mitglied           Member
 2    H           Helper        Helfer             Helper
 3    N           Non-member    Nicht-Mitglied     Non-member
==== =========== ============= ================== ==================
class lino_voga.lib.courses.TeacherType

Django model used to represent a teacher type.

>>> ses.show(rt.models.courses.TeacherTypes)
... 
==== =========== ================== ======================= ======================
 ID   Reference   Designation        Designation (de)        Designation (fr)
---- ----------- ------------------ ----------------------- ----------------------
 1    S           Independant        Selbstständig           Indépendant
 2    EP          Voluntary (flat)   Ehrenamtlich pauschal   Volontaire (forfait)
 3    ER          Voluntary (real)   Ehrenamtlich real       Volontaire (réel)
 4    LBA         LEA                LBA                     ALE
==== =========== ================== ======================= ======================

See also Managing participants in Lino Voga.

Enrolments

class lino_voga.lib.courses.Course

Extends the standard model (lino_xl.lib.courses.Course) by adding a field fee.

Also adds a ref field and defines a custom __str__() method.

The custom __str__() method defines how to textually represent a course e.g. in the dropdown list of a combobox or in reports. Rules:

  • If ref is given, it is shown, but see also the two following cases.

  • If name is given, it is shown (possibly behind the ref).

  • If a line (series) is given, it is shown (possibly behind the ref).

  • If neither ref nor name nor line are given, show a simple “Course #”.

ref

An identifying public course number to be used by both external and internal partners for easily referring to a given course.

name

A short designation for this course. An extension of the ref.

line

Pointer to the course series.

fee

The default participation fee to apply for new enrolments.

payment_term

The payment term to use when writing an invoice. If this is empty, Lino will use the partner’s default payment term.

paper_type

The paper_type to use when writing an invoice. If this is empty, Lino will use the site’s default paper type.

class lino_voga.lib.courses.Enrolment

Adds some fields and inherits from InvoiceGenerator.

fee

The participation fee to apply for this enrolment.

free_events

Number of events to add for first invoicing for this enrolment.

amount

The total amount to pay for this enrolment. This is places * fee.

pupil_info

Show the name and address of the participant. Overrides lino_xl.lib.courses.models.Enrolment.pupil_info in order to add (between parentheses after the name) some information needed to compute the price.

invoicing_info

A virtual field showing a summary of recent invoicings.

payment_info

A virtual field showing a summary of due accounting movements (debts and payments).

>>> rt.show('courses.EnrolmentStates')
======= =========== =========== ============= ============= ==============
 value   name        text        Button text   invoiceable   Uses a place
------- ----------- ----------- ------------- ------------- --------------
 10      requested   Requested                 No            No
 11      trying      Trying                    No            Yes
 20      confirmed   Confirmed                 Yes           Yes
 30      cancelled   Cancelled                 No            No
======= =========== =========== ============= ============= ==============
>>> rt.show('courses.EnrolmentStates', language="de")
====== =========== =========== ============= ============== =====================
 Wert   name        Text        Button text   Fakturierbar   Besetzt einen Platz
------ ----------- ----------- ------------- -------------- ---------------------
 10     requested   Angefragt                 Nein           Nein
 11     trying      Test                      Nein           Ja
 20     confirmed   Bestätigt                 Ja             Ja
 30     cancelled   Storniert                 Nein           Nein
====== =========== =========== ============= ============== =====================

The fee of a course

Per course and per enrolment we get a new field fee.

Number of places

The max_places (Available places) field of a course contains the number of available places.

It is a simple integer value and expresses an absolute upper limit which cannot be bypassed. Lino will refuse to confirm an enrolment if this limit is reached. Here is a user statement about this:

Also im Prinzip nehmen wir bei den Computerkursen maximal 10 Leute an. Da wir aber überall über 12 Geräte verfügen, können wir immer im Bedarfsfall um 2 Personen aufstocken. Also bei PC-Kursen setzen wir das Maximum immer auf 12. Als Regel gilt dann, dass wir immer nur 10 annehmen, aber falls unbedingt erforderlich auf 12 gehen können.

Every enrolment has a field places (Places used) which expresses how many places this enrolment takes. This is usually 1, but for certain types of courses, e.g. bus travels, it can happen that one enrolment is for two or more persons.

Waiting things

The following is waiting for #526 before it can work:

>>> # demo_get('robin', 'choices/courses/Courses/city', 'bla', 0)

ActivitiesByLine

There are two Yoga courses, i.e. two courses in the Yoga line:

>>> obj = courses.Line.objects.get(pk=10)
>>> obj
Line #10 ('Yoga')
>>> rt.show(rt.models.courses.ActivitiesByLine, obj)
=================== ============== ================= ============= ====================
 Activity            When           Room              Times         Instructor
------------------- -------------- ----------------- ------------- --------------------
 `024C Yoga <…>`__   Every Monday   Conference room   18:00-19:30   Marc Malmendier
 `025C Yoga <…>`__   Every Friday   Conference room   19:00-20:30   Edgard Radermacher
=================== ============== ================= ============= ====================
>>> ContentType = rt.models.contenttypes.ContentType
>>> json_fields = 'count html_text rows title success no_data_text param_values'
>>> kw = dict(fmt='json', limit=10, start=0)
>>> mt = ContentType.objects.get_for_model(courses.Line).pk
>>> demo_get('robin',
...          'api/courses/ActivitiesByLine', json_fields, 3,
...          mt=mt, mk=obj.pk, **kw)

Status report

The status report gives an overview of active courses.

(TODO: demo fixture should avoid negative free places)

>>> rt.show(rt.models.courses.StatusReport)
... 
~~~~~~~~
Journeys
~~~~~~~~

========================= ======================= ======= ================== =========== ============= =========== ========
 Activity                 When                    Times   Available places   Confirmed   Free places   Requested   Trying
------------------------- ----------------------- ------- ------------------ ----------- ------------- ----------- --------
 `001 Greece 2014 <…>`__   14/08/2014-20/08/2014                              3                         0           0
 **Total (1 rows)**                                       **0**              **3**       **0**         **0**       **0**
========================= ======================= ======= ================== =========== ============= =========== ========

~~~~~~~~
Computer
~~~~~~~~

=========================================== ================= ============= ================== =========== ============= =========== ========
 Activity                                   When              Times         Available places   Confirmed   Free places   Requested   Trying
------------------------------------------- ----------------- ------------- ------------------ ----------- ------------- ----------- --------
 `003 comp (First Steps) <…>`__              Every Monday      13:30-15:00   3                  2           1             0           0
 `004 comp (First Steps) <…>`__              Every Wednesday   17:30-19:00   3                  3           0             1           0
 `005 comp (First Steps) <…>`__              Every Friday      13:30-15:00   3                  2           1             0           0
 `006C WWW (Internet for beginners) <…>`__   Every Monday      13:30-15:00   4                  2           2             1           0
 `007C WWW (Internet for beginners) <…>`__   Every Wednesday   17:30-19:00   4                  2           2             0           0
 `008C WWW (Internet for beginners) <…>`__   Every Friday      13:30-15:00   4                  3           1             0           0
 **Total (6 rows)**                                                         **21**             **14**      **7**         **2**       **0**
=========================================== ================= ============= ================== =========== ============= =========== ========

~~~~~
Sport
~~~~~

========================================= ================= ============= ================== =========== ============= =========== ========
 Activity                                 When              Times         Available places   Confirmed   Free places   Requested   Trying
----------------------------------------- ----------------- ------------- ------------------ ----------- ------------- ----------- --------
 `009C BT (Belly dancing) <…>`__           Every Wednesday   19:00-20:00   10                 3           7             0           0
 `010C FG (Functional gymnastics) <…>`__   Every Monday      11:00-12:00   5                  2           3             0           0
 `011C FG (Functional gymnastics) <…>`__   Every Monday      13:30-14:30   5                  2           3             1           0
 `012 Rücken (Swimming) <…>`__             Every Monday      11:00-12:00   20                 3           17            0           0
 `013 Rücken (Swimming) <…>`__             Every Monday      13:30-14:30   20                 3           17            1           0
 `014 Rücken (Swimming) <…>`__             Every Tuesday     11:00-12:00   20                 3           17            0           0
 `015 Rücken (Swimming) <…>`__             Every Tuesday     13:30-14:30   20                 0           20            0           0
 `016 Rücken (Swimming) <…>`__             Every Thursday    11:00-12:00   20                 3           17            0           0
 `017 Rücken (Swimming) <…>`__             Every Thursday    13:30-14:30   20                 3           17            1           0
 `018 SV (Self-defence) <…>`__             Every Friday      18:00-19:00   12                 2           10            0           0
 `019 SV (Self-defence) <…>`__             Every Friday      19:00-20:00   12                 3           9             0           0
 **Total (11 rows)**                                                      **164**            **27**      **137**       **3**       **0**
========================================= ================= ============= ================== =========== ============= =========== ========

~~~~~~~~~~
Meditation
~~~~~~~~~~

============================================= ============== ============= ================== =========== ============= =========== ========
 Activity                                     When           Times         Available places   Confirmed   Free places   Requested   Trying
--------------------------------------------- -------------- ------------- ------------------ ----------- ------------- ----------- --------
 `020C GLQ (GuoLin-Qigong) <…>`__              Every Monday   18:00-19:30                      0                         0           0
 `021C GLQ (GuoLin-Qigong) <…>`__              Every Friday   19:00-20:30                      2                         1           0
 `022C MED (Finding your inner peace) <…>`__   Every Monday   18:00-19:30   30                 2           28            0           0
 `023C MED (Finding your inner peace) <…>`__   Every Friday   19:00-20:30   30                 3           27            0           0
 `024C Yoga <…>`__                             Every Monday   18:00-19:30   20                 2           18            0           0
 `025C Yoga <…>`__                             Every Friday   19:00-20:30   20                 2           18            1           0
 **Total (6 rows)**                                                        **100**            **11**      **91**        **2**       **0**
============================================= ============== ============= ================== =========== ============= =========== ========

Free places

Note the free places field which is not always trivial. Basicially it contains max_places - number of confirmed enrolments. But it also looks at the end_date of these enrolments.

List of courses which have a confirmed ended enrolment and a limited number of places:

>>> qs = courses.Enrolment.objects.filter(end_date__lt=dd.today(),
...     state=courses.EnrolmentStates.confirmed, course__max_places__isnull=False)
>>> for obj in qs:
...     print("{} {} {} {}".format(
...         obj.course.id, obj.course.max_places,
...         obj.course.confirmed,
...         obj.course.get_free_places(dd.today())))
9 10 3 7
19 12 3 9
5 3 2 1
22 30 2 28
25 20 2 18
10 5 2 3
8 4 3 1
3 3 2 1
23 30 3 27
7 4 2 2
18 12 2 10
6 4 2 2
24 20 2 18

In course #5 there are 3 confirmed enrolments, but (on 2015-05-22) only 2 of them are actually taking a place because one has already ended.

>>> obj = courses.Course.objects.get(pk=5)
>>> rt.show(courses.EnrolmentsByCourse, obj, column_names="pupil start_date end_date places state")
======================================== ============ ============ ============= ===========
 Participant                              Start date   End date     Places used   State
---------------------------------------- ------------ ------------ ------------- -----------
 Didier di Rupo (MS)                                                1             Confirmed
 Dorothée Dobbelstein-Demeulenaere (ME)                22/04/2014   1             Confirmed
 Josefine Leffin (MEL)                    02/04/2014                1             Confirmed
 **Total (3 rows)**                                                 **3**
======================================== ============ ============ ============= ===========
>>> print(obj.max_places)
3
>>> print(obj.get_free_places())
1

Above situation is because we are looking at it on 20150522:

>>> print(dd.today())
2015-05-22

The same request on earlier dates yields different results:

On 20140403 nobody has left yet, all 3 places are taken and therefore no place is free:

>>> print(obj.get_free_places(i2d(20140403)))
0

On 20140422 is Dorothée’s last day, so her place is not yet free:

>>> print(obj.get_free_places(i2d(20140422)))
0

But the next day she is gone and her place available again:

>>> print(obj.get_free_places(i2d(20140423)))
1

Filtering pupils

>>> print(rt.models.courses.Pupils.params_layout.main)
course partner_list #aged_from #aged_to gender show_members show_lfv show_ckk show_raviva

There are 36 pupils (21 men and 15 women) in our database:

>>> json_fields = 'count rows html_text title success no_data_text param_values'
>>> kwargs = dict(fmt='json', limit=10, start=0)
>>> demo_get('robin', 'api/courses/Pupils', json_fields, 36, **kwargs)
>>> kwargs.update(pv=['', '', 'M', '', '', '', ''])
>>> demo_get('robin', 'api/courses/Pupils', json_fields, 21, **kwargs)
>>> kwargs.update(pv=['', '', 'F', '', '', '', ''])
>>> demo_get('robin', 'api/courses/Pupils', json_fields, 15, **kwargs)
>>> json_fields = 'navinfo disable_delete data id title'
>>> kwargs = dict(fmt='json', an='detail')
>>> demo_get('robin', 'api/courses/Lines/2', json_fields, **kwargs)

Presence sheet

The presence sheet of a course is a printable document where course instructors can manually record the presences of the participants for every event.

Item description

courses/Enrolment/item_description.html

The template used to fill the items description.