How to Integrate a New Module
This guide helps in extending HELIPORT. You will find that Extensions in HELIPORT are just Django apps. But they may use the available functionality from heliport and hook into the HELIPORT core to integrate seamlessly.
Quick Start
Start your Django app by running the following command inside the top-level
heliport
module (i.e.$REPO_ROOT/heliport
):
heliport-cli startapp your_app_name
This will create a new app directory on the same level as the existing ones.
Implement your Django app. A simple test view is sufficient for now. For a more serious implementation you may find it useful to use HELIPORT functionality. A relatively quick path could be to to create a DigitalObject subclass and a DigitalObjectListView for it.
Add your URLs by adding
path("", include("heliport.your_app_name.urls"))
, to the URL patterns inheliport_config/urls.py
. If your app exposes a Django Rest Framework API, also import the router in this file and register it in theapi_routers
list.Register your app in
heliport_config/settings.py
by addingyour_app_name
to theHELIPORT_SYSTEM_APPS
list, which just adds the app toINSTALLED_APPS
but keeps HELIPORT apps separate. The most basic way to make you app do something in HELIPORT is by providing a HELIPORT Module which creates an entry to your app from the project graph.Update the database after you changed the database schema:
Note
In some environments your_app_name
might not be required as by default migrations for
all apps are made.
heliport-cli makemigrations your_app_name
heliport-cli migrate
Use HELIPORT functionality
Writing Templates
Use the heliport base.html
in your templates:
Begin your template with
{% extends "core/base/base.html" %}
Implement
{% block content %} <!-- your content --> {% endblock %}
If you have scripts use
{% block script %} <script> // your scripts </script> {% endblock %}
Writing Models
To create Django models that make use of HELIPORTS functionality, for example that can be added to a HELIPORT project,
inherit from DigitalObject
(which inherits from Django’s Model
class).
# my_app_name/models.py
from heliport.core.models import DigitalObject
from django.db import models
class MyModel(DigitalObject):
my_model_id = models.AutoField(primary_key=True)
...
Consider reading the HELIPORT documentation on heliport.core.models.DigitalObject
. Here is an overview:
Digital Objects help with access control. See
heliport.core.models.DigitalObject.access()
To save a Digital Object including project and namespace, you can use
heliport.core.models.DigitalObject.save_with_namespace()
Allow user to invoke actions defined in other apps by calling
heliport.core.digital_object_interface.GeneralDigitalObject.actions()
. (See also how to register actions for other apps)Define how the metadata is exported by registering a serializer
Manage dynamic attributes of your objects by using tags (
heliport.core.models.DigitalObjects.add_tag()
) or setting relations (heliport.core.models.assert_relation()
)
Writing Views
To use heliport authentication, inherit from one of the following mixins:
heliport.core.mixins.HeliportLoginRequiredMixin
is the most basic and just asserts that a user to be logged inheliport.core.mixins.HeliportProjectMixin
asserts that the user has access to the current projectheliport.core.mixins.HeliportObjectMixin
asserts that the user has access to the current digital object
To let HELIPORT “generate” a view and template for you (and of course also handle authentication), create a View inheriting from
heliport.core.views.generic.HeliportObjectListView
.To be able to retrieve rendered HTML partials from the API,
heliport.core.renderers.HeliportPartialRenderer
can be used. Checkheliport.core.views.api.MaintenanceMessageView
for an example.
HELIPORT Hooks
Apps can register entrypoints to their functionality using hooks to allow interaction between the HELIPORT core and the
app as well as interaction between apps. A list of extension points can be found below. An extension point is typically
a heliport.core.utils.collections.DecoratorIndex
(or similar) instance, where a class of appropriate type can be registered.
For larger apps we recommend to not register hooks via decorators but instead do it explicitly in apps.py
,
like Django signal connecting. This makes it explicit what extensions an app registers and avoids problems where an
extension is no longer registered because the file that contains it is no longer imported anywhere.
An example:
# my_app/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
def ready(self):
from .models import MyModule
from heliport.core.app_interaction import heliport_modules
heliport_modules.register(MyModule)
Some hooks can not be registered in this way and instead need to exist with correct name in a file called interface.py
in your app. This file needs to be imported in __init__.py
of your app like from . import interface
to be recognized.
You need to be careful with cyclical imports.
Modules
Modules represent a bundle of functionality. For example, there may be a module for a specific kind of research object. The entries in the HELIPORT project graph are modules.
To register a module implement a subclass of heliport.core.app_interaction.Module
. You can think of a module
like giving a name to a URL that is entrypoint for some bundle of functionality. Read the documentation of
that class for more detail.
For modules that represent a subclass of heliport.core.models.DigitalObject
you may use heliport.core.app_interaction.DigitalObjectModule
which is simpler to implement and usable in more contexts in HELIPORT because of the additional information contained in the heliport.core.app_interaction.DigitalObjectModule.object_class
.
An example:
from django.urls import reverse
from heliport.core.app_interaction import DigitalObjectModule
class MyModule(DigitalObjectModule):
name = "My Module"
module_id = "my_module"
object_class = MyDigitalObject
def get_url(self, project):
return reverse("my_app:my_digital_object_list", kwargs={"project": project.pk})
Register your module at heliport.core.app_interaction.heliport_modules
. Alternatively a module is automatically
recognized by HELIPORT if placed in the interface.py
file in your app and that file is imported in your __init__.py
.
To show a module in HELIPORT configure it in settings.py
. Refer to the module by its module_id
.
Add it to a phase in
HELIPORT_GRAPH
, you can also create an arbitrary new phase.Add it to the navbar in the top right via
HELIPORT_PROJECT_NAVBAR_ITEMS
.Add it to the drop-down menu in the navbar via
HELIPORT_PROJECT_DROPDOWN_ITEMS
.Add dependencies of a module to
HELIPORT_DEPENDENCIES
so that the module only becomes available to a project if the dependencies are already in the project.
Text Search
To make objects in your app searchable with the global text search add a function called get_search_url()
with no
arguments to interface.py
returning the url to a view that renders a html div containing search results given a query parameter
q=multi word search string
Make sure from . import interface
is in __init__.py
of your app for HELIPORT to find this function.
Project Creation
To register a view for creating a HELIPORT Project in a custom way add a function get_project_create_urls()
with no
arguments, returning a dict
mapping the name of your Project creation method (Arbitrary String) to a URL in your app
where the user can create a new HELIPORT project. This is then added to the create options in the HELIPORT project
dashboard.
Make sure from . import interface
is in __init__.py
of your app for HELIPORT to find this function.
Define Actions
To support an action that may be performed on a digital object of any app, subclass
heliport.core.digital_object_actions.Action
and register it to
heliport.core.digital_object_actions.actions
in apps.py
of your app.
You decide in your implementation for which objects the action is applicable.
See also the docs of heliport.core.digital_object_actions
for more information.
External Objects
Most of what HELIPORT shows are instances of heliport.core.models.DigitalObject
. But HELIPORT can also
show and work with objects that are not stored in heliport and are dynamically accessed on demand.
To register such a type of object you can implement a subclass of
heliport.core.digital_object_interface.GeneralDigitalObject
and register it at
heliport.core.digital_object_resolution.object_types
.
See also the docu on heliport.core.digital_object_resolution
for more information.
Most importantly the heliport.core.views.digital_objects.AsDigitalObjectView
, for importing such external
object, supports this resolution mechanism. Other examples include views for files and directories.
Metadata
There are different hooks for registering functionality that enhances available metadata in HELIPORT. This mostly is about the metadata export but can also affect some other functionality that can work with general metadata.
Registries of these hooks are in heliport.core.serializers
, which is also a good place for further information.
They are instances of heliport.core.utils.collections.DecoratorDict
which requires an additional parameter - the key in
the dict - when registering something: static_attributes.register(MyClass)(func_that_returns_list_of_attributes)
.
serializers.py
of your app gets imported by Django Rest Framework and is therefore slightly better for registering the
hook as decorator like this:
@static_attributes.register(MyClass)
def func_that_returns_list_of_attributes():
return []
Note
You may find that the static_attributes
hook, where you need to understand the
heliport.core.attribute_description
module, and the rdf_serializers
hook, where you need to understand the
heliport.core.vocabulary_descriptor
module, are complex. An alternative approach already exist, and gets included in the serialization in the future, where you
basically just need to know the rdflib library and the process of running the serialization is also much simpler.
Have a look at heliport.core.utils.collections.RootedRDFGraph
which you can produce just by implementing
heliport.core.utils.serialization.GeneralValue.as_rdf()
. Note that
heliport.core.utils.serialization.GeneralValue
is a base class of all kinds of digital objects in HELIPORT.
These extension points are available:
heliport.core.serializers.static_attributes
/ heliport.core.serializers.register_digital_object_attributes()
:
Register a function that returns a list of heliport.core.attribute_description.BaseAttribute
subclass instances.
This is used to describe the static Django database model for example to be included in the metadata export.
Static attributes can also include constant attributes like a type that is the same for all instances.
See heliport.core.attribute_description.TypeAttribute
.
Normally you would register attributes for a subclass of heliport.core.models.DigitalObject
. For this the
decorator heliport.core.serializers.register_digital_object_attributes()
exists, which includes some general attributes automatically.
It is also possible to register attributes for arbitrary classes by using
heliport.core.serializers.static_attributes
, with the static_attributes.register(cls)
decorator.
Also have a look at rdf_serializers
below in that case.
heliport.core.serializers.datacite_serializers
:
Register a subclass of heliport.core.serializers.DATACITESerializer
to allow serializing a class to DataCite
metadata.
If you want to register a serializer for a class that is a subclass of heliport.core.models.DigitalObject
,
you can register a subclass of heliport.core.serializers.DigitalObjectDATACITESerializer
where you often can
simply set just the resource_type
and in some cases related_identifiers
.
heliport.core.serializers.rdf_serializers
:
A more flexible alternative to heliport.core.serializers.static_attributes
where you can just generate triples
as you like. Register a subclass of heliport.core.serializers.RDFSerializer
for your class.
heliport.core.serializers.DigitalObjectRDFSerializer
is the serializer that implements the RDF serialization
using heliport.core.serializers.static_attributes
and also includes the dynamic attributes.
If you want to serialize things that are not digital objects you need to implement your own
heliport.core.serializers.RDFSerializer
optionally also making use of
heliport.core.serializers.static_attributes
.
heliport.core.serializers.renderers
:
For renderers that are not RDF or DataCite or to support additional serialization formats.
Special Digital Objects
You can implement additional interfaces for your Django models that inherit from heliport.core.models.DigitalObject
.
To do this inherit additionally from one or more of the classes in heliport.core.digital_object_aspects
and
implement the respective interface. Digital object classes implementing these interfaces are automatically found and
recognized by HELIPORT in some places.
There is a dedicated guide for this at Special Digital Objects.
Customize Digital Object Subclass
Some behaviour can be tweaked by implementing / overwriting certain methods in your
heliport.core.models.DigitalObject
subclass.
get_custom_view_url(user) -> str
If this function does not returnNone
, but a URL, the user can be redirected to a special page representing the digital object instead of the landing page. The user parameter can be used to avoid redirecting to a page where the user has no permission. The user may beNone
if there is no logged-inheliport.core.models.HeliportUser
. This gets evaluated in theheliport.core.views.landing_page.LandingPageView.get_custom_url()
.As digital objects inherit from
heliport.core.utils.serialization.GeneralValue
you can overwrite the conversion functions defined there as appropriate.