Skip to content

Commit

Permalink
Merge pull request #1452 from archesproject/multi-card-primary-descri…
Browse files Browse the repository at this point in the history
…ptor

Add multi-card primary descriptor #1368
  • Loading branch information
chiatt authored Dec 21, 2023
2 parents b9208b6 + 2175749 commit f8de390
Show file tree
Hide file tree
Showing 26 changed files with 797 additions and 41 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ If you don't already have an Arches project, you'll need to create one by follow
Since Arches for Science uses `Cantaloupe` as its IIIF server, take notice of the
Cantaloupe [installation instructions](https://arches.readthedocs.io/en/stable/developing/advanced/managing-and-hosting-iiif/), too.

When your project is ready add "arches_templating" and "arches_for_science" to INSTALLED_APPS and "arches_for_science" to ARCHES_APPLICATIONS in your project's settings.py file:
When your project is ready add "arches_templating", "arches_for_science", and "pgtrigger" to INSTALLED_APPS and "arches_for_science" to ARCHES_APPLICATIONS in your project's settings.py file:
```
INSTALLED_APPS = (
...
"arches_templating",
"arches_for_science",
"pgtrigger",
"myappname",
)
Expand All @@ -45,6 +46,8 @@ before Arches 6.2.6 or 7.5.0 did not create ``RENDERERS``, so you may need to
[add it first](https://github.com/archesproject/arches/pull/10171/files)
before extending it as shown below):
```
FUNCTION_LOCATIONS.append("arches_for_science.pkg.extensions.functions")
TEMPLATES[0]["OPTIONS"]["context_processors"].append("arches_for_science.utils.context_processors.project_settings")
RENDERERS += [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
define([
'jquery',
'underscore',
'arches',
'knockout',
'knockout-mapping',
'views/list',
'views/components/functions/primary-descriptors',
'templates/views/components/functions/multicard-resource-descriptor.htm',
'bindings/select2-query',
],
function($, _, arches, ko, koMapping, ListView, PrimaryDescriptorsView, multicardResourceDescriptor) {
// Get the parent component we want to inherit from from the ko registry.
let parentComponent;
const setParentComponent = (found) => {
parentComponent = found;
}
ko.components.defaultLoader.getConfig('views/components/functions/primary-descriptors', setParentComponent);

return ko.components.register('views/components/functions/multicard-resource-descriptor', {
viewModel: function(params) {
var self = this;
parentComponent.viewModel.apply(this, arguments);

this.parseNodeIdsFromStringTemplate = (initialValue) => {
const regex = /<(.*?)>/g;
const aliases = [...initialValue.matchAll(regex)].map(matchObj => matchObj[1]);
return self.graph.nodes.filter(n => aliases.includes(n.alias)).map(n => n.nodeid);
}

this.selectedNodes = {
name: ko.observableArray(
self.parseNodeIdsFromStringTemplate(self.name.string_template())
),
description: ko.observableArray(
self.parseNodeIdsFromStringTemplate(self.description.string_template())
),
map_popup: ko.observableArray(
self.parseNodeIdsFromStringTemplate(self.map_popup.string_template())
),
};

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
const sortedCards = this.graph.cards.toSorted((a, b) => {
const nameA = a.name.toUpperCase();
const nameB = b.name.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}

return 0;
});

this.groupedNodesForSelect2 = [];
sortedCards.forEach(card => {
const stringNodes = this.graph.nodes.filter(
node => node.datatype === 'string' && node.nodegroup_id === card.nodegroup_id
);

if (stringNodes.length) {
this.groupedNodesForSelect2.push({
text: card.name,
children: stringNodes.map(node => {
return {
id: node.nodeid,
text: node.alias,
}
}),
});
}
});

Object.entries(this.selectedNodes).forEach(
([observableName, observable]) => {
observable.subscribe(actions => {
actions.forEach(action => {
self.updateTemplate(action.value, action.status, observableName)
})
}, this, 'arrayChange')
}
);

this.baseSelect2Config = {
multiple: true,
placeholder: arches.translations.selectPrimaryDescriptionIdentifierCard,
data: self.groupedNodesForSelect2,
};

this.updateTemplate = (nodeid, actionType, descriptorName) => {
const templateObservable = params.config.descriptor_types[descriptorName].string_template;
const priorValue = templateObservable();
const nodeAlias = self.graph.nodes.find(n => n.nodeid === nodeid).alias;

if (actionType === 'deleted') {
if (priorValue.startsWith(`<${nodeAlias}>`)) {
templateObservable(priorValue.replace(`<${nodeAlias}>`, ''));
} else {
templateObservable(priorValue.replace(` <${nodeAlias}>`, ''));
}
} else {
if (priorValue === '') {
templateObservable(`<${nodeAlias}>`);
} else {
templateObservable(`${priorValue} <${nodeAlias}>`);
}
}
};
},
template: multicardResourceDescriptor,
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def assign_view_plugin(apps, schema_editor):

class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("arches_for_science", "0001_initial"),
("guardian", "0002_generic_permissions_index"),
]
Expand Down
77 changes: 77 additions & 0 deletions arches_for_science/migrations/0004_improve_primary_descriptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generated by Django 4.2.4 on 2023-11-20 13:43

from django.db import migrations


STRING_TEMPLATE_REPLACEMENTS = {
"Statement_content": "statement_content",
"Name_content": "name_content",
"Name (top)_content": "name_top__content",
"<content >": "<statement_content>",
}
STRING_TEMPLATE_REPLACEMENTS_REVERSED = {v: k for k, v in STRING_TEMPLATE_REPLACEMENTS.items()}

ORIGINAL_DESCRIPTOR_FUNCTION_PK = "60000000-0000-0000-0000-000000000001"
MULTICARD_DESCRIPTOR_FUNCTION_PK = "00b2d15a-fda0-4578-b79a-784e4138664b"


def update_graphs(apps, from_function, to_function, string_replacement_map):
FunctionXGraph = apps.get_model("models", "FunctionXGraph")

for fn_x_graph in FunctionXGraph.objects.filter(function=from_function):
fn_x_graph.function = to_function
for descriptor_object in fn_x_graph.config["descriptor_types"].values():
for before, after in string_replacement_map.items():
descriptor_object["string_template"] = descriptor_object["string_template"].replace(before, after)
fn_x_graph.save()


def retire_core_primary_descriptor(apps, schema_editor):
Function = apps.get_model("models", "Function")
original_fn = Function.objects.get(pk=ORIGINAL_DESCRIPTOR_FUNCTION_PK)
original_fn.functiontype = "primarydescriptors_retired"
original_fn.name = "(retired) Define Resource Descriptors"
original_fn.save()

# create multi-card function
multi_card_fn, created = Function.objects.update_or_create(
pk=MULTICARD_DESCRIPTOR_FUNCTION_PK,
modulename="multicard_resource_descriptor.py",
classname="MulticardResourceDescriptor",
functiontype="primarydescriptors",
name="Multi-card Resource Descriptor",
description="Configure the name, description, and map popup of a resource",
defaultconfig=original_fn.defaultconfig,
component="views/components/functions/multicard-resource-descriptor",
)

update_graphs(apps, from_function=original_fn, to_function=multi_card_fn,
string_replacement_map=STRING_TEMPLATE_REPLACEMENTS)


def restore_core_primary_descriptor(apps, schema_editor):
Function = apps.get_model("models", "Function")
original_fn = Function.objects.get(pk=ORIGINAL_DESCRIPTOR_FUNCTION_PK)
original_fn.functiontype = "primarydescriptors"
original_fn.name = "Define Resource Descriptors"
original_fn.save()

# Update FunctionXGraph records before cascade deleted below
update_graphs(apps, from_function=multi_card_fn, to_function=original_fn,
string_replacement_map=STRING_TEMPLATE_REPLACEMENTS_REVERSED)

multi_card_fn = Function.objects.get(pk=MULTICARD_DESCRIPTOR_FUNCTION_PK)
multi_card_fn.delete()


class Migration(migrations.Migration):
dependencies = [
("arches_for_science", "0003_manifest_x_canvas_x_digitalresource"),
]

operations = [
migrations.RunPython(
retire_core_primary_descriptor,
restore_core_primary_descriptor,
)
]

Large diffs are not rendered by default.

34 changes: 33 additions & 1 deletion arches_for_science/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,41 @@
import uuid

from arches.app.models.models import IIIFManifest, TileModel, FunctionXGraph
from django.db import models
from django.db.models import JSONField
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from arches.app.models.models import IIIFManifest
import pgtrigger

from .trigger_functions import CALCULATE_MULTICARD_PRIMARY_DESCRIPTOR_SINGLE, CALCULATE_MULTICARD_PRIMARY_DESCRIPTOR_ALL

class TileModelProxy(TileModel):
class Meta:
proxy = True
triggers = [
pgtrigger.Trigger(
name='calculate_multicard_primary_descriptor_single',
when=pgtrigger.After,
operation=pgtrigger.Insert | pgtrigger.Update | pgtrigger.Delete,
func=CALCULATE_MULTICARD_PRIMARY_DESCRIPTOR_SINGLE,
),
]

class FunctionXGraphProxy(FunctionXGraph):
class Meta:
proxy = True
triggers = [
pgtrigger.Trigger(
name='calculate_multicard_primary_descriptor_all',
when=pgtrigger.After,
condition=pgtrigger.Q(
new__function_id="00b2d15a-fda0-4578-b79a-784e4138664b",
new__config__isnull=False,
),
operation=pgtrigger.Insert | pgtrigger.Update,
func=CALCULATE_MULTICARD_PRIMARY_DESCRIPTOR_ALL,
),
]


class RendererConfig(models.Model):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from arches.app.functions.primary_descriptors import AbstractPrimaryDescriptorsFunction
from arches.app.models.system_settings import settings

from django.utils.translation import get_language, gettext as _

# This duplicates the configuration declared in migration 0004,
# but on first package load, the function will be re-registered, because
# the .py file has not yet been placed in the destination folder.
# Re-registration will overwrite whatever the migration inserted.
details = {
"functionid": "00b2d15a-fda0-4578-b79a-784e4138664b",
"name": "Multi-card Resource Descriptor",
"type": "primarydescriptors",
"description": "Configure the name, description, and map popup of a resource",
"defaultconfig": {
"descriptor_types": {
"name": {
"nodegroup_id": "",
"string_template": "",
},
"map_popup": {
"nodegroup_id": "",
"string_template": "",
},
"description": {
"nodegroup_id": "",
"string_template": "",
},
}
},
"classname": "MulticardResourceDescriptor",
"component": "views/components/functions/multicard-resource-descriptor",
}


class MulticardResourceDescriptor(AbstractPrimaryDescriptorsFunction):
"""Implemented in the database via triggers on tiles table.
This implementation just fetches the calculated result from the db."""

def get_primary_descriptor_from_nodes(self, resource, config, context=None, descriptor=None):
result = ""
requested_language = context.get("language", None) if context else None

lookup_language = requested_language or get_language() or settings.LANGUAGE_CODE
try:
result = resource.descriptors[lookup_language][descriptor]
except KeyError:
pass

if result.strip() == "":
result = _("Undefined")

return result
Original file line number Diff line number Diff line change
Expand Up @@ -5629,20 +5629,20 @@
"descriptor_types": {
"description": {
"nodegroup_id": "56c7a5dc-c450-11e9-ad38-a4d18cec433a",
"string_template": "<Statement_content>"
"string_template": "<statement_content>"
},
"map_popup": {
"nodegroup_id": "",
"string_template": ""
},
"name": {
"nodegroup_id": "52aa1673-c450-11e9-8640-a4d18cec433a",
"string_template": "<Name_content>"
"string_template": "<name_content>"
}
},
"triggering_nodegroups": []
},
"function_id": "60000000-0000-0000-0000-000000000001",
"function_id": "00b2d15a-fda0-4578-b79a-784e4138664b",
"graph_id": "1b210ef3-b25c-11e9-a037-a4d18cec433a",
"id": "410bb31f-2c25-4801-a974-67fc1c9ad578"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6767,20 +6767,20 @@
"descriptor_types": {
"description": {
"nodegroup_id": "da1fac57-ca7a-11e9-86a3-a4d18cec433a",
"string_template": "<Statement_content>"
"string_template": "<statement_content>"
},
"map_popup": {
"nodegroup_id": "",
"string_template": ""
},
"name": {
"nodegroup_id": "d2fdae3d-ca7a-11e9-ad84-a4d18cec433a",
"string_template": "<Name_content>"
"string_template": "<name_content>"
}
},
"triggering_nodegroups": []
},
"function_id": "60000000-0000-0000-0000-000000000001",
"function_id": "00b2d15a-fda0-4578-b79a-784e4138664b",
"graph_id": "707cbd78-ca7a-11e9-990b-a4d18cec433a",
"id": "81e78264-86be-41f5-a22d-3e7c7e1e788d"
}
Expand Down
6 changes: 3 additions & 3 deletions arches_for_science/pkg/graphs/resource_models/Group.json
Original file line number Diff line number Diff line change
Expand Up @@ -7837,20 +7837,20 @@
"descriptor_types": {
"description": {
"nodegroup_id": "32db94f8-c05e-11e9-a2c1-a4d18cec433a",
"string_template": "<Statement_content>"
"string_template": "<statement_content>"
},
"map_popup": {
"nodegroup_id": "",
"string_template": ""
},
"name": {
"nodegroup_id": "12707705-c05e-11e9-8177-a4d18cec433a",
"string_template": "<Name_content>"
"string_template": "<name_content>"
}
},
"triggering_nodegroups": []
},
"function_id": "60000000-0000-0000-0000-000000000001",
"function_id": "00b2d15a-fda0-4578-b79a-784e4138664b",
"graph_id": "07883c9e-b25c-11e9-975a-a4d18cec433a",
"id": "cb049a30-7542-4efa-8b8d-9f465ab61fcb"
}
Expand Down
Loading

0 comments on commit f8de390

Please sign in to comment.