Custom fields easy
Custom fields let you add extra data to a model without changing its metadata schema.
They are defined in the repository configuration (invenio.cfg), not in the model’s YAML,
so they are useful when the model is shared (for example, a model based on the CCMM or RDM
templates) and you only need a few extra fields in your repository.
Custom field values are stored in the record’s top-level custom_fields object,
next to metadata:
{
"metadata": { "title": "..." },
"custom_fields": {
"myorg:instrument": "HPLC-2000"
}
}Enabling custom fields in a model
Custom fields are provided by the custom_fields_preset of oarepo-model. If your model does not
include it yet, add it to the presets in the model’s model.py:
from oarepo_model.api import model
from oarepo_model.presets.custom_fields import custom_fields_preset
equipment_model = model(
"equipment",
presets=[
# ... presets already used by the model
custom_fields_preset,
],
# ...
)The preset:
- adds the
custom_fieldsfield to the record (and to the draft, if the model has drafts), - validates
custom_fieldsagainst the configured custom fields when a record is created or updated, - adds
custom_fieldsto the JSON Schema and to the search index mapping, - resolves vocabulary custom fields (
VocabularyCF) when the record is saved.
Adding the preset to an existing model changes its search index mapping. Reinitialize the indices afterwards, see Database update.
Defining custom fields
Custom fields are defined in invenio.cfg under the key <MODEL>_CUSTOM_FIELDS, where <MODEL>
is the model name in uppercase (dashes and spaces replaced with underscores). For a model called
equipment, the key is EQUIPMENT_CUSTOM_FIELDS. The value is a list of custom field instances:
from invenio_records_resources.services.custom_fields.text import KeywordCF
EQUIPMENT_CUSTOM_FIELDS = [
KeywordCF("myorg:instrument"),
]The first argument is the name of the field, i.e. its key inside custom_fields.
To store a list of values, pass multiple=True.
Only the configured fields are accepted: a record containing an unknown key in
custom_fields fails validation.
To add more validation rules, pass field_args (a dictionary of arguments for the underlying
marshmallow field). See the
marshmallow documentation
for more information.
from marshmallow.validate import Length
from invenio_records_resources.services.custom_fields.text import KeywordCF
EQUIPMENT_CUSTOM_FIELDS = [
KeywordCF("myorg:instrument", field_args={"validate": Length(min=1, max=100)}),
]Changes to the custom fields take effect after the server is restarted.
Custom field names
Although not required, it is recommended to prefix the custom field names with your own
namespace (e.g., myorg:instrument). This avoids conflicts with custom fields from other
organizations and simplifies serialization to JSON-LD.
Built-in custom field types
| Field type | Import |
|---|---|
| KeywordCF | from invenio_records_resources.services.custom_fields.text import KeywordCF |
| TextCF | from invenio_records_resources.services.custom_fields.text import TextCF |
| BooleanCF | from invenio_records_resources.services.custom_fields.boolean import BooleanCF |
| EDTFDateStringCF | from invenio_records_resources.services.custom_fields.date import EDTFDateStringCF |
| ISODateStringCF | from invenio_records_resources.services.custom_fields.date import ISODateStringCF |
| IntegerCF | from invenio_records_resources.services.custom_fields.number import IntegerCF |
| DoubleCF | from invenio_records_resources.services.custom_fields.number import DoubleCF |
| VocabularyCF | from invenio_vocabularies.services.custom_fields import VocabularyCF |
| ComplexCF | from oarepo_ui.services.custom_fields import ComplexCF |
Notes:
KeywordCFstores short text that is not analyzed, such as an instrument code, a name or an identifier.TextCFstores text that can be searched with a fulltext search.ISODateStringCFstores a strict ISO date.EDTFDateStringCFstores a date in the EDTF format, i.e. it can store a partial date (only a year, a year and month, etc.).VocabularyCFstores a reference to a controlled vocabulary. Passvocabulary_id="<vocabulary id>", where<vocabulary id>is the id of the vocabulary. The value is entered as{"id": "<term id>"}and the rest of the term is filled in when the record is saved.
Complex custom fields intermediate
If a custom field needs several sub-fields, use ComplexCF with a list of nested custom fields:
from invenio_records_resources.services.custom_fields.text import KeywordCF
from oarepo_ui.services.custom_fields import ComplexCF
EQUIPMENT_CUSTOM_FIELDS = [
ComplexCF("myorg:instrument", [
KeywordCF("maker"),
KeywordCF("serial_number"),
]),
# a list of complex values
ComplexCF("myorg:calibrations", [
KeywordCF("date"),
KeywordCF("performed_by"),
], multiple=True),
]The value is an object (or a list of objects with multiple=True):
{
"custom_fields": {
"myorg:instrument": {"maker": "Acme", "serial_number": "SN-1234"}
}
}Searching custom fields
custom_fields is indexed as a dynamic object: OpenSearch creates the mapping of each custom
field automatically from the first value it indexes. Search custom fields by their full path, e.g.
custom_fields.myorg\:instrument:"HPLC-2000" (the : in the field name must be escaped in the query).
UI configuration
The UI configuration of custom fields is defined in invenio.cfg under the key
<MODEL>_CUSTOM_FIELDS_UI (e.g., EQUIPMENT_CUSTOM_FIELDS_UI). It is a list of sections, each with
a title and a list of fields:
from invenio_i18n import lazy_gettext as _
EQUIPMENT_CUSTOM_FIELDS_UI = [
{
"section": _("Instrument"),
"fields": [
{
"field": "myorg:instrument",
"ui_widget": "Input",
"props": {
"label": _("Instrument"),
"placeholder": _("Serial number ..."),
"icon": "pencil",
"description": _("Specify the serial number of the instrument..."),
},
},
],
},
]| Key | Description |
|---|---|
section | Title of the section |
hidden | If True, the section title is not shown on the record detail page |
fields[].field | Name of the custom field, without the custom_fields. prefix |
fields[].ui_widget | Widget used to edit the field in the deposit form, e.g. Input, TextArea, RichInput, Dropdown or AutocompleteDropdown (for vocabularies) |
fields[].props | Properties passed to the widget: label, placeholder, icon, description, … |
fields[].props.landing_page_component | Optional name of a JinjaX component that renders the value on the record detail page |
Wrap labels and descriptions in lazy_gettext so that they can be translated.
Record detail page intermediate
On the record detail page, the configured sections are rendered by the ICustomFields
component from oarepo-ui. Each field is rendered as a label with its value. To render a field
differently, create a JinjaX component and set its name in landing_page_component inside props:
EQUIPMENT_CUSTOM_FIELDS_UI = [
{
"section": _("Instrument"),
"fields": [
{
"field": "myorg:instrument",
"ui_widget": "Input",
"props": {
"label": _("Instrument"),
"landing_page_component": "InstrumentField",
},
},
],
},
]The component receives the value of the field as d:
{# def d #}
<strong>{{ d }}</strong>