cal : Calendar functionality

The lino_xl.lib.cal plugin adds calendar functionality.

A tested document

This is a tested document. The following instructions are used for initialization:

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

Glossary

Calendar entry

A lapse of time to be visualized in a calendar.

An instance of the Event database model.

A calendar entry usually has a given type. It can have a list of guests or participants (more precisely presences).

Calendar entry type

The type of a calendar entry.

An instance of the EventType database model.

Often used calendar entry types are appointment, informal meeting, public event, holiday.

Appointment

A calendar entry for which other people (external partners or colleagues) are involved and should be informed about schedule changes.

French: Rendez-vous, German: "Termin".

Appointments are defined by assigning them a calendar entry type that has the EventType.is_appointment field checked.

all-day

An all-day calendar entry is one that has no starting and ending time.

same-day

A same-day calendar entry is one that ends on the same date as it starts. The ending date field is empty. If a somae-day calendar entry has its ending time before the starting time, the ending date is understood as the day after the starting date.

Presence

The fact that a given person is invited guest or participant to a given calendar entry.

Task

Something a user plans to do.

Daily planner

A table showing a calendar overview for a given day. Both the rows and the columns can be configured per application or locally per site.

See DailyPlanner.

My calendar entries

A table showing today's and all future appointments of the user who requests it. It starts today and is sorted ascending.

Automatic calendar entry

A calendar entry that has been generated by a calendar generator.

The rules for generating automatic calendar entries are application-specific. The application programmer does this by defining one or several calendar generators.

Calendar generator

A database object that can generate a series of calendar entries.

Calendar entries

class lino_xl.lib.cal.Event

The Django model which represents a calendar entry.

The internal model name is Event for historical reasons. Users see it as "Calendar entry".

>>> print(rt.models.cal.Event._meta.verbose_name)
Calendar entry
start_date

The starting date of this entry. May not be empty.

end_date

The ending date of this entry. Leave empty for same-day entries.

start_time

The starting time. If this is empty, the entry is considered a all-day entry.

Changing this field will change the end_time field as well, if the entry's type has a default duration (EventType.default_duration).

end_time

The ending time. If this is before the starting time, and no ending date is given, the entry ends the day after.

summary

A one-line descriptive text.

description

A longer descriptive text.

user

The responsible user.

assigned_to

Another user who is expected to take responsibility for this entry.

See lino.modlib.users.Assignable.assigned_to.

event_type

The type of this calendar entry.

Every calendar entry should have this field pointing to a given EventType, which holds extended configurable information about this entry.

state

The state of this entry. The state can change according to rules defined by the workflow.

transparent

Whether this entry should allow other entries at the same time.

when_html

Shows the date and time of the entry with a link that opens all entries on that day.

See EntriesByDay.

Deprecated because it is usually irritating. Use when_text, and users open the detail window as usualy by double-clicking on the row. And then they have an action on each entry for opening EntriesByDay if they want.

show_conflicting

A ShowSlaveTable button which opens the ConflictingEvents table for this event.

update_guests()

Populate or update the list of participants for this calendar entry according to the suggestions.

Calls suggest_guests() to instantiate them.

  • No guests are added when loading from dump

  • The entry must be in a state which allows editing the guests

  • Deletes existing guests in state invited that are no longer suggested

update_events()

Create or update all other automatic calendar entries of this series.

show_today()
get_conflicting_events(self)

Return a QuerySet of calendar entries that conflict with this one. Must work also when called on an unsaved instance. May return None to indicate an empty queryset. Applications may override this to add specific conditions.

has_conflicting_events(self)

Whether this entry has any conflicting entries.

This is roughly equivalent to asking whether get_conflicting_events() returns more than 0 events.

Except when this event's type tolerates more than one events at the same time.

suggest_guests(self)

Yield the list of unsaved Guest instances to be added to this calendar entry.

This method is called from update_guests().

get_event_summary(self, ar)

How this event should be summarized in contexts where possibly another user is looking (i.e. currently in invitations of guests, or in the extensible calendar panel).

before_ui_save(self, ar)

Mark the entry as "user modified" by setting a default state. This is important because EventGenerators may not modify any user-modified Events.

auto_type_changed(self, ar)

Called when the number of this automatically generated entry (auto_type ) has changed.

The default updates the summary.

get_calendar(self)

Returns the Calendar which contains this entry, or None if no subscription is found.

Needed for ext.ensible calendar panel.

The default implementation returns None. Override this if your app uses Calendars.

>>> show_fields(rt.models.cal.Event,
...     'start_date start_time end_date end_time user summary description event_type state')
... 
+---------------+---------------------+---------------------------------------------------------------------------+
| Internal name | Verbose name        | Help text                                                                 |
+===============+=====================+===========================================================================+
| start_date    | Start date          | The starting date of this entry.  May not be empty.                       |
+---------------+---------------------+---------------------------------------------------------------------------+
| start_time    | Start time          | The starting time.  If this is empty, the entry is considered a           |
|               |                     | all-day entry.                                                            |
+---------------+---------------------+---------------------------------------------------------------------------+
| end_date      | End Date            | The ending date of this entry. Leave empty for same-day entries.          |
+---------------+---------------------+---------------------------------------------------------------------------+
| end_time      | End Time            | The ending time.  If this is before the starting time, and no ending date |
|               |                     | is given, the entry ends the day after.                                   |
+---------------+---------------------+---------------------------------------------------------------------------+
| user          | Responsible user    | The responsible user.                                                     |
+---------------+---------------------+---------------------------------------------------------------------------+
| summary       | Short description   | A one-line descriptive text.                                              |
+---------------+---------------------+---------------------------------------------------------------------------+
| description   | Description         | A longer descriptive text.                                                |
+---------------+---------------------+---------------------------------------------------------------------------+
| event_type    | Calendar entry type | The type of this calendar entry.                                          |
+---------------+---------------------+---------------------------------------------------------------------------+
| state         | State               | The state of this entry. The state can change according to                |
|               |                     | rules defined by the workflow.                                            |
+---------------+---------------------+---------------------------------------------------------------------------+

Lifecycle of a calendar entry

Every calendar entry has a given state which can change according to rules defined by the application.

The default list of choices for this field contains the following values.

>>> 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
======= ============ ============ ============= ============= ======== ============= =========
class lino_xl.lib.cal.EntryStates

The list of possible states of a calendar entry.

class lino_xl.lib.cal.EntryState

Every calendar entry state is an instance of this and has some attributes.

fill_guests

Whether the presences of an entry in this state are filled in automatically. If this is True (and if the entry type's fill_presences is True as well), the presences cannot be modified manually by the used.

TODO: rename this to fill_presences

guest_state

Force the given guest state for all guests when an entry is set to this state and when EventType.force_guest_states is True.

transparent

Whether an entry in this state is considered transparent, i.e. dos not conflict with other entries at the same moment.

fixed

Whether an entry in this state is considered "stable" when differentiating between "stable" and "pending" entries.

This does not influence editability of the entry.

See EventEvents.stable and EventEvents.pending.

noauto

Whether switching to this state will clear the entry's auto_type field, i.e. it is no longer considered an automatically generated entry, IOW it "escapes" from its entry generator.

edit_guests

Old name for fill_guests.

Calendar entry types

Every calendar entry has a field Event.event_type which points to its type.

There are different types of calendar entries. The list of calendar entry types is configurable via Configure ‣ Calendar ‣ Calendar entry types. The type of a calendar entry can be used to change behaviour and rules.

class lino_xl.lib.cal.EventType

Django model representing a calendar entry type.

The possible value of the Event.event_type field.

default_duration

An optional default duration for calendar entries of this type.

If this field is set, Lino will help with entering Event.end_time and Event.start_date of an calendar entries by changing the end_time of an entry when the start_date is changed (and the start_time when the end_date)

event_label

Default text for summary of new entries.

is_appointment

Whether entries of this type are considered "appointments" (i.e. whose time and place have been agreed upon with other users or external parties).

Certain tables show only entries whose type has the is_appointment field checked. See show_appointments.

max_days

The maximal number of days allowed as duration. 0 means no limit.

If this is 1, Lino will set end_date to None.

See also LongEntryChecker

locks_user

Whether calendar entries of this type make the user unavailable for other locking events at the same time.

max_conflicting

How many conflicting events should be tolerated.

transparent

Allow entries of this type to conflict with other events.

force_guest_states

Whether presence states should be forced to those defined by the entry state.

This will have an effect only if the application programmer has defined a mapping from entry state to the guest state by setting EntryState.guest_state for at least one entry state.

Lino Tera uses this for individual and family therapies where they don't want to manage presences of every participant. When an appointment is set to "Took place", Lino sets all guests to "Present". See Calendar in Lino Tera for a usage example.

fill_presences

Whether guests should be automatically filled for calendar entries of this type.

fill_guests

Planned name for fill_presences.

class lino_xl.lib.cal.EventTypes

The list of entry types defined on this site.

This is usually filled in the std demo fixture of the application.

>>> rt.show(cal.EventTypes)
=========== =============== ================== ================== ================ ============= ===================== =================
 Reference   Designation     Designation (de)   Designation (fr)   Planner column   Appointment   Automatic presences   Locks all rooms
----------- --------------- ------------------ ------------------ ---------------- ------------- --------------------- -----------------
             Absences        Absences           Absences           External         Yes           No                    No
             First contact   First contact      First contact                       Yes           No                    No
             Holidays        Feiertage          Jours fériés       External         No            No                    Yes
             Internal        Intern             Interne            Internal         No            No                    No
             Lesson          Lesson             Lesson                              Yes           No                    No
             Meeting         Versammlung        Réunion            External         Yes           No                    No
=========== =============== ================== ================== ================ ============= ===================== =================

Calendar views

class lino_xl.lib.cal.CalendarView

Base class for all calendar views (daily, weekly and monthly).

class lino_xl.lib.cal.DailyView

Shows a calendar navigator with a configurable daily view.

class lino_xl.lib.cal.WeeklyView

Shows a calendar navigator with a configurable weekly view.

class lino_xl.lib.cal.MonthlyView

Shows a calendar navigator with a configurable monthly view.

The daily planner

The daily planner is a table that shows an overview on all events of a day.

>>> rt.show(cal.DailyPlanner)
============ =================================================== ==========
 Time range   External                                            Internal
------------ --------------------------------------------------- ----------
 *All day*    *Rolf Rompen Absent for private reasons Absences*
 *AM*         *08:30 Romain Raffault Rencontre Meeting*
 *PM*
============ =================================================== ==========
class lino_xl.lib.cal.DailyPlanner

The virtual table used to render the daily planner.

class lino_xl.lib.cal.PlannerColumns

A choicelist that defines the columns to appear in the daily planner. This list can be modified locally.

A default configuration has two columns in the daily planner:

>>> rt.show(cal.PlannerColumns)
======= ========== ==========
 value   name       text
------- ---------- ----------
 10      external   External
 20      internal   Internal
======= ========== ==========
class lino_xl.lib.cal.DailyPlannerRow

A database object that represents one row of the daily planner. The default configuration has "AM", "PM" and "All day".

>>> rt.show(cal.DailyPlannerRows)
===== ============= ================== ================== ============ ==========
 No.   Designation   Designation (de)   Designation (fr)   Start time   End time
----- ------------- ------------------ ------------------ ------------ ----------
 3     All day       Ganztags           Journée entière
 1     AM            Vormittags         Avant-midi                      12:00:00
 2     PM            Nachmittags        Après-midi         12:00:00
===== ============= ================== ================== ============ ==========

Calendars

Calendar entries can be grouped into "calendars".

class lino_xl.lib.cal.Calendar

The django model representing a calendar.

color

The color to use for entries of this calendar (in lino_xl.lib.extensible).

class lino_xl.lib.cal.Calendars
>>> rt.show(cal.Calendars)
==== ============= ================== ================== ============= =======
 ID   Designation   Designation (de)   Designation (fr)   Description   color
---- ------------- ------------------ ------------------ ------------- -------
 1    General       Allgemein          Général                          1
                                                                        **1**
==== ============= ================== ================== ============= =======

Note that the default implementation has no "Calendar" field per calendar entry. The Event model instead has a Event.get_calendar() method.

You might extend Event in your plugin as follows:

from lino_xl.lib.cal.models import *
class Event(Event):

    calendar = dd.ForeignKey('cal.Calendar')

    def get_calendar(self):
        return self.calendar

But in other cases it would create unnecessary complexity to add such a field. For example in Lino Welfare there is one calendar per User, and thus the get_calendar method is implemented as follows:

def get_calendar(self):
    if self.user is not None:
        return self.user.calendar

Or in Lino Voga there is one calendar per Room. Thus the get_calendar method is implemented as follows:

def get_calendar(self):
    if self.room is not None:
        return self.room.calendar

Automatic calendar entries

An event generator is something that can generate automatic calendar entries. Examples of event generators include

  • Holidays

  • A course, workshop or activity as used by Welfare, Voga and Avanti (subclasses of lino_xl.lib.courses.models.Course).

  • A reservation of a room in Lino Voga (lino_xl.lib.rooms.Reservation).

  • A coaching contract with a client in Lino Welfare (lino_welfare.modlib.isip.Contract and lino_welfare.modlib.jobs.Contract)

The main effect of the EventGenerator mixin is to add the UpdateEntries action.

The event generator itself does not necessarily also contain all those fields needed for specifying which events should be generated. These fields are implemented by another mixin named RecurrenceSet. A recurrence set is something that specifies which calendar events should get generated.

class lino_xl.lib.cal.EventGenerator

Base class for things that generate a series of events.

See Automatic calendar entries.

update_all_guests()

Update the presence lists of all calendar events generated by this.

do_update_events()

Create or update the automatic calendar entries controlled by this generator.

This is the :guilabel:` ⚡ ` button.

See UpdateEntries.

get_wanted_auto_events(self, ar)

Return a tuple of two dicts of "wanted" and "unwanted" events.

Both dicts map a sequence number to an Event instances. wanted holds events to be saved, unwanted holds events to be deleted.

If an event has been manually moved to another date, all subsequent events adapt to the new rythm (except those which have themselves been manually modified).

care_about_conflicts(self, we)

Whether this event generator should try to resolve conflicts (in resolve_conflicts())

resolve_conflicts(self, we, ar, rset, until)

Check whether given entry we conflicts with other entries and move it to a new date if necessary. Returns (a) the entry's start_date if there is no conflict, (b) the next available alternative date if the entry conflicts with other existing entries and should be moved, or (c) None if there are conflicts but no alternative date could be found.

ar is the action request who asks for this. rset is the RecurrenceSet.

class lino_xl.lib.cal.UpdateEntries

Generate or update the automatic events controlled by this object.

This action is installed as EventGenerator.do_update_events.

class lino_xl.lib.cal.UpdateEntriesByEvent

Update all events of this series.

This is installed as update_events on Event.

The generated calendar entries are "controlled" by their generator (their owner field points to the generator) and have a non-empty Event.auto_type field.

Recurrencies

The recurrency expresses how often something is to be repeated.

When generating automatic calendar events, Lino supports the following date recurrenies:

>>> rt.show(cal.Recurrencies)
======= ============= ====================
 value   name          text
------- ------------- --------------------
 O       once          once
 D       daily         daily
 W       weekly        weekly
 M       monthly       monthly
 Y       yearly        yearly
 P       per_weekday   per weekday
 E       easter        Relative to Easter
======= ============= ====================
class lino_xl.lib.cal.Recurrencies

List of possible choices for a 'recurrency' field.

Note that a recurrency (an item of this choicelist) is also a DurationUnit.

easter

Repeat events yearly, moving them together with the Easter data of that year.

Lino computes the offset (number of days) between this rule's start_date and the Easter date of that year, and generates subsequent events so that this offset remains the same.

Adding a duration unit

>>> start_date = i2d(20160327)
>>> cal.Recurrencies.once.add_duration(start_date, 1)
Traceback (most recent call last):
...
Exception: Invalid DurationUnit once
>>> cal.Recurrencies.daily.add_duration(start_date, 1)
datetime.date(2016, 3, 28)
>>> cal.Recurrencies.weekly.add_duration(start_date, 1)
datetime.date(2016, 4, 3)
>>> cal.Recurrencies.monthly.add_duration(start_date, 1)
datetime.date(2016, 4, 27)
>>> cal.Recurrencies.yearly.add_duration(start_date, 1)
datetime.date(2017, 3, 27)
>>> cal.Recurrencies.easter.add_duration(start_date, 1)
datetime.date(2017, 4, 16)

Recurrent events

In lino_book.projects.min2 we have a database model RecurrentEvent used to generate holidays. See also Defining holidays.

We are going to use this model for demonstrating some more features (which it inherits from RecurrenceSet and EventGenerator)

>>> obj = cal.RecurrentEvent(start_date=i2d(20160628))
>>> isinstance(obj, cal.RecurrenceSet)
True
>>> isinstance(obj, cal.EventGenerator)
True
>>> obj.tuesday = True
>>> obj.every_unit = cal.Recurrencies.weekly
>>> print(obj.weekdays_text)
Every Tuesday
>>> obj.every
1
>>> obj.every = 2
>>> print(obj.weekdays_text)
Every 2nd Tuesday
>>> obj.every_unit = cal.Recurrencies.monthly
>>> print(obj.weekdays_text)
Every 2nd month
>>> rt.show(cal.EventTypes, column_names="id name")
==== =============== ================== ==================
 ID   Designation     Designation (de)   Designation (fr)
---- --------------- ------------------ ------------------
 1    Absences        Absences           Absences
 5    First contact   First contact      First contact
 2    Holidays        Feiertage          Jours fériés
 4    Internal        Intern             Interne
 6    Lesson          Lesson             Lesson
 3    Meeting         Versammlung        Réunion
==== =============== ================== ==================
>>> obj.event_type = cal.EventType.objects.get(id=1)
>>> obj.max_events = 5
>>> ses = rt.login('robin')
>>> wanted, unwanted = obj.get_wanted_auto_events(ses)
>>> for num, e in wanted.items():
...     print(dd.fds(e.start_date))
28/06/2016
30/08/2016
01/11/2016
03/01/2017
07/03/2017

Note that above dates are not exactly every 2 months because

  • they are only on Tuesdays

  • Lino also avoids conflicts with existing events

>>> cal.Event.objects.order_by('start_date')[0]
Event #1 ("New Year's Day (01.01.2013)")
>>> obj.monday = True
>>> obj.wednesday = True
>>> obj.thursday = True
>>> obj.friday = True
>>> obj.saturday = True
>>> obj.sunday = True
>>> obj.start_date=i2d(20120628)
>>> wanted, unwanted = obj.get_wanted_auto_events(ses)
>>> for num, e in wanted.items():
...     print(dd.fds(e.start_date))
28/06/2012
28/08/2012
28/10/2012
28/12/2012
28/02/2013

Conflicting events

The demo datebase contains two appointments on Ash Wednesday 2017. These conflicting calendar events are visible as data problems (see checkdata : High-level integrity tests).

>>> chk = checkdata.Checkers.get_by_value('cal.ConflictingEventsChecker')
>>> rt.show(checkdata.ProblemsByChecker, chk)
... 
================= ================================ ====================================================
 Responsible       Database object                  Message
----------------- -------------------------------- ----------------------------------------------------
 Robin Rood        *Ash Wednesday (01.03.2017)*     Event conflicts with Seminar (01.03.2017 08:30).
 Robin Rood        *Rosenmontag (27.02.2017)*       Event conflicts with Rencontre (27.02.2017 11:10).
 Romain Raffault   *Rencontre (27.02.2017 11:10)*   Event conflicts with Rosenmontag (27.02.2017).
 Robin Rood        *Seminar (01.03.2017 08:30)*     Event conflicts with Ash Wednesday (01.03.2017).
================= ================================ ====================================================
>>> obj = cal.Event.objects.get(id=123)
>>> print(obj)
Ash Wednesday (01.03.2017)
>>> rt.show(cal.ConflictingEvents, obj)
============ ============ ========== ======== ====== ==================
 Start date   Start time   End Time   Client   Room   Responsible user
------------ ------------ ---------- -------- ------ ------------------
 01/03/2017   08:30:00     09:45:00                   Robin Rood
============ ============ ========== ======== ====== ==================

Transparent events

The entry type "Internal" is marked "transparent".

>>> obj = cal.EventType.objects.get(id=4)
>>> obj
EventType #4 ('Internal')
>>> obj.transparent
True

The guests of a calendar entry

A calendar entry can have a list of guests. A guest is the fact that a given person is expected to attend or has been present at a given calendar entry. Depending on the context the guests of a calendar entry may be labelled "guests", "participants", "presences", ...

class lino_xl.lib.cal.Guest

The Django model representing a guest.

event

The calendar event to which this presence applies.

partner

The partner to which this presence applies.

role

The role of this partner in this presence.

state

The state of this presence. See GuestStates.

The following three fields are injected by the reception plugin:

waiting_since

Time when the visitor arrived (checked in).

busy_since

Time when the visitor was received by agent.

gone_since

Time when the visitor left (checked out).

Every participant of a calendar entry can have a "role". For example in a class meeting you might want to differentiate between the teacher and the pupils.

class lino_xl.lib.cal.GuestRole

The role of a guest expresses what the partner is going to do there.

class lino_xl.lib.cal.GuestRoles

Global table of guest roles.

class lino_xl.lib.cal.GuestStates

Global choicelist of possible guest states.

Possible values for the state of a participation. The list of choices for the Guest.state field.

The actual content can be redefined by other apps, e.g. lino_xl.lib.reception.

>>> 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   ⚕
======= ========= ============ ========= =============
class lino_xl.lib.cal.UpdateGuests

See Event.update_guests().

class lino_xl.lib.cal.UpdateAllGuests

See EventGenerator.update_all_guests().

Presence lists

To introduce the problem:

  • runserver in lino_book.projects.avanti1 and sign in as robin.

  • create a calendar entry, leave it in draft mode

  • Note that you cannot manually enter a guest in the presences list.

This is a situation where we want Lino to automatically keep the list of guests synchronized with the "suggested guests" for this meeting. For example in Lino Avanti when we have a course with participants (enrolments), and we have generated a series of calendar entries having their suggested guests filled already, and now one participant cancels their enrolment. We want Lino to update all participants of meetings that are still in draft state. The issue is that Lino doesn't correctly differentiate between those two situations:

  • manually enter and manage the list of guests

  • fill guests automatically and keep it synchronized with the guests suggested by the entry generator.

Lino should not let me manually create a guest when the entry is in "fill guests" mode.

The Event.update_guests action is always called in the Event.after_ui_save() method. That's okay, but in our case the action obviously comes to the conclusion that we do want to update our guests. More precisely the event state obviously has EntryState.edit_guests set to False, and the entry type has fill_presences set to True. The solution is to simply set

  • The Event.can_edit_guests_manually() method which encapsulates this condition.

  • That method is now also used to decide whether the presences lists can be modified manually.

Note the difference between "guest" and "presence". The model name is currently still cal.Guest, but this should be renamed to cal.Presence. Because the "guest" is actually the field of a presence which points to the person who is the guest.

Remote calendars

A remote calendar is a set of calendar entries stored on another server. Lino periodically synchronized the local data from the remote server, and local modifications will be sent back to the remote calendar.

The feature is not currently being used anywhere.

See also lino_xl.lib.cal.management.commands.watch_calendars.

class lino_xl.lib.cal.RemoteCalendar

Django model for representing a remote calendar.

Rooms

A room is location where calendar entries can happen. For a given room you can see the EntriesByRoom that happened (or will happen) there. A room has a multilingual name which can be used in printouts.

Applications might change the user label for this model e.g. to "Team" (as done in Lino Presto) if the application is not interested in physical rooms.

class lino_xl.lib.cal.Room

Django model for representing a room.

name

The designation of the room. This is not required to be unique.

display_color

The color to use when displaying entries in this room in the calendar view.

See DisplayColors.

class lino_xl.lib.cal.Rooms

Base class for all list of rooms.

class lino_xl.lib.cal.AllRooms

Show a list of all rooms.

class lino_xl.lib.cal.RoomDetail

The detail layout for Rooms and subclasses.

Subscriptions

A suscription is when a user subscribes to a calendar. It corresponds to what the extensible CalendarPanel calls "Calendars"

class lino_xl.lib.cal.Subscription

Django model for representing a subscription.

User

points to the author (recipient) of this subscription

Other_user

class lino_xl.lib.cal.Subscriptions
class lino_xl.lib.cal.SubscriptionsByUser
class lino_xl.lib.cal.SubscriptionsByCalendar

Tasks

A task is when a user plans to do something (and optionally wants to get reminded about it).

class lino_xl.lib.cal.Task

Django model for representing a subscription.

priority

How urgent this task is.

Choicelist field pointing to lino_xl.lib.xl.Priorities.

state

The state of this Task. one of TaskStates.

class lino_xl.lib.cal.TaskStates

Possible values for the state of a Task. The list of choices for the Task.state field.

class lino_xl.lib.cal.Tasks

Global table of all tasks for all users.

class lino_xl.lib.cal.TasksByUser

Shows the list of tasks for this user.

class lino_xl.lib.cal.MyTasks

Shows my tasks whose start date is today or in the future.

Recurrent calendar entries

class lino_xl.lib.cal.EventPolicy

A recurrency policy is a rule used for generating automatic calendar entries.

event_type

Generated calendar entries will have this type.

class lino_xl.lib.cal.EventPolicies

Global table of all possible recurrencly policies.

class lino_xl.lib.cal.RecurrentEvent

A recurring event describes a series of recurrent calendar entries.

name

See lino.utils.mldbc.mixins.BabelNamed.name.

every_unit

Inherited from RecurrentSet.every_unit.

event_type
description
care_about_conflicts(self, we)

Recurrent events don't care about conflicts. A holiday won't move just because some other event has been created before on that date.

class lino_xl.lib.cal.RecurrentEvents

The list of all recurrent events (RecurrentEvent).

Miscellaneous

class lino_xl.lib.cal.Events

Table which shows all calendar events.

Filter parameters:

show_appointments

Whether only appointments should be shown. "Yes" means only appointments, "No" means no appointments and leaving it to blank shows both types of events.

An appointment is an event whose event type has appointment checked.

presence_guest

Show only entries that have a presence for the specified guest.

project

Show only entries assigned to this project, where project is defined by lino.core.site.Site.project_model.

assigned_to

Show only entries assigned to this user.

observed_event
event_type

Show only entries having this type.

state

Show only entries having this state.

user

Show only entries having this user as author.

class lino_xl.lib.cal.ConflictingEvents

Shows events conflicting with this one (the master).

class lino_xl.lib.cal.EntriesByDay

This table is usually labelled "Appointments today". It has no "date" column because it shows events of a given date.It is ordred with increasing times.

The default filter parameters are set to show only appointments.

class lino_xl.lib.cal.EntriesByRoom

Displays the calendar entries at a given Room.

class lino_xl.lib.cal.EntriesByController

Shows the calendar entries controlled by this database object.

If the master is an EventGenerator, then this includes especially the entries which were automatically generated.

class lino_xl.lib.cal.EntriesByProject
class lino_xl.lib.cal.OneEvent

Show a single calendar event.

class lino_xl.lib.cal.MyEntries

Table of appointments for which I am responsible.

Table which shows today's and all future appointments of the requesting user. The default filter parameters are set to show only appointments.

class lino_xl.lib.cal.MyEntriesToday

Like MyEntries, but only today.

class lino_xl.lib.cal.MyAssignedEvents

The table of calendar entries which are assigned to me. That is, whose Event.assigned_to field refers to the requesting user.

This table also causes a welcome message "X events have been assigned to you" in case it is not empty.

class lino_xl.lib.cal.OverdueAppointments

Shows overdue appointments, i.e. appointments whose date is over but who are still in a nonstable state.

show_appointments is set to "Yes", observed_event is set to "Unstable", end_date is set to today.

class lino_xl.lib.cal.MyOverdueAppointments

Like OverdueAppointments, but only for myself.

class lino_xl.lib.cal.MyUnconfirmedAppointments

Shows my appointments in the near future which are in suggested or draft state.

Appointments before today are not shown. The parameters end_date and start_date can manually be modified in the parameters panel.

The state filter (draft or suggested) cannot be removed.

class lino_xl.lib.cal.Guests

The default table of presences.

class lino_xl.lib.cal.GuestsByEvent
class lino_xl.lib.cal.GuestsByRole
class lino_xl.lib.cal.MyPresences

Shows all my presences in calendar events, independently of their state.

class lino_xl.lib.cal.MyPendingPresences

Received invitations waiting for my feedback (accept or reject).

class lino_xl.lib.cal.RecurrenceSet

Mixin for models that express a set of repeating calendar events. See Automatic calendar entries.

start_date
start_time
end_date
end_time
every

The frequency of periodic iteration: daily, weekly, monthly or yearly.

every_unit

The interval between each periodic iteration.

For example, when every is yearly, an every_unit of 2 means once every two years. The default value is 1.

positions

Space-separated list of one or several positions within the recurrency cycle.

Each position is a positive or negative integer expressing which occurrence is to be taken from the recurrency period. For example if positions is -1 and every_unit is monthly, we get the last day of every month.

Inspired by dateutil.rrule.

max_events

Maximum number of calendar entries to generate.

monday
tuesday
wednesday
thursday
friday
saturday
sunday
weekdays_text

A virtual field returning the textual formulation of the weekdays where the recurrence occurs.

Usage examples see cal : Calendar functionality.

class lino_xl.lib.cal.GuestsByPartner

Show the calendar entries having this partner as a guest.

This might get deprecated some day. You probably prefer EntriesByGuest.

class lino_xl.lib.cal.EntriesByGuest

Show the calendar entries having this partner as a guest.

Similar to GuestsByPartner, but EntriesByGuest can be used to create a new calendar entry. It also makes sure that the new entry has at least one guest, namely the partner who is the master. Because otherwise, if the user creates an entry and forgets to manually add our master as a guest, they would not see the new entry.

class lino_xl.lib.cal.Reservation

Base class for lino_xl.lib.rooms.models.Booking and lino.modlib.courses.models.Course.

Inherits from both EventGenerator and RecurrenceSet.

room
max_date

Don't generate calendar entries beyond this date.

Display colors

class lino_xl.lib.cal.DisplayColors

A list of colors to be specified for displaying.

>>> rt.show(cal.DisplayColors)
========= ========= =========
 value     name      text
--------- --------- ---------
 White     White     White
 Silver    Silver    Silver
 Gray      Gray      Gray
 Black     Black     Black
 Red       Red       Red
 Maroon    Maroon    Maroon
 Yellow    Yellow    Yellow
 Olive     Olive     Olive
 Lime      Lime      Lime
 Green     Green     Green
 Aqua      Aqua      Aqua
 Teal      Teal      Teal
 Blue      Blue      Blue
 Navy      Navy      Navy
 Fuchsia   Fuchsia   Fuchsia
 Purple    Purple    Purple
========= ========= =========

The days of the week

class lino_xl.lib.cal.Weekdays

A choicelist with the seven days of a week.

>>> rt.show(cal.Weekdays)
======= =========== ===========
 value   name        text
------- ----------- -----------
 1       monday      Monday
 2       tuesday     Tuesday
 3       wednesday   Wednesday
 4       thursday    Thursday
 5       friday      Friday
 6       saturday    Saturday
 7       sunday      Sunday
======= =========== ===========
lino_xl.lib.cal.WORKDAYS

The five workdays of the week (Monday to Friday).

Duration units

The calendar plugin defines DurationUnits choicelist, a site-wide list of duration units. In a default configuration it has the following values:

>>> rt.show(cal.DurationUnits)
======= ========= =========
 value   name      text
------- --------- ---------
 s       seconds   seconds
 m       minutes   minutes
 h       hours     hours
 D       days      days
 W       weeks     weeks
 M       months    months
 Y       years     years
======= ========= =========
class lino_xl.lib.cal.DurationUnits

The list of possible duration units defined by this application.

This is used as the selection list for the duration_unit <Event.duration_unit> field of a calendar entry.

Every item is an instance of DurationUnit.

class lino_xl.lib.cal.DurationUnit

Base class for the choices in the DurationUnits choicelist.

add_duration(unit, orig, value)

Return a date or datetime obtained by adding value times this unit to the specified value orig. Returns None is orig is empty.

This is intended for use as a curried magic method of a specified list item:

Duration units can be used for arithmetic operation on durations. For example:

>>> from lino_xl.lib.cal.choicelists import DurationUnits
>>> start_date = i2d(20111026)
>>> DurationUnits.months.add_duration(start_date, 2)
datetime.date(2011, 12, 26)
>>> from lino.utils import i2d
>>> start_date = i2d(20111026)
>>> DurationUnits.months.add_duration(start_date, 2)
datetime.date(2011, 12, 26)
>>> DurationUnits.months.add_duration(start_date, -2)
datetime.date(2011, 8, 26)
>>> start_date = i2d(20110131)
>>> DurationUnits.months.add_duration(start_date, 1)
datetime.date(2011, 2, 28)
>>> DurationUnits.months.add_duration(start_date, -1)
datetime.date(2010, 12, 31)
>>> DurationUnits.months.add_duration(start_date, -2)
datetime.date(2010, 11, 30)
>>> start_date = i2d(20140401)
>>> DurationUnits.months.add_duration(start_date, 3)
datetime.date(2014, 7, 1)
>>> DurationUnits.years.add_duration(start_date, 1)
datetime.date(2015, 4, 1)

Miscellaneous

class lino_xl.lib.cal.AccessClasses

The sitewide list of access classes.

class lino_xl.lib.cal.ShowEntriesByDay

Show all calendar events of the same day.

class lino_xl.lib.cal.Component

Model mixin inherited by both Event and Task.

auto_type

Contains the sequence number if this is an automatically generated component. Otherwise this field is empty.

Automatically generated components behave differently at certain levels.

Plugin configuration

class lino_xl.lib.cal.Plugin
partner_model

The model to use as the guest of a presence.

ignore_dates_before

Ignore dates before the given date.

Default value is None, meaning "no limit".

Unlike hide_events_before this is not editable through the web interface.

ignore_dates_after

Ignore dates after the given date. This should never be None. Default value is 5 years after today.

User roles

Most calendar functionality requires lino.modlib.office.roles.OfficeUser

class lino_xl.lib.cal.CalendarReader

Can read public calendar entries. This is a kind of minimal calendar functionality which can be given to anonymous users, as done e.g. by Lino Vilma.

class lino_xl.lib.cal.GuestOperator

Can see presences and guests of a calendar entry.

Data checkers

class lino_xl.lib.cal.ConflictingEventsChecker

Check whether this entry conflicts with other events.

class lino_xl.lib.cal.ObsoleteEventTypeChecker

Check whether the type of this calendar entry should be updated.

This can happen when the configuration has changed and there are automatic entries which had been generated using the old configuration.

class lino_xl.lib.cal.LongEntryChecker

Check for entries which last longer than the maximum number of days allowed by their type.

class lino_xl.lib.cal.EventGuestChecker

Check for calendar entries without participants.

No participants although N suggestions exist. -- This is probably due to some problem in the past, so we repair this by adding the suggested guests.

lino_xl.lib.cal.check_subscription(user, calendar)

Check whether the given subscription exists. If not, create it.

Default duration and start time

Note the difference between a DurationField and a TimeField:

>>> fld = cal.EventType._meta.get_field('default_duration')
>>> fld.__class__
<class 'lino.core.fields.DurationField'>
>>> fld.to_python("1:00")
Duration('1:00')
>>> fld = cal.Event._meta.get_field('start_time')
>>> fld.__class__
<class 'lino.core.fields.TimeField'>
>>> fld.to_python("1:00")
datetime.time(1, 0)
>>> et = cal.EventType.objects.get(planner_column=cal.PlannerColumns.internal)
>>> et.default_duration
Duration('0:30')

So when we create an entry which starts at 8:00, Lino will automaticallt set end_time to 8:30

>>> entry = cal.Event(start_date=dd.today(), start_time="8:00", event_type=et)
>>> entry.full_clean()
>>> entry.end_time
datetime.time(8, 30)

It works also across midnight:

>>> entry = cal.Event(start_date=dd.today(), start_time="23:55", event_type=et)
>>> entry.full_clean()
>>> entry.start_time
datetime.time(23, 55)
>>> entry.end_time
datetime.time(0, 25)
>>> entry.start_date
datetime.date(2017, 2, 15)
>>> entry.end_date

User roles

Besides the user roles defined in lino.modlib.office this plugins also defines two specific roles.

class lino_xl.lib.cal.CalendarReader

Has read-only access to calendars of other users.

class lino_xl.lib.cal.GuestOperator

Can see guests of calendar entries.

etpos

The RecurrenceSet.positions field allows to specify rules like "every last Friday of the month".

The following examples use a utility function:

>>> def show(obj, today):
...     print(obj.weekdays_text)
...     for i in range(5):
...         x = obj.get_next_suggested_date(None, today)
...         print(dd.fdf(x))
...         today = x

Every last Friday of the month:

>>> obj = cal.RecurrentEvent()
>>> obj.friday = True
>>> obj.positions = "-1"
>>> obj.every_unit = cal.Recurrencies.monthly
>>> show(obj, i2d(20191001))
Every last Friday of the month
Friday, 25 October 2019
Friday, 29 November 2019
Friday, 27 December 2019
Friday, 31 January 2020
Friday, 28 February 2020

The first and third Wednesday of every month:

>>> obj = cal.RecurrentEvent()
>>> obj.wednesday = True
>>> obj.positions = "1 3"
>>> obj.every_unit = cal.Recurrencies.monthly
>>> show(obj, i2d(20191001))
Every first and third Wednesday of the month
Wednesday, 2 October 2019
Wednesday, 16 October 2019
Wednesday, 6 November 2019
Wednesday, 20 November 2019
Wednesday, 4 December 2019