Coverage for portality / settings.py: 99%
297 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 00:09 +0100
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 00:09 +0100
1"""
2~~AppSettings:Config~~
3"""
4import os
5from portality import constants
6from portality.lib import paths
7from datetime import datetime
9###########################################
10# Application Version information
11# ~~->API:Feature~~
13DOAJ_VERSION = "8.6.2"
14API_VERSION = "4.0.1"
16######################################
17# Deployment configuration
19HOST = '0.0.0.0'
20DEBUG = False
21DEBUG_DEV_LOG = False # show all log of each module
22PORT = 5004
23SSL = True
24VALID_ENVIRONMENTS = ['dev', 'test', 'staging', 'production', 'harvester']
25CMS_BUILD_ASSETS_ON_STARTUP = False
26# Cookies security
27SESSION_COOKIE_SAMESITE = 'Strict'
28SESSION_COOKIE_SECURE = True
29REMEMBER_COOKIE_SECURE = True
31####################################
32# Testdrive for setting up the test environment.
33# CAUTION - this can modify the index so should NEVER be used in production!
34TESTDRIVE_ENABLED = False
36####################################
37# Debug Mode
39# PyCharm debug settings
40DEBUG_PYCHARM = False # do not try to connect to the PyCharm debugger by default
41DEBUG_PYCHARM_SERVER = 'localhost'
42DEBUG_PYCHARM_PORT = 6000
44# ~~->DebugToolbar:Framework~~
45DEBUG_TB_TEMPLATE_EDITOR_ENABLED = True
46DEBUG_TB_INTERCEPT_REDIRECTS = False
48# set to True to enable the env list panel in the debug toolbar
49DEBUG_TB_ENV_LIST_ENABLED = False
51#######################################
52# Elasticsearch configuration
53# ~~->Elasticsearch:Technology
55# elasticsearch settings # TODO: changing from single host / esprit to multi host on ES & correct the default
56ELASTICSEARCH_HOSTS = [{'host': 'localhost', 'port': 9200}, {'host': 'localhost', 'port': 9201}]
57ELASTIC_SEARCH_VERIFY_CERTS = True # Verify the SSL certificate of the ES host. Set to False in dev.cfg to avoid having to configure your local certificates
59# 2 sets of elasticsearch DB settings - index-per-project and index-per-type. Keep both for now so we can migrate.
60# e.g. host:port/index/type/id
61ELASTIC_SEARCH_DB = "doaj"
62ELASTIC_SEARCH_TEST_DB = "doajtest"
64# e.g. host:port/type/doc/id
65ELASTIC_SEARCH_INDEX_PER_TYPE = True
66INDEX_PER_TYPE_SUBSTITUTE = '_doc' # Migrated from esprit
67ELASTIC_SEARCH_DB_PREFIX = "doaj-" # note: include the separator
68ELASTIC_SEARCH_TEST_DB_PREFIX = "doajtest-"
70INITIALISE_INDEX = True # whether or not to try creating the index and required index types on startup
71ELASTIC_SEARCH_SNAPSHOT_REPOSITORY = None
72ELASTIC_SEARCH_SNAPSHOT_TTL = 366
74ES_TERMS_LIMIT = 1024
75ELASTICSEARCH_REQ_TIMEOUT = 20 # Seconds - used in core.py for whole ES connection request timeout
76ES_READ_TIMEOUT = '2m' # Minutes - used in DAO for searches
78#####################################################
79# Elastic APM config (MUST be configured in env file)
80# ~~->APM:Feature~~
82ENABLE_APM = False
84ELASTIC_APM = {
85 # Set required service name. Allowed characters:
86 # a-z, A-Z, 0-9, -, _, and space
87 'SERVICE_NAME': '',
89 # Use if APM Server requires a token
90 'SECRET_TOKEN': '',
92 # Set custom APM Server URL (default: http://localhost:8200)
93 'SERVER_URL': '',
94}
96###########################################
97# Event handler
99# Process events immediately/synchronously
100EVENT_SEND_FUNCTION = "portality.events.background.send_event"
102###########################################
103# Read Only Mode
104#
105# Use these options to place the application into READ ONLY mode
107# This puts the UI into READ_ONLY mode
108# ~~->ReadOnlyMode:Feature~~
109READ_ONLY_MODE = False
111# This puts the cron jobs into READ_ONLY mode
112SCRIPTS_READ_ONLY_MODE = False
114###########################################
115# Feature Toggles
117# ~~->OfflineMode:Feature~~
118OFFLINE_MODE = False
120# List the features we want to be active (API v1 and v2 remain with redirects to v3)
121# ~~->API:Feature~~
122FEATURES = ['api1', 'api2', 'api3', 'api4']
123VALID_FEATURES = ['api1', 'api2', 'api3', 'api4']
125########################################
126# File Path and URL Path settings
128# root of the git repo
129ROOT_DIR = paths.rel2abs(__file__, "..")
131# base path, to the directory where this settings file lives
132BASE_FILE_PATH = paths.abs_dir_path(__file__)
134BASE_URL = "https://doaj.org"
135if BASE_URL.startswith('https://'):
136 BASE_DOMAIN = BASE_URL[8:]
137elif BASE_URL.startswith('http://'):
138 BASE_DOMAIN = BASE_URL[7:]
139else:
140 BASE_DOMAIN = BASE_URL
142# ~~->API:Feature~~
143BASE_API_URL = "https://doaj.org/api/"
144API_CURRENT_BLUEPRINT_NAME = "api_v4" # change if upgrading API to new version and creating new view
145CURRENT_API_MAJOR_VERSION = "4"
147# URL used for the journal ToC URL in the journal CSV export
148# NOTE: must be the correct route as configured in view/doaj.py
149# ~~->ToC:WebRoute~~
150JOURNAL_TOC_URL_FRAG = BASE_URL + '/toc/'
152# Used when generating external links, e.g. in the API docs
153PREFERRED_URL_SCHEME = 'https'
155# Whether the app is running behind a proxy, for generating URLs based on X-Forwarded-Proto
156# ~~->ProxyFix:Framework~~
157PROXIED = False
159# directory to upload files to. MUST be full absolute path
160# The default takes the directory above this, and then down in to "upload"
161UPLOAD_DIR = os.path.join(ROOT_DIR, "upload")
162UPLOAD_ASYNC_DIR = os.path.join(ROOT_DIR, "upload_async")
163FAILED_ARTICLE_DIR = os.path.join(ROOT_DIR, "failed_articles")
165# directory where reports are output
166REPORTS_BASE_DIR = "/home/cloo/reports/"
168##################################
169# File store
170# ~~->FileStore:Feature~~
172# put this in your production.cfg, to store everything on S3:
173# STORE_IMPL = "portality.store.StoreS3"
175STORE_IMPL = "portality.store.StoreLocal"
176STORE_SCOPE_IMPL = {
177 # Enable this by scope in order to have different scopes store via different storage implementations
178 # constants.STORE__SCOPE__PUBLIC_DATA_DUMP: "portality.store.StoreS3",
179 # constants.STORE__SCOPE__JOURNAL_CSV: "portality.store.StoreS3"
180}
182STORE_TMP_IMPL = "portality.store.TempStore"
184STORE_LOCAL_DIR = paths.rel2abs(__file__, "..", "local_store", "main")
185STORE_TMP_DIR = paths.rel2abs(__file__, "..", "local_store", "tmp")
186STORE_LOCAL_EXPOSE = False # if you want to allow files in the local store to be exposed under /store/<path> urls. For dev only.
187STORE_LOCAL_WRITE_BUFFER_SIZE = 16777216
188STORE_TMP_WRITE_BUFFER_SIZE = 16777216
190# containers (buckets in AWS) where various content will be stored
191# These values are placeholders, and must be overridden in live deployment
192# this prevents test environments from accidentally writing to the production buckets
193STORE_ANON_DATA_CONTAINER = "doaj-anon-data-placeholder"
194STORE_CACHE_CONTAINER = "doaj-data-cache-placeholder"
195STORE_PUBLIC_DATA_DUMP_CONTAINER = "doaj-data-dump-placeholder"
196STORE_HARVESTER_CONTAINER = "doaj-harvester"
197STORE_EXPORT_CONTAINER = "doaj-export-placeholder"
198STORE_JOURNAL_CSV_CONTAINER = "doaj-journal-csv-placeholder"
200# S3 credentials for relevant scopes
201# ~~->S3:Technology~~
202STORE_S3_SCOPES = {
203 "anon_data": {
204 "aws_access_key_id": "put this in your dev/test/production.cfg",
205 "aws_secret_access_key": "put this in your dev/test/production.cfg"
206 },
207 "cache": {
208 "aws_access_key_id": "put this in your dev/test/production.cfg",
209 "aws_secret_access_key": "put this in your dev/test/production.cfg"
210 },
211 # Used by the api_export script to dump data from the api
212 constants.STORE__SCOPE__PUBLIC_DATA_DUMP: {
213 "aws_access_key_id": "put this in your dev/test/production.cfg",
214 "aws_secret_access_key": "put this in your dev/test/production.cfg"
215 },
216 # Used to store harvester run logs to S3
217 "harvester": {
218 "aws_access_key_id": "put this in your dev/test/production.cfg",
219 "aws_secret_access_key": "put this in your dev/test/production.cfg"
220 },
221 # Used to store the admin-generated CSV reports
222 "export": {
223 "aws_access_key_id": "put this in your dev/test/production.cfg",
224 "aws_secret_access_key": "put this in your dev/test/production.cfg"
225 },
226 constants.STORE__SCOPE__JOURNAL_CSV: {
227 "aws_access_key_id": "put this in your dev/test/production.cfg",
228 "aws_secret_access_key": "put this in your dev/test/production.cfg"
229 }
230}
232STORE_S3_MULTIPART_THRESHOLD = 5 * 1024 ** 3 # 5GB
234####################################
235# CMS configuration
237# paths where static content should be served from.
238# * in the order you want them searched
239# * relative to the portality directory
240# ~~->CMS:DataStore~~
241STATIC_PATHS = [
242 "static",
243 "../cms/assets"
244]
246# GitHub base url where static content can be edited by the DOAJ team (you can leave out the trailing slash)
247# ~~->GitHub:ExternalService~~
248CMS_EDIT_BASE_URL = "https://github.com/DOAJ/doaj/edit/static_pages/cms"
250# Where static files are served from - in case we need to serve a file
251# from there ourselves using Flask instead of nginx (e.g. to support a
252# legacy route to that file)
253# Changing this will not change the actual folder that Flask serves
254# static files from.
255# http://flask.pocoo.org/snippets/102/
256STATIC_DIR = os.path.join(ROOT_DIR, "portality", "static")
258######################################
259# Service Descriptive Text
261SERVICE_NAME = "Directory of Open Access Journals"
263###################################
264# Cookie settings
266# make this something secret in your overriding app.cfg
267# ~~->Cookies:Feature~~
268SECRET_KEY = "default-key"
270# Consent Cookie and other Top-Level dismissable notes
271# ~~->ConsentCookie:Feature~~
272CONSENT_COOKIE_KEY = "doaj-cookie-consent"
274# site notes, which can be configured to run any time with any content
275# ~~-> SiteNote:Feature~~
276SITE_NOTE_ACTIVE = False
277SITE_NOTE_KEY = "doaj-site-note"
278SITE_NOTE_SLEEP = 259200 # every 3 days
279SITE_NOTE_COOKIE_VALUE = "You have seen our most recent site wide announcement"
281####################################
282# Authorisation settings
283# ~~->AuthNZ:Feature~~
285# Can people register publicly? If false, only the superuser can create new accounts
286PUBLIC_REGISTER = True
288SUPER_USER_ROLE = "admin"
290LOGIN_VIA_ACCOUNT_ID = True
292# amount of time a reset token is valid for (86400 is 24 hours)
293PASSWORD_RESET_TIMEOUT = 86400
294# amount of time a reset token for a new account is valid for
295PASSWORD_CREATE_TIMEOUT = PASSWORD_RESET_TIMEOUT * 14
297# "api" top-level role is added to all accounts on creation; it can be revoked per account by removal of the role.
298TOP_LEVEL_ROLES = [
299 "admin",
300 "publisher",
301 "editor",
302 "associate_editor",
303 "api",
304 "ultra_bulk_delete",
305 "preservation",
306 constants.ROLE_PUBLIC_DATA_DUMP,
307 constants.ROLE_PUBLISHER_JOURNAL_CSV,
308 constants.ROLE_ADMIN_REPORT_WITH_NOTES,
309 constants.ROLE_PREMIUM,
310 constants.ROLE_PREMIUM_OAI,
311 constants.ROLE_PREMIUM_PDD,
312 constants.ROLE_PREMIUM_CSV
313]
315ROLE_MAP = {
316 "editor": [
317 "associate_editor", # note, these don't cascade, so we still need to list all the low-level roles
318 "edit_journal",
319 "edit_suggestion",
320 "edit_application", # todo: switchover from suggestion to application
321 "editor_area",
322 "assign_to_associate",
323 "list_group_journals",
324 "list_group_suggestions",
325 "read_notifications"
326 ],
327 "associate_editor": [
328 "edit_journal",
329 "edit_suggestion",
330 "editor_area",
331 "read_notifications"
332 ],
333 constants.ROLE_PREMIUM: [
334 constants.ROLE_PREMIUM_OAI,
335 constants.ROLE_PREMIUM_PDD,
336 constants.ROLE_PREMIUM_CSV
337 ]
338}
340# If you change the reserved usernames, your data will likely need a migration to remove their
341# existing use in the system.
342SYSTEM_USERNAME = "system"
343RESERVED_USERNAMES = [SYSTEM_USERNAME] # do not allow the creation of user accounts with this id
345# Role map to destination route on login (when no other destination page is present)
346# checked in order, if the user has the role in the first tuple position, they will
347# be redirected to the endpoint in the second tuple position
348ROLE_LOGIN_DESTINATIONS = [
349 ("admin", "dashboard.top_todo"),
350 ("editor", "editor.index"),
351 ("associate_editor", "editor.index"),
352 ("publisher", "publisher.index")
353]
355# if the user doesn't have one of the above roles, where should they be sent after login
356DEFAULT_LOGIN_DESTINATION = "doaj.home"
358####################################
359# Email Settings
360# ~~->Email:ExternalService
362# Settings for Flask-Mail. Set in app.cfg
363MAIL_SERVER = None # default localhost
364MAIL_PORT = 25 # default 25
365#MAIL_USE_TLS # default False
366#MAIL_USE_SSL # default False
367#MAIL_DEBUG # default app.debug
368#MAIL_USERNAME # default None
369#MAIL_PASSWORD # default None
370#MAIL_DEFAULT_SENDER # default None
371#MAIL_MAX_EMAILS # default None
372#MAIL_SUPPRESS_SEND # default app.testing
374ENABLE_EMAIL = True
375ENABLE_PUBLISHER_EMAIL = True
377ADMIN_NAME = "DOAJ"
378ADMIN_EMAIL = "sysadmin@cottagelabs.com"
379ADMINS = ["steve@cottagelabs.com", "mark@cottagelabs.com"]
381MANAGING_EDITOR_EMAIL = "managing-editors@doaj.org"
382CONTACT_FORM_ADDRESS = "feedback+contactform@doaj.org"
383SCRIPT_TAG_DETECTED_EMAIL_RECIPIENTS = ["helpdesk@doaj.org"]
385SYSTEM_EMAIL_FROM = 'helpdesk@doaj.org'
386CC_ALL_EMAILS_TO = SYSTEM_EMAIL_FROM # DOAJ may get a dedicated inbox in the future
388# Error logging via email
389SUPPRESS_ERROR_EMAILS = False
390ERROR_LOGGING_EMAIL = 'doaj.internal@gmail.com'
391ERROR_MAIL_HOSTNAME = 'smtp.mailgun.org'
392ERROR_MAIL_USERNAME = None
393ERROR_MAIL_PASSWORD = None
395# Reports email recipient
396REPORTS_EMAIL_TO = ["helpdesk@doaj.org"]
398########################################
399# workflow email notification settings
400# ~~->WorkflowNotifications:Feature~~
402MAN_ED_IDLE_WEEKS = 4 # weeks before an application is considered reminder-worthy
403ED_IDLE_WEEKS = 3 # weeks before the editor is warned about idle applications in their group
404ASSOC_ED_IDLE_DAYS = 10
405ASSOC_ED_IDLE_WEEKS = 3
407# Which statuses the notification queries should be filtered to show
408# ~~-> ApplicationStatuses:Config~~
409MAN_ED_NOTIFICATION_STATUSES = [
410 constants.APPLICATION_STATUS_PENDING,
411 constants.APPLICATION_STATUS_IN_PROGRESS, constants.APPLICATION_STATUS_COMPLETED,
412 constants.APPLICATION_STATUS_ON_HOLD
413]
414ED_NOTIFICATION_STATUSES = [
415 constants.APPLICATION_STATUS_PENDING,
416 constants.APPLICATION_STATUS_IN_PROGRESS,
417 constants.APPLICATION_STATUS_COMPLETED
418]
419ASSOC_ED_NOTIFICATION_STATUSES = [
420 constants.APPLICATION_STATUS_PENDING,
421 constants.APPLICATION_STATUS_IN_PROGRESS
422]
424###################################
425# status endpoint configuration
426# ~~->StatusEndpoint:Feature~~
428# /status endpoint connection to all app machines
429APP_MACHINES_INTERNAL_IPS = [HOST + ':' + str(PORT)] # This should be set in production.cfg (or dev.cfg etc)
431###########################################
432# Background Jobs settings
433# ~~->Huey:Technology~~
434# ~~->Redis:Technology~~
435# ~~->BackgroundTasks:Feature~~
437# huey/redis settings
438REDIS_HOST = os.getenv('REDIS_HOST', '127.0.0.1')
439REDIS_PORT = os.getenv('REDIS_PORT', 6379)
440HUEY_IMMEDIATE = False
441HUEY_ASYNC_DELAY = 10
443# Crontab for never running a job - February 31st (use to disable tasks)
444CRON_NEVER = {"month": "2", "day": "31", "day_of_week": "*", "hour": "*", "minute": "*"}
446# Crontab schedules must be for unique times to avoid delays due to perceived race conditions
447HUEY_SCHEDULE = {
448 "sitemap": {"month": "*", "day": "*", "day_of_week": "*", "hour": "8", "minute": "0"},
449 "reporting": {"month": "*", "day": "1", "day_of_week": "*", "hour": "0", "minute": "0"},
450 "journal_csv": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "20"},
451 "read_news": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "30"},
452 "article_cleanup_sync": {"month": "*", "day": "2", "day_of_week": "*", "hour": "0", "minute": "0"},
453 "async_workflow_notifications": {"month": "*", "day": "*", "day_of_week": "1", "hour": "5", "minute": "0"},
454 "request_es_backup": {"month": "*", "day": "*", "day_of_week": "*", "hour": "6", "minute": "0"},
455 "check_latest_es_backup": {"month": "*", "day": "*", "day_of_week": "*", "hour": "9", "minute": "0"},
456 "prune_es_backups": {"month": "*", "day": "*", "day_of_week": "*", "hour": "9", "minute": "15"},
457 "public_data_dump": {"month": "*", "day": "*", "day_of_week": "*", "hour": "10", "minute": "0"},
458 "harvest": {"month": "*", "day": "*", "day_of_week": "*", "hour": "5", "minute": "30"},
459 "anon_export": {"month": "*", "day": "10", "day_of_week": "*", "hour": "1", "minute": "10"},
460 "old_data_cleanup": {"month": "*", "day": "12", "day_of_week": "*", "hour": "6", "minute": "30"},
461 "monitor_bgjobs": {"month": "*", "day": "*/6", "day_of_week": "*", "hour": "10", "minute": "0"},
462 "find_discontinued_soon": {"month": "*", "day": "*", "day_of_week": "*", "hour": "0", "minute": "3"},
463 "datalog_journal_added_update": {"month": "*", "day": "*", "day_of_week": "*", "hour": "4", "minute": "30"},
464 "auto_assign_editor_group_data": {"month": "*", "day": "*/7", "day_of_week": "*", "hour": "3", "minute": "30"},
465 "ris_export": {"month": "*", "day": "15", "day_of_week": "*", "hour": "3", "minute": "30"},
466 "site_statistics": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "40"},
467}
470HUEY_TASKS = {
471 "ingest_articles": {"retries": 10, "retry_delay": 15},
472 "preserve": {"retries": 0, "retry_delay": 15},
473 "application_autochecks": {"retries": 0, "retry_delay": 15},
474 "journal_autochecks": {"retries": 0, "retry_delay": 15}
475}
477####################################
478# publisher area settings
480# the earliest date accepted on the publisher's 'enter article metadata' form.
481# code will default to 15 years before current year if commented out.
482# ~~->ArticleMetadata:Page~~
483METADATA_START_YEAR = 1960
485# tick (on toc) and doaj seal settings
486# ~~->Tick:Feature~~
487TICK_THRESHOLD = '2014-03-19T00:00:00Z'
489# ~~->UpdateRequests:Feature~~
490UPDATE_REQUESTS_SHOW_OLDEST = "2018-01-01T00:00:00Z"
492##############################################
493# Elasticsearch Mappings
494# ~~->Elasticsearch:Technology~~
496FACET_FIELD = ".exact"
498# an array of DAO classes from which to retrieve the type-specific ES mappings
499# to be loaded into the index during initialisation.
500ELASTIC_SEARCH_MAPPINGS = [
501 "portality.models.Article", # ~~->Article:Model~~
502 "portality.models.Journal", # ~~->Journal:Model~~
503 "portality.models.Application", # ~~->Application:Model~~
504 "portality.models.DraftApplication", # ~~-> DraftApplication:Model~~
505 "portality.models.harvester.HarvestState", # ~~->HarvestState:Model~~
506 "portality.models.background.BackgroundJob", # ~~-> BackgroundJob:Model~~
507 "portality.models.autocheck.Autocheck", # ~~-> Autocheck:Model~~
508 "portality.models.export.Export", # ~~-> Export:Model~~
509 "portality.models.DataDump", # ~~-> DataDump:Model~~
510 "portality.models.JournalCSV", # ~~-> JournalCSV:Model~~
511 "portality.models.ur_review_route.URReviewRoute", # ~~-> URReviewRoute:Model~~
512 "portality.models.admin_alert.AdminAlert", # ~~-> AdminAlert:Model~~
513 "portality.models.ris_export.RISExport",
514]
516# Map from dataobj coercion declarations to ES mappings
517# ~~->DataObj:Library~~
518# ~~->Seamless:Library~~
519DATAOBJ_TO_MAPPING_DEFAULTS = {
520 "unicode": {
521 "type": "text",
522 "fields": {
523 "exact": {
524 "type": "keyword",
525 # "index": False,
526 "store": True
527 }
528 }
529 },
530 "str": {
531 "type": "text",
532 "fields": {
533 "exact": {
534 "type": "keyword",
535 # "index": False,
536 "store": True
537 }
538 }
539 },
540 "unicode_upper": {
541 "type": "text",
542 "fields": {
543 "exact": {
544 "type": "keyword",
545 # "index": False,
546 "store": True
547 }
548 }
549 },
550 "unicode_lower": {
551 "type": "text",
552 "fields": {
553 "exact": {
554 "type": "keyword",
555 # "index": False,
556 "store": True
557 }
558 }
559 },
560 "isolang": {
561 "type": "text",
562 "fields": {
563 "exact": {
564 "type": "keyword",
565 # "index": False,
566 "store": True
567 }
568 }
569 },
570 "isolang_2letter_strict": {
571 "type": "text",
572 "fields": {
573 "exact": {
574 "type": "keyword",
575 # "index": False,
576 "store": True
577 }
578 }
579 },
580 "isolang_2letter_lax": {
581 "type": "text",
582 "fields": {
583 "exact": {
584 "type": "keyword",
585 # "index": False,
586 "store": True
587 }
588 }
589 },
590 "country_code": {
591 "type": "text",
592 "fields": {
593 "exact": {
594 "type": "keyword",
595 # "index": False,
596 "store": True
597 }
598 }
599 },
600 "currency_code_strict": {
601 "type": "text",
602 "fields": {
603 "exact": {
604 "type": "keyword",
605 # "index": False,
606 "store": True
607 }
608 }
609 },
610 "currency_code_lax": {
611 "type": "text",
612 "fields": {
613 "exact": {
614 "type": "keyword",
615 # "index": False,
616 "store": True
617 }
618 }
619 },
620 "issn": {
621 "type": "text",
622 "fields": {
623 "exact": {
624 "type": "keyword",
625 # "index": False,
626 "store": True
627 }
628 }
629 },
630 "url": {
631 "type": "text",
632 "fields": {
633 "exact": {
634 "type": "keyword",
635 # "index": False,
636 "store": True
637 }
638 }
639 },
640 "utcdatetimemicros": {
641 "type": "date",
642 "format": "date_optional_time"
643 },
644 "utcdatetime": {
645 "type": "date",
646 "format": "date_optional_time"
647 },
648 "bool": {
649 "type": "boolean"
650 },
651 "integer": {
652 "type": "long"
653 },
654 "bigenddate": {
655 "type": "date",
656 "format": "date_optional_time"
657 },
658 "year": {
659 "type": "date",
660 "format": "year"
661 }
662}
664# Extension Map from dataobj coercion declarations to ES mappings.
665# This is useful when some extensions required for some objects additional to defaults.
666# ~~->DataObj:Library~~
667# ~~->Seamless:Library~~
668DATAOBJ_TO_MAPPING_COPY_TO_EXTENSIONS = {
669 "unicode": {"copy_to": ["all_meta"]},
670 "str": {"copy_to": ["all_meta"]},
671 "unicode_upper": {"copy_to": ["all_meta"]},
672 "unicode_lower": {"copy_to": ["all_meta"]},
673 "issn": {"copy_to": ["all_meta"]},
674 "url": {"copy_to": ["all_meta"]}
675}
677# TODO: we may want a big-type and little-type setting
678DEFAULT_INDEX_SETTINGS = \
679 {
680 'number_of_shards': 4,
681 'number_of_replicas': 1,
682 'analysis': {
683 'analyzer': {
684 'ascii_folded': {
685 'tokenizer': 'standard',
686 'filter': ['lowercase', 'asciifolding']
687 }
688 }
689 }
690 }
692DEFAULT_DYNAMIC_MAPPING = {
693 'dynamic_templates': [
694 {
695 "strings": {
696 "match_mapping_type": "string",
697 "mapping": {
698 "type": "text",
699 "fields": {
700 "exact": {
701 "type": "keyword",
702 # "normalizer": "lowercase"
703 }
704 }
705 }
706 }
707 }
708 ]
709}
711# LEGACY MAPPINGS
712# a dict of the ES mappings. identify by name, and include name as first object name
713# and identifier for how non-analyzed fields for faceting are differentiated in the mappings
714MAPPINGS = {
715 'account': { # ~~->Account:Model~~
716 # 'aliases': {
717 # 'account': {}
718 # },
719 'mappings': DEFAULT_DYNAMIC_MAPPING,
720 'settings': DEFAULT_INDEX_SETTINGS
721 }
722}
724MAPPINGS['upload'] = MAPPINGS["account"] # ~~->Upload:Model~~
725MAPPINGS['bulk_articles'] = MAPPINGS["account"] # ~~->BulkArticles:Model~~
726MAPPINGS['cache'] = MAPPINGS["account"] # ~~->Cache:Model~~
727MAPPINGS['lcc'] = MAPPINGS["account"] # ~~->LCC:Model~~
728MAPPINGS['editor_group'] = MAPPINGS["account"] # ~~->EditorGroup:Model~~
729MAPPINGS['news'] = MAPPINGS["account"] # ~~->News:Model~~
730MAPPINGS['lock'] = MAPPINGS["account"] # ~~->Lock:Model~~
731MAPPINGS['provenance'] = MAPPINGS["account"] # ~~->Provenance:Model~~
732MAPPINGS['preserve'] = MAPPINGS["account"] # ~~->Preservation:Model~~
733MAPPINGS['notification'] = MAPPINGS["account"] # ~~->Notification:Model~~
734MAPPINGS['article_tombstone'] = MAPPINGS["account"] # ~~->ArticleTombstone:Model~~
735MAPPINGS['shortened_url'] = MAPPINGS["account"] #~~->URLShortener:Model~~
737#########################################
738# Query Routes
739# ~~->Query:WebRoute~~
741QUERY_ROUTE = {
742 "query": {
743 # ~~->PublicJournalQuery:Endpoint~~
744 "journal": {
745 "auth": False,
746 "role": None,
747 "query_validators": ["non_public_fields_validator", "public_query_validator"],
748 "query_filters": ["only_in_doaj", "last_update_fallback", "search_all_meta"],
749 "result_filters": ["public_result_filter"],
750 "dao": "portality.models.Journal", # ~~->Journal:Model~~
751 "required_parameters": {"ref": ["ssw", "public_journal", "subject_page"]}
752 },
753 # ~~->PublicArticleQuery:Endpoint~~
754 "article": {
755 "auth": False,
756 "role": None,
757 "query_validators": ["non_public_fields_validator", "public_query_validator"],
758 "query_filters": ["only_in_doaj"],
759 "result_filters": ["public_result_filter"],
760 "dao": "portality.models.Article", # ~~->Article:Model~~
761 "required_parameters": {"ref": ["public_article", "toc", "subject_page"]}
762 },
763 # back-compat for fixed query widget
764 # ~~->PublicJournalArticleQuery:Endpoint~~
765 "journal,article": {
766 "auth": False,
767 "role": None,
768 "query_validators": ["non_public_fields_validator", "public_query_validator"],
769 "query_filters": ["only_in_doaj", "strip_facets", "es_type_fix", "journal_article_filter"],
770 "result_filters": ["public_result_filter", "add_fqw_facets", "fqw_back_compat"],
771 "dao": "portality.models.JournalArticle", # ~~->JournalArticle:Model~~
772 "required_parameters": {"ref": ["fqw"]}
773 }
774 },
775 "publisher_query": {
776 # ~~->PublisherJournalQuery:Endpoint~~
777 "journal": {
778 "auth": True,
779 "role": "publisher",
780 "query_validators": ["non_public_fields_validator"],
781 "query_filters": ["owner", "only_in_doaj", "search_all_meta"],
782 "result_filters": ["publisher_result_filter"],
783 "dao": "portality.models.Journal" # ~~->Journal:Model~~
784 },
785 # ~~->PublisherApplicationQuery:Endpoint~~
786 "applications": {
787 "auth": True,
788 "role": "publisher",
789 "query_validators": ["non_public_fields_validator"],
790 "query_filters": ["owner", "not_update_request", "search_all_meta"],
791 "result_filters": ["publisher_result_filter"],
792 "dao": "portality.models.AllPublisherApplications" # ~~->AllPublisherApplications:Model~~
793 },
794 # ~~->PublisherUpdateRequestsQuery:Endpoint~~
795 "update_requests": {
796 "auth": True,
797 "role": "publisher",
798 "query_validators": ["non_public_fields_validator"],
799 "query_filters": ["owner", "update_request", "search_all_meta"],
800 "result_filters": ["publisher_result_filter"],
801 "dao": "portality.models.Application" # ~~->Application:Model~~
802 }
803 },
804 "admin_query": {
805 # ~~->AdminJournalQuery:Endpoint~~
806 "journal": {
807 "auth": True,
808 "role": "admin",
809 "dao": "portality.models.Journal" # ~~->Journal:Model~~
810 },
811 # ~~->AdminApplicationQuery:Endpoint~~
812 "suggestion": {
813 "auth": True,
814 "role": "admin",
815 "query_filters": ["not_update_request"],
816 "dao": "portality.models.Application" # ~~->Application:Model~~
817 },
818 # ~~->AdminUpdateRequestQuery:Endpoint~~
819 "update_requests": {
820 "auth": True,
821 "role": "admin",
822 "query_filters": ["update_request"],
823 "dao": "portality.models.Application" # ~~->Application:Model~~
824 },
825 # ~~->AdminEditorGroupQuery:Endpoint~~
826 "editor,group": {
827 "auth": True,
828 "role": "admin",
829 "dao": "portality.models.EditorGroup" # ~~->EditorGroup:Model~~
830 },
831 # ~~->AdminAccountQuery:Endpoint~~
832 "account": {
833 "auth": True,
834 "role": "admin",
835 "dao": "portality.models.Account" # ~~->Account:Model~~
836 },
837 # ~~->AdminJournalArticleQuery:Endpoint~~
838 "journal,article": {
839 "auth": True,
840 "role": "admin",
841 "dao": "portality.models.search.JournalArticle" # ~~->JournalArticle:Model~~
842 },
843 # ~~->AdminBackgroundJobQuery:Endpoint~~
844 "background,job": {
845 "auth": True,
846 "role": "admin",
847 "dao": "portality.models.BackgroundJob" # ~~->BackgroundJob:Model~~
848 },
849 # ~~->APINotificationQuery:Endpoint~~
850 "notifications": {
851 "auth": True,
852 "role": "admin",
853 "dao": "portality.models.Notification", # ~~->Notification:Model~~
854 "required_parameters": None
855 },
856 "reports": {
857 "auth": True,
858 "role": "admin",
859 "dao": "portality.models.Export"
860 },
861 "journal_csv": {
862 "auth": True,
863 "role": "admin",
864 "dao": "portality.models.JournalCSV" # ~~->JournalCSV:Model~~
865 },
866 "pdd": {
867 "auth": True,
868 "role": "admin",
869 "dao": "portality.models.DataDump" # ~~->JournalCSV:Model~~
870 },
871 "alerts": {
872 "auth": True,
873 "role": "admin",
874 "dao": "portality.models.AdminAlert", # ~~->AdminAlert:Model~~
875 "required_parameters": None
876 },
877 "autoassign": {
878 "auth": True,
879 "role": "admin",
880 "dao": "portality.models.URReviewRoute", # ~~->AdminAlert:Model~~
881 "required_parameters": None
882 },
883 "ris": {
884 "auth": True,
885 "role": "admin",
886 "dao": "portality.models.RISExport", # ~~->AdminAlert:Model~~
887 "required_parameters": None
888 }
889 },
890 "associate_query": {
891 # ~~->AssEdJournalQuery:Endpoint~~
892 "journal": {
893 "auth": True,
894 "role": "associate_editor",
895 "query_validators": ["non_public_fields_validator"],
896 "query_filters": ["associate", "search_all_meta", "flagged"],
897 "dao": "portality.models.Journal" # ~~->Journal:Model~~
898 },
899 # ~~->AssEdApplicationQuery:Endpoint~~
900 "suggestion": {
901 "auth": True,
902 "role": "associate_editor",
903 "query_validators": ["non_public_fields_validator"],
904 "query_filters": ["associate", "search_all_meta"],
905 "dao": "portality.models.Application" # ~~->Application:Model~~
906 }
907 },
908 "editor_query": {
909 # ~~->EditorJournalQuery:Endpoint~~
910 "journal": {
911 "auth": True,
912 "role": "editor",
913 "query_validators": ["non_public_fields_validator"],
914 "query_filters": ["editor", "search_all_meta"],
915 "dao": "portality.models.Journal" # ~~->Journal:Model~~
916 },
917 # ~~->EditorApplicationQuery:Endpoint~~
918 "suggestion": {
919 "auth": True,
920 "role": "editor",
921 "query_validators": ["non_public_fields_validator"],
922 "query_filters": ["editor", "search_all_meta"],
923 "dao": "portality.models.Application" # ~~->Application:Model~~
924 }
925 },
926 "api_query": {
927 # ~~->APIArticleQuery:Endpoint~~
928 "article": {
929 "auth": False,
930 "role": None,
931 "query_filters": ["only_in_doaj", "public_source"],
932 "dao": "portality.models.Article", # ~~->Article:Model~~
933 "required_parameters": None,
934 "keepalive": "10m"
935 },
936 # ~~->APIJournalQuery:Endpoint~~
937 "journal": {
938 "auth": False,
939 "role": None,
940 "query_validators": ["non_public_fields_validator"],
941 "query_filters": ["only_in_doaj", "public_source", "search_all_meta"],
942 "dao": "portality.models.Journal", # ~~->Journal:Model~~
943 "required_parameters": None
944 },
945 # ~~->APIApplicationQuery:Endpoint~~
946 "application": {
947 "auth": True,
948 "role": None,
949 "query_validators": ["non_public_fields_validator"],
950 "query_filters": ["owner", "private_source", "search_all_meta"],
951 "dao": "portality.models.Suggestion", # ~~->Application:Model~~
952 "required_parameters": None
953 }
954 },
955 "dashboard_query": {
956 # ~~->APINotificationQuery:Endpoint~~
957 "notifications": {
958 "auth": True,
959 "role": "read_notifications",
960 "query_filters": ["who_current_user"], # ~~-> WhoCurrentUser:Query
961 "dao": "portality.models.Notification", # ~~->Notification:Model~~
962 "required_parameters": None
963 }
964 }
965}
967QUERY_FILTERS = {
968 # sanitisers
969 "public_query_validator": "portality.lib.query_filters.public_query_validator",
970 "non_public_fields_validator": "portality.lib.query_filters.non_public_fields_validator",
972 # query filters
973 "only_in_doaj": "portality.lib.query_filters.only_in_doaj",
974 "owner": "portality.lib.query_filters.owner",
975 "update_request": "portality.lib.query_filters.update_request",
976 "associate": "portality.lib.query_filters.associate",
977 "editor": "portality.lib.query_filters.editor",
978 "strip_facets": "portality.lib.query_filters.strip_facets",
979 "es_type_fix": "portality.lib.query_filters.es_type_fix",
980 "last_update_fallback": "portality.lib.query_filters.last_update_fallback",
981 "not_update_request": "portality.lib.query_filters.not_update_request",
982 "who_current_user": "portality.lib.query_filters.who_current_user", # ~~-> WhoCurrentUser:Query ~~
983 "search_all_meta": "portality.lib.query_filters.search_all_meta", # ~~-> SearchAllMeta:Query ~~
984 "journal_article_filter": "portality.lib.query_filters.journal_article_filter",
985 # ~~-> JournalArticleFilter:Query ~~
987 # result filters
988 "public_result_filter": "portality.lib.query_filters.public_result_filter",
989 "publisher_result_filter": "portality.lib.query_filters.publisher_result_filter",
990 "add_fqw_facets": "portality.lib.query_filters.add_fqw_facets",
991 "fqw_back_compat": "portality.lib.query_filters.fqw_back_compat",
993 # source filters
994 "private_source": "portality.lib.query_filters.private_source",
995 "public_source": "portality.lib.query_filters.public_source",
996}
997# Exclude the fields that doesn't want to be searched by public queries
998# This is part of non_public_fields_validator.
999PUBLIC_QUERY_VALIDATOR__EXCLUDED_FIELDS = [
1000 "admin.notes.note",
1001 "admin.notes.id",
1002 "admin.notes.author_id"
1003]
1005ADMIN_NOTES_INDEX_ONLY_FIELDS = {
1006 "all_meta": {
1007 "type": "text",
1008 "fields": {
1009 "exact": {
1010 "type": "keyword",
1011 "store": True
1012 }
1013 }
1014 }
1015}
1017ADMIN_NOTES_SEARCH_MAPPING = {
1018 "admin.notes.id": {
1019 "type": "text",
1020 "fields": {
1021 "exact": {
1022 "type": "keyword",
1023 "store": True
1024 }
1025 }
1026 },
1027 "admin.notes.note": {
1028 "type": "text",
1029 "fields": {
1030 "exact": {
1031 "type": "keyword",
1032 "store": True
1033 }
1034 }
1035 },
1036 "admin.notes.author_id": {
1037 "type": "text",
1038 "fields": {
1039 "exact": {
1040 "type": "keyword",
1041 "store": True
1042 }
1043 }
1044 }
1045}
1047ASCII_FOLDED = {"analyzer": "ascii_folded", "search_analyzer": "ascii_folded"}
1049JOURNAL_EXCEPTION_MAPPING = {
1050 "bibjson.title" : {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1051 "bibjson.alternative_title" : {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1052 "bibjson.publisher.name" : {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1053 "index.country" : {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1054 "index.title": {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED}
1055}
1057ARTICLE_EXCEPTION_MAPPING = {
1058 "bibjson.abstract" : {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1059 "bibjson.author.name" : {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1060 "bibjson.journal.publisher": {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1061 "index.country": {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED},
1062 "bibjson.title": {**DATAOBJ_TO_MAPPING_DEFAULTS["unicode"], **ASCII_FOLDED}
1063}
1065####################################################
1066# Autocomplete
1068# ~~->BibJSON:Model~~
1069AUTOCOMPLETE_ADVANCED_FIELD_MAPS = {
1070 "bibjson.publisher.name": "index.publisher_ac",
1071 "bibjson.institution.name": "index.institution_ac"
1072}
1074####################################################
1075# Application Form
1076# ~~->ApplicationForm:Feature~~
1078# save the public application form as a draft every 60 seconds
1079PUBLIC_FORM_AUTOSAVE = 60000
1081############################################
1082# Atom Feed
1083# ~~->AtomFeed:Feature~~
1085FEED_TITLE = "DOAJ Recent Journals Added"
1087# Maximum number of feed entries to be given in a single response. If this is omitted, it will
1088# default to 20
1089MAX_FEED_ENTRIES = 100
1091# Maximum age of feed entries (in seconds) (default value here is 30 days).
1092MAX_FEED_ENTRY_AGE = 2592000
1094# Licensing terms for feed content
1095# ~~->SiteLicence:Content~~
1096FEED_LICENCE = "(c) DOAJ 2024. CC BY-SA."
1098# name of the feed generator (goes in the atom:generator element)
1099FEED_GENERATOR = "CottageLabs feed generator"
1101# Larger image to use as the logo for all of the feeds
1102# ~~->Favicon:Content~~
1103FEED_LOGO = "https://doaj.org/static/doaj/images/favicon.ico"
1105###########################################
1106# OAI-PMH SETTINGS
1107# ~~->OAIPMH:Feature~~
1109OAI_ADMIN_EMAIL = 'helpdesk+oai@doaj.org'
1111# ~~->OAIAriticleXML:Crosswalk~~
1112# ~~->OAIJournalXML:Crosswalk~~
1113OAI_DC_METADATA_FORMAT = {
1114 "metadataPrefix": "oai_dc",
1115 "schema": "http://www.openarchives.org/OAI/2.0/oai_dc.xsd",
1116 "metadataNamespace": "http://www.openarchives.org/OAI/2.0/oai_dc/"
1117}
1119OAI_DOAJ_METADATA_FORMAT = {
1120 "metadataPrefix": "oai_doaj",
1121 "schema": "https://doaj.org/static/doaj/doajArticles.xsd",
1122 "metadataNamespace": "https://doaj.org/features/oai_doaj/1.0/"
1123}
1125OAIPMH_METADATA_FORMATS = {
1126 # "specific endpoint": [list, of, formats, supported]
1128 None: [OAI_DC_METADATA_FORMAT], # no specific endpoint, the request is to the root /oai path
1129 "article": [OAI_DC_METADATA_FORMAT, OAI_DOAJ_METADATA_FORMAT]
1130}
1132OAIPMH_IDENTIFIER_NAMESPACE = "doaj.org"
1134OAIPMH_LIST_RECORDS_PAGE_SIZE = 100
1136OAIPMH_LIST_IDENTIFIERS_PAGE_SIZE = 300
1138OAIPMH_RESUMPTION_TOKEN_EXPIRY = 86400
1140##########################################
1141# Article XML configuration
1143# paths to schema files to validate incoming documents against for the various
1144# crosswalks available
1145# ~~->CrossrefXML:Schema~~
1146# ~~->DOAJArticleXML:Schema~~
1147SCHEMAS = {
1148 "doaj": os.path.join(BASE_FILE_PATH, "static", "doaj", "doajArticles.xsd"),
1149 "crossref442": os.path.join(BASE_FILE_PATH, "static", "crossref", "crossref4.4.2.xsd"),
1150 "crossref531": os.path.join(BASE_FILE_PATH, "static", "crossref", "crossref5.3.1.xsd")
1151}
1153# placeholders for the loaded schemas
1154DOAJ_SCHEMA = None
1155CROSSREF442_SCHEMA = None
1156CROSSREF531_SCHEMA = None
1157LOAD_CROSSREF_THREAD = None
1159# mapping of format names to modules which implement the crosswalks
1160# ~~->DOAJArticleXML:Crosswalk~~
1161# ~~->CrossrefXML:Crosswalk~~
1162ARTICLE_CROSSWALKS = {
1163 "doaj": "portality.crosswalks.article_doaj_xml.DOAJXWalk",
1164 "crossref442": "portality.crosswalks.article_crossref_xml.CrossrefXWalk442",
1165 "crossref531": "portality.crosswalks.article_crossref_xml.CrossrefXWalk531"
1166}
1168# maximum size of files that can be provided by-reference (the default value is 250Mb)
1169MAX_REMOTE_SIZE = 262144000
1171#################################################
1172# Cache settings
1173# ~~->Cache:Feature~~
1175# number of seconds site statistics should be considered fresh
1176# 1800s = 30mins
1177SITE_STATISTICS_TIMEOUT = 1800
1179# directory into which to put files which are cached (e.g. the csv)
1180CACHE_DIR = os.path.join(ROOT_DIR, "cache")
1182# Article and Journal History directories - they should be different
1183# ~~->ArticleHistory:Feature~~
1184# ~~->JournalHistory:Feature~~
1185ARTICLE_HISTORY_DIR = os.path.join(ROOT_DIR, "history", "article")
1186JOURNAL_HISTORY_DIR = os.path.join(ROOT_DIR, "history", "journal")
1188#################################################
1189# Sitemap settings
1190# ~~->Sitemap:Feature~~
1192# approximate rate of change of the Table of Contents for journals
1193TOC_CHANGEFREQ = "monthly"
1195# Maximum number of sitemap entries per index file before splitting into chunks (50K entries, 50MB size is actual limit)
1196SITEMAP_INDEX_MAX_ENTRIES = 90
1198##################################################
1199# News feed settings
1200# ~~->News:Feature~~
1202BLOG_URL = "https://blog.doaj.org/"
1204BLOG_FEED_URL = "https://blog.doaj.org/feed/"
1206FRONT_PAGE_NEWS_ITEMS = 4
1208NEWS_PAGE_NEWS_ITEMS = 20
1210##################################################
1211# Edit Lock settings
1212# ~~->Lock:Feature~~
1214# amount of time loading an editable page locks it for, in seconds.
1215EDIT_LOCK_TIMEOUT = 1200
1217# amount of time a background task can lock a resource for, in seconds
1218BACKGROUND_TASK_LOCK_TIMEOUT = 3600
1220###############################################
1221# Bit.ly configuration
1222# ~~->Bitly:ExternalService~~
1224# bit,ly api shortening service
1225# BITLY_SHORTENING_API_URL = "https://api-ssl.bitly.com/v4/shorten"
1227# bitly oauth token
1228# ENTER YOUR OWN TOKEN IN APPROPRIATE .cfg FILE
1229# BITLY_OAUTH_TOKEN = ""
1232#################################################
1233# API configuration
1234# ~~->API:Feature~~
1235# ~~->APISearch:Feature~~
1237# maximum number of records to return per page
1238DISCOVERY_MAX_PAGE_SIZE = 100
1240# maximum number of records to return in total (a request for a page starting beyond this number will fail)
1241DISCOVERY_MAX_RECORDS_SIZE = 1000
1243# ~~->ArticleBibJSON:Model~~
1244DISCOVERY_ARTICLE_SEARCH_SUBS = {
1245 "title": "bibjson.title",
1246 "doi": "bibjson.identifier.id.exact",
1247 "issn": "index.issn.exact",
1248 "publisher": "bibjson.journal.publisher",
1249 "journal": "bibjson.journal.title",
1250 "abstract": "bibjson.abstract"
1251}
1253DISCOVERY_ARTICLE_SORT_SUBS = {
1254 "title": "index.unpunctitle.exact"
1255}
1257# ~~->JournalBibJSON:Model~~
1258DISCOVERY_JOURNAL_SEARCH_SUBS = {
1259 "title": "index.title",
1260 "issn": "index.issn.exact",
1261 "publisher": "bibjson.publisher",
1262 "license": "index.license.exact",
1263 "username": "admin.owner.exact"
1264}
1266DISCOVERY_JOURNAL_SORT_SUBS = {
1267 "title": "index.unpunctitle.exact",
1268 "issn": "index.issn.exact"
1269}
1271DISCOVERY_APPLICATION_SEARCH_SUBS = {
1272 "title": "index.title",
1273 "issn": "index.issn.exact",
1274 "publisher": "bibjson.publisher",
1275 "license": "index.license.exact"
1276}
1278DISCOVERY_APPLICATION_SORT_SUBS = {
1279 "title": "index.unpunctitle.exact",
1280 "issn": "index.issn.exact"
1281}
1283# API data dump settings
1284DISCOVERY_BULK_PAGE_SIZE = 1000
1285DISCOVERY_RECORDS_PER_FILE = 100000
1287######################################################
1288# Hotjar configuration
1289# ~~->Hotjar:ExternalService~~
1291# hotjar id - only activate this in production
1292HOTJAR_ID = ""
1294######################################################
1295# Analytics configuration
1296# specify in environment .cfg file - avoids sending live analytics events from test and dev environments
1298# ~~->PlausibleAnalytics:ExternalService~~
1299# Plausible analytics
1300# root url of plausible
1301PLAUSIBLE_URL = "https://plausible.io"
1302PLAUSIBLE_JS_URL = PLAUSIBLE_URL + "/js/script.outbound-links.file-downloads.js"
1303PLAUSIBLE_API_URL = PLAUSIBLE_URL + "/api/event"
1304# site name / domain name that used to register in plausible
1305PLAUSIBLE_SITE_NAME = BASE_DOMAIN
1306PLAUSIBLE_LOG_DIR = None
1308# Analytics custom dimensions. These are configured in the interface. #fixme: are these still configured since the move from GA?
1309ANALYTICS_DIMENSIONS = {
1310 'oai_res_id': 'dimension1', # In analytics as OAI:Record
1311}
1313# Plausible for OAI-PMH
1314# ~~-> OAIPMH:Feature~~
1315ANALYTICS_CATEGORY_OAI = 'OAI-PMH'
1317# Plausible for Atom
1318# ~~-> Atom:Feature~~
1319ANALYTICS_CATEGORY_ATOM = 'Atom'
1320ANALYTICS_ACTION_ACTION = 'Feed request'
1322# Plausible for JournalCSV
1323# ~~-> JournalCSV:Feature~~
1324ANALYTICS_CATEGORY_JOURNALCSV = 'JournalCSV'
1325ANALYTICS_ACTION_JOURNALCSV = 'Download'
1327# Plausible for OpenURL
1328# ~~->OpenURL:Feature~~
1329ANALYTICS_CATEGORY_OPENURL = 'OpenURL'
1331# Plausible for PublicDataDump
1332# ~~->PublicDataDump:Feature~~
1333ANALYTICS_CATEGORY_PUBLICDATADUMP = 'PublicDataDump'
1334ANALYTICS_ACTION_PUBLICDATADUMP = 'Download'
1336# Plausible for RIS
1337# ~~->PublicDataDump:Feature~~
1338ANALYTICS_CATEGORY_RIS = 'RIS'
1339ANALYTICS_ACTION_RISEXPORT = 'Export'
1341# Plausible for Urlshort
1342# ~~->URLShortener:Feature~~
1343ANALYTICS_CATEGORY_URLSHORT = 'ShortURL'
1344ANALYTICS_ACTION_URLSHORT_ADD = 'Find or create short url'
1345ANALYTICS_ACTION_URLSHORT_REDIRECT = 'Redirect'
1347# Plausible for API
1348# ~~-> API:Feature~~
1349ANALYTICS_CATEGORY_API = 'API Hit'
1350ANALYTICS_ACTIONS_API = {
1351 'search_applications': 'Search applications',
1352 'search_journals': 'Search journals',
1353 'search_articles': 'Search articles',
1354 'create_application': 'Create application',
1355 'retrieve_application': 'Retrieve application',
1356 'update_application': 'Update application',
1357 'delete_application': 'Delete application',
1358 'create_article': 'Create article',
1359 'retrieve_article': 'Retrieve article',
1360 'update_article': 'Update article',
1361 'delete_article': 'Delete article',
1362 'retrieve_journal': 'Retrieve journal',
1363 'bulk_application_create': 'Bulk application create',
1364 'bulk_application_delete': 'Bulk application delete',
1365 'bulk_article_create': 'Bulk article create',
1366 'bulk_article_create_status': 'Bulk article create status',
1367 'bulk_article_delete': 'Bulk article delete'
1368}
1370# Plausible for fixed query widget
1371# ~~->FixedQueryWidget:Feature~~
1372ANALYTICS_CATEGORY_FQW = 'FQW'
1373ANALYTICS_ACTION_FQW = 'Hit'
1375#####################################################
1376# Anonymised data export (for dev) configuration
1377# ~~->AnonExport:Feature~~
1379ANON_SALT = 'changeme'
1381# ========================================
1382# Quick Reject Feature Config
1383# ~~->QuickReject:Feature~~
1385QUICK_REJECT_REASONS = [
1386 "The journal has not published enough research content to qualify for DOAJ inclusion.",
1387 "The ISSN is incorrect, provisional or not registered with issn.org.",
1388 "The URL(s) provided in the application do not work.",
1389 "The journal is already in DOAJ.",
1390 "The journal is not Open Access.",
1391 "The journal title in the application and/or website does not match the title at issn.org.",
1392 "The application has incorrect answers or the URLs given do not provide the required information.",
1393 "This application is a duplicate.",
1394 "The full-text articles are not available article by article with individual links.",
1395 "The information in the journal implies that it does not employ a fair & robust peer review process.",
1396 "The journal or publisher has been previously rejected or removed from DOAJ.",
1397 "The journal's copyright policy is not available or unclear.",
1398 "The journal's licensing policy is not available or unclear.",
1399 "The information about the journal is in different languages.",
1400 "The information about the journal is not the same in all languages.",
1401 "The journal makes a false claim to be indexed in DOAJ or other databases or displays non-standard Impact Factors.",
1402 "The journal does not employ good publishing practices."
1403]
1405MINIMAL_OA_START_DATE = 1900
1407#############################################
1408# Harvester Configuration
1409# ~~->Harvester:Feature~~
1411# Configuration options for the DOAJ API Client
1413# EPMC Client configuration
1414# ~~-> EPMC:ExternalService~~
1415EPMC_REST_API = "https://www.ebi.ac.uk/europepmc/webservices/rest/"
1416EPMC_TARGET_VERSION = "6.9" # doc here: https://europepmc.org/docs/Europe_PMC_RESTful_Release_Notes.pdf
1417EPMC_HARVESTER_THROTTLE = 0.2
1419# General harvester configuration
1420HARVESTERS = [
1421 "portality.tasks.harvester_helpers.epmc.epmc_harvester.EPMCHarvester"
1422]
1424INITIAL_HARVEST_DATE = "2015-12-01T00:00:00Z"
1426# List of account ids to harvest from. MUST NOT be checked into the repo, put these
1427# in the local.cfg instead
1428HARVEST_ACCOUNTS = []
1430# Amount of time a harvester record is allowed to be in "queued" or "processing" state before we
1431# assume it's a zombie, and ignore it
1432HARVESTER_ZOMBIE_AGE = 604800
1434#######################################################
1435# Preservation configuration
1436# ~~->Preservation:Feature
1437PRESERVATION_URL = "http://PresevatinURL"
1438PRESERVATION_USERNAME = "user_name"
1439PRESERVATION_PASSWD = "password"
1440PRESERVATION_COLLECTION = {}
1442#########################################################
1443# Background tasks --- anon export
1444TASKS_ANON_EXPORT_CLEAN = False
1445TASKS_ANON_EXPORT_LIMIT = None
1446TASKS_ANON_EXPORT_BATCH_SIZE = 100000
1447TASKS_ANON_EXPORT_SCROLL_TIMEOUT = '5m'
1449#########################################################
1450# Background tasks --- old_data_cleanup
1451TASK_DATA_RETENTION_DAYS = {
1452 "notification": 180, # ~~-> Notifications:Feature ~~
1453 "background_job": 180, # ~~-> BackgroundJobs:Feature ~~
1454 "admin_alert": 180, # ~~-> AdminAlerts:Feature ~~
1455}
1457########################################
1458# Editorial Dashboard - set to-do list size
1459TODO_LIST_SIZE = 48
1461#########################################################
1462# Background tasks --- monitor_bgjobs
1463TASKS_MONITOR_BGJOBS_TO = ["helpdesk@doaj.org", ]
1464TASKS_MONITOR_BGJOBS_FROM = "helpdesk@doaj.org"
1466##################################
1467# Background monitor
1468# ~~->BackgroundMonitor:Feature~~
1470# some time period for convenience
1471_MIN = 60
1472_HOUR = 3600
1473_DAY = 24 * _HOUR
1474_WEEK = 7 * _DAY
1476# Configures the age of the last completed job on the queue before the queue is marked as unstable
1477# (in seconds)
1478BG_MONITOR_LAST_COMPLETED = {
1479 'events': 2 * _HOUR, # 2 hours
1480 'scheduled_short': 2 * _HOUR, # 2 hours
1481 'scheduled_long': _DAY + 2 * _HOUR, # 26 hours
1482}
1484# Default monitoring config for background job types which are not enumerated in BG_MONITOR_ERRORS_CONFIG below
1485BG_MONITOR_DEFAULT_CONFIG = {
1486 ## default values for queued config
1488 # the total number of items that are allowed to be in `queued` state at the same time.
1489 # Any more than this and the result is flagged
1490 'total': 2,
1492 # The age of the oldest record allowed to be in the `queued` state.
1493 # If the oldest queued item was created before this, the result is flagged
1494 'oldest': 20 * _MIN,
1496 ## default values for error config
1498 # The time period over which to check for errors, from now to now - check_sec
1499 'check_sec': _HOUR,
1501 # The number of errors allowed in the check period before the result is flagged
1502 'allowed_num_err': 0,
1504 # The last time this job ran within the specified time period, was it successful.
1505 # If the most recent job in the timeframe is an error, this will trigger an "unstable" state (0 turns this off)
1506 'last_run_successful_in': 0
1507}
1509# Configures the monitoring period and the allowed number of errors in that period before a queue is marked
1510# as unstable
1511BG_MONITOR_ERRORS_CONFIG = {
1512 'anon_export': {
1513 'check_sec': _WEEK, # a week
1514 'allowed_num_err': 0
1515 },
1516 'article_bulk_create': {
1517 'check_sec': _DAY, # 1 day
1518 'allowed_num_err': 0,
1519 },
1520 'article_cleanup_sync': {
1521 'check_sec': 2 * _DAY, # 2 days
1522 'allowed_num_err': 0
1523 },
1524 'harvest': {
1525 'check_sec': _DAY,
1526 'allowed_num_err': 0,
1527 },
1528 'ingest_articles': {
1529 'check_sec': _DAY,
1530 'allowed_num_err': 0
1531 },
1532 'journal_csv': {
1533 'check_sec': 3 * _HOUR,
1534 'allowed_num_err': 1,
1535 },
1536 'public_data_dump': {
1537 'check_sec': 2 * _HOUR,
1538 'allowed_num_err': 0
1539 },
1540 'process_event': {
1541 'check_sec': 2 * _HOUR,
1542 'allowed_num_err': 0
1543 }
1544}
1546# Configures the total number of queued items and the age of the oldest of those queued items allowed
1547# before the queue is marked as unstable. This is provided by type, so we can monitor all types separately
1548BG_MONITOR_QUEUED_CONFIG = {
1549 'anon_export': {
1550 'total': 1,
1551 'oldest': 20 * _MIN
1552 },
1553 'article_bulk_create': {
1554 'total': 3,
1555 'oldest': 10 * _MIN
1556 },
1557 'harvest': {
1558 'total': 1,
1559 'oldest': _DAY
1560 },
1561 'ingest_articles': {
1562 'total': 10,
1563 'oldest': 10 * _MIN
1564 },
1565 'journal_bulk_edit': {
1566 'total': 2,
1567 'oldest': 10 * _MIN
1568 },
1569 'journal_csv': {
1570 'total': 1,
1571 'oldest': 20 * _MIN
1572 },
1573 'public_data_dump': {
1574 'total': 1,
1575 'oldest': _DAY
1576 },
1577 'set_in_doaj': {
1578 'total': 2,
1579 'oldest': 10 * _MIN
1580 },
1581 'suggestion_bulk_edit': {
1582 'total': 2,
1583 'oldest': 10 * _MIN
1584 },
1585 'process_event': {
1586 'total': 500,
1587 'oldest': 6 * _HOUR
1588 }
1589}
1591BG_MONITOR_LAST_SUCCESSFULLY_RUN_CONFIG = {
1592 'anon_export': {
1593 'last_run_successful_in': 32 * _DAY
1594 },
1595 'article_cleanup_sync': {
1596 'last_run_successful_in': 33 * _DAY
1597 },
1598 'async_workflow_notifications': {
1599 'last_run_successful_in': _WEEK + _DAY
1600 },
1601 'check_latest_es_backup': {
1602 'last_run_successful_in': _DAY + _HOUR
1603 },
1604 'datalog_journal_added_update': {
1605 'last_run_successful_in': _HOUR
1606 },
1607 'find_discontinued_soon': {
1608 'last_run_successful_in': _DAY + _HOUR
1609 },
1610 'harvest': {
1611 'last_run_successful_in': _DAY + _HOUR
1612 },
1613 'journal_csv': {
1614 'last_run_successful_in': 2 * _HOUR
1615 },
1616 'monitor_bgjobs': {
1617 'last_run_successful_in': _WEEK + _DAY
1618 },
1619 'old_data_cleanup': {
1620 'last_run_successful_in': 32 * _DAY
1621 },
1622 'prune_es_backups': {
1623 'last_run_successful_in': _DAY + _HOUR
1624 },
1625 'public_data_dump': {
1626 'last_run_successful_in': 32 * _DAY
1627 },
1628 'read_news': {
1629 'last_run_successful_in': 2 * _HOUR
1630 },
1631 'reporting': {
1632 'last_run_successful_in': 32 * _DAY
1633 },
1634 'request_es_backup': {
1635 'last_run_successful_in': _DAY + _HOUR
1636 },
1637 'sitemap': {
1638 'last_run_successful_in': _DAY + _HOUR
1639 }
1640}
1642##################################################
1643# Public data dump settings
1645# how long should the temporary URL for public data dumps last
1646PUBLIC_DATA_DUMP_URL_TIMEOUT = 3600
1648##################################################
1649# Journal CSV Setings
1651# how long should the temporary URL for journal csvs last
1652JOURNAL_CSV_URL_TIMEOUT = 3600
1654##################################################
1655# Pages under maintenance
1657PRESERVATION_PAGE_UNDER_MAINTENANCE = False
1659# report journals that discontinue in ... days (eg. 1 = tomorrow)
1660DISCONTINUED_DATE_DELTA = 0
1662##################################################
1663# Feature tours currently active
1665TOUR_COOKIE_PREFIX = "doaj_tour_"
1666TOUR_COOKIE_MAX_AGE = 31536000
1668TOURS = {
1669 "/admin/application/*": [
1670 {
1671 "roles": ["admin"],
1672 "selectors": [".flags__container"],
1673 "content_id": "admin_flags",
1674 "name": "Flags",
1675 "description": "Make teamwork smoother by adding a flag to journals and applications — a note assigned to a teammate, with an optional deadline."
1676 }
1677 ],
1678 "/admin/journal/*": [
1679 {
1680 "roles": ["admin"],
1681 "selectors": [".autochecks-manager-toggle"],
1682 "content_id": "admin_journal_autochecks",
1683 "name": "Autochecks",
1684 "description": "Autochecks are available on some journals, and can help you to identify potential problems with the journal's metadata."
1685 }
1686 ],
1687 "/editor/": [
1688 {
1689 "roles": ["editor"],
1690 "content_id": "application_by_status",
1691 "name": "New Links in the Colour Legend",
1692 "description": "Discover how the colour legend labels now serve as links to quickly filter and view applications by group and status."
1693 }
1694 ],
1695 "/dashboard/": [
1696 {
1697 "roles": ["admin"],
1698 "content_id": "application_by_status",
1699 "name": "New Links in the Colour Legend",
1700 "description": "Discover how the colour legend labels now serve as links to quickly filter and view applications by group and status."
1701 }
1702 ]
1703}
1705#######################################################
1706# Selenium test environment
1708# url of selenium server, selenium remote will be used if it's not empty
1709# usually it's a docker container and the url should be 'http://localhost:4444/wd/hub'
1710SELENIUM_REMOTE_URL = 'http://localhost:4444/wd/hub'
1712# host and port that used to run doaj server in background for selenium testcases
1713# if you use docker selenium browser container, ip should be ip of docker network interface such as 172.17.0.1
1714# SELENIUM_DOAJ_HOST = 'localhost'
1715SELENIUM_DOAJ_HOST = '172.17.0.1'
1716SELENIUM_DOAJ_PORT = 5014
1718#################################################
1719# Concurrency timeout(s)
1721UR_CONCURRENCY_TIMEOUT = 10
1723#############################################
1724# Google Sheet
1725# ~~->GoogleSheet:ExternalService~~
1727# Google Sheet API
1728# value should be key file path of json, empty string means disabled
1729GOOGLE_KEY_PATH = ''
1731# The /export path to test users CSV file on google sheets (file is public)
1732TEST_USERS_CSV_DL_PATH = ""
1735#############################################
1736# Datalog
1737# ~~->Datalog:Feature~~
1739# Datalog for Journal Added
1741# google sheet filename for datalog ja
1742DATALOG_JA_FILENAME = 'DOAJ: journals added and withdrawn'
1744# worksheet name or tab name that datalog will write to
1745DATALOG_JA_WORKSHEET_NAME = 'Added'
1747##################################################
1748# Autocheck Resource configurations
1750# Should we autocheck incoming applications and update requests
1751AUTOCHECK_INCOMING = False
1753AUTOCHECK_RESOURCE_ISSN_ORG_TIMEOUT = 10
1754AUTOCHECK_RESOURCE_ISSN_ORG_THROTTLE = 1 # seconds between requests
1756###################################################
1757# Automatic Update Request editor group assignment
1759AUTO_ASSIGN_UR_EDITOR_GROUP = True
1760AUTO_ASSIGN_EDITOR_BY_PUBLISHER_SHEET = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQg09oCuqQcP0XTFyRiLzpPFoqUeEE6hSDEIglUvSLU-TGVP9C3j4XLgslmBLJmQcdlGujz1b9TN6CN/pub?gid=0&single=true&output=csv"
1761AUTO_ASSIGN_EDITOR_BY_COUNTRY_SHEET = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQg09oCuqQcP0XTFyRiLzpPFoqUeEE6hSDEIglUvSLU-TGVP9C3j4XLgslmBLJmQcdlGujz1b9TN6CN/pub?gid=1948254841&single=true&output=csv"
1762AUTO_ASSIGN_EDITOR_GOOGLE_SHEET = "https://docs.google.com/spreadsheets/d/1EDvesL3si4zRj97RCUjTcqglin_XJ5OLGAR8xifoTr0/edit"
1764##################################################
1765# Background jobs Management settings
1767# list of actions name that will be cleaned up if they are redundant
1768BGJOB_MANAGE_REDUNDANT_ACTIONS = [
1769 'read_news', 'journal_csv'
1770]
1772##################################################
1773# Honeypot bot-trap settings for forms (now: only registration form)
1774HONEYPOT_TIMER_THRESHOLD = 5000
1776################################
1777# Url Shortener
1778# ~~->URLShortener:Feature~~
1780# URLSHORT_LIMIT* used to limit the number of short URLs (URLSHORT_LIMIT) created
1781# by a user within a certain time period (URLSHORT_LIMIT_WITHIN_DAYS)
1782URLSHORT_LIMIT_WITHIN_DAYS = 7
1783URLSHORT_LIMIT = 50_000
1785URLSHORT_ALLOWED_SUPERDOMAINS = ['doaj.org']
1786URLSHORT_ALIAS_LENGTH = 6
1787HONEYPOT_TIMER_THRESHOLD = 5000
1789##################################################
1790# Premium membership configurations
1792# Should the system enforce premium membership mode
1793PREMIUM_MODE = True
1795# should the system respect phase-in mode, accommodating the phase-in start as the
1796# oldest date for non-premium content
1797PREMIUM_PHASE_IN = False
1798PREMIUM_PHASE_IN_START = datetime(2026, 3, 19)
1800# What is the delay non-premium users have to data access
1801NON_PREMIUM_DELAY_SECONDS = 30 * _DAY
1803##################################################
1804# Object validation settings
1806SEAMLESS_JOURNAL_LIKE_SILENT_PRUNE = False