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

Calendar functions in Lino Avanti

This document describes how standard calendar functionality is being extended by Lino Avanti.

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.

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

Lino Avanti defines a plugin lino_avanti.lib.cal that extends lino_xl.lib.cal.

class lino_avanti.lib.cal.Guest
absence_reason

Why the pupil was absent. Choices for this field are defined in AbsenceReasons.

Calendar workflow

It’s almost like lino_xl.lib.cal.workflows.voga, except that the workflow transition for presence state “excused” (GuestStates) is customized. It gives permission only when the course has “Excuses permitted” checked.

In existing data (until June 2018) the “excused” and “absent” states. In August 2018 we decided to no longer make this differentiation. In September 2024 we partly re-introduced it for courses that have the new database field can_excuse checked.

>>> rt.show(cal.GuestStates)
======= =========== ============ =========== =============
 value   name        Afterwards   text        Button text
------- ----------- ------------ ----------- -------------
 10      invited     No           Invited     ?
 40      present     Yes          Present     ☑
 50      missing     Yes          Missing     ☉
 60      excused     No           Excused     ⚕
 90      cancelled   No           Cancelled   ☒
======= =========== ============ =========== =============
>>> show_workflow(cal.GuestStates.workflow_actions)
============= ============== =========== ============== ===================================
 Action name   Verbose name   Help text   Target state   Required states
------------- -------------- ----------- -------------- -----------------------------------
 wf1           ☑              Present     Present        invited
 wf2           ☉              Missing     Missing        invited
 wf3           Excused        Excused     Excused        invited
 wf4           ?              Invited     Invited        missing present excused cancelled
 wf5           ☒              Cancelled   Cancelled      invited
============= ============== =========== ============== ===================================
>>> rt.show(cal.EntryStates)
======= ============ ============ ============= ============= ======== ============= =========
 value   name         text         Button text   Fill guests   Stable   Transparent   No auto
------- ------------ ------------ ------------- ------------- -------- ------------- ---------
 10      suggested    Suggested    ?             Yes           No       No            No
 20      draft        Draft        ☐             Yes           No       No            No
 50      took_place   Took place   ☑             No            Yes      No            No
 70      cancelled    Cancelled    ☒             No            Yes      Yes           Yes
======= ============ ============ ============= ============= ======== ============= =========
>>> show_workflow(cal.EntryStates.workflow_actions)
============== ============== ============ ============== ================================
 Action name    Verbose name   Help text    Target state   Required states
-------------- -------------- ------------ -------------- --------------------------------
 reset_event    Reset          Suggested    Suggested      suggested took_place cancelled
 wf2            ☐              Draft        Draft          suggested cancelled took_place
 wf3            Took place     Took place   Took place     suggested draft cancelled
 cancel_entry   Cancel         Cancelled    Cancelled      suggested draft scheduled
============== ============== ============ ============== ================================

Choicelists

>>> base = '/choices/cal/Guests/partner'
>>> show_choices("rolf", base + '?query=') 

ABAD Aábdeen (114/nathalie)
ABBASI Aáishá (118/romain)
ABDALLA Aádil (120/rolf)
ABDALLAH Aáish (127/robin)
ABDELLA Aákif (128/nathalie)
...
>>> show_choices("audrey", base + '?query=') 

(114) from Eupen
(118) from Eupen
(120) from Eupen
(127) from Eupen
(128) from Eupen
(136) from Eupen
...

GuestsByPartner

GuestsByPartner shows all presences except those in more than one week and sorts them chronologically:

>>> obj = avanti.Client.objects.get(pk=115)
>>> rt.show(cal.GuestsByPartner, obj) 
January 2017: *Mon 16.*☑ *Tue 17.*☑ *Thu 19.*☑ *Fri 20.*☑ *Mon 23.*☑ *Tue 24.*☑ *Thu 26.*☒ *Fri 27.*☑ *Mon 30.*☑ *Tue 31.*☑
February 2017: *Thu 02.*☑ *Fri 03.*☑ *Mon 06.*☑ *Tue 07.*☑ *Thu 09.*? *Fri 10.*? *Mon 13.*? *Tue 14.*? *Thu 16.*? *Fri 17.*? *Mon 20.*? *Tue 21.*?
Suggested : 8 ,  Draft : 0 ,  Took place : 13 ,  Cancelled : 1

Absence reasons

In Lino Avanti we record and analyze why pupils have been missing.

class lino_avanti.lib.cal.AbsenceReasons

The table of possible absence reasons.

Accessible via Configure ‣ Calendar ‣ Absence reasons.

>>> show_menu_path(cal.AbsenceReasons)
Configure --> Calendar --> Absence reasons
>>> rt.show(cal.AbsenceReasons)
==== ==================== ========================== ====================
 ID   Designation          Designation (de)           Designation (fr)
---- -------------------- -------------------------- --------------------
 1    Sickness             Krankheit                  Sickness
 2    Other valid reason   Sonstiger gültiger Grund   Other valid reason
 3    Unknown              Unbekannt                  Inconnu
 4    Unjustified          Unberechtigt               Unjustified
==== ==================== ========================== ====================
class lino_avanti.lib.cal.AbsenceReason
name

Courses with excuses

The following snippets show how the checkbox Allow excuses on a course influences the choices in the Workflow field when recording presences of participants. Participants in state “Invited” have either four workflow actions or only three because [⚕] is available only when Allow excuses is checked.

>>> ses = rt.login("robin")
>>> obj = cal.Event.objects.get(pk=193)
>>> obj
Event #193 ('Lesson 9 (30.01.2017 18:00)')
>>> obj.owner.can_excuse = True
>>> obj.owner.save()
>>> ses.show(cal.GuestsByEvent, master_instance=obj)
=================================== ======= ================================= ================ ========
 Participant                         Role    Workflow                          Absence reason   Remark
----------------------------------- ------- --------------------------------- ---------------- --------
 ABAD Aábdeen (114/nathalie)         Pupil   **? Invited** → [☑] [☉] [⚕] [☒]
 ABDALLAH Aáish (127/robin)          Pupil   **☑ Present** → [?]
 ABDELLA Aákif (128/nathalie)        Pupil   **☉ Missing** → [?]
 ABDI Aátifá (136/rolf)              Pupil   **⚕ Excused** → [?]
 ABDOU Abeer (143/nelly)             Pupil   **☒ Cancelled** → [?]
 ABDULLA Abbáás (152/rolf)           Pupil   **? Invited** → [☑] [☉] [⚕] [☒]
 ABDULLAH Afááf (155/robin)          Pupil   **☑ Present** → [?]
 ABEZGAUZ Adrik (112/nelly)          Pupil   **☉ Missing** → [?]
 ABOOD Abdul Fáttááh (163/rolf)      Pupil   **⚕ Excused** → [?]
 ARSHAN Afimiá (132/nelly)           Pupil   **☒ Cancelled** → [?]
 ARTEMIEVA Aloyshá (139/rolf)        Pupil   **? Invited** → [☑] [☉] [⚕] [☒]
 BAH Aráli (119/nelly)               Pupil   **☑ Present** → [?]
 BEK-MURZIN Agápiiá (160/romain)     Pupil   **☉ Missing** → [?]
 DEMEULENAERE Dorothée (121/nelly)   Pupil   **⚕ Excused** → [?]
 FOFANA Denzel (147/romain)          Pupil   **☒ Cancelled** → [?]
=================================== ======= ================================= ================ ========
>>> obj.owner.can_excuse = False
>>> obj.owner.save()
>>> ses.show(cal.GuestsByEvent, master_instance=obj)
=================================== ======= ============================= ================ ========
 Participant                         Role    Workflow                      Absence reason   Remark
----------------------------------- ------- ----------------------------- ---------------- --------
 ABAD Aábdeen (114/nathalie)         Pupil   **? Invited** → [☑] [☉] [☒]
 ABDALLAH Aáish (127/robin)          Pupil   **☑ Present** → [?]
 ABDELLA Aákif (128/nathalie)        Pupil   **☉ Missing** → [?]
 ABDI Aátifá (136/rolf)              Pupil   **⚕ Excused** → [?]
 ABDOU Abeer (143/nelly)             Pupil   **☒ Cancelled** → [?]
 ABDULLA Abbáás (152/rolf)           Pupil   **? Invited** → [☑] [☉] [☒]
 ABDULLAH Afááf (155/robin)          Pupil   **☑ Present** → [?]
 ABEZGAUZ Adrik (112/nelly)          Pupil   **☉ Missing** → [?]
 ABOOD Abdul Fáttááh (163/rolf)      Pupil   **⚕ Excused** → [?]
 ARSHAN Afimiá (132/nelly)           Pupil   **☒ Cancelled** → [?]
 ARTEMIEVA Aloyshá (139/rolf)        Pupil   **? Invited** → [☑] [☉] [☒]
 BAH Aráli (119/nelly)               Pupil   **☑ Present** → [?]
 BEK-MURZIN Agápiiá (160/romain)     Pupil   **☉ Missing** → [?]
 DEMEULENAERE Dorothée (121/nelly)   Pupil   **⚕ Excused** → [?]
 FOFANA Denzel (147/romain)          Pupil   **☒ Cancelled** → [?]
=================================== ======= ============================= ================ ========

Don’t read on

>>> print(cal.Event.objects.get(pk=123))
Ash Wednesday (01.03.2017)
>>> test_client.force_login(rt.login('robin').user)
>>> def mytest(k):
...     url = 'http://127.0.0.1:8000/api/cal/MyEntries/{}'.format(k)
...     # url = 'http://127.0.0.1:8000/#/api/cal/Entries/{}'.format(k)
...     res = test_client.get(url, REMOTE_USER='robin')
...     print(res)
...     # assert res.status_code == 200
...     # print(res.content)
>>> mytest("123")  
Not Found: /api/cal/MyEntries/123
<HttpResponseNotFound status_code=404, "text/html; charset=utf-8">

Until 20241004 the result was a traceback:

Traceback (most recent call last):
...
AttributeError: 'Renderer' object has no attribute 'html_page'
>>> url = '/choices/cal/Events?query=¹'
>>> res = test_client.get(url, REMOTE_USER='robin')
>>> assert res.status_code == 200
>>> res.content
b'{ "count": 0, "rows": [  ] }'