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

users : Manage the users of this site

The lino.modlib.users plugin defines functionality for managing the users of a Lino site.

This page is the developer reference. For end-user documentation, see The users plugin. For getting started with user management, see Introduction to user management. If you wonder why Lino replaces Django’s user management and permission system, see Lino has its own user management.

This page contains code snippets (lines starting with >>>), which are being tested during our development workflow. The following snippet initializes the demo project used throughout this page.

>>> from lino_book.projects.noi1r.startup import *

User types

User types are stored in the UserTypes choicelist.

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

User roles and their usage

This plugin defines the UserRoles table.

class lino.modlib.users.UserRoles

Shows the user roles used on this site and the user types that have them.

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

Examples: Noi

The table doesn’t show all user roles, only those that are “meaningful”. Where meaningful means: those which are imported or defined in the global context of the user_types_module.

Querying users

All active 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

This plugin defines the Sessions table.

class lino.modlib.users.Sessions

Show a list of all user sessions.

See User sessions.

The current user type

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

Plugin configuration

This plugin adds the following plugin settings:

lino.modlib.users.private_default

Default value for the PrivacyRelevant.private field.

An individual database model can customize whether it actually uses this site-wide default value by overriding the get_default_value_for_private() method.

lino.modlib.users.third_party_authentication

Whether this site provides third party authentication.

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

lino.modlib.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.

lino.modlib.users.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.

verification_code_expires = 5

Number of minutes after which a user verification code becomes invalid.

lino.modlib.users.user_type_new

The default user type for an new site user.

Default value is 'user'. See also Register form.

lino.modlib.users.user_type_verified

The default User.user_type for a verified user.

lino.modlib.users.with_nickname

Whether to add the User.nickname field.

lino.modlib.users.with_phone

Whether we want to store the phone number of a user account in its User.phone field.

Default value is False.

lino.modlib.users.demo_password

The password to set for users of a demo site.

lino.modlib.users.demo_username

The username of the default site user.

The username of the root user to use in demo fixtures.

Default value is "robin" (except when the Site’s first language is not English). It is needed on sites that don’t have English in their lino.core.site.Site.languages.

The checkdata fixture uses this.

This is also the default value for the get_responsible_user method of data checker.

User 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.

Editing user settings

Rules:

Only System admins may change the user_type of users. Other users cannot change this field.

A site manager can modify everything on every user, even define or change their password. With one exception: they cannot do this for another site manager. Because if a site manager gets hacked, they could disable all other site managers and then the site would be lost.

The User model overrides the following methods to implement these rules.

>>> rolf = users.User.objects.get(username='rolf')
>>> jean = users.User.objects.get(username='jean')
>>> ar = rt.login('robin')
>>> jean.disabled_fields(ar)
set()
>>> sorted(rolf.disabled_fields(ar))
['change_password', 'user_type']
>>> ar = rt.login('jean')
>>> sorted(jean.disabled_fields(ar))
['merge_row', 'partner', 'send_email', 'user_type']
>>> sorted(rolf.disabled_fields(ar))
['change_password', 'merge_row', 'partner', 'send_email', 'user_type']

Actions

class lino.modlib.users.ChangePassword

Implements User.change_password.

current

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

new1

The new password.

new2

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

class lino.modlib.users.InitPassword

Same as ChangePassword, but without asking for the current password.

class lino.modlib.users.VerifyMe

Implements User.verify_me.

When the verification code is correct, this will mark your email address as verified.

verification_code

The verification code you have received to your email address.

class lino.modlib.users.VerifyUser
username
email
verification_code

The verification code that has been sent to the user’s email address.

class lino.modlib.users.SignIn

Implements lino.modlib.system.About.sign_in.

When third_party_authentication is enabled, this also shows alternative authentication methods.

This opens a dialog window that asks for username and password and authenticates as this user when submitted.

class lino.modlib.users.SignOut

Implements lino.modlib.users.User.sign_out.

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.

class lino.modlib.users.PrivacyRelevant

Model mixin to mark a database model as privacy-relevant data, i.e. something that is to be shown only to the author and the author’s group mates.

When private of a row is True, only the author and members of the group can see the row. The author can see the row even if they are no longer member of the group.

private

Whether this row is confidential.

Default value is private_default (unless the database model has a customized get_default_value_for_private() method).

The internal field name is “private” instead of “confidential” for historical reasons.

group

The user group this row belongs to. This field may be empty, which means that it is not shared in a group, only its author can see it.

If you have permission to edit this field, you can select only groups you are member of.

This is a dummy field when the lino_xl.lib.groups plugin isn’t installed.

get_default_value_for_private()

Return the default value for the private field.

Privacy-relevant data

Privacy-relevant data is data that belongs to a given site user user group and is to be shown only to members of this group.

Every database model that inherits from PrivacyRelevant is potentially privacy-relevant.

Privacy-relevant data is automatically filtered with the following rules:

  • Anonymous users can see only rows that are marked as not private (confidential).

  • A SiteAdmin can see all rows.

  • Normal users can see only rows for which one of the following is true:

    • row is marked as not private (confidential)

    • user is the author

    • user is member of the row’s group

doctests

Verify whether the help_text of the User.change_password action is set:

>>> ba = rt.models.users.Users.get_action_by_name('change_password')
>>> print(ba.help_text)
Change the password to be used for signing in as this user.
>>> with translation.override("fr"):
...     print(ba.help_text)
Changer le mot de passe de cet utilisateur.

Verify whether #3766 is fixed:

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

AS Express Post
AS Matsalu Veevärk
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.

  • lino.modlib.system.About.sign_in

  • lino.modlib.system.About.reset_password

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 email

Welcome email

An email message sent by Lino to a user in certain situations, e.g. to set their initial password, to verify their email address, and such.

users/welcome_email.eml

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

The Welcome email starts with a greeting followed by one or several sentences of “instructions” for the user to execute. These instructions depend on the user’s state.

>>> ar = rt.login("robin", renderer=settings.SITE.kernel.default_renderer)
>>> user = ar.get_user()
>>> ar.permalink_uris = True
>>> ar.show_urls = True
>>> def test():
...    txt = beautiful_soup(user.get_welcome_email_body(ar)).text.strip()
...    txt = txt.replace("\n\n", "\n")
...    txt = user.get_welcome_subject() + "\n" + txt
...    print(txt)

For a good user it says:

>>> user.verification_code = ''
>>> test()
Welcome on noi1r
Hello, Robin Rood!
Your user settings on the noi1r site are perfect.
Thank you for being with us.

When user has no password, the instruction says:

>>> user.set_unusable_password()
>>> test()
Please join us on noi1r
Hello, Robin Rood!
You have been invited to the noi1r site.
Please click here to set your password.
Thank you for joining us.
>>> user.set_password("1234")
>>> user.must_verify()
>>> test()
...
Please verify your email address on noi1r
Hello, Robin Rood!
Please click here to verify your email address.
Or visit the site and enter your verification code manually: ....
Your verification code will expire in 5 minutes.
Thank you for being with us.

After the ResetPassword action, the welcome message says:

>>> user.verification_password = '****'
>>> test()
Please confirm your new password on noi1r
Hello, Robin Rood!
You asked us to reset your password on the noi1r site.
(If this was not you then please contact us.)
Please click here to confirm your new password.
Or visit the site and enter your verification code manually: ....
Your verification code will expire in 5 minutes.
Thank you for being with us.

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

A first series used the instance action:

>>> print(tostring(ar.instance_action_button(
...  user.verify_me, request_kwargs=dict(action_param_values=dict(
...    email="foo@example.com", verification_code="123")))))
...
<a
href="/api/users/Users/1?fv=None&amp;fv=foo%40example.com&amp;fv=123&amp;an=verify_me"
title="Enter the verification code we sent to you when you asked us to reset
your password." style="text-decoration:none; white-space:nowrap;">Verify</a>
>>> # ba = users.Users.get_action_by_name('verify')
>>> url = ar.get_permalink(user.verify_me.bound_action, user, email="foo@example.com", verification_code="123")
>>> print(url)
/api/users/Users/1?fv=None&fv=foo%40example.com&fv=123&an=verify_me

But this wasn’t good because it uses the lino.modlib.users.Users 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(
...    user, ba, _("Click here to verify"),
...    request_kwargs=dict(action_param_values=pv))))
...
<a
href="/api/users/Me/1?fv=None&amp;fv=foo%40example.com&amp;fv=123&amp;an=verify_me"
title="Enter the verification code we sent to you when you asked us to reset
your password." style="text-decoration:none; white-space:nowrap;">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(
...    user, ba, _("Click here to verify"),
...    request_kwargs=dict(action_param_values=pv))))
...
<a href="/api/about/About?fv=None&amp;fv=foo%40example.com&amp;fv=123&amp;an=verify_user"
title="Enter the verification code for any user." style="text-decoration:none;
white-space:nowrap;">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 = user.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(user)))
...
<a href="javascript:window.App.runAction(...)" title="..."
  style="text-decoration:none; white-space:nowrap;">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=user.email, verification_code="123")))
...
<a href="/api/about/About?fv=None&amp;fv=demo%40example.com&amp;fv=123&amp;an=verify_user"
style="text-decoration:none; white-space:nowrap;">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

The welcome email for a user with unusable password contains a link to the lino.modlib.about.About.reset_password action. Here is that link:

>>> ar = rt.login("robin")
>>> user = ar.get_user()
>>> ba = rt.models.about.About.get_action_by_name('reset_password')
>>> print(ba.get_action_permalink(ar, pv=dict(email=user.email, username=user.username)))
/#/?clone=JSON%3A%3A%3A%7B%22runnable%22%3A+%7B%22action_full_name%22%3A+%22about.About.reset_password%22%2C+%22actorId%22%3A+%22about.About%22%2C+%22status%22%3A+%7B%22field_values%22%3A+%7B%22email%22%3A+%22%22%2C+%22username%22%3A+%22%22%2C+%22new1%22%3A+%22%22%2C+%22new2%22%3A+%22%22%7D%2C+%22base_params%22%3A+%7B%7D%2C+%22pv%22%3A+%7B%22email%22%3A+%22demo%40example.com%22%2C+%22username%22%3A+%22robin%22%7D%7D%7D%7D

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

lino.core.site.get_by_username(username)

Equivalent to User.objects.get(username=x) except that the text of the DoesNotExist exception is more useful and that you can specify a default value.

>>> users.User.get_by_username('robin')
User #1 ('Robin Rood')
>>> users.User.get_by_username('foo')
Traceback (most recent call last):
...
lino.modlib.users.models.User.DoesNotExist: No User with username 'foo'
>>> users.User.get_by_username('foo', None)

Linking to users

The link behind a foreign key to a site user can vary depending on your user type. It can lead to Users, to ListedUsers or to Me.

For example, look at the user names in the “Assigned to” column.

>>> from django.db import models
>>> kwargs = dict(limit=3,
...     display_mode=DISPLAY_MODE_GRID, show_urls=True,
...     filter=models.Q(user__username="jean"))
>>> ar.show(tickets.Tickets, column_names="id assigned_to", **kwargs)
...
====================================== ====================================
 ID                                     Assigned to
-------------------------------------- ------------------------------------
 `111 </#/api/tickets/Tickets/111>`__   `Jean </#/api/users/Users/7>`__
 `102 </#/api/tickets/Tickets/102>`__   `Mathieu </#/api/users/Users/5>`__
 `93 </#/api/tickets/Tickets/93>`__
====================================== ====================================
>>> rt.login("jean").show(tickets.Tickets, column_names="id assigned_to", **kwargs)
...
====================================== ==========================================
 ID                                     Assigned to
-------------------------------------- ------------------------------------------
 `111 </#/api/tickets/Tickets/111>`__   `Jean </#/api/users/Me/7>`__
 `102 </#/api/tickets/Tickets/102>`__   `Mathieu </#/api/users/ListedUsers/5>`__
 `93 </#/api/tickets/Tickets/93>`__
====================================== ==========================================
>>> jean = users.User.objects.get(username="jean")
>>> robin = users.User.objects.get(username="robin")
>>> rt.login("jean", show_urls=True).obj2url(jean)
'/#/api/users/Me/7'
>>> rt.login("robin", show_urls=True).obj2url(jean)
'/#/api/users/Users/7'

Even when showing to a site admin a link to themselves, Lino will use the Users table, not the users.Me table.

>>> rt.login("robin", show_urls=True).obj2url(robin)
'/#/api/users/Users/1'

Reference

The User model

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.

How to communicate with a user

email

Your email address.

phone

Your phone number.

If with_phone is False, this is a dummy field.

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 .

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.

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.

build_method

The default build method to use when this user requests to build a printable document.

date_format

The date format to use when parsing or displaying dates.

See lino.modlib.system.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.

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.

Actions

send_welcome_email

Send a Welcome email to the user.

Implemented by SendWelcomeMail

sign_out

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

Implemented by SignOut

change_password

Change the password to be used for signing in as this user.

See also How to change your password.

Implemented by ChangePassword.

init_password

Set the password to be used for authentication.

Implemented by InitPassword.

verify_me

Enter the verification code we sent to you when you asked us to reset your password.

Implemented by VerifyMe

The Authorization model

class lino.modlib.users.Authorization

Django model used to represent an authorization.

user

The author of this authorization; the user who grants the right of representation.

recipient

The grantee; the user who gets the right of representing the author.

This field was named authorized until 20261014.

Data tables

class lino.modlib.users.Users

List of all users on this site.

class lino.modlib.users.ListedUsers

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.

class lino.modlib.users.Me

Edit your user preferences.

A special variant of users list with myself as the only row.

Choicelists

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.