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

  1. 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.

  1. 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.

  2. Add your URLs by adding path("", include("heliport.your_app_name.urls")), to the URL patterns in heliport_config/urls.py. If your app exposes a Django Rest Framework API, also import the router in this file and register it in the api_routers list.

  3. Register your app in heliport_config/settings.py by adding your_app_name to the HELIPORT_SYSTEM_APPS list, which just adds the app to INSTALLED_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.

  4. 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:

Writing Views

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.

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.