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

google : Synchronize with Google

The lino_xl.lib.google plugin adds functionality and database models to synchronize calendar and contacts data between a Lino site and the Google data of its users. See also the end-user documentation.

This document contains code snippets (lines starting with >>>) that get tested as part of our development workflow.

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

Overview

The site operator must define a web application with the People API and the Calendar API on the Google developer console.

More precisely it synchronizes three database models:

  • contacts.Person

  • cal.Entry

  • cal.Calendar

Plugin configuration

The following plugin attributes can be configured in the settings.py.

google.contacts_model

The database model used to represent a person on this site. If this is a str. Lino will resolve it into a database model during site startup.

Default value is 'contacts.Person'.

google.application_name

The application’s name defined in the Google API Console

google.num_retries

How many times to call GoogleAPI in case of googleapiclient.errors.HttpError

Type:

int

Value:

3

google.client_secret_file

JSON-formatted GoogleAPI client secret. To be obtained from Google.

If this is a str, Lino will convert it into a pathlib.Path during site startup.

Default value is a file named google_creds.json in the site_dir.

See How to get GoogleAPI credentials below.

google.scopes

The list of scopes to which Lino will ask access to when connecting to the Google API.

Type:

list

>>> pprint(dd.plugins.google.scopes)
['https://www.googleapis.com/auth/userinfo.profile',
 'https://www.googleapis.com/auth/userinfo.email',
 'https://www.googleapis.com/auth/contacts',
 'https://www.googleapis.com/auth/calendar',
 'https://www.googleapis.com/auth/calendar.events']

After modifying these scopes, delete all database entries for this auth provider and reauthenticate.

google.entry_state_translation

Translate EntryState into google status.

Type:

tuple[tuple[str, tuple[str, …]]]

Value:

((‘confirmed’, (‘confirmed’, )), (‘tentative’, (‘tentative’, )), (‘cancelled’, (‘cancelled’, )))

The first value of the inner tuples is the corresponding Google event status for the EntryState names in the second value (which is also a tuple).

google.guest_state_translation

Translate between possible values in Google and Lino guest state choices.

Type:

tuple[tuple[str, tuple[str, …]]

Value:

((‘needsAction’, (‘needsAction’, )), (‘declined’, (‘decliend’, )), (‘tentative’, (‘tentative’, )), (‘accepted’, (‘accepted’, )))

Add more items on the second item of the inner toples so that they translate to the first item on the inner tuple.

Utilities

The plugin also provides a utility function:

lino_xl.lib.google.has_scope(scope_type, user)
Parameters:

scope_type (str) – Should be either ‘calendar’ or ‘contact’

Return bool:

Tells whether a certain type of scope is available for the user.

Model mixins

class lino_xl.lib.google.AccessLockable

A model mixin to lock access to the database object (on each database query), allowing only one transaction to such objects.

Inherits from Modified.

On calling the save method. The object is unlocked automatically and should not be used at all afterwards. To use it further fetch it from the database again, and further access will be locked automatically.

This might be dangerous in case a locked instance is lost from a python session, and could be hard to get this object back from the database. Workarounds: Use the unlock_all() or unlock_objects() to modify them at the database level. Or get a list of pk(s) using get_locked_objects_pk() and use _force_get() instead. The following methods are available for troubleshooting:

classmethod unlock_all()

Unlocks all database objects.

classmethod unlock_objects(pk_list: list)

Given a list of pk(s) it unlocks them at the database level.

classmethod get_locked_objects_pk()

Returns a list of pk(s) of the currently locked objects.

classmethod _force_get(pk: int)

Given a pk it returns a locked database object. Use this method only for troubleshooting.

class lino_xl.lib.google.SyncToken

A model mixin inherits from AccessLockable and UserAuthored.

sync_token

A CharField to store nextSyncToken retrieved from Google API call.

page_token

A CharField to store nextPageToken retrieved from Google API call.

class lino_xl.lib.google.GoogleSynchronized

Google’s related database models inherits from this mixin.

google_id

Used in making reference to the object stored in Google’s database.

synchronize_with_google(self, user: User | None) bool

Decides whether the entry is synchronizable with Google.

Parameters:

user (Optional[User]) – The user who’s Google account lino is synchronizing with.

class lino_xl.lib.google.GoogleCalendarSynchronized

A subclass of GoogleSynchronized.

modified

Keeps the timestamp of the last modification of the Calendar as an entry.

Type:

datetime.datetime

Helps in synchronization with Google.

insert_or_update_into_google(self, resource, synchronizer) None

Insert or updates the calendar entry with google.

Parameters:
  • resource – Google calendar API resource. Can be obtained by calling Resource.calendars()

  • synchronizer – An instance of Synchronizer

classmethod get_outward_insert_update_queryset(cls, user)

This method returns a queryset of Calendar that are not stored in Google or should be updated.

Parameters:

user – An instance of User

Returns:

django.db.models.query.QuerySet

classmethod delete_google_calendar(cls, cal: dict, synchronizer) None

This method deletes a Calendar at sync time when the calendar is deleted from the Google calendar.

Parameters:
  • cal (dict) – Dictionary of attributes of the deleted calendar.

  • synchronizer – An instance of Synchronizer

Also deletes the DeletedEntry record from the database to keep it clean.

classmethod sync_deleted_records(cls, resource, synchronizer) None

Deletes calendars by looking at DeletedEntry from Google calendar.

Parameters:
  • resource – Google calendar API resource. Can be obtained by calling Resource.calendars()

  • synchronizer – An instance of Synchronizer

classmethod insert_or_update_google_calendar(cls, cal: dict, synchronizer)

Inserts or updates a calendar entry and the default Room.

Parameters:
Returns:

A tuple of the saved calendar entry and the default room.

Return type:

tuple[Calendar, Room]

class lino_xl.lib.google.GoogleCalendarEventSynchronized

A subclass of GoogleSynchronized.

classmethod delete_google_event(cls, cal: dict, synchronizer) None

This method deletes a Event at sync time when the event is deleted from the Google calendar.

Parameters:
  • event (dict) – Dictionary of attributes of the deleted event.

  • synchronizer – An instance of Synchronizer

Also deletes the DeletedEntry record from the database to keep it clean.

classmethod sync_deleted_records(cls, resource, synchronizer) None

Deletes events by looking at DeletedEntry from Google calendar.

Parameters:
  • resource – Google calendar API resource. Can be obtained by calling Resource.events()

  • synchronizer – An instance of Synchronizer

classmethod get_outward_insert_update_queryset(cls, user)

This method yields Event (s) which are not stored in Google or should be updated.

Parameters:

user – An instance of User

Returns:

Generator[Event, None, None]

insert_or_update_into_google(self, resource, synchronizer) None

Insert or updates the events into Google.

Parameters:
  • resource – Google calendar API resource. Can be obtained by calling Resource.events()

  • synchronizer – An instance of Synchronizer

classmethod insert_or_update_google_event(cls, event: dict, room, synchronizer)

Inserts or updates a calendar entry and related Room and Guest ‘s.

Parameters:
Returns:

Saved calendar event entry.

Return type:

Event.

class lino_xl.lib.google.GoogleContactSynchronized

A subclass of GoogleSynchronized.

classmethod delete_google_contact(cls, contact: dict, synchronizer) None

This method deletes a Contact at sync time when the contact is deleted from Google.

Parameters:
  • contact (dict) – Dictionary of attributes of the deleted contact.

  • synchronizer – An instance of Synchronizer

Also deletes the DeletedContact record from the database to keep it clean.

classmethod sync_deleted_records(cls, resource, synchronizer) None

Deletes contacts by looking at DeletedContact from Google.

Parameters:
  • resource – Google people API resource. Can be obtained by calling Resource.people()

  • synchronizer – An instance of Synchronizer

classmethod get_outward_insert_update_queryset(cls, user: users.User = None)

Returns contacts insertable or updatable into Google.

Parameters:

user – An instance of User

Returns:

django.db.models.QuerySet

insert_or_update_into_google(self, resource, synchronizer)

Insert or updates the contact into Google.

Parameters:
  • resource – Google people API resource. Can be obtained by calling Resource.people()

  • synchronizer – An instance of Synchronizer

classmethod insert_or_update_google_contact(cls, contact: dict, synchronizer)

Inserts or updates a contact Contact.

Parameters:
Returns:

Saved contact entry

Return type:

Contact

Choices and choicelists

Defines ChoiceList(s) and some utility functions.

lino_xl.lib.google.google_status(state: EntryState | GuestState) str | None
Parameters:

state – Takes either a EntryState or a GuestState.

Returns:

An str as status acceptable by Google.

Internally it works by looking at state_translation either google.entry_state_translation when the input parameter is an instance of an EntryState or google.guest_state_translation when the input parameter is an instance of a GuestState. It returns None if a value is not found for a corresponding state.

>>> google.google_status(cal.EntryStates.tentative)
'tentative'
class lino_xl.lib.google.AccessRoles

Keeps the choices for the type of access to a Google calendar.

Used for checking whether a user can insert into a Google calendar. The available values are freeBusyReader (read public info only), reader, writer and owner.

>>> rt.show(google.AccessRoles)
======= ================ ==================
 value   name             text
------- ---------------- ------------------
 p       freeBusyReader   Free busy reader
 r       reader           Reader
 w       writer           Writer
 o       owner            Owner
======= ================ ==================

Database objects

class lino_xl.lib.google.CalendarSubscription

A subclass of BaseSubscription.

primary

A boolean field which indicated whether calendar referenced in this subscription is the primary calendar for the user in Google.

access_role

User’s access role on the subscribed calendar.

See: AccessRoles

class lino_xl.lib.google.EventSyncToken

A subclass of SyncToken, stores necessary tokens to sync the events updated in a user’s google calendar.

subscription

A ForeignKey pointing to a CalendarSubscription object.

class lino_xl.lib.google.CalendarSyncToken

A subclass of SyncToken, store the necessary tokens to sync the calendars updated on a user’s google account.

class lino_xl.lib.google.DeletedEntry

Keeps a record of the natively deleted Calendar or Event for deleting from Google when the next sync is run.

calendar

A boolean field which says whether the deleted item is a Calendar if not it is an Event.

Type:

bool

Value:

False

event_id

Takes the value of the Event.google_id when the deleted record is an Event otherwise an empty string.

Type:

str

calendar_id

Takes the value of the Calendar.google_id

Type:

str

If the deleted record is an Event it takes the google_id from the Calendar in which the deleted Event belongs to.

class lino_xl.lib.google.Contact

Keeps a reference to a google contact.

A subclass of UserAuthored, GoogleContactSynchronized and Modified.

contact

A ForeignKey pointing to google.contacts_model.

class lino_xl.lib.google.DeletedContact

Keeps meta information of a deleted contact to sync with google upon next synchronization.

A subclass of UserAuthored.

contact_id

The google resourceName.

class lino_xl.lib.google.ContactSyncToken

Stores the nextSyncToken and nextPageToken from google.

A subclass of SyncToken.

class lino_xl.lib.google.SyncSummary

Database model to store the summaries of a Synchronizer.sync() session.

Subclass of UserAuthored and Created

halted

A BooleanField indicate whether the sync session has failed.

stats

A TextField containing the textual representation of synchronization session statistics.

class lino_xl.lib.google.FailedForeignItem

Database model to store the foreign objects failed to put into the local database.

job

A ForeignKey pointing to the corresponding SyncSummary.

value

A JSONField containing the actual remote object.

item_class

A ForeignKey pointing to the related database model (contenttypes.ContentType) specifying the item synchronization class.

Interaction with other plugins

This plugin adds the following method get_country to lino.modlib.users.User model:

class lino.modlib.users.User
get_contact()

Returns the user’s country.

Synchronization

Calendar and contacts synchronization in Lino with Google works in both ways. Lino application can fetch entries from Google as well as it can insert and update entries into Google.

By default google.contacts_model are not synchronisable with google unless they pointed to by some Contact instance.

class lino_xl.lib.google.FailedEntries

A subclass of typing.NamedTuple. And has the following attributes.

calendars

Contains reference to the Calendar instance(s) that failed to sync with Google.

Type:

list[django.db.models.QuerySet]

Value:

[]

events

Contains reference to the Event instance(s) that failed to sync with Google.

Type:

list[django.db.models.Model]

Value:

[]

contacts

Contains reference to the Contact instance(s) that failed to sync with Google.

Type:

list[django.db.models.QuerySet]

Value:

[]

foreign_item

Objects fetched from the remote and failed to update (/ put) on the local database.

Type:

list[tuple[dd.Model, dict]]

The first item of the tuple is any non-abstract subclass of dd.Model and the second item is the fetched object from the remote.

Value:

[]

class lino_xl.lib.google.Synchronizer

The class that wraps the synchronization functionality.

_failed_entries

Keeps reference to the database objects that failed to sync with google from the last sync() call.

Type:

FailedEntries

failed_entries

Keeps reference to the database objects that failed to sync with google in the running sync() call.

Type:

FailedEntries

user

The User, whose records should be synchronized.

setup(user) None

Sets up the user scope and initializes necessary data objects.

sync() self

Synchronizes latest changes with Google.

How to get GoogleAPI credentials

Log in to your Google Developer Console.

Create a project in the console if you don’t have one already.

In the Google console, navigate to APIs & Services ‣ Enabled APIs & Services and then

  • click on + ENABLE APIS AND SERVICES

  • search for Google People API and enable this API

  • search for Google Calendar API and enable this API

For detailed information follow this Google API Console Help page.

Navigate to APIs & Services ‣ Credentials then

  • click on + CREATE CREDENTIALS

  • choose OAuth client ID.

  • Set the Application type to Web application.

  • In the Authorized redirect URIs section click on + ADD URI and put your matching URI to the following regex and hit CREATE:

    >>> your_server_host_name = r".*"
    >>> uri_pattern = r"^http(s)?://" + your_server_host_name + r"/oauth/complete/google/$"
    

Click on the DOWNLOAD JSON button to download the credentials. Save them as a file named google_creds.json in the site_dir.

You can also store them in a different place and specify the file’s full path name in google.client_secret_file in your settings.py:

class Site(...):
    ...
    def get_plugin_configs(self):
        ...
        yield 'google', 'client_secret_file', '/path/to/my/credentials.json'
    ...

Navigate to APIs & Services ‣ OAuth consent screen and put the email addresses of some test users.

And that’s almost it for getting Google API credentials.

Otherwise see this thread.