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:
He may only access the instance of
Profile
that is associated with his account within the project (by default this is an instance ofdjango.contrib.auth.models.User
but internallyProfile.owner
is referencingAUTH_USER_MODEL
; this makes the app pluggable, even if a project uses a custom user model, see Referencing the User Model).He may only access instances of
Event
that are tied to hisProfile
(as provided byEvent.profile
).
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:
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 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).