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

users : user management

This document describes the lino.modlib.users plugin, which in Lino replaces django.contrib.auth. See also Introduction to user management for getting started with user management. If you wonder why Lino replaces Django’s user management and permission system, see Lino has its own user management.

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.noi1r.settings')
>>> from lino.api.doctest import *

Users

class lino.modlib.users.User

The Django model used to represent a user account.

Database fields that must be edited by a site manager:

username

Must be either unique or empty. User accounts having this field empty cannot be used to sign in.

user_type

The user type assigned to this user. User accounts having this field empty cannot be used to sign in.

See also Introduction to permissions.

partner

The partner record with additional contact information about this user account.

This field is used to optionally link a user account to a partner. Some applications provide functionalities that aren’t available unless this field is given.

A same partner can have more than one user accounts.

This is a pointer to lino_xl.lib.contacts.Partner. This is a DummyField when lino_xl.lib.contacts is not installed or when User is a subclass of Partner .

Database fields that can be edited by the user as their user settings:

nickname

The nickname used to refer to you when speaking to other site users. Does not need to be unique but should be reasonably identifying.

This field is hidden unless users.with_nickname is True. See also __str__() and get_full_name().

initials

The initials used to refer to you when speaking to business partners. Does not need to be unique but should be reasonably identifying.

time_zone

The time zone Lino should use in time fields or when displaying timestamps to this user.

date_format

The date format to use when parsing or displaying dates.

See lino.modlib.about.DateFormats.

Database fields that aren’t directly editable:

verification_code

A random string that has been sent to the user via email in order to verify their email address.

When this is non empty, is_verified() returns False and the verify_me action is available.

verification_password

A not-yet-active password given by anonymous when they asked for a password reset. This will become the active password when the user verifies.

person

A virtual read-only field that returns the Person MTI child of the partner (if it exists) and otherwise None.

company

A virtual read-only field that returns the Company MTI child of the partner (if it exists) and otherwise None.

last_login

Not used in Lino.

end_date
start_date

If start_date is given, then the user cannot sign in before that date. If end_date is given, then the user cannot sign in after that date.

These fields are also used for userstats: User statistics.

Instance attributes:

authenticated

No longer used. See as is_authenticated.

is_authenticated

This is always True. Compare with AnonymousUser.is_authenticated.

Querying users:

classmethod get_active_users(cls, required_roles=None)

Return a queryset of users that satisfy the specified criteria.

Usage examples see Querying users below.

Other methods

__str__(self)

Returns nickname if this field is not empty, otherwise get_full_name() unless

get_full_name(self)

Return the user’s full name, i.e. first_name followed by last_name. If both fields are empty, return the initials or the username.

get_row_permission(self, ar, state, ba)

Only system managers may edit other users. See also disabled_fields().

One exception is when AnonymousUser is not readonly. This means that we want to enable online registration. In this case everybody can modify an unsaved user.

Actions

change_password

Ask for a new password to be used for authentication.

See ChangePassword.

verify_me

Ask for the verification code you have received by email and mark your email address as verified.

Data tables

class lino.modlib.users.Users

Base class for all data tables on User.

class lino.modlib.users.AllUsers

Shows the list of all users on this site.

class lino.modlib.users.UsersOverview

A variant of Users showing only active users and only some fields. This is used on demo sites in admin_main.html to display the list of available users.

User types

>>> rt.show('users.UserTypes', language="en")
======= =============== ===============
 value   name            text
------- --------------- ---------------
 000     anonymous       Anonymous
 100     customer user   Customer
 200     contributor     Contributor
 400     developer       Developer
 900     admin           Administrator
======= =============== ===============
class lino.modlib.users.UserTypes

The list of user types available in this application.

Every application should define at least three named user types:

anonymous
user
admin

Class attributes:

user_role

The user role of users having this type.

hidden_languages

Default value for the hidden_languages of newly added choice items.

classmethod get_anonymous_user(cls)

Return an instance of AnonymousUser.

class lino.modlib.users.UserType

An item of the UserTypes list. Every instance of this represents a user type.

role

The role of users having this type. This is an instance of UserRole or some subclass thereof.

readonly

Whether users of this type get only write-proteced access.

hidden_languages

A subset of languages which should be hidden for users of this type. Default value is hidden_languages. This is used on multilingual sites with more than 4 or 5 languages.

context(self)

Return a context manager so you can write code to be run with this as the current user type:

with UserTypes.admin.context():
    # some code
mask_message_types

A set of notification message types to be masked for users of this type.

Notifications will not be forwarded to users whose user type filters them away.

mask_notifications(self, *args)

Add the given notification message types to the notification mask.

has_required_roles(self, required_roles)

Return True if this user type’s role satisfies the specified requirements.

find_menu_item(self, bound_action)

Find the item of the main menu for the specified bound action.

User roles and their usage

class lino.modlib.users.UserRoles

Shows a list of the user roles used in this application together with the user type that have them.

This table can help when designing the list of user types and what permissions each of them should have.

Example:

>>> rt.show(users.UserRoles)
... 
================================ ===== ===== ===== ===== =====
 Name                             000   100   200   400   900
-------------------------------- ----- ----- ----- ----- -----
 accounting.LedgerStaff                                   ☑
 cal.CalendarReader               ☑
 checkdata.CheckdataUser                      ☑     ☑     ☑
 comments.CommentsReader          ☑     ☑     ☑     ☑     ☑
 comments.CommentsStaff                             ☑     ☑
 comments.CommentsUser                  ☑     ☑     ☑     ☑
 comments.PrivateCommentsReader                     ☑     ☑
 contacts.ContactsStaff                                   ☑
 contacts.ContactsUser                              ☑     ☑
 core.DataExporter                      ☑     ☑     ☑     ☑
 core.Expert                                        ☑     ☑
 core.SiteUser                          ☑     ☑     ☑     ☑
 courses.CoursesUser                          ☑     ☑     ☑
 excerpts.ExcerptsStaff                             ☑     ☑
 excerpts.ExcerptsUser                        ☑     ☑     ☑
 invoicing.InvoicingStaff                                 ☑
 invoicing.InvoicingUser                                  ☑
 noi.Anonymous                    ☑
 noi.Contributor                              ☑     ☑     ☑
 noi.Customer                           ☑     ☑     ☑     ☑
 noi.Developer                                      ☑     ☑
 noi.SiteAdmin                                            ☑
 office.OfficeStaff                                       ☑
 office.OfficeUser                      ☑     ☑     ☑     ☑
 products.ProductsStaff                                   ☑
 storage.StorageStaff                                     ☑
 storage.StorageUser                                      ☑
 tickets.Reporter                       ☑     ☑     ☑     ☑
 tickets.Searcher                 ☑     ☑     ☑     ☑     ☑
 tickets.TicketsStaff                               ☑     ☑
 tickets.Triager                                    ☑     ☑
 topics.TopicsUser                      ☑     ☑     ☑     ☑
 votes.VotesStaff                                         ☑
 votes.VotesUser                        ☑     ☑     ☑     ☑
 working.Worker                               ☑     ☑     ☑
================================ ===== ===== ===== ===== =====

The table doesn’t show all user roles, only those that are “meaningful”.

Where meaningful means: those which are mentioned (either imported or defined) in the global context of the user_types_module. We tried more “intelligent” approaches, but it is not trivial for Lino to guess which roles are “meaningful”.

Querying users

All users:

>>> users.User.get_active_users()
<QuerySet [User #7 ('Jean'), User #6 ('Luc'), User #4 ('Marc'), User #5 ('Mathieu'), User #3 ('Romain Raffault'), User #2 ('Rolf Rompen'), User #1 ('Robin Rood')]>

Only site administrators:

>>> from lino.core.roles import SiteAdmin
>>> users.User.get_active_users([SiteAdmin])
<QuerySet [User #3 ('Romain Raffault'), User #2 ('Rolf Rompen'), User #1 ('Robin Rood')]>

All except site administrators:

>>> users.User.get_active_users(unwanted_roles=[SiteAdmin])
<QuerySet [User #7 ('Jean'), User #6 ('Luc'), User #4 ('Marc'), User #5 ('Mathieu')]>

All who speak English:

>>> users.User.get_active_users(language="en")
<QuerySet [User #7 ('Jean'), User #6 ('Luc'), User #4 ('Marc'), User #5 ('Mathieu'), User #1 ('Robin Rood')]>

User sessions

class lino.modlib.users.Sessions

Show a list of all user sessions.

See User sessions.

Authorities : let other users work in your name

class lino.modlib.users.Authority

Django model used to represent a authority.

user

The user who gives the right of representation. author of this authority

authorized

The user who gets the right to represent the author

The current user type

This is used by lino.utils.jsgen, i.e. when generating the linoweb.js file for a given user type.

Site configuration

This plugin adds the following plugin settings:

users.third_party_authentication

Whether this site provides third party authentication.

This should be activated only when there is at least one authentication provider.

This plugin has the following site settings:

users.allow_online_registration

Whether users can register online. When this is set to True, a create_account action will be available under About actor. This also activates the verification system, and on account creation new users are asked to enter a verification code to verify their email address.

active_sessions_limit

The sessions limit for this site. The default value -1 means that there is no limitation. Setting this to 0 will prevent any new login attempt and might be useful as a temporary value before shutting down a site.

users.verification_code_expires = 5

Number of minutes after which a user verification code becomes invalid. Used to invalidate a user verification code after the elapsed minutes.

users.user_type_new = 'user'

The default user_type for an unverified user.

users.user_type_verified = 'user'

The default user_type for a verified user.

users.with_nickname

Whether to add the User.nickname field.

Roles

This plugin defines the following user roles.

class lino.modlib.users.Helper

Somebody who can help others by running AssignToMe action.

class lino.modlib.users.AuthorshipTaker

Somebody who can help others by running TakeAuthorship action.

Actions

class lino.modlib.users.SendWelcomeMail

Send a welcome mail to this user.

class lino.modlib.users.ChangePassword

Change the password of this user.

current

The current password. Leave empty if the user has no password yet. A site manager doesn’t need to specify this at all.

new1

The new password.

new2

The new password a second time. Both passwords must match.

class lino.modlib.users.SignIn

Open a window that asks for username and password and authenticates as this user when submitted.

class lino.modlib.users.SignOut

Sign out the current user and return to the welcome screen for anonymous visitors.

class lino.modlib.users.CreateAccount
first_name

Your first name.

last_name

Your last name.

email

Your email address. This is required to verify your account.

username

The username you want to get. Leave empty to get your email address as your username.

password

Your password.

Model mixins

class lino.modlib.users.Authored
manager_roles_required

The list of required roles for getting permission to edit other users’ work.

By default, only SiteStaff users can edit other users’ work.

An application can set manager_roles_required to some other user role class or a tuple of such classes.

Setting manager_roles_required to [] will disable this behaviour (i.e. everybody can edit the work of other users).

This is going to be passed to has_required_roles of the requesting user’s profile.

Usage examples see lino_xl.lib.notes.models.Note or lino_xl.lib.cal.Component.

author_field_name

No longer used. The name of the field that defines the author of this object.

class lino.modlib.users.UserAuthored

Inherits from Authored.

Mixin for models with a user field that points to the “author” of this object. The default user of new instances is automatically set to the requesting user.

user

The author of this database object.

A pointer to lino.modlib.users.models.User.

class lino.modlib.users.StartPlan

The action to start a user plan.

This is the default implementation for UserPlan.start_plan, but it may be subclassed. For example StartInvoicing extends this because there may be more than one types of invoicing plans.

update_after_start

Whether to run Plan.update_plan() after starting the plan.

class lino.modlib.users.UserPlan

Mixin for anything that represents a “plan” of a given user on a given day.

What a “plan” means, depends on the inheriting child. Examples are an invoicing plan (lino_xl.lib.invoicing.Plan), a shopping cart (lino_xl.lib.shopping.Cart) or an accounting report (lino_xl.lib.sheets.Report).

The mixin makes sure that there is only one database instance per user. A plan is considered a low value database object to be reused frequently.

Inherits from UserAuthored.

user

The user who owns and uses this plan.

today

This date of this plan. This is automatically set to today each time the plan is called or updated.

update_plan_button
create_user_plan(self, user)

Return the database object for this plan and user. or create

update_plan(self, ar)

Implementing models should provide this method.

class lino.modlib.users.UpdatePlan

Build a new list of suggestions. This will remove all current suggestions.

doctests

Verify whether the help_text of the change_password action is set:

>>> ba = rt.models.users.AllUsers.get_action_by_name('change_password')
>>> print(ba.action.help_text)
Ask for a new password to be used for authentication.

Verify whether #3766 is fixed:

>>> show_choices('robin', '/choices/users/Users/partner')
... 

Altenberg Hans
Arens Andreas
...
Ärgerlich Erna
Õunapuu Õie
Östges Otto
>>> show_choices('robin', '/choices/users/Users/user_type')

000 (Anonymous)
100 (Customer)
200 (Contributor)
400 (Developer)
900 (Administrator)

Reset password and verify email

The following actions are available also to anonymous users.

class lino.modlib.about.About
sign_in

Ask for your username and password in order to authenticate.

If users.third_party_authentication is enabled, this also shows alternative authentication methods.

reset_password

Ask for your email address and send a verification code.

verify_user

Ask for the verification code you have received by email and mark your email address as verified.

User types module

The default value for Site.user_types_module is None, meaning that permission control is inactive: everything is permitted. But note that Site.set_user_model() sets it to lino.core.user_types.

This must be set if you want to enable permission control based on user roles defined in Permittable.required_roles and UserType.role.

If set, Lino will import the named module during site startup. It is expected to define application-specific user roles (if necessary) and to populate the UserTypes choicelist.

Examples of such user types modules are lino.core.user_types and lino_noi.lib.noi.user_types.

The welcome message

users/welcome_email.eml

The template used to generate the welcome email to new users.

Here are several ways for generating the verify link in the users/welcome_email.eml.

A first series used the instance action:

>>> ar = rt.login("robin", renderer=settings.SITE.kernel.default_renderer)
>>> obj = ar.get_user()
>>> ar.permalink_uris = True
>>> print(tostring(ar.instance_action_button(
...  obj.verify_me, request_kwargs=dict(action_param_values=dict(
...    email="foo@example.com", verification_code="123")))))  
<a href="/api/users/AllUsers/1?fv=123&amp;an=verify_me" title="Ask ... verified." style="text-decoration:none">Verify</a>
>>> # ba = users.Users.get_action_by_name('verify')
>>> url = ar.get_permalink(obj.verify_me.bound_action, obj, email="foo@example.com", verification_code="123")
>>> print(url)
/api/users/AllUsers/1?fv=123&an=verify_me

But this wasn’t good because it uses the lino.modlib.users.AllUsers data table, which is visible only to SiteAdmin.

>>> ba = users.Me.get_action_by_name('verify_me')
>>> pv = dict(email="foo@example.com", verification_code="123")
>>> from lino.api import _
>>> print(tostring(ar.row_action_button(
...    obj, ba, _("Click here to verify"),
...    request_kwargs=dict(action_param_values=pv))))  
<a href="/api/users/Me/1?fv=123&amp;an=verify_me" title="Ask ... mark your email address as verified." style="text-decoration:none">Click here to verify</a>

But even this had the disadvantage that the user had to sign in before being able to verify.

>>> ba = about.About.get_action_by_name('verify_user')
>>> pv = dict(email="foo@example.com", verification_code="123")
>>> from lino.api import _
>>> print(tostring(ar.row_action_button(
...    obj, ba, _("Click here to verify"),
...    request_kwargs=dict(action_param_values=pv))))
... 
<a href="/api/about/About?fv=foo%40example.com&amp;fv=123&amp;an=verify_user"
  title="Ask ... verified." style="text-decoration:none">Click here to verify</a>

Note how lino.modlib.users.User.verify_me inherits from lino.modlib.about.About.verify_user. They do the same, but in different contexts.

TODO: Is there a more intuitive syntax for rendering our button?

Here is a first attempt, which works only for row actions:

>>> sar = obj.verify_me.request_from(ar, action_param_values=dict(
...    email="foo@example.com", verification_code="123"), permalink_uris=True)
>>> sar.permalink_uris = True
>>> print(tostring(sar.row_action_button_ar(obj)))
... 
<a href="javascript:window.App.runAction(...)" title="..."
  style="text-decoration:none">Verify</a>

Here is a second attempt (used since 20240407):

>>> print(ar.action_link("about.About.verify_user",
...     text=_("click here to verify"),
...     pv=dict(email=obj.email, verification_code="123")))
... 
<a href="/api/about/About?fv=demo%40example.com&amp;fv=123&amp;an=verify_user"
style="text-decoration:none">click here to verify</a>

In the body of the welcome_message.eml, we use lino.core.site.Site.server_url as a base href header field:

<html><head><base href="{{settings.SITE.server_url}}" target="_blank"></head><body>
>>> print(settings.SITE.server_url)
http://127.0.0.1:8000/

Utility functions

lino.core.site.with_user_profile(profile, func, *args, **kwargs)

Run the given callable func with the given user type activated. Optional args and kwargs are forwarded to the callable, and the return value is returned.

This might get deprecated some day since we now have the UserType.context() method