App’s Internals

django-calingen’s Permission System

Django’s built-in Permission System is working on model level, meaning that if a project’s user is permitted to view, create, update or delete a given model, he may tinker with all instances of that model.

For django-calingen, that would mean, that any user of the Django project may view, update or delete any instance of Event, even the ones of other users.

Obviously, that is not the desired behaviour.

Instead, the app’s views ensure that a user can only access objects owned by himself, meaning:

Note

Actually this is not really a permission system, but rather a restriction system, limiting what a user can access.

It is not possible to share objects between users of the project. Please refer to Sharing Events for further details.

The following image visualizes the mechanism:

Displays which classes are involved in the implementation

Visualization of the Permission implementation

For model-specific CRUD views (see Django models and their related views), the row-level permissions are enforced by RestrictToUserMixin. EventUpdateView is used in the visualization, but the concept is applicable to all views inheriting from one of Django’s built-in Class-Based Views (CBV), that are intended to work with models / objects.

Internally, all of them use a method get_queryset() to determine the instance of django.db.models.query.QuerySet to use in order to retrieve objects from the database (see django.views.generic.list.MultipleObjectMixin.get_queryset() and django.views.generic.detail.SingleObjectMixin.get_queryset() for the actual implementation details).

However, with the app-specific RestrictToUserMixin that method is overwritten to use the (app-specific) implementation of django.db.models.manager.Manager, which is accessible as a custom model manager provided with the calingen_manager attribute (see calingen.models.event.Event.calingen_manager for implementation details).

The calingen_manager (e.g. calingen.models.event.Event.calingen_manager) is also used by other views of the app, that access the app’s models. The visualization includes CalendarEntryListView as an example. It inherits its get_context_data() method from AllCalendarEntriesMixin, which internally uses the (app-specific) implementations of django.db.models.manager.Manager provided with the calingen_manager attribute.

This app- and model-specific Manager implementation uses a model-specific implementation of django.db.models.query.QuerySet, which provides a filter_by_user() method. This method is used to provide a filtered sub set of the original QuerySet to django.views.generic.detail.SingleObjectMixin, django.views.generic.list.MultipleObjectMixin and other app-specific views, effectively achieving the desired result:

A user can only retrieve, update and delete objects that are associated with his account.

Warning

Please be aware, that the app-specific permission system is implemented by providing an additional Manager to the models.

In Django’s administration backend the app’s models are accessed using the default manager, which is Django’s default implementation of Manager and QuerySet, thus, administrators will have access to all objects of every user.

Layout Rendering and Compilation

This is the core functionality of the app.

The following image visualizes the process on an abstract level, before diving deeper into the implementation details.

The layout rendering and compilation process

Visualization of the Process

The rendering and compilation process starts by selecting a layout.

The layout selection is implemented by LayoutSelectionView, which is a fairly standard wrapper to display LayoutSelectionForm. The list of available layouts is automatically determined by accessing LayoutProvider.list_available_plugins().

Submitting the form will store the selected layout in the user’s session and proceed to the layout configuration.

This step is optional! Layouts - or more specifically: implementations of LayoutProvider - may choose to provide a layout-specific configuration form (an implementation of LayoutConfigurationForm; see LayoutProvider Development for details).

If such a form is provided, it will be displayed in this step and when submitted, its data will be included in the user’s session.

During the final stage, the actual rendering and compilation is performed. In this case rendering refers to processing the layout’s templates using the layout’s render() method (in its default implementation, this will use Django’s render_to_string()), while compilation refers to running the rendering result through a fitting compiler.

Implementations of CompilerProvider are required to return a valid Django Response object. What actually is returned is dependent on the compiler (see CompilerProvider Development for details).