Plugins#
This page lists everything the plugin framework gives you: the registration decorator, the Plugin base class, the registry, the helper functions and the errors a refused registration raises. It does not show how to build a plugin, which is in How to create a plugin, or how access to a record is decided, which is in How access to a record is decided.
A plugin is a Django view attached to a core record type. Registering it gives the view an address under the record, an entry in the record’s tab navigation, and the record itself as base_object.
Import from fairdm.plugins, which re-exports fairdm.contrib.plugins:
from fairdm import plugins
from fairdm.plugins import Plugin
Record types that accept plugins#
FairDM mounts the plugin addresses of five record types. A plugin registered against any other model is never reachable.
Record type |
Import |
Address prefix |
URL namespace |
|---|---|---|---|
Project |
|
|
|
Dataset |
|
|
|
Sample |
|
|
|
Measurement |
|
|
|
Contributor |
|
|
|
A sample or measurement type that your portal defines shares the namespace of its base type. Register a plugin against Sample or Measurement and narrow it to your type with is_instance_of.
Registration#
plugins.register#
plugins.register(*models, label=None, icon="circle", order=0, menu=True)
A decorator factory. It registers the decorated class against each model in models and returns the class unchanged. A registration that cannot work raises PluginRegistrationError when the module is imported.
Keyword |
Type |
Default |
What it does |
Accepts |
|---|---|---|---|---|
|
|
The plugin name with hyphens replaced by spaces, in title case |
The text of the navigation entry |
Any string |
|
|
|
The icon of the navigation entry |
An icon name |
|
|
|
The position of the entry among the record’s entries. Lower comes first |
Any integer |
|
|
|
Whether the plugin gets a navigation entry |
|
Any other keyword is stored with the registration and has no effect.
FairDM imports the plugins module of every installed app at start-up, so a registration placed in plugins.py is found without any other step.
The Plugin class#
from fairdm.contrib.plugins import Plugin
Plugin extends Django’s PermissionRequiredMixin and View. List it before the view class you combine it with:
class Analysis(Plugin, FairDMTemplateView): ...
Attributes you set#
Attribute |
Type |
Default |
What it does |
Accepts |
|---|---|---|---|---|
|
|
|
The plugin’s identifier, unique for each record type. It forms the URL name |
|
|
|
|
The path segment of the plugin’s address |
|
|
|
|
The permission or permissions needed to open the page |
A permission with its app label, such as |
|
|
|
Decides whether the page opens and the entry appears |
A bool, or a function |
|
list of |
|
Further views that belong to this plugin |
Plugin classes with a non-empty |
|
|
|
The page heading and the last entry of the breadcrumb trail |
Any string |
|
|
|
The model field Django’s single-object views look the record up by |
A field name |
|
|
|
The URL keyword that carries that value |
A URL keyword |
An inner Media class, written as on a Django form, adds stylesheets and scripts to the page.
Attributes set for you#
Attribute |
What it holds |
|---|---|
|
The model the mount serves. A plugin registered against two models is mounted once for each, and each mount holds its own model |
|
On an additional view’s mount, the plugin that declares it. |
|
The navigation menu of the record type, bound to each mount |
Methods and properties#
Name |
Signature |
Returns |
|---|---|---|
|
|
|
|
|
The segment, or |
|
|
A new list copied from |
|
|
The URL patterns of the plugin and its additional views |
|
Cached property |
The core record the plugin hangs from, or |
|
|
The record named by the address. Reads through the model’s |
|
|
The result of |
|
|
The trail: the model’s plural name (linked when a list page resolves), the record, then |
base_object is separate from self.object, which stays whatever your view class makes it.
Context#
Plugin.get_context_data adds to the context of the view it is combined with:
Key |
What it holds |
|---|---|
|
The core record |
|
The view’s own object, or |
|
The trail from |
|
The record type’s navigation menu. FairDM’s base template draws it as tabs |
|
The |
Addresses and URL names#
A plugin is mounted at <record prefix>/<url_path>/. An additional view is mounted at <record prefix>/<plugin url_path>/<view url_path>/.
URL names are flat, with no nested namespace:
View |
URL name |
|---|---|
The plugin |
|
An additional view |
|
The name of a view is its class name, slugified, unless it sets name. Notes becomes notes and NoteEdit becomes note-edit, so the additional view in Notes.extra_views is named notes-note-edit.
Names are unique for each record type, not across record types.
Mounting a record type#
A record type accepts plugins when a URL configuration mounts its patterns. FairDM does this for the five record types above. For another record type, include the patterns under the route the registry gives:
from django.urls import include, path
from fairdm.plugins import registry
urlpatterns = [
path(
f"stations/{registry.route_for(Station)}/",
include((registry.get_urls_for_model(Station), "station")),
),
]
The namespace is the lowercase model name. get_urls_for_model also builds the record type’s navigation menu, so register the plugins before the URL configuration is imported.
The registry#
from fairdm.plugins import registry
registry is an instance of PluginRegistry.
Name |
Signature |
Returns |
|---|---|---|
|
|
The decorator. This is |
|
|
|
|
|
The |
|
|
The route fragment a URL configuration mounts the record’s plugins beneath |
|
|
The map from URL keyword to model field |
|
|
The registered |
|
|
The URL patterns of every plugin registered for |
|
|
The record type’s menu, named |
The default addressing is the route <str:uuid> with the lookup {"uuid": "uuid"}. A record identified some other way declares it before its patterns are built:
registry.declare_addressing(
Station,
route="<str:code>",
lookup={"code": "code"},
)
A plugin then finds its record by code, and reverse fills code in from the record.
Helper functions#
All are importable from fairdm.plugins.
is_instance_of#
is_instance_of(*model_classes)
Returns a check(request, obj) predicate. It passes when obj is an instance of one of model_classes, and when obj is None.
has_perm#
has_perm(request, permission, obj=None)
Returns True when the request’s user holds permission for the whole portal or on obj. The answer is remembered for the rest of the request.
holds_level#
holds_level(request, level, obj)
Returns True when the viewer holds level on a project, dataset, sample or measurement. level is "view", "edit" or "manage", standing for the view_<model>, change_<model> and delete_<model> permissions. Returns False when obj is None or is not one of those four, and for a visitor who is not signed in. Raises ValueError for any other level. The template tag of the same name is in Template tags. The levels are described in Contribution levels and record access.
can_open#
can_open(view_class, request, obj=None)
Returns True when the view’s check passes and every permission in its permission is held. Both the navigation entry and the page itself ask this one function. It reads only the check and permission of the class it is given. An additional view takes nothing from its plugin.
reverse#
reverse(instance, view_name, *args, **kwargs)
Returns the URL of a plugin view for a record. The namespace is the record’s type_of model when it has one, otherwise its own model, lowercased. Keywords named in the record type’s addressing are filled in from the record.
from fairdm.plugins import reverse
reverse(sample, "analysis")
slugify#
slugify(text)
Returns the slug a class name becomes. URLTestPlugin becomes url-test-plugin.
Mixins#
Both are in fairdm.contrib.plugins.mixins. List them before Plugin.
Mixin |
What it does |
|---|---|
|
Answers a refused request for a non-public record with a 404, so the address does not confirm the record exists. A refusal on a public record is the usual one. It reads |
|
On a deletion page, points Back at the record’s own page rather than its list. List it before the delete view |
PluginRegistrationError#
fairdm.contrib.plugins.checks.PluginRegistrationError subclasses Django’s ImproperlyConfigured. Its message reads <plugin> registered against <model>: <problem>.
A registration is refused when:
no model is given, or a value is not a model class
checkis neither a bool nor a callableurl_pathis not a valid routeanother plugin on the record type has the same name
another plugin on the record type has the same
url_paththe URL names the plugin generates collide with another plugin’s
an
extra_viewsentry is not aPluginsubclassan
extra_viewsentry is the plugin itselfan
extra_viewsentry declaresextra_viewsof its ownan
extra_viewsentry hasurl_path = Nonetwo
extra_viewsentries claim the same segment
Subclassing#
A plugin is an ordinary class. A package can ship a plugin without registering it, and a portal registers a subclass:
@plugins.register(Sample, label=_("Citations"), icon="quote", order=520)
class Citations(CitationsPlugin):
pass