-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin_settings.py
More file actions
657 lines (603 loc) · 23.5 KB
/
plugin_settings.py
File metadata and controls
657 lines (603 loc) · 23.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
__copyright__ = "Copyright 2025 TU Dresden / KOMET Project"
__author__ = "Daniel Nüst & KOMET Team"
__license__ = "AGPL v3"
__maintainer__ = "KOMET Project"
from django.db.utils import OperationalError
from utils import models, setting_handler
from utils.logger import get_logger
logger = get_logger(__name__)
PLUGIN_NAME = "geometadata"
DISPLAY_NAME = "Geometadata"
DESCRIPTION = (
"Adds geospatial and temporal metadata support for articles and preprints. "
"Allows authors to specify geographic coverage and time periods during submission, "
"and displays interactive maps on article pages."
)
AUTHOR = "Daniel Nüst & KOMET Team (TU Dresden)"
VERSION = "0.1.0"
SHORT_NAME = "geometadata"
MANAGER_URL = "geometadata_manager"
JANEWAY_VERSION = "1.7.0"
# Not a workflow plugin - just adds metadata
IS_WORKFLOW_PLUGIN = False
def get_self():
"""Get the plugin instance from the database."""
try:
plugin = models.Plugin.objects.get(name=PLUGIN_NAME)
return plugin
except models.Plugin.DoesNotExist:
return None
def _force_press_default(setting, value):
"""Force the press-level SettingValue for ``setting`` to ``value``.
Unlike ``setting_handler.get_or_create_default_setting``, which preserves
an existing row's value, this rewrites the press default on every
install. Used for toggles whose intended default-on (or default-off)
policy must converge across upgrades — early installs that captured a
different default would otherwise stay stuck on the old value forever.
Per-journal overrides remain untouched; only the row with ``journal=None``
is updated.
"""
import core.models as core_models
sv, _ = core_models.SettingValue.objects.update_or_create(
setting=setting,
journal=None,
defaults={"value": value},
)
return sv
# Embedding toggles whose press-level default should always reflect the
# documented "default on" design. install_plugins re-applies these on
# every run so a one-time empty default from an early install gets fixed.
_DEFAULT_ON_EMBEDDING_SETTINGS = (
"embed_dc_coverage",
"embed_geo_meta",
"embed_schema_spatial",
"embed_geojson_link",
"embed_wkt",
"embed_iso19139",
)
def install():
"""Install the plugin and create necessary settings."""
import core.models as core_models
# Create or update plugin record
plugin, created = models.Plugin.objects.get_or_create(
name=PLUGIN_NAME,
defaults={
"enabled": True,
"version": VERSION,
"display_name": DISPLAY_NAME,
"press_wide": True,
},
)
if not created:
plugin.version = VERSION
plugin.display_name = DISPLAY_NAME
plugin.save()
logger.debug("Plugin updated.")
else:
logger.debug("Plugin installed.")
# Create settings group for the plugin
plugin_group_name = f"plugin:{PLUGIN_NAME}"
setting_group, _ = core_models.SettingGroup.objects.get_or_create(
name=plugin_group_name,
)
# Setting: Enable geometadata collection for journals
setting, _ = core_models.Setting.objects.get_or_create(
name="enable_geometadata",
group=setting_group,
defaults={
"pretty_name": "Enable Geometadata",
"types": "boolean",
"description": (
"Enable collection and display of geospatial and temporal metadata "
"for articles in this journal."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="")
# Setting: Enable spatial metadata
setting, _ = core_models.Setting.objects.get_or_create(
name="enable_spatial",
group=setting_group,
defaults={
"pretty_name": "Enable Spatial Metadata",
"types": "boolean",
"description": "Allow collection of geographic location/area metadata.",
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Setting: Enable temporal metadata
setting, _ = core_models.Setting.objects.get_or_create(
name="enable_temporal",
group=setting_group,
defaults={
"pretty_name": "Enable Temporal Metadata",
"types": "boolean",
"description": "Allow collection of time period metadata.",
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Setting: Show map on article page
setting, _ = core_models.Setting.objects.get_or_create(
name="show_article_map",
group=setting_group,
defaults={
"pretty_name": "Show Map on Article Page",
"types": "boolean",
"description": "Display an interactive map on article pages showing geographic coverage.",
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Setting: Default map center (latitude)
setting, _ = core_models.Setting.objects.get_or_create(
name="default_map_lat",
group=setting_group,
defaults={
"pretty_name": "Default Map Center Latitude",
"types": "text",
"description": "Default latitude for map center (e.g., 0 for equator).",
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="0")
# Setting: Default map center (longitude)
setting, _ = core_models.Setting.objects.get_or_create(
name="default_map_lng",
group=setting_group,
defaults={
"pretty_name": "Default Map Center Longitude",
"types": "text",
"description": "Default longitude for map center (e.g., 0 for prime meridian).",
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="0")
# Setting: Default map zoom level
setting, _ = core_models.Setting.objects.get_or_create(
name="default_map_zoom",
group=setting_group,
defaults={
"pretty_name": "Default Map Zoom Level",
"types": "text",
"description": "Default zoom level for maps (1-18, where 1 is world view).",
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="2")
# Setting: Enable aggregated map page (scoped by context)
setting, _ = core_models.Setting.objects.get_or_create(
name="enable_map",
group=setting_group,
defaults={
"pretty_name": "Enable Map Page",
"types": "boolean",
"description": (
"Enable the aggregated map page. At press level this "
"controls the press-wide map; per-journal overrides "
"control journal-wide maps."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Setting: Require geometadata during submission
setting, _ = core_models.Setting.objects.get_or_create(
name="require_geometadata",
group=setting_group,
defaults={
"pretty_name": "Require Geometadata on Submission",
"types": "boolean",
"description": (
"Require authors to provide geospatial metadata "
"during article submission."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="")
# Setting: Show temporal coverage on article pages
setting, _ = core_models.Setting.objects.get_or_create(
name="show_article_temporal",
group=setting_group,
defaults={
"pretty_name": "Show Temporal Coverage on Article Pages",
"types": "boolean",
"description": (
"Display temporal coverage (date range) on article landing pages."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Setting: Show place names on article pages
setting, _ = core_models.Setting.objects.get_or_create(
name="show_article_placenames",
group=setting_group,
defaults={
"pretty_name": "Show Place Names on Article Pages",
"types": "boolean",
"description": (
"Display place name labels alongside the map on article landing pages."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Setting: Show temporal coverage on issue pages
setting, _ = core_models.Setting.objects.get_or_create(
name="show_issue_temporal",
group=setting_group,
defaults={
"pretty_name": "Show Temporal Coverage on Issue Pages",
"types": "boolean",
"description": (
"Display aggregated temporal coverage (date range) on issue landing pages."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Setting: Show GeoJSON download links
setting, _ = core_models.Setting.objects.get_or_create(
name="show_download_geojson",
group=setting_group,
defaults={
"pretty_name": "Show GeoJSON Download Links",
"types": "boolean",
"description": (
"Show download links for geometadata in GeoJSON format "
"on article pages, issue pages, and the journal-wide map page."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# HTML metadata embedding settings
setting, _ = core_models.Setting.objects.get_or_create(
name="embed_dc_coverage",
group=setting_group,
defaults={
"pretty_name": "Embed Dublin Core Coverage Meta Tags",
"types": "boolean",
"description": (
"Embed DC.SpatialCoverage, DC.box, DC.temporal, and "
"DC.PeriodOfTime meta tags in article HTML head."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
setting, _ = core_models.Setting.objects.get_or_create(
name="embed_geo_meta",
group=setting_group,
defaults={
"pretty_name": "Embed geo.* Meta Tags",
"types": "boolean",
"description": ("Embed geo.placename meta tags in article HTML head."),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
setting, _ = core_models.Setting.objects.get_or_create(
name="embed_schema_spatial",
group=setting_group,
defaults={
"pretty_name": "Embed Schema.org Spatial/Temporal Coverage",
"types": "boolean",
"description": (
"Embed Schema.org spatialCoverage and temporalCoverage "
"as JSON-LD in article HTML head. Respects the "
"enable_spatial and enable_temporal toggles."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
setting, _ = core_models.Setting.objects.get_or_create(
name="embed_geojson_link",
group=setting_group,
defaults={
"pretty_name": "Include GeoJSON Link in HTML Head",
"types": "boolean",
"description": (
"Include a <link> element pointing to the GeoJSON API "
"endpoint in the HTML head of article pages."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
setting, _ = core_models.Setting.objects.get_or_create(
name="embed_wkt",
group=setting_group,
defaults={
"pretty_name": "Embed WKT Geometry Meta Tag",
"types": "boolean",
"description": (
"Embed the raw geometry as a Well-Known Text (WKT) string in "
"a <meta name=\"DC.SpatialCoverage\" scheme=\"WKT\"> tag, "
"alongside the GeoJSON DC.SpatialCoverage variant. Respects "
"the enable_spatial toggle."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
setting, _ = core_models.Setting.objects.get_or_create(
name="embed_iso19139",
group=setting_group,
defaults={
"pretty_name": "Embed ISO 19139 XML Metadata",
"types": "boolean",
"description": (
"Embed an ISO 19139 EX_Extent XML fragment (geographic "
"bounding box, geographic description and temporal extent) "
"as a <script type=\"application/xml\"> block in the article "
"HTML head. Respects the enable_spatial and enable_temporal "
"toggles."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
# Map colour settings
setting, _ = core_models.Setting.objects.get_or_create(
name="enable_map_colours",
group=setting_group,
defaults={
"pretty_name": "Enable Map Colour Coding",
"types": "boolean",
"description": (
"Colour-code geometries on aggregated maps (journal map, "
"press map) by their grouping (e.g. issue or journal)."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
setting, _ = core_models.Setting.objects.get_or_create(
name="map_colour_method",
group=setting_group,
defaults={
"pretty_name": "Colour Generation Method",
"types": "char",
"description": (
"Method for generating the map colour palette: "
"'colorbrewer' selects a preset ColorBrewer scheme, "
"'startrek' uses Star Trek themed palettes, "
"'custom' allows entering your own colour codes."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="colorbrewer")
setting, _ = core_models.Setting.objects.get_or_create(
name="map_colour_scheme",
group=setting_group,
defaults={
"pretty_name": "ColorBrewer Scheme",
"types": "char",
"description": (
"ColorBrewer colour scheme name (used when method is "
"'colorbrewer'). Qualitative schemes (Set1, Set2, Dark2, "
"etc.) are recommended for categorical data."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="Set2")
setting, _ = core_models.Setting.objects.get_or_create(
name="map_colour_palette",
group=setting_group,
defaults={
"pretty_name": "Colour Palette",
"types": "char",
"description": (
"JSON array of hex colour strings used on aggregated maps. "
"Populated automatically from the selected method/scheme. "
"When more groups exist than palette entries, colours wrap."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="")
# Custom colours setting (one colour per line)
setting, _ = core_models.Setting.objects.get_or_create(
name="custom_colours",
group=setting_group,
defaults={
"pretty_name": "Custom Colours",
"types": "text",
"description": (
"Custom colour palette for maps. Enter one HTML colour code "
"per line (e.g., #3388ff, rgb(51,136,255))."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="")
# Map feature colour (for article and issue pages without palette)
setting, _ = core_models.Setting.objects.get_or_create(
name="article_map_colour",
group=setting_group,
defaults={
"pretty_name": "Map Feature Colour",
"types": "char",
"description": (
"Colour for map features on article pages and issue pages "
"(when colour coding is disabled). "
"Enter a hex colour code or select from the palette."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="#3388ff")
# Map feature opacity setting
setting, _ = core_models.Setting.objects.get_or_create(
name="map_feature_opacity",
group=setting_group,
defaults={
"pretty_name": "Map Feature Opacity",
"types": "char",
"description": (
"Opacity of map features (polygons, lines, markers). "
"Value between 0.0 (transparent) and 1.0 (opaque). "
"Lower values help features blend better with darker basemaps."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="0.7")
# Map basemap provider setting (leaflet-providers key)
setting, _ = core_models.Setting.objects.get_or_create(
name="map_tile_provider",
group=setting_group,
defaults={
"pretty_name": "Map Basemap Provider",
"types": "char",
"description": (
"Basemap provider key for leaflet-providers, e.g. "
"OpenStreetMap.Mapnik, OpenTopoMap, CyclOSM."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(
setting, default_value="OpenStreetMap.Mapnik"
)
# Reverse geocoding settings
setting, _ = core_models.Setting.objects.get_or_create(
name="geocoding_enabled",
group=setting_group,
defaults={
"pretty_name": "Enable Reverse Geocoding",
"types": "boolean",
"description": (
"Enable the reverse geocoding feature that allows editors "
"to automatically derive place names from drawn geometries."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="on")
setting, _ = core_models.Setting.objects.get_or_create(
name="geocoding_provider",
group=setting_group,
defaults={
"pretty_name": "Geocoding Provider",
"types": "char",
"description": (
"Reverse geocoding provider: nominatim, photon, or geonames."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="nominatim")
setting, _ = core_models.Setting.objects.get_or_create(
name="geocoding_user_agent",
group=setting_group,
defaults={
"pretty_name": "Geocoding User Agent",
"types": "char",
"description": (
"User-Agent string sent to Nominatim or Photon. "
"Should identify your Janeway instance."
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(
setting, default_value="janeway-geometadata"
)
setting, _ = core_models.Setting.objects.get_or_create(
name="geocoding_geonames_username",
group=setting_group,
defaults={
"pretty_name": "GeoNames Username",
"types": "char",
"description": (
"GeoNames API username (required when provider is 'geonames'). "
"Register at https://www.geonames.org/login"
),
"is_translatable": False,
},
)
setting_handler.get_or_create_default_setting(setting, default_value="")
# Force press-level defaults for the embedding toggles so re-installs
# converge on the documented default-on policy (see comment on
# _DEFAULT_ON_EMBEDDING_SETTINGS).
for name in _DEFAULT_ON_EMBEDDING_SETTINGS:
try:
forced = core_models.Setting.objects.get(
name=name, group=setting_group
)
_force_press_default(forced, "on")
except core_models.Setting.DoesNotExist:
logger.warning(
"Embedding setting '%s' not found while forcing default", name
)
logger.info(f"Geometadata plugin v{VERSION} installation complete.")
def hook_registry():
"""Register hooks for template integration."""
try:
return {
# Display map on article pages (journal articles)
"article_footer_block": {
"module": "plugins.geometadata.hooks",
"function": "article_footer_block",
"name": PLUGIN_NAME,
},
# Display map in article sidebar (alternative to footer for themes
# where footer hook is inside a conditional block)
"article_sidebar": {
"module": "plugins.geometadata.hooks",
"function": "article_sidebar",
"name": PLUGIN_NAME,
},
# Display map on preprint pages (repository)
# Note: Uses same hook name - works for both article and preprint templates
# Display map on issue pages (journal issues)
"issue_footer_block": {
"module": "plugins.geometadata.hooks",
"function": "issue_footer_block",
"name": PLUGIN_NAME,
},
# Add navigation link for map page
"nav_block": {
"module": "plugins.geometadata.hooks",
"function": "nav_block",
"name": PLUGIN_NAME,
},
# Inject CSS in head
"base_head_css": {
"module": "plugins.geometadata.hooks",
"function": "inject_head_css",
"name": PLUGIN_NAME,
},
# Display geometadata summary during submission review
"submission_review": {
"module": "plugins.geometadata.hooks",
"function": "submission_review",
"name": PLUGIN_NAME,
},
# Display link to geometadata editing on article dashboard
"edit_article": {
"module": "plugins.geometadata.hooks",
"function": "edit_article",
"name": PLUGIN_NAME,
},
# Display link to geometadata editing in review workflow
"in_review_editor_actions": {
"module": "plugins.geometadata.hooks",
"function": "in_review_editor_actions",
"name": PLUGIN_NAME,
},
}
except OperationalError:
# Database not yet created
return {}
except Exception:
return {}