Saturday, September 2, 2017

Avanti

  • I verified that there is still no fix for #1989. See Monday, July 31, 2017.

  • Courses (All courses) is no longer visible for teachers.

  • Idem for ActivityPlanning

Custom permissions for the detail action

Lino has a subtle new feature: a DetailLayout can now have a required_roles attribute. If this is given, then it overrides the default value which until now was always the required_roles of the actor.

This was needed because otherwise after above change (make Courses hidden for teachers), MyCoursesGiven had no detail action as well (for teacher). Also e.g. as_summary_item() calls get_default_table() which, for a course, is customized because the detail of a course can be different depending on what we call the “course area”. E.g. in Lino Voga we have “courses”, “hikes” and “travels”. They all are activities, but their detail layouts defer.

This new feature required a few internal changes. Don’t try to understand the following snippets in detail (I just used them in order to understand myself)

>>> from lino import startup
>>> startup('lino_book.projects.adg.settings.demo')
>>> from lino.api.doctest import *
>>> ses = rt.login('laura')
>>> user_type = ses.get_user().user_type
>>> user_type
<users.UserTypes.teacher:100>
>>> user_type.role
... 
<lino_avanti.lib.avanti.user_types.Teacher object at ...>
>>> obj = rt.models.courses.Course.objects.get(pk=1)
>>> obj.get_detail_action(None)
<BoundAction(courses.Activities, <lino.core.actions.ShowDetail detail ('Detail')>)>
Note this returned before::

<BoundAction(courses.Courses, <lino.core.actions.ShowDetail detail (‘Detail’)>)>

Problem: When a session with a user is given, then get_detail_action(ses) returned None because the default table was (and is) Courses, not Activities. But here we want Lino to understand that we actually do allow to open the detail window because that detail window has been inherited from Activities.

>>> obj.get_detail_action(ses)
<BoundAction(courses.Activities, <lino.core.actions.ShowDetail detail ('Detail')>)>
>>> obj.line.course_area.courses_table
u'courses.Courses'
>>> table = rt.models.resolve('courses.Courses')
>>> table
lino_xl.lib.courses.desktop.Courses
>>> table.detail_action
<BoundAction(courses.Courses, <lino.core.actions.ShowDetail detail ('Detail')>)>

Teachers have no permission to see the Courses table:

>>> table.default_action.get_view_permission(user_type)
False
>>> actor = table.detail_action.action.defining_actor
>>> actor
lino_xl.lib.courses.desktop.Activities
>>> actor.get_view_permission(user_type)
True
>>> actor.detail_action.action.get_view_permission(user_type)
True
>>> actor.detail_action.allow_view(user_type)
True

But now we have the following problem:

>>> ba = rt.models.users.MySettings.detail_action
>>> ba
<BoundAction(users.MySettings, <lino.core.actions.ShowDetail detail ('Detail')>)>

The detail on MySettings now can be viewed only by those who can view the Users table:

>>> ba.actor.get_view_permission(ses.user.user_type)
True
>>> ba.action.get_view_permission(ses.user.user_type)
True
>>> ba.action.owner.required_roles is None
True
>>> ba.required
set([<class 'lino.core.roles.SiteUser'>])
>>> user_type.has_required_roles(ba.required)
True
>>> ba.allow_view(ses.user.user_type)
True
>>> ba.get_view_permission(ses.user.user_type)
True

MySettings does allow a detail, but Lino

>>> #dl = rt.models.courses.MyCoursesGiven.get_detail_layout()
>>> ba = rt.models.courses.MyCoursesGiven.detail_action
>>> ba.actor
lino_xl.lib.courses.desktop.MyCoursesGiven
>>> ba.action
<lino.core.actions.ShowDetail detail ('Detail')>
>>> ba.get_view_permission(ses.user.user_type)
True
>>> rt.models.courses.Courses.detail_action.action
<lino.core.actions.ShowDetail detail ('Detail')>
>>> rt.models.courses.Courses.detail_action.action.defining_actor
lino_xl.lib.courses.desktop.Activities
>>> rt.models.courses.Courses.detail_action.action.owner
... 
<lino_xl.lib.courses.desktop.CourseDetail object at ...>
>>> rt.models.courses.MyCoursesGiven.detail_action.action.defining_actor
lino_xl.lib.courses.desktop.Activities

Aha. Course defines a custom get_detail_action() because the detail_layout to use when displaying a course depends on the course_area (given by the course’s line). And this is Courses in our case. And yes I told Lino that teachers don’t have permission to see Courses.

We don’t want teachers to see all courses, but we do want them to see the detail of a course for which they are author or instructor.

The library actions (default_action, detail_action, submit_detail, insert_action, delete_action and update_action) had their defining_actor to None.

The defining_actor of an action is the actor on which it has been instantiated for the first time. Subclasses of the defining actor can use the same action instance. That differentiation is used by the extjs renderer: for actions that cause some JS code to be rendered it makes no sense to generate that code several times.

A side effect is that I probably discovered and fixed a bug: teachers had no permission to edit their own settings.