users : user management

This document describes the lino.modlib.users plugin, which in Lino replaces django.contrib.auth. See also User management à la Lino 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.

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

Code examples in this document use the lino_book.projects.min9 demo project:

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

Users

class lino.modlib.users.Users

Base class for all tables of 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.

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. Users with an empty username cannot 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 are considered user preferences:

initials

The nickname or initials of this user. This 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 set for every new user. Used for online registration. When this is non empty, is_verified() returns False and the verify action is available.

When use_verify is False, this is a dummy field.

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.

__str__(self)

Returns either the initials or get_full_name().

get_full_name(self)

Return the first_name plus the last_name, with a space in between. 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.

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.

User types

>>> rt.show('users.UserTypes', language="en")
======= =========== ===============
 value   name        text
------- ----------- ---------------
 000     anonymous   Anonymous
 100     user        User
 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 lino.modlib.users.UserType

Base class for all user types. Any instance if this represents a possible user type.

role

The role of users having this type. This is an instance of <lino.core.roles.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

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   900
------------------------ ----- ----- -----
 cal.GuestOperator              ☑     ☑
 comments.CommentsStaff               ☑
 comments.CommentsUser          ☑     ☑
 contacts.ContactsStaff               ☑
 contacts.ContactsUser          ☑     ☑
 excerpts.ExcerptsStaff               ☑
 excerpts.ExcerptsUser          ☑     ☑
 notes.NotesStaff                     ☑
 notes.NotesUser                ☑     ☑
 office.OfficeStaff                   ☑
 office.OfficeUser              ☑     ☑
 polls.PollsAdmin                     ☑
 polls.PollsUser                ☑     ☑
 xl.SiteAdmin                         ☑
 xl.SiteUser                    ☑
======================== ===== ===== =====

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

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.

Plugin configuration

The plugin has the following options:

lino.modlib.users.use_verify

Whether this new users are asked to enter a verification code to verify their email address. This should be set to True on a Lino site with site offers online registration.

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.

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. And SiteAdmin users don'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 which asks for username and password and which 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.

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 that have a user field which 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 object. A pointer to lino.modlib.users.models.User.

class lino.modlib.users.StartPlan
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. Usage examples are an invoicing plan (lino_xl.lib.invoicing.Plan) or an accounting report ():class:lino_xl.ledger.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
run_start_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)
Change the password of this user.

Verify whether #3766 is fixed:

>>> show_choices('robin', '/choices/users/Users/partner')
... 
<br/>
AS Express Post
AS Matsalu Veevärk
...
Ärgerlich Erna
Õunapuu Õie
Östges Otto
>>> show_choices('robin', '/choices/users/Users/user_type')
<br/>
000 (000 (Anonymous))
100 (100 (User))
900 (900 (Administrator))

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, request_kwargs=dict(action_param_values=dict(
...    email="foo@example.com", verification_code="123")))))
<a style="text-decoration:none" href="/api/users/AllUsers/1?fv=foo%40example.com&amp;fv=123&amp;an=verify">Verify</a>
>>> # ba = users.Users.get_action_by_name('verify')
>>> url = ar.get_permalink(obj.verify.bound_action, obj, email="foo@example.com", verification_code="123")
>>> print(url)
/api/users/AllUsers/1?fv=foo%40example.com&fv=123&an=verify
>>> # test_client.force_login(obj)
>>> # test_client.get(url)

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

>>> ba = users.Me.get_action_by_name('verify')
>>> 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 style="text-decoration:none" href="/api/users/Me/1?fv=foo%40example.com&amp;fv=123&amp;an=verify">Click here to verify</a>

TODO: Is there a more intuitive syntax for rendering our button? Here is an attempt:

>>> sar = obj.verify.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 style="text-decoration:none" href="javascript:Lino.users.AllUsers.verify.run(null,{ &quot;base_params&quot;: {  }, &quot;field_values&quot;: { &quot;email&quot;: &quot;&quot;, &quot;verification_code&quot;: &quot;&quot; }, &quot;param_values&quot;: { &quot;end_date&quot;: null, &quot;start_date&quot;: null, &quot;user_type&quot;: null, &quot;user_typeHidden&quot;: null }, &quot;record_id&quot;: 1 })">Verify</a>

Note that 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/