Coverage for portality/forms/application_forms.py: 82%
584 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-09-13 22:06 +0100
« prev ^ index » next coverage.py v6.4.2, created at 2022-09-13 22:06 +0100
1"""
2~~ApplicationForm:Feature~~
3"""
4import datetime
5from copy import deepcopy
7from wtforms import StringField, TextAreaField, IntegerField, BooleanField, RadioField, SelectMultipleField, \
8 SelectField, \
9 FormField, FieldList, HiddenField
10from wtforms import widgets, validators
11from wtforms.widgets.core import html_params, HTMLString
13from portality import constants
14from portality import regex
15from portality.core import app
16from portality.crosswalks.application_form import ApplicationFormXWalk
17from portality.crosswalks.journal_form import JournalFormXWalk
18from portality.datasets import language_options, country_options, currency_options
19from portality.forms import application_processors
20from portality.forms.fields import TagListField, NestedFormField, UnconstrainedRadioField
21from portality.forms.validate import (
22 HTTPURL,
23 OptionalIf,
24 MaxLen,
25 RegexpOnTagList,
26 StopWords,
27 ISSNInPublicDOAJ,
28 JournalURLInPublicDOAJ,
29 DifferentTo,
30 RequiredIfOtherValue,
31 OnlyIf,
32 NotIf,
33 GroupMember,
34 RequiredValue,
35 BigEndDate,
36 ReservedUsernames,
37 CustomRequired,
38 OwnerExists, NoScriptTag, Year
39)
40from portality.lib.formulaic import Formulaic, WTFormsBuilder
41from portality.models import EditorGroup
42from portality.regex import ISSN, ISSN_COMPILED
44# Stop words used in the keywords field
45STOP_WORDS = [
46 "open access",
47 "high quality",
48 "peer-reviewed",
49 "peer-review",
50 "peer review",
51 "peer reviewed",
52 "quality",
53 "medical journal",
54 "multidisciplinary",
55 "multi-disciplinary",
56 "multi-disciplinary journal",
57 "interdisciplinary",
58 "inter disciplinary",
59 "inter disciplinary research",
60 "international journal",
61 "journal",
62 "scholarly journal",
63 "open science",
64 "impact factor",
65 "scholarly",
66 "research",
67 "research journal"
68]
70########################################################
71# Define all our individual fields
72########################################################
74class FieldDefinitions:
75 # ~~->$ BOAI:FormField~~
76 BOAI = {
77 "name": "boai",
78 "label": "Does the journal adhere to DOAJ’s definition of open access?",
79 "input": "radio",
80 "options": [
81 {"display": "Yes", "value": "y"},
82 {"display": "No", "value": "n"}
83 ],
84 "help": {
85 "long_help": ["See <a href='https://blog.doaj.org/2020/11/17/"
86 "what-does-doaj-define-as-open-access/' "
87 "target='_blank' rel='noopener'>"
88 "DOAJ’s definition of open access explained "
89 "in full</a>."],
90 "doaj_criteria": "You must answer 'Yes'"
91 },
92 "validate": [
93 {"required": {"message": "You must answer <strong>Yes</strong> to continue"}},
94 {"required_value" : {"value" : "y"}}
95 ],
96 "contexts": {
97 "admin" : {
98 "validate" : []
99 },
100 "editor": {
101 "validate" : [],
102 "disabled": True
103 },
104 "associate_editor": {
105 "validate" : [],
106 "disabled": True
107 }
108 }
109 }
111 # ~~->$ OAStatementURL:FormField~~
112 OA_STATEMENT_URL = {
113 "name": "oa_statement_url",
114 "label": "The journal website must display its open access statement. Where can we find this information?",
115 "input": "text",
116 "help": {
117 "long_help": ["Here is an example of a suitable Open Access "
118 "statement that meets our criteria: <blockquote>This"
119 " is an open access journal which means that all "
120 "content is freely available without charge to the "
121 "user or his/her institution. Users are allowed to "
122 "read, download, copy, distribute, print, search, or"
123 " link to the full texts of the articles, or use "
124 "them for any other lawful purpose, without asking "
125 "prior permission from the publisher or the author. "
126 "This is in accordance with the BOAI definition of "
127 "open access.</blockquote>"],
128 "short_help": "Link to the journal’s open access statement",
129 "placeholder": "https://www.my-journal.com/open-access"
130 },
131 "validate": [
132 {"required": {"message": "Enter the URL for the journal’s Open Access statement page"}},
133 "is_url" # ~~^->IsURL:FormValidator~~
134 ],
135 "widgets": [
136 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
137 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
138 ],
139 "attr": {
140 "type": "url"
141 }
142 }
144 #~~->$ Title:FormField~~
145 TITLE = {
146 "name": "title",
147 "label": "Journal title",
148 "input": "text",
149 "help": {
150 "long_help": ["The journal title must match what is displayed on the website and what is registered at the "
151 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal</a>.",
152 "For translated titles, you may add the "
153 "translation as an alternative title."],
154 "placeholder": "Journal title",
155 "doaj_criteria": "Title in application form, title at ISSN and website must all match"
156 },
157 "validate": [
158 {"required": {"message": "Enter the journal’s name"}},
159 "no_script_tag" # ~~^-> NoScriptTag:FormValidator
160 ],
161 "widgets": [
162 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
163 "full_contents" # ~~^->FullContents:FormWidget~~
164 ],
165 "contexts": {
166 "editor": {
167 "disabled": True
168 },
169 "associate_editor": {
170 "disabled": True
171 },
172 "update_request": {
173 "disabled": True
174 }
175 }
176 }
178 # ~~->$ AlternativeTitle:FormField~~
179 ALTERNATIVE_TITLE = {
180 "name": "alternative_title",
181 "label": "Alternative title (including translation of the title)",
182 "input": "text",
183 "optional": True,
184 "help": {
185 "placeholder": "Ma revue"
186 },
187 "validate": [
188 "no_script_tag" # ~~^-> NoScriptTag:FormValidator
189 ],
190 "widgets": [
191 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
192 {"full_contents" : {"empty_disabled" : "[The journal has no alternative title]"}} # ~~^->FullContents:FormWidget~~
193 ],
194 "contexts": {
195 "update_request": {
196 "disabled": True
197 }
198 }
199 }
201 # ~~->$ JournalURL:FormField~~
202 JOURNAL_URL = {
203 "name": "journal_url",
204 "label": "Link to the journal’s homepage",
205 "input": "text",
206 "validate": [
207 "required",
208 "is_url" # ~~^->IsURL:FormValidator~~
209 ],
210 "widgets": [
211 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
212 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
213 ],
214 "help": {
215 "placeholder": "https://www.my-journal.com"
216 },
217 "contexts": {
218 "public" : {
219 "validate": [
220 {"required": {"message": "Enter the URL for the journal’s <strong>homepage</strong>"}},
221 "is_url", # ~~^->IsURL:FormValidator~~
222 "journal_url_in_public_doaj" # ~~^-> JournalURLInPublicDOAJ:FormValidator~~
223 ],
224 }
225 }
226 }
228 #~~->$ PISSN:FormField~~
229 PISSN = {
230 "name": "pissn",
231 "label": "ISSN (print)",
232 "input": "text",
233 "help": {
234 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
235 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal.</a>",
236 "The ISSN must match what is given on the journal website."],
237 "short_help": "For example, 2049-3630",
238 "doaj_criteria": "ISSN must be provided"
239 },
240 "validate": [
241 {"optional_if": {"field": "eissn", # ~~^-> OptionalIf:FormValidator~~
242 "message": "You must provide <strong>one or both</strong> of an online ISSN or a print ISSN"}},
243 {"is_issn": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~
244 {"different_to": {"field": "eissn", "message": "This field must contain a different value to 'ISSN ("
245 "online)'"}} # ~~^-> DifferetTo:FormValidator~~
246 ],
247 "widgets" : [
248 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
249 "full_contents" # ~~^->FullContents:FormWidget~~
250 ],
251 "contexts": {
252 "public" : {
253 "validate": [
254 {"optional_if": {"field": "eissn", # ~~^-> OptionalIf:FormValidator~~
255 "message": "You must provide <strong>one or both</strong> of an online ISSN or a print ISSN"}},
256 {"is_issn": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~
257 {"different_to": {"field": "eissn",
258 "message": "This field must contain a different value to 'ISSN ("
259 "online)'"}}, # ~~^-> DifferetTo:FormValidator~~
260 "issn_in_public_doaj"
261 ],
262 },
263 "admin" : {
264 "help": {
265 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
266 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal</a>",
267 "The ISSN must match what is given on the journal website."],
268 "placeholder": "",
269 "doaj_criteria": "ISSN must be provided"
270 }
271 },
272 "editor": {
273 "disabled": True,
274 "help": {
275 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
276 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal.</a>",
277 "The ISSN must match what is given on the journal website."],
278 "placeholder": "",
279 "doaj_criteria": "ISSN must be provided"
280 },
281 },
282 "associate_editor": {
283 "disabled": True,
284 "help": {
285 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
286 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal.</a>",
287 "The ISSN must match what is given on the journal website."],
288 "placeholder": "",
289 "doaj_criteria": "ISSN must be provided"
290 }
291 },
292 "update_request": {
293 "disabled": True
294 }
295 }
296 }
298 #~~->$ EISSN:FormField~~
299 EISSN = {
300 "name": "eissn",
301 "label": "ISSN (online)",
302 "input": "text",
303 "help": {
304 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
305 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal</a>",
306 "The ISSN must match what is given on the journal website."],
307 "short_help": "For example, 0378-5955",
308 "doaj_criteria": "ISSN must be provided"
309 },
310 "validate": [
311 {"optional_if": {"field": "pissn", # ~~^-> OptionalIf:FormValidator~~
312 "message": "You must provide <strong>one or both</strong> of an online ISSN or a print ISSN"}},
313 {"is_issn": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~
314 {"different_to": {"field": "pissn", "message" : "This field must contain a different value to 'ISSN (print)'"}} # ~~^-> DifferetTo:FormValidator~~
315 ],
316 "widgets" : [
317 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
318 "full_contents" # ~~^->FullContents:FormWidget~~
319 ],
320 "contexts": {
321 "public" : {
322 "validate" : [
323 {"optional_if": {"field": "pissn", # ~~^-> OptionalIf:FormValidator~~
324 "message": "You must provide <strong>one or both</strong> of an online ISSN or a print ISSN"}},
325 {"is_issn": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~
326 {"different_to": {"field": "pissn",
327 "message": "This field must contain a different value to 'ISSN (print)'"}}, # ~~^-> DifferetTo:FormValidator~~
328 "issn_in_public_doaj"
329 ]
330 },
331 "admin" : {
332 "help": {
333 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
334 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal</a>",
335 "The ISSN must match what is given on the journal website."],
336 "placeholder": "",
337 "doaj_criteria": "ISSN must be provided"
338 }
339 },
340 "editor": {
341 "disabled": True,
342 "help": {
343 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
344 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal.</a>",
345 "The ISSN must match what is given on the journal website."],
346 "placeholder": "",
347 "doaj_criteria": "ISSN must be provided"
348 },
349 },
350 "associate_editor": {
351 "disabled": True,
352 "help": {
353 "long_help": ["Must be a valid ISSN, fully registered and confirmed at the "
354 "<a href='https://portal.issn.org/' target='_blank' rel='noopener'> ISSN Portal.</a>",
355 "The ISSN must match what is given on the journal website."],
356 "placeholder": "",
357 "doaj_criteria": "ISSN must be provided"
358 }
359 },
360 "update_request": {
361 "disabled": True,
362 "validate" : [
363 {"optional_if": {"field": "pissn", # ~~^-> OptionalIf:FormValidator~~
364 "message": "You must provide <strong>one or both</strong> of an online ISSN or a print ISSN"}},
365 {"is_issn": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~
366 {"different_to": {"field": "pissn", # ~~^-> DifferetTo:FormValidator~~
367 "message": "This field must contain a different value to 'ISSN (print)'"}}
368 ]
369 }
370 }
371 }
373 # ~~->$ Keywords:FormField~~
374 KEYWORDS = {
375 "name": "keywords",
376 "label": "Up to 6 subject keywords in English",
377 "input": "taglist",
378 "help": {
379 "long_help": ["Choose upto 6 keywords that describe the subject matter of the journal. "
380 "Keywords must be in English.", "Use single words or short phrases (2 to 3 words) "
381 "that describe the journal's main topic.", "Do not add acronyms, abbreviations or descriptive sentences.",
382 "Note that the keywords may be edited by DOAJ editorial staff." ],
383 },
384 "validate": [
385 {"required": {"message": "Enter at least <strong>one subject keyword</strong> in English"}},
386 {"stop_words": {"disallowed": STOP_WORDS}}, # ~~^->StopWords:FormValidator~~
387 {"max_tags": {"max": 6}}
388 ],
389 "postprocessing": [
390 "to_lower" # FIXME: this might just be a feature of the crosswalk
391 ],
392 "widgets": [
393 {
394 "taglist": {
395 "maximumSelectionSize": 6,
396 "stopWords": STOP_WORDS,
397 "field": "bibjson.keywords"
398 }
399 }
400 ],
401 "attr": {
402 "class": "input-xlarge"
403 }
404 }
406 # ~~->$ Language:FormField~~
407 LANGUAGE = {
408 "name": "language",
409 "label": "Languages in which the journal accepts manuscripts",
410 "input": "select",
411 "default" : "",
412 "options_fn": "iso_language_list",
413 "repeatable": {
414 "minimum" : 1,
415 "initial": 5
416 },
417 "validate": [
418 {"required": {"message": "Enter <strong>at least one</strong> language"}}
419 ],
420 "widgets": [
421 {"select": {}},
422 "multiple_field"
423 ],
424 "help": {
425 "placeholder": "Type or select the language"
426 },
427 "attr": {
428 "class": "input-xlarge unstyled-list"
429 }
430 }
432 # ~~->$ PublisherName:FormField~~
433 PUBLISHER_NAME = {
434 "name": "publisher_name",
435 "label": "Publisher’s name",
436 "input": "text",
437 "validate": [
438 {"required": {"message": "Enter the name of the journal’s publisher"}}
439 ],
440 "widgets": [
441 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
442 {"autocomplete": {"type" : "journal", "field": "bibjson.publisher.name.exact"}},
443 "full_contents" # ~~^->FullContents:FormWidget~~
444 ],
445 "help": {
446 "placeholder": "Type or select the publisher’s name"
447 },
448 "contexts" : {
449 "bulk_edit" : {
450 "validate" : []
451 }
452 }
453 }
455 # ~~->$ PublisherCountry:FormField~~
456 PUBLISHER_COUNTRY = {
457 "name": "publisher_country",
458 "label": "Publisher’s country",
459 "input": "select",
460 "default": "",
461 "options_fn": "iso_country_list",
462 "help": {
463 "long_help": ["The country where the publisher carries out its business operations and is registered."],
464 "doaj_criteria": "You must provide a publisher country",
465 "placeholder": "Type or select the country"
466 },
467 "validate": [
468 {"required": {"message": "Enter the <strong>country</strong> where the publisher carries out its business operations and is registered"}}
469 ],
470 "widgets": [
471 {"select": {}}
472 ],
473 "attr": {
474 "class": "input-xlarge"
475 },
476 "contexts": {
477 "associate_editor": {
478 "disabled": True
479 },
480 "bulk_edit" : {
481 "validate" : []
482 }
483 }
484 }
486 # ~~->$ InstitutionName:FormField~~
487 INSTITUTION_NAME = {
488 "name": "institution_name",
489 "label": "Society or institution’s name",
490 "input": "text",
491 "optional": True,
492 "help": {
493 "short_help": "The society or institution responsible for the journal",
494 "long_help": ["Some societies or institutions are linked to a journal in some way but are not responsible "
495 "for publishing it. The publisher can be a separate organisation. If your journal is linked to "
496 "a society or other type of institution, enter that here."],
497 "placeholder": "Type or select the society or institution’s name"
498 },
499 "widgets": [
500 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
501 {"autocomplete": {"type" : "journal", "field": "bibjson.institution.name.exact"}},
502 "full_contents" # ~~^->FullContents:FormWidget~~
503 ]
504 }
506 # ~~->$ InstitutionCountry:FormField~~
507 INSTITUTION_COUNTRY = {
508 "name": "institution_country",
509 "label": "Society or institution’s country",
510 "input": "select",
511 "default" : "",
512 "options_fn": "iso_country_list",
513 "optional": True,
514 "help": {
515 "short_help": "The country in which the society or institution is based",
516 "placeholder": "Type or select the country"
517 },
518 "widgets": [
519 {"select": {"allow_clear" : True}}
520 ],
521 "attr": {
522 "class": "input-xlarge"
523 }
524 }
526 # ~~->$ License:FormField~~
527 LICENSE = {
528 "name": "license",
529 "label": "License(s) permitted by the journal",
530 "input": "checkbox",
531 "multiple": True,
532 "options": [
533 {"display": "CC BY", "value": "CC BY"},
534 {"display": "CC BY-SA", "value": "CC BY-SA"},
535 {"display": "CC BY-ND", "value": "CC BY-ND"},
536 {"display": "CC BY-NC", "value": "CC BY-NC"},
537 {"display": "CC BY-NC-SA", "value": "CC BY-NC-SA"},
538 {"display": "CC BY-NC-ND", "value": "CC BY-NC-ND"},
539 {"display": "CC0", "value": "CC0"},
540 {"display": "Public domain", "value": "Public domain"},
541 {"display": "Publisher's own license", "value": "Publisher's own license", "subfields": ["license_attributes"]},
542 ],
543 "help": {
544 "long_help": ["The journal must use some form of licensing to be considered for indexing in DOAJ. ",
545 "If Creative Commons licensing is not used, then select <em>Publisher's own license</em> and enter "
546 "more details below.",
547 "More information on CC licenses: <br/>"
548 "<a href='https://creativecommons.org/licenses/by/4.0/"
549 "' target='_blank' 'rel='noopener'>CC BY</a> <br/>"
550 "<a href='https://creativecommons.org/licenses/by-sa/4.0/"
551 "' target='_blank' 'rel='noopener'>CC BY-SA</a> <br/>"
552 "<a href='https://creativecommons.org/licenses/by-nd/4.0/"
553 "' target='_blank' 'rel='noopener'>CC BY-ND</a> <br/>"
554 "<a href='https://creativecommons.org/licenses/by-nc/4.0/"
555 "' target='_blank' 'rel='noopener'>CC BY-NC</a> <br/>"
556 "<a href='https://creativecommons.org/licenses/by-nc-sa/4.0/"
557 "' target='_blank' 'rel='noopener'>CC BY-NC-SA</a> <br/>"
558 "<a href='https://creativecommons.org/licenses/by-nc-nd/4.0/"
559 "' target='_blank' 'rel='noopener'>CC BY-NC-ND</a>",
560 "<a href='https://wiki.creativecommons.org/wiki/CC0_"
561 "FAQ#What_is_the_difference_between_CC0_and_the_Publ"
562 "ic_Domain_Mark_.28.22PDM.22.29.3F' target='_blank' "
563 "rel='noopener'>What is the difference between CC0 "
564 "and the Public Domain Mark (\"PDM\")?</a>"],
565 "doaj_criteria": "Content must be licensed",
566 "seal_criteria": "Yes: CC BY, CC BY-SA, CC BY-NC"
567 },
568 "validate": [
569 {"required": {"message": "Select <strong>at least one</strong> type of license"}}
570 ]
571 }
573 # ~~->$ LicenseAttributes:FormField~~
574 LICENSE_ATTRIBUTES = {
575 "name": "license_attributes",
576 "label": "Select all the attributes that your license has",
577 "input": "checkbox",
578 "multiple": True,
579 "conditional": [
580 {"field": "license", "value": "Publisher's own license"}
581 ],
582 "options": [
583 {"display": "Attribution", "value": "BY"},
584 {"display": "Share Alike", "value": "SA"},
585 {"display": "No Derivatives", "value": "ND"},
586 {"display": "No Commercial Usage", "value": "NC"}
587 ],
588 "help": {
589 "doaj_criteria": "Content must be licensed"
590 }
591 }
593 # ~~->$ LicenseTermsURL:FormField~~
594 LICENSE_TERMS_URL = {
595 "name": "license_terms_url",
596 "label": "Where can we find this information?",
597 "input": "text",
598 "diff_table_context": "License terms",
599 "validate": [
600 {"required": {"message": "Enter the URL for the journal’s <strong>license terms</strong> page"}},
601 "is_url" # ~~^->IsURL:FormValidator~~
602 ],
603 "help": {
604 "short_help": "Link to the page where the license terms are stated on your site.",
605 "doaj_criteria": "You must provide a link to your license terms",
606 "placeholder": "https://www.my-journal.com/about#licensing",
607 },
608 "widgets": [
609 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
610 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
611 ]
612 }
614 # ~~->$ LicenseDisplay:FormField~~
615 LICENSE_DISPLAY = {
616 "name": "license_display",
617 "label": "Does the journal embed and/or display licensing information in its articles?",
618 "input": "radio",
619 "options": [
620 {"display": "Yes", "value": "y", "subfields": ["license_display_example_url"]},
621 {"display": "No", "value": "n"}
622 ],
623 "help": {
624 "long_help": ["It is recommended that licensing information is included in full-text articles "
625 "but it is not required for inclusion. "
626 "Answer <strong>Yes</strong> if licensing is displayed or "
627 "embedded in all versions of each article."],
628 "seal_criteria": "If the answer is Embed"
629 },
630 "validate": [
631 {"required": {"message": "Select Yes or No"}}
632 ]
633 }
635 #~~->$ LicenseDisplayExampleUrl:FormField~~
636 LICENSE_DISPLAY_EXAMPLE_URL = {
637 "name": "license_display_example_url",
638 "label": "Recent article displaying or embedding a license in the full text",
639 "input": "text",
640 "conditional": [
641 {"field": "license_display", "value": "y"}
642 ],
643 "help": {
644 "short_help": "Link to an example article",
645 "placeholder": "https://www.my-journal.com/articles/article-page"
646 },
647 "validate": [
648 {"required_if": {
649 "field": "license_display",
650 "value": "y",
651 "message": "Enter the URL for any recent article that displays or embeds a license"
652 }
653 },
654 "is_url" # ~~^->IsURL:FormValidator~~
655 ],
656 "widgets": [
657 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
658 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
659 ]
660 }
662 # ~~->$ CopyrightAuthorRetails:FormField~~
663 COPYRIGHT_AUTHOR_RETAINS = {
664 "name": "copyright_author_retains",
665 "label": "For all the licenses you have indicated above, do authors retain the copyright "
666 "<b>and</b> full publishing rights without restrictions?",
667 "input": "radio",
668 "options": [
669 {"display": "Yes", "value": "y"},
670 {"display": "No", "value": "n"}
671 ],
672 "validate": [
673 {"required": {"message": "Select Yes or No"}}
674 ],
675 "help": {
676 "long_help": ["Answer <strong>No</strong> if authors transfer "
677 "copyright or assign exclusive rights to the publisher"
678 " (including commercial rights). <br/><br/> Answer "
679 "<strong>Yes</strong> only if authors publishing "
680 "under any license allowed by the journal "
681 "retain all rights."],
682 "seal_criteria": "The author must retain the copyright"
683 }
684 }
686 # ~~->$ CopyrightURL:FormField~~
687 COPYRIGHT_URL = {
688 "name": "copyright_url",
689 "label": "Where can we find this information?",
690 "input": "text",
691 "diff_table_context": "Copyright terms",
692 "help": {
693 "short_help": "Link to the journal’s copyright terms"
694 },
695 "placeholder": "https://www.my-journal.com/about#licensing",
696 "validate": [
697 "is_url" # ~~^->IsURL:FormValidator~~
698 ],
699 "widgets": [
700 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
701 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
702 ],
703 "contexts": {
704 "public": {
705 "validate": [
706 {"required": {"message": "Enter the URL for the journal’s <strong>copyright terms</strong> page"}},
707 "is_url" # ~~^->IsURL:FormValidator~~
708 ]
709 },
710 "update_request": {
711 "validate": [
712 "required",
713 "is_url" # ~~^->IsURL:FormValidator~~
714 ]
715 }
716 }
717 }
719 # ~~->$ ReviewProcess:FormField~~
720 REVIEW_PROCESS = {
721 "name": "review_process",
722 "label": "DOAJ only accepts peer-reviewed journals. "
723 "Which type(s) of peer review does this journal use?",
724 "input": "checkbox",
725 "multiple": True,
726 "options": [
727 {"display": "Editorial review", "value": "Editorial review"},
728 {"display": "Peer review", "value": "Peer review"},
729 {"display": "Blind peer review", "value": "Blind peer review"},
730 {"display": "Double blind peer review", "value": "Double blind peer review"},
731 {"display": "Post-publication peer review", "value": "Post-publication peer review"},
732 {"display": "Open peer review", "value": "Open peer review"},
733 {"display": "Other", "value": "other", "subfields": ["review_process_other"]}
734 ],
735 "help": {
736 "long_help": ["Enter all types of review used by the journal for "
737 "research articles. Note that editorial review is "
738 "only accepted for arts and humanities journals."],
739 "doaj_criteria": "Peer review must be carried out"
740 },
741 "validate": [
742 {"required": {"message": "Select <strong>at least one</strong> type of review process"}}
743 ]
744 }
746 # ~~->$ ReviewProcessOther:FormField~~
747 REVIEW_PROCESS_OTHER = {
748 "name": "review_process_other",
749 "label": "Other peer review",
750 "input": "text",
751 "help": {
752 "placeholder": "Other peer review"
753 },
754 "conditional": [{"field": "review_process", "value": "other"}],
755 "validate": [
756 {"required_if": {
757 "field": "review_process",
758 "value": "other",
759 "message": "Enter the name of another type of peer review"
760 }
761 }
762 ],
763 "widgets" : [
764 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
765 ],
766 "asynchronous_warning": [
767 {"warn_on_value": {"value": "None"}}
768 ]
769 }
771 # ~~->$ ReviewURL:FormField~~
772 REVIEW_URL = {
773 "name": "review_url",
774 "label": "Where can we find this information?",
775 "input": "text",
776 "diff_table_context": "Peer review policy",
777 "help": {
778 "doaj_criteria": "You must provide a URL",
779 "short_help": "Link to the journal’s peer review policy"
780 },
781 "validate": [
782 {"required": {"message": "Enter the URL for the journal’s <strong>peer review policy</strong> page"}},
783 "is_url" # ~~^->IsURL:FormValidator~~
784 ],
785 "widgets": [
786 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
787 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
788 ]
789 }
791 # ~~->$ OAStart:FormField~~
792 OA_START = {
793 "name": "oa_start",
794 "label": "When did the journal start to publish all content using an open license?",
795 "input": "number",
796 "datatype": "integer",
797 "help": {
798 "long_help": ["Please enter the year that the journal started to publish all content as true open access, according to DOAJ's <a href='https://blog.doaj.org/2020/11/17/what-does-doaj-define-as-open-access/' target='_blank' rel='nofollow'>definition</a>.",
799 "For journals that have flipped to open access, enter the year that the journal flipped, not the original launch date of the journal.",
800 "For journals that have made digitised backfiles freely available, enter the year that the journal started publishing as a fully open access title, not the date of the earliest free content."]
801 },
802 "validate": [
803 {"required": {"message": "Enter the Year (YYYY)."}},
804 {"int_range": {"gte": app.config.get('MINIMAL_OA_START_DATE', 1900), "lte": datetime.datetime.utcnow().year}},
805 {"year": {"message": "OA Start Date must be a year in a 4 digit format (eg. 1987) and must be greater than {}".format(app.config.get('MINIMAL_OA_START_DATE', 1900))}}
806 ],
807 "attr": {
808 "min": app.config.get('MINIMAL_OA_START_DATE', 1900),
809 "max": datetime.datetime.utcnow().year
810 }
811 }
813 # ~~->$ PlagiarismDetection:FormField~~
814 PLAGIARISM_DETECTION = {
815 "name": "plagiarism_detection",
816 "label": "Does the journal routinely screen article submissions for plagiarism?",
817 "input": "radio",
818 "help": {
819 "long_help": ["Screening for plagiarism is recommended, but is not"
820 " a requirement for inclusion in DOAJ. If the "
821 "journal does screen for plagiarism, state the "
822 "services(s) used on your website."],
823 },
824 "options": [
825 {"display": "Yes", "value": "y", "subfields": ["review_process_other"]},
826 {"display": "No", "value": "n"}
827 ],
828 "validate": [
829 {"required": {"message": "Select Yes or No"}}
830 ]
831 }
833 #~~->$ PlagiarismURL:FormField~~
834 PLAGIARISM_URL = {
835 "name": "plagiarism_url",
836 "label": "Where can we find this information?",
837 "diff_table_context": "Plagiarism screening",
838 "input": "text",
839 "conditional": [{"field": "plagiarism_detection", "value": "y"}],
840 "help": {
841 "doaj_criteria": "You must provide a URL",
842 "placeholder": "https://www.my-journal.com/about#plagiarism",
843 "short_help": "Link to the journal’s plagiarism policy",
844 "long_help": ["The page should state that the journal actively checks for plagiarism and explain how this "
845 "is done (including the name of any software or service used)."]
846 },
847 "validate": [
848 {"required_if": {
849 "field": "plagiarism_detection",
850 "value": "y",
851 "message": "Enter the URL for the journal’s <strong>plagiarism policy</strong> page"
852 }
853 },
854 "is_url" # ~~^->IsURL:FormValidator~~
855 ],
856 "widgets": [
857 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
858 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
859 ]
860 }
862 # ~~->$ AimsScopeURL:FormField~~
863 AIMS_SCOPE_URL = {
864 "name": "aims_scope_url",
865 "label": "Link to the journal’s <b>Aims & Scope</b>",
866 "input": "text",
867 "help": {
868 "doaj_criteria": "You must provide a URL",
869 "placeholder": "https://www.my-journal.com/about#aims"
870 },
871 "validate": [
872 {"required": {"message": "Enter the URL for the journal’s <strong>Aims & Scope</strong> page"}},
873 "is_url" # ~~^->IsURL:FormValidator~~
874 ],
875 "widgets": [
876 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
877 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
878 ]
879 }
881 # ~~->$ EditorialBoardURL:FormField~~
882 EDITORIAL_BOARD_URL = {
883 "name": "editorial_board_url",
884 "label": "Link to the journal’s <b>Editorial Board</b>",
885 "input": "text",
886 "help": {
887 "doaj_criteria": "You must provide a URL",
888 "placeholder": "https://www.my-journal.com/about#board"
889 },
890 "validate": [
891 {"required": {"message": "Enter the URL for the journal’s <strong>Editorial Board</strong> page"}},
892 "is_url" # ~~^->IsURL:FormValidator~~
893 ],
894 "widgets": [
895 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
896 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
897 ]
898 }
900 # ~~->$ AuthorInstructionsURL:FormField~~
901 AUTHOR_INSTRUCTIONS_URL = {
902 "name": "author_instructions_url",
903 "label": "Link to the journal’s <b>Instructions for Authors</b>",
904 "input": "text",
905 "help": {
906 "doaj_criteria": "You must provide a URL",
907 "placeholder": "https://www.my-journal.com/for_authors"
908 },
909 "validate": [
910 {"required": {"message": "Enter the URL for the journal’s <strong>Instructions for Authors</strong> page"}},
911 "is_url" # ~~^->IsURL:FormValidator~~
912 ],
913 "widgets": [
914 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
915 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
916 ]
917 }
919 # ~~->$ PublicationTimeWeeks:FormField~~
920 PUBLICATION_TIME_WEEKS = {
921 "name": "publication_time_weeks",
922 "label": "Average number of <strong>weeks</strong> between article submission & publication",
923 "input": "number",
924 "datatype": "integer",
925 "validate": [
926 {"required": {"message": "Enter an average number of weeks"}},
927 {"int_range": {"gte": 1, "lte": 100}}
928 ],
929 "asynchronous_warning": [
930 {"int_range": {"lte": 2}}
931 ],
932 "attr": {
933 "min": "1",
934 "max": "100"
935 }
936 }
938 # ~~->$ APC:FormField~~
939 APC = {
940 "name": "apc",
941 "label": "Does the journal charge fees for publishing an article (APCs)?",
942 "input": "radio",
943 "options": [
944 {"display": "Yes", "value": "y", "subfields": ["apc_charges"]},
945 {"display": "No", "value": "n"}
946 ],
947 "help": {
948 "long_help": ["Publication fees are sometimes called "
949 "article processing charges (APCs). You should answer"
950 " Yes if any fee is required from the author for "
951 "publishing their paper."],
952 "doaj_criteria": "You must tell us about any APCs"
953 },
954 "validate": [
955 {"required": {"message": "Select Yes or No"}}
956 ]
957 }
959 # ~~->$ APCCharges:FormField~~
960 APC_CHARGES = {
961 "name": "apc_charges",
962 "input": "group",
963 "label": "Highest fee charged",
964 "repeatable": {
965 "minimum": 1,
966 "initial": 5
967 },
968 "conditional": [
969 {"field": "apc", "value": "y"}
970 ],
971 "help": {
972 "long_help": [" If the journal charges a range of fees for "
973 "publication of an article, enter the highest fee. "
974 "If the fee can be paid in more than one currency, "
975 "you may list them here."]
976 },
977 "subfields": [
978 "apc_currency",
979 "apc_max"
980 ],
981 "template": "application_form/_list.html",
982 "entry_template": "application_form/_entry_group_horizontal.html",
983 "widgets": [
984 "multiple_field"
985 ]
986 }
988 # ~~->$ APCCurrency:FormField~~
989 APC_CURRENCY = {
990 "subfield": True,
991 "group": "apc_charges",
992 "name": "apc_currency",
993 "input": "select",
994 "options_fn": "iso_currency_list",
995 "default": "",
996 "help": {
997 "placeholder": "Currency"
998 },
999 "widgets": [
1000 {"select": {}}
1001 ],
1002 "attr": {
1003 "class": "input-xlarge"
1004 },
1005 "validate": [
1006 {"required_if": {
1007 "field": "apc",
1008 "value": "y",
1009 "message": "Enter the currency or currencies for the journal’s publishing fees"
1010 }
1011 }
1012 ]
1013 }
1015 # ~~->$ APCMax:FormField~~
1016 APC_MAX = {
1017 "subfield": True,
1018 "group": "apc_charges",
1019 "name": "apc_max",
1020 "input": "number",
1021 "datatype": "integer",
1022 "help": {
1023 "placeholder": "Highest fee charged"
1024 },
1025 "validate":[
1026 {"required_if": {
1027 "field": "apc",
1028 "value": "y",
1029 "message": "Enter the value of the highest publishing fee the journal has charged"
1030 }
1031 }
1032 ],
1033 "attr": {
1034 "min": "1"
1035 }
1036 }
1038 # ~~->$ APCURL:FormField~~
1039 APC_URL = {
1040 "name": "apc_url",
1041 "label": "Where can we find this information?",
1042 "diff_table_context": "Publication fees",
1043 "input": "text",
1044 "help": {
1045 "short_help": "Link to the page where this is stated. The page "
1046 "must declare <b>whether or not</b> there is a fee "
1047 "to publish an article in the journal.",
1048 "doaj_criteria": "You must provide a URL",
1049 "placeholder": "https://www.my-journal.com/about#apc"
1050 },
1051 "validate": [
1052 {"required": {"message": "Enter the URL for the journal’s <strong>publication fees</strong> information page"}},
1053 "is_url" # ~~^->IsURL:FormValidator~~
1054 ],
1055 "widgets": [
1056 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
1057 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
1058 ]
1059 }
1061 # ~~->$ HasWaiver:FormField~~
1062 HAS_WAIVER = {
1063 "name": "has_waiver",
1064 "label": "Does the journal provide a waiver or discount "
1065 "on publication fees for authors?",
1066 "input": "radio",
1067 "options": [
1068 {"display": "Yes", "value": "y", "subfields": ["waiver_url"]},
1069 {"display": "No", "value": "n"}
1070 ],
1071 "help": {
1072 "long_help": ["Answer <strong>Yes</strong> if the journal provides"
1073 " publication fee waivers for authors from "
1074 "low-income economies, discounts for authors from "
1075 "lower middle-income economies, and/or waivers and "
1076 "discounts for other authors with "
1077 "demonstrable needs."]
1078 },
1079 "validate": [
1080 {"required": {"message": "Select Yes or No"}}
1081 ]
1082 }
1084 # ~~->$ WaiverURL:FormField~~
1085 WAIVER_URL = {
1086 "name": "waiver_url",
1087 "label": "Where can we find this information?",
1088 "input": "text",
1089 "diff_table_context": "Publication fee waiver",
1090 "conditional": [
1091 {"field": "has_waiver", "value": "y"}
1092 ],
1093 "help": {
1094 "short_help": "Link to the journal’s waiver information.",
1095 "doaj_criteria": "You must provide a URL",
1096 "placeholder": "https://www.my-journal.com/about#waiver"
1097 },
1098 "validate": [
1099 {"required_if": {
1100 "field": "has_waiver",
1101 "value": "y",
1102 "message": "Enter the URL for the journal’s <strong>waiver information</strong> page"
1103 }
1104 },
1105 "is_url" # ~~^->IsURL:FormValidator~~
1106 ],
1107 "widgets": [
1108 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
1109 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
1110 ]
1111 }
1113 # ~~->$ HasOtherCharges:FormField~~
1114 HAS_OTHER_CHARGES = {
1115 "name": "has_other_charges",
1116 "label": "Does the journal charge any other fees to authors?",
1117 "input": "radio",
1118 "options": [
1119 {"display": "Yes", "value": "y", "subfields": ["other_charges_url"]},
1120 {"display": "No", "value": "n"}
1121 ],
1122 "help": {
1123 "long_help": ["Declare all other charges: editorial processing charges, language editing fees, "
1124 "colour charges, submission fees, page charges, membership fees, print subscription costs, "
1125 "other supplementary charges"],
1126 "doaj_criteria": "You must declare any other charges if they exist"
1127 },
1128 "validate": [
1129 {"required": {"message": "Select Yes or No"}}
1130 ]
1131 }
1133 # ~~->$ OtherChargesURL:FormField~~
1134 OTHER_CHARGES_URL = {
1135 "name": "other_charges_url",
1136 "label": "Where can we find this information?",
1137 "input": "text",
1138 "diff_table_context": "Other fees",
1139 "conditional": [
1140 {"field": "has_other_charges", "value": "y"}
1141 ],
1142 "help": {
1143 "short_help": "Link to the journal’s fees information",
1144 "doaj_criteria": "You must provide a URL"
1145 },
1146 "validate": [
1147 {"required_if": {
1148 "field": "has_other_charges",
1149 "value": "y",
1150 "message": "Enter the URL for the journal’s <strong>fees<strong> information page"
1151 }
1152 },
1153 "is_url" # ~~^->IsURL:FormValidator~~
1154 ],
1155 "widgets": [
1156 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
1157 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
1158 ]
1159 }
1161 # ~~->$ PreservationService:FormField~~
1162 PRESERVATION_SERVICE = {
1163 "name": "preservation_service",
1164 "label": "Long-term preservation service(s) where the journal is currently archived",
1165 "input": "checkbox",
1166 "multiple": True,
1167 "options": [
1168 {"display": "CINES", "value": "CINES", "subfields": ["preservation_service_url"]},
1169 {"display": "CLOCKSS", "value": "CLOCKSS", "subfields": ["preservation_service_url"]},
1170 {"display": "LOCKSS", "value": "LOCKSS", "subfields": ["preservation_service_url"]},
1171 {"display": "Internet Archive", "value": "Internet Archive", "subfields": ["preservation_service_url"]},
1172 {"display": "PKP PN", "value": "PKP PN", "subfields": ["preservation_service_url"]},
1173 {"display": "PubMed Central (PMC)", "value": "PMC", "subfields": ["preservation_service_url"]},
1174 {"display": "Portico", "value": "Portico", "subfields": ["preservation_service_url"]},
1175 {"display": "A national library", "value": "national_library", "subfields": ["preservation_service_library", "preservation_service_url"]},
1176 {"display": "Other", "value": "other", "subfields": ["preservation_service_other", "preservation_service_url"]},
1177 {"display": "<em>The journal content isn’t archived with a long-term preservation service</em>",
1178 "value": "none", "exclusive": True}
1179 ],
1180 "help": {
1181 "long_help": [
1182 "Content must be actively deposited in each of the options you choose. "
1183 "If the journal is registered with a service but archiving is not yet active,"
1184 " choose <em>The journal content isn’t archived with a long-term preservation service</em>.",
1185 "PubMed Central covers PMC U.S.A. and EuropePMC(Wellcome Trust)."]
1186 },
1187 "validate": [
1188 {"required": {"message": "Select <strong>at least one</strong> option"}}
1189 ]
1190 }
1192 # ~~->$ PreservationServiceLibrary:FormField~~
1193 PRESERVATION_SERVICE_LIBRARY = {
1194 "name": "preservation_service_library",
1195 "label": "A national library",
1196 "input": "text",
1197 "repeatable" : {
1198 "minimum": 1,
1199 "initial" : 2
1200 },
1201 "help": {
1202 "short_help": "Name of national library"
1203 },
1204 "conditional": [{"field": "preservation_service", "value": "national_library"}],
1205 "validate": [
1206 {"required_if": {
1207 "field": "preservation_service",
1208 "value": "national_library",
1209 "message": "Enter the name(s) of the national library or libraries where the journal is archived"
1210 }
1211 }
1212 ],
1213 "asynchronous_warning": [
1214 {"warn_on_value": {"value": "None"}}
1215 ],
1216 "widgets": [
1217 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
1218 "multiple_field"
1219 ],
1220 "attr": {
1221 "class": "input-xlarge unstyled-list"
1222 }
1223 }
1225 # ~~->$ PreservationServiceOther:FormField~~
1226 PRESERVATION_SERVICE_OTHER = {
1227 "name": "preservation_service_other",
1228 "label": "Other archiving policy:",
1229 "input": "text",
1230 "conditional": [{"field": "preservation_service", "value": "other"}],
1231 "validate": [
1232 {"required_if": {
1233 "field": "preservation_service",
1234 "value": "other",
1235 "message": "Enter the name of another archiving policy"
1236 }
1237 }
1238 ],
1239 "asynchronous_warning": [
1240 {"warn_on_value": {"value": "None"}}
1241 ],
1242 "widgets" : [
1243 "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~
1244 ]
1245 }
1247 # ~~->$ PreservationServiceURL:FormField~~
1248 PRESERVATION_SERVICE_URL = {
1249 "name": "preservation_service_url",
1250 "label": "Where can we find this information?",
1251 "input": "text",
1252 "diff_table_context": "Archiving policy",
1253 "help": {
1254 "short_help": "Link to the preservation and archiving information",
1255 "doaj_criteria": "You must provide a URL",
1256 "placeholder": "https://www.my-journal.com/about#archiving"
1257 },
1258 "conditional": [
1259 {"field": "preservation_service", "value": "CINES"},
1260 {"field": "preservation_service", "value": "CLOCKSS"},
1261 {"field": "preservation_service", "value": "LOCKSS"},
1262 {"field": "preservation_service", "value": "Internet Archive"},
1263 {"field": "preservation_service", "value": "PKP PN"},
1264 {"field": "preservation_service", "value": "PMC"},
1265 {"field": "preservation_service", "value": "Portico"},
1266 {"field": "preservation_service", "value": "national_library"},
1267 {"field": "preservation_service", "value": "other"}
1268 ],
1269 "validate": [
1270 {
1271 "required_if": {
1272 "field": "preservation_service",
1273 "value": [
1274 "CINES",
1275 "CLOCKSS",
1276 "LOCKSS",
1277 "Internet Archive",
1278 "PKP PN",
1279 "PMC",
1280 "Portico",
1281 "national_library",
1282 "other"
1283 ]
1284 }
1285 },
1286 "is_url" # ~~^->IsURL:FormValidator~~
1287 ],
1288 "widgets": [
1289 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
1290 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
1291 ]
1292 }
1294 # ~~->$ DepositPolicy:FormField~~
1295 DEPOSIT_POLICY = {
1296 "name": "deposit_policy",
1297 "label": "Does the journal have a policy allowing authors to deposit versions of their work in an "
1298 "institutional or other repository of their choice? Where is this policy recorded?",
1299 "input": "checkbox",
1300 "multiple": True,
1301 "options": [
1302 {"display": "Sherpa/Romeo", "value": "Sherpa/Romeo", "subfields": ["deposit_policy_url"]},
1303 {"display": "Dulcinea", "value": "Dulcinea", "subfields": ["deposit_policy_url"]},
1304 {"display": "Diadorim", "value": "Diadorim", "subfields": ["deposit_policy_url"]},
1305 {"display": "Other (including publisher’s own site)", "value": "other", "subfields": ["deposit_policy_other", "deposit_policy_url"]},
1306 {"display": "<em>The journal has no repository policy</em>", "value": "none", "exclusive": True}
1307 ],
1308 "help": {
1309 "long_help": ["Many authors wish to deposit a copy of their paper in an institutional or other repository "
1310 "of their choice. What is the journal’s policy for this?",
1311 "You should state your policy with regard to the different versions of the paper:"
1312 "<ul style='list-style-type: none;'>"
1313 "<li>Submitted version</li>"
1314 "<li>Accepted version (Author Accepted Manuscript)</li>"
1315 "<li>Published version (Version of Record)</li>"
1316 "</ul>",
1317 "For a journal to qualify for the DOAJ Seal, it must allow all versions to be deposited in an institutional or other repository of the author’s choice without embargo."
1318 ]},
1319 "validate": [
1320 {"required": {"message": "Select <strong>at least one</strong> option"}}
1321 ]
1322 }
1324 # ~~->$ DepositPolicyOther:FormField~~
1325 DEPOSIT_POLICY_OTHER = {
1326 "name": "deposit_policy_other",
1327 "label": "Name of other website where policy is registered",
1328 "input": "text",
1329 "conditional": [{"field": "deposit_policy", "value": "other"}],
1330 "validate": [
1331 {"required_if": {
1332 "field": "deposit_policy",
1333 "value": "other",
1334 "message": "Enter the name of another repository policy"
1335 }
1336 }
1337 ],
1338 "asynchronous_warning": [
1339 {"warn_on_value": {"value": "None"}}
1340 ],
1341 "widgets" : [
1342 "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~
1343 ]
1344 }
1346 # ~~->$ DepositPolicyURL:FormField~~
1347 DEPOSIT_POLICY_URL = {
1348 "name": "deposit_policy_url",
1349 "label": "Where can we find this information?",
1350 "input": "text",
1351 "diff_table_context": "Repository policy",
1352 "conditional": [{"field": "deposit_policy", "value": "Sherpa/Romeo"},
1353 {"field": "deposit_policy", "value": "Dulcinea"},
1354 {"field": "deposit_policy", "value": "Diadorim"},
1355 {"field": "deposit_policy", "value": "Diadorim"},
1356 {"field": "deposit_policy", "value": "other"}],
1357 "help": {
1358 "doaj_criteria": "You must provide a URL",
1359 "short_help": "Link to the policy in a directory or on the "
1360 "publisher’s site",
1361 "placeholder": "https://www.my-journal.com/about#repository_policy"
1362 },
1363 "validate": [
1364 "is_url" # ~~^->IsURL:FormValidator~~
1365 ],
1366 "widgets": [
1367 "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~
1368 "clickable_url" # ~~^-> ClickableURL:FormWidget~~
1369 ],
1370 "contexts" : {
1371 "public" : {
1372 "validate": [
1373 {
1374 "required_if": {
1375 "field": "deposit_policy",
1376 "value": [
1377 "Sherpa/Romeo",
1378 "Dulcinea",
1379 "Diadorim",
1380 "other"
1381 ]
1382 }
1383 },
1384 "is_url" # ~~^->IsURL:FormValidator~~
1385 ]
1386 },
1387 "update_request" : {
1388 "validate" : [
1389 {
1390 "required_if": {
1391 "field": "deposit_policy",
1392 "value": [
1393 "Sherpa/Romeo",
1394 "Dulcinea",
1395 "Diadorim",
1396 "other"
1397 ]
1398 }
1399 },
1400 "is_url" # ~~^->IsURL:FormValidator~~
1401 ]
1402 }
1403 }
1404 }
1406 # ~~->$ PersistentIdentifiers:FormField~~
1407 PERSISTENT_IDENTIFIERS = {
1408 "name": "persistent_identifiers",
1409 "label": "Persistent article identifiers used by the journal",
1410 "input": "checkbox",
1411 "multiple": True,
1412 "hint": "Select at least one",
1413 "options": [
1414 {"display": "DOIs", "value": "DOI"},
1415 {"display": "ARKs", "value": "ARK"},
1416 {"display": "Handles", "value": "Handles"},
1417 {"display": "PURLs", "value": "PURL"},
1418 {"display": "Other", "value": "other", "subfields": ["persistent_identifiers_other"]},
1419 {"display": "<em>The journal does not use persistent article identifiers</em>", "value": "none", "exclusive": True}
1420 ],
1421 "help": {
1422 "long_help": ["A persistent article identifier (PID) is used to find the article no matter where it is "
1423 "located. The most common type of PID is the digital object identifier (DOI). ",
1424 "<a href='https://en.wikipedia.org/wiki/Persistent_identifier' target='_blank' rel='noopener'>Read more about PIDs.</a>"],
1425 },
1426 "validate": [
1427 {"required": {"message": "Select <strong>at least one</strong> option"}}
1428 ]
1429 }
1431 # ~~->$ PersistentIdentifiersOther:FormField~~
1432 PERSISTENT_IDENTIFIERS_OTHER = {
1433 "name": "persistent_identifiers_other",
1434 "label": "Other identifier",
1435 "input": "text",
1436 "conditional": [{"field": "persistent_identifiers", "value": "other"}],
1437 "validate": [
1438 {"required_if": {
1439 "field": "persistent_identifiers",
1440 "value": "other",
1441 "message": "Enter the name of another type of identifier"
1442 }
1443 }
1444 ],
1445 "asynchronous_warning": [
1446 {"warn_on_value": {"value": "None"}}
1447 ],
1448 "widgets" : [
1449 "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~
1450 ]
1451 }
1453 # ~~->$ Orcids:FormField~~
1454 ORCID_IDS = {
1455 "name": "orcid_ids",
1456 "label": "Does the journal allow for ORCID iDs to be present in article metadata?",
1457 "input": "radio",
1458 "options": [
1459 {"display": "Yes", "value": "y"},
1460 {"display": "No", "value": "n"}
1461 ],
1462 "default" : "",
1463 "help": {
1464 "long_help": ["An <a href='https://orcid.org/' target='_blank' rel='noopener'>ORCID</a> (Open Researcher and Contributor) iD is an alphanumeric code to uniquely identify "
1465 "authors."],
1466 },
1467 "contexts" : {
1468 "public" : {
1469 "validate": [
1470 {"required": {"message": "Select Yes or No"}}
1471 ]
1472 },
1473 "update_request" : {
1474 "validate" : [
1475 {"required": {"message": "Select Yes or No"}}
1476 ]
1477 }
1478 }
1479 }
1481 # ~~->$ OpenCitations:FormField~~
1482 OPEN_CITATIONS = {
1483 "name": "open_citations",
1484 "label": "Does the journal comply with I4OC standards for open citations?",
1485 "input": "radio",
1486 "options": [
1487 {"display": "Yes", "value": "y"},
1488 {"display": "No", "value": "n"}
1489 ],
1490 "default" : "",
1491 "help": {
1492 "long_help": ["The <a href='https://i4oc.org/#goals' target='_blank' rel='noopener'>I4OC standards</a> ask that citations are structured, separable, and open. "],
1493 },
1494 "contexts" : {
1495 "public" : {
1496 "validate": [
1497 {"required": {"message": "Select Yes or No"}}
1498 ]
1499 },
1500 "update_request" : {
1501 "validate" : [
1502 {"required": {"message": "Select Yes or No"}}
1503 ]
1504 }
1505 }
1506 }
1508 #######################################
1509 ## Ediorial fields
1511 # ~~->$ DOAJSeal:FormField~~
1512 DOAJ_SEAL = {
1513 "name": "doaj_seal",
1514 "label": "The journal has fulfilled all the criteria for the Seal. Award the Seal?",
1515 "input": "checkbox",
1516 "validate": [
1517 {
1518 "only_if" : {
1519 "fields" : [
1520 {"field" : "license_display", "value" : "y"},
1521 {"field" : "copyright_author_retains", "value" : "y"},
1522 {"field" : "preservation_service", "not" : "none"},
1523 {"field" : "preservation_service_url", "not" : ""},
1524 {"field" : "deposit_policy", "not" : "none"},
1525 {"field" : "persistent_identifiers", "not" : "none"},
1526 {"field" : "license", "or" : ["CC BY", "CC BY-SA", "CC BY-NC", "CC BY-NC-SA"]}
1527 ],
1528 "message" : "In order to award the query: the license must be CC BY, CC BY-SA, CC BY-NC, or CC BY-NC-SA; "
1529 "the license must be displayed or embedded; "
1530 "the author must retain their copyright; "
1531 "the journal must make use of a preservation service; "
1532 "a url for the preservation service must be provided; "
1533 "the journal must have a deposit policy; "
1534 "the journal must use a persistent identifier"
1535 }
1536 }
1537 ]
1538 }
1540 # FIXME: this probably shouldn't be in the admin form fieldsets, rather its own separate form
1541 # ~~->$ QuickReject:FormField~~
1542 QUICK_REJECT = {
1543 "name": "quick_reject",
1544 "label": "Reason for rejection",
1545 "input": "select",
1546 "options_fn": "quick_reject"
1547 }
1549 # ~~->$ QuickRejectDetails:FormField~~
1550 QUICK_REJECT_DETAILS = {
1551 "name": "quick_reject_details",
1552 "label": "Additional info",
1553 "input": "textarea",
1554 "help": {
1555 "long_help": ["The selected reason for rejection, and any additional information you include, "
1556 "are sent to the journal contact with the rejection email."]
1557 },
1558 "validate": [
1559 {"required_if": {"field": "quick_reject", "value": "other"}}
1560 ],
1561 }
1563 # ~~->$ Owner:FormField~~
1564 OWNER = {
1565 "name": "owner",
1566 "label": "DOAJ Account",
1567 "input": "text",
1568 "validate": [
1569 "reserved_usernames",
1570 "owner_exists"
1571 ],
1572 "widgets": [
1573 {"autocomplete": {"type" : "account", "field": "id", "include" : False}},
1574 "clickable_owner"
1575 ],
1576 "contexts" : {
1577 "associate_editor" : {
1578 "validate" : [
1579 {"required": {"message": "You must confirm the account id"}},
1580 "reserved_usernames",
1581 "owner_exists"
1582 ]
1583 }
1584 }
1585 }
1587 # ~~->$ ApplicationStatus:FormField~~
1588 APPLICATION_STATUS = {
1589 "name": "application_status",
1590 "label": "Change status",
1591 "input": "select",
1592 "options_fn": "application_statuses",
1593 "validate": [
1594 "required"
1595 ],
1596 "help" : {
1597 "update_requests_diff" : False,
1598 "render_error_box": False
1599 },
1600 "disabled" : "application_status_disabled",
1601 "contexts" : {
1602 "associate_editor" : {
1603 "help" : {
1604 "render_error_box": False,
1605 "short_help" : "Set the status to 'In Progress' to signal to the applicant that you have started your review."
1606 "Set the status to 'Completed' to alert the Editor that you have completed your review.",
1607 "update_requests_diff": False
1608 }
1609 },
1610 "editor" : {
1611 "help" : {
1612 "render_error_box" : False,
1613 "short_help" : "Revert the status to 'In Progress' to signal to the Associate Editor that further work is needed."
1614 "Set the status to 'Ready' to alert the Managing Editor that you have completed your review.",
1615 "update_requests_diff": False
1616 }
1617 }
1618 },
1619 "widgets" : [
1620 # When Accepted selected display. 'This journal is currently assigned to its applicant account XXXXXX. Is this the correct account for this journal?'
1621 "owner_review"
1622 ]
1623 }
1625 # ~~->$ EditorGroup:FormField~~
1626 EDITOR_GROUP = {
1627 "name": "editor_group",
1628 "label": "Group",
1629 "input": "text",
1630 "widgets": [
1631 {"autocomplete": {"type" : "editor_group", "field": "name", "include" : False}}
1632 ],
1633 "contexts" : {
1634 "editor" : {
1635 "disabled" : True
1636 },
1637 "admin" : {
1638 "widgets" : [
1639 {"autocomplete": {"type": "editor_group", "field": "name", "include" : False}},
1640 {"load_editors" : {"field" : "editor"}}
1641 ]
1642 }
1643 }
1644 }
1646 # ~~->$ Editor:FormField~~
1647 EDITOR = {
1648 "name": "editor",
1649 "label": "Individual",
1650 "input": "select",
1651 "options_fn": "editor_choices",
1652 "default" : "",
1653 "validate" : [
1654 { "group_member" : {"group_field" : "editor_group"}}
1655 ],
1656 "help" : {
1657 "render_error_box": False
1658 }
1659 }
1661 # ~~->$ DiscontinuedDate:FormField~~
1662 DISCONTINUED_DATE = {
1663 "name": "discontinued_date",
1664 "label": "Discontinued on",
1665 "input": "text",
1666 "validate" : [
1667 {"bigenddate" : {"message" : "Date must be a big-end formatted date (e.g. 2020-11-23)"}},
1668 {
1669 "not_if" : {
1670 "fields" : [
1671 {"field" : "continues"},
1672 {"field" : "continued_by"}
1673 ],
1674 "message" : "You cannot enter both a discontinued date and continuation information."
1675 }
1676 }
1677 ],
1678 "help" : {
1679 "short_help" : "Please enter the discontinued date in the form YYYY-MM-DD (e.g. 2020-11-23). "
1680 "If the day of the month is not known, please use '01' (e.g. 2020-11-01)",
1681 "render_error_box" : False
1682 }
1683 }
1685 # ~~->$ Continues:FormField~~
1686 CONTINUES = {
1687 "name": "continues",
1688 "label": "Continues an <strong>older</strong> journal with the ISSN(s)",
1689 "input": "taglist",
1690 "validate": [
1691 {"is_issn_list": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~
1692 {"different_to": {"field": "continued_by"}}, # ~~^-> DifferetTo:FormValidator~~
1693 {
1694 "not_if" : {
1695 "fields" : [{"field" : "discontinued_date"}],
1696 "message" : "You cannot enter both continuation information and a discontinued date"
1697 }
1698 }
1699 ],
1700 "widgets" : [
1701 "tagentry"
1702 ],
1703 "help" : {
1704 "render_error_box": False
1705 }
1706 }
1708 # ~~->$ ContinuedBy:FormField~~
1709 CONTINUED_BY = {
1710 "name": "continued_by",
1711 "label": "Continued by a <strong>newer</strong> journal with the ISSN(s)",
1712 "input": "taglist",
1713 "validate": [
1714 {"is_issn_list": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~
1715 {"different_to": {"field": "continues"}}, # ~~^-> DifferetTo:FormValidator~~
1716 {
1717 "not_if": {
1718 "fields": [{"field": "discontinued_date"}],
1719 "message": "You cannot enter both continuation information and a discontinued date"
1720 }
1721 }
1722 ],
1723 "widgets" : [
1724 "tagentry"
1725 ],
1726 "help" : {
1727 "render_error_box": False
1728 }
1729 }
1731 # ~~->$ Subject:FormField~~
1732 SUBJECT = {
1733 "name": "subject",
1734 "label": "Assign one or a maximum of two subject classifications",
1735 "input": "taglist",
1736 "help": {
1737 "short_help": "Selecting a subject will not automatically select its sub-categories",
1738 "render_error_box" : False,
1739 },
1740 "validate": [
1741 {"required_if" : {
1742 "field" : "application_status",
1743 "value" : [
1744 constants.APPLICATION_STATUS_READY,
1745 constants.APPLICATION_STATUS_COMPLETED,
1746 constants.APPLICATION_STATUS_ACCEPTED
1747 ],
1748 "message" : "This field is required when setting the Application Status to {y}, {z} or {a}".format(
1749 y=constants.APPLICATION_STATUS_READY,
1750 z=constants.APPLICATION_STATUS_COMPLETED,
1751 a=constants.APPLICATION_STATUS_ACCEPTED
1752 )
1753 }
1754 }
1755 ],
1756 "widgets": [
1757 "subject_tree"
1758 ],
1759 "contexts" : {
1760 "associate_editor" : {
1761 "validate" : [
1762 "required"
1763 ]
1764 }
1765 }
1766 }
1768 # ~~->$ Notes:FormField~~
1769 NOTES = {
1770 "name" : "notes",
1771 "input": "group",
1772 "label": "Notes",
1773 "repeatable" : {
1774 "initial" : 1,
1775 "add_button_placement" : "top"
1776 },
1777 "subfields": [
1778 "note_date",
1779 "note",
1780 "note_id"
1781 ],
1782 "template": "application_form/_list.html",
1783 "entry_template": "application_form/_entry_group.html",
1784 "widgets": [
1785 {"infinite_repeat" : {"enable_on_repeat" : ["textarea"]}},
1786 "note_modal"
1787 ],
1788 "merge_disabled" : "merge_disabled_notes",
1789 "contexts" : {
1790 "admin" : {
1791 "widgets": [
1792 {"infinite_repeat": {"enable_on_repeat": ["textarea"], "allow_delete" : True}},
1793 "note_modal"
1794 ]
1795 }
1796 }
1797 }
1799 # ~~->$ Note:FormField~~
1800 NOTE = {
1801 "subfield": True,
1802 "name": "note",
1803 "group": "notes",
1804 "input": "textarea",
1805 "disabled": True
1806 }
1808 # ~~->$ NoteDate:FormField~~
1809 NOTE_DATE = {
1810 "subfield": True,
1811 "name" : "note_date",
1812 "group": "notes",
1813 "input": "text",
1814 "disabled": True
1815 }
1817 # ~~->$ NoteID:FormField~~
1818 NOTE_ID = {
1819 "subfield" : True,
1820 "name": "note_id",
1821 "group": "notes",
1822 "input": "hidden"
1823 }
1825 # ~~->$ OptionalValidation:FormField~~
1826 OPTIONAL_VALIDATION = {
1827 "name" : "make_all_fields_optional",
1828 "label" : "Allow save without validation",
1829 "input" : "checkbox",
1830 "widget" : {
1831 "optional_validation"
1832 }
1833 }
1835 # Bulk Edit fields (that couldn't be overriden in the normal way)
1836 # ~~->$ BulkDOAJSeal:FormField~~
1837 BULK_DOAJ_SEAL = {
1838 "name": "change_doaj_seal",
1839 "label": 'Award the Seal',
1840 "input": "select",
1841 "default" : "",
1842 "options" :[
1843 {"value": "", "display" : "Leave unchanged"},
1844 {"value" : "True", "display" : "Yes"},
1845 {"value" : "False", "display" : "No"}
1846 ],
1847 }
1850##########################################################
1851# Define our fieldsets
1852##########################################################
1854class FieldSetDefinitions:
1855 #~~->$ BasicCompliance:FieldSet~~
1856 BASIC_COMPLIANCE = {
1857 "name": "basic_compliance",
1858 "label": "Open access compliance",
1859 "fields": [
1860 FieldDefinitions.BOAI["name"],
1861 FieldDefinitions.OA_STATEMENT_URL["name"],
1862 FieldDefinitions.OA_START["name"]
1863 ]
1864 }
1866 # ~~->$ AboutJournal:FieldSet~~
1867 ABOUT_THE_JOURNAL = {
1868 "name": "about_the_journal",
1869 "label": "About the journal",
1870 "fields": [
1871 FieldDefinitions.TITLE["name"],
1872 FieldDefinitions.ALTERNATIVE_TITLE["name"],
1873 FieldDefinitions.JOURNAL_URL["name"],
1874 FieldDefinitions.PISSN["name"],
1875 FieldDefinitions.EISSN["name"],
1876 FieldDefinitions.KEYWORDS["name"],
1877 FieldDefinitions.LANGUAGE["name"]
1878 ]
1879 }
1881 # ~~->$ Publisher:FieldSet~~
1882 PUBLISHER = {
1883 "name": "publisher",
1884 "label": "Publisher",
1885 "fields": [
1886 FieldDefinitions.PUBLISHER_NAME["name"],
1887 FieldDefinitions.PUBLISHER_COUNTRY["name"],
1888 ]
1889 }
1891 # ~~->$ Institution:FieldSet~~
1892 SOCIETY_OR_INSTITUTION = {
1893 "name": "society_or_institution",
1894 "label": "Society or institution, if applicable",
1895 "fields": [
1896 FieldDefinitions.INSTITUTION_NAME["name"],
1897 FieldDefinitions.INSTITUTION_COUNTRY["name"]
1898 ]
1899 }
1901 # ~~->$ Licensing:FieldSet~~
1902 LICENSING = {
1903 "name": "licensing",
1904 "label": "Licensing",
1905 "fields": [
1906 FieldDefinitions.LICENSE["name"],
1907 FieldDefinitions.LICENSE_ATTRIBUTES["name"],
1908 FieldDefinitions.LICENSE_TERMS_URL["name"]
1909 ]
1910 }
1912 # ~~->$ EmbeddedLicense:FieldSet~~
1913 EMBEDDED_LICENSING = {
1914 "name": "embedded_licensing",
1915 "label": "Embedded licenses",
1916 "fields": [
1917 FieldDefinitions.LICENSE_DISPLAY["name"],
1918 FieldDefinitions.LICENSE_DISPLAY_EXAMPLE_URL["name"]
1919 ]
1920 }
1922 # ~~->$ Copyright:FieldSet~~
1923 COPYRIGHT = {
1924 "name": "copyright",
1925 "label": "Copyright",
1926 "fields": [
1927 FieldDefinitions.COPYRIGHT_AUTHOR_RETAINS["name"],
1928 FieldDefinitions.COPYRIGHT_URL["name"]
1929 ]
1930 }
1932 # ~~->$ PeerReview:FieldSet~~
1933 PEER_REVIEW = {
1934 "name": "peer_review",
1935 "label": "Peer review",
1936 "fields": [
1937 FieldDefinitions.REVIEW_PROCESS["name"],
1938 FieldDefinitions.REVIEW_PROCESS_OTHER["name"],
1939 FieldDefinitions.REVIEW_URL["name"]
1940 ]
1941 }
1943 # ~~->$ Plagiarism:FieldSet~~
1944 PLAGIARISM = {
1945 "name": "plagiarism",
1946 "label": "Plagiarism",
1947 "fields": [
1948 FieldDefinitions.PLAGIARISM_DETECTION["name"],
1949 FieldDefinitions.PLAGIARISM_URL["name"]
1950 ]
1951 }
1953 # ~~->$ Editorial:FieldSet~~
1954 EDITORIAL = {
1955 "name": "editorial",
1956 "label": "Editorial",
1957 "fields": [
1958 FieldDefinitions.AIMS_SCOPE_URL["name"],
1959 FieldDefinitions.EDITORIAL_BOARD_URL["name"],
1960 FieldDefinitions.AUTHOR_INSTRUCTIONS_URL["name"],
1961 FieldDefinitions.PUBLICATION_TIME_WEEKS["name"]
1962 ]
1963 }
1965 # ~~->$ APC:FieldSet~~
1966 APC = {
1967 "name": "apc",
1968 "label": "Publication fees",
1969 "fields": [
1970 FieldDefinitions.APC["name"],
1971 FieldDefinitions.APC_CHARGES["name"],
1972 FieldDefinitions.APC_CURRENCY["name"],
1973 FieldDefinitions.APC_MAX["name"],
1974 FieldDefinitions.APC_URL["name"]
1975 ]
1976 }
1978 # ~~->$ Waivers:FieldSet~~
1979 APC_WAIVERS = {
1980 "name": "apc_waivers",
1981 "label": "Publication fee waivers",
1982 "fields": [
1983 FieldDefinitions.HAS_WAIVER["name"],
1984 FieldDefinitions.WAIVER_URL["name"]
1985 ]
1986 }
1988 # ~~->$ OtherFees:FieldSet~~
1989 OTHER_FEES = {
1990 "name": "other_fees",
1991 "label": "Other fees",
1992 "fields": [
1993 FieldDefinitions.HAS_OTHER_CHARGES["name"],
1994 FieldDefinitions.OTHER_CHARGES_URL["name"]
1995 ]
1996 }
1998 # ~~->$ ArchivingPolicy:FieldSet~~
1999 ARCHIVING_POLICY = {
2000 "name": "archiving_policy",
2001 "label": "Archiving policy",
2002 "fields": [
2003 FieldDefinitions.PRESERVATION_SERVICE["name"],
2004 FieldDefinitions.PRESERVATION_SERVICE_LIBRARY["name"],
2005 FieldDefinitions.PRESERVATION_SERVICE_OTHER["name"],
2006 FieldDefinitions.PRESERVATION_SERVICE_URL["name"]
2007 ]
2008 }
2010 # ~~->$ RepositoryPolicy:FieldSet~~
2011 REPOSITORY_POLICY = {
2012 "name": "deposit_policy",
2013 "label": "Repository policy",
2014 "fields": [
2015 FieldDefinitions.DEPOSIT_POLICY["name"],
2016 FieldDefinitions.DEPOSIT_POLICY_OTHER["name"],
2017 FieldDefinitions.DEPOSIT_POLICY_URL["name"]
2018 ]
2019 }
2021 # ~~->$ UniqueIdentifiers:FieldSet~~
2022 UNIQUE_IDENTIFIERS = {
2023 "name": "unique_identifiers",
2024 "label": "Unique identifiers & structured data",
2025 "fields": [
2026 FieldDefinitions.PERSISTENT_IDENTIFIERS["name"],
2027 FieldDefinitions.PERSISTENT_IDENTIFIERS_OTHER["name"],
2028 FieldDefinitions.ORCID_IDS["name"],
2029 FieldDefinitions.OPEN_CITATIONS["name"]
2030 ]
2031 }
2033 # ~~->$ Seal:FieldSet~~
2034 SEAL = {
2035 "name": "seal",
2036 "label": "Award the seal",
2037 "fields": [
2038 FieldDefinitions.DOAJ_SEAL["name"]
2039 ]
2040 }
2042 # ~~->$ QuickReject:FieldSet~~
2043 # ~~^-> QuickReject:Feature~~
2044 QUICK_REJECT = {
2045 "name": "quick_reject",
2046 "label": "Quick reject",
2047 "fields": [
2048 FieldDefinitions.QUICK_REJECT["name"],
2049 FieldDefinitions.QUICK_REJECT_DETAILS["name"]
2050 ]
2051 }
2053 # ~~->$ Reassign:FieldSet~~
2054 REASSIGN = {
2055 "name": "reassign",
2056 "label": "Re-assign publisher account",
2057 "fields": [
2058 FieldDefinitions.OWNER["name"]
2059 ]
2060 }
2062 # ~~->$ Status:FieldSet~~
2063 STATUS = {
2064 "name": "status",
2065 "label": "Status",
2066 "fields": [
2067 FieldDefinitions.APPLICATION_STATUS["name"]
2068 ]
2069 }
2071 # ~~->$ Reviewers:FieldSet~~
2072 REVIEWERS = {
2073 "name": "reviewers",
2074 "label": "Assign for review",
2075 "fields": [
2076 FieldDefinitions.EDITOR_GROUP["name"],
2077 FieldDefinitions.EDITOR["name"]
2078 ]
2079 }
2081 # ~~->$ Continuations:FieldSet~~
2082 # ~~^-> Continuations:Feature~~
2083 CONTINUATIONS = {
2084 "name": "continuations",
2085 "label": "Continuations",
2086 "fields": [
2087 FieldDefinitions.CONTINUES["name"],
2088 FieldDefinitions.CONTINUED_BY["name"],
2089 FieldDefinitions.DISCONTINUED_DATE["name"]
2090 ]
2091 }
2093 # ~~->$ Subject:FieldSet~~
2094 SUBJECT = {
2095 "name": "subject",
2096 "label": "Subject classification",
2097 "fields": [
2098 FieldDefinitions.SUBJECT["name"]
2099 ]
2100 }
2102 # ~~->$ Notes:FieldSet~~
2103 NOTES = {
2104 "name": "notes",
2105 "label": "Notes",
2106 "fields": [
2107 FieldDefinitions.NOTES["name"],
2108 FieldDefinitions.NOTE["name"],
2109 FieldDefinitions.NOTE_DATE["name"],
2110 FieldDefinitions.NOTE_ID["name"]
2111 ]
2112 }
2114 # ~~->$ OptionalValidation:FieldSet~~
2115 OPTIONAL_VALIDATION = {
2116 "name": "optional_validation",
2117 "label": "Allow save without validation",
2118 "fields" : [
2119 FieldDefinitions.OPTIONAL_VALIDATION["name"]
2120 ]
2121 }
2123 # ~~->$ BulkEdit:FieldSet~~
2124 # ~~^-> BulkEdit:Feature~~
2125 BULK_EDIT = {
2126 "name" : "bulk_edit",
2127 "label" : "Bulk edit",
2128 "fields" : [
2129 FieldDefinitions.PUBLISHER_NAME["name"],
2130 FieldDefinitions.BULK_DOAJ_SEAL["name"],
2131 FieldDefinitions.PUBLISHER_COUNTRY["name"],
2132 FieldDefinitions.OWNER["name"]
2133 ]
2134 }
2137###########################################################
2138# Define our Contexts
2139###########################################################
2141class ApplicationContextDefinitions:
2142 #~~->$ NewApplication:FormContext~~
2143 #~~^-> ApplicationForm:Crosswalk~~
2144 #~~^-> NewApplication:FormProcessor~~
2145 PUBLIC = {
2146 "name": "public",
2147 "fieldsets": [
2148 FieldSetDefinitions.BASIC_COMPLIANCE["name"],
2149 FieldSetDefinitions.ABOUT_THE_JOURNAL["name"],
2150 FieldSetDefinitions.PUBLISHER["name"],
2151 FieldSetDefinitions.SOCIETY_OR_INSTITUTION["name"],
2152 FieldSetDefinitions.LICENSING["name"],
2153 FieldSetDefinitions.EMBEDDED_LICENSING["name"],
2154 FieldSetDefinitions.COPYRIGHT["name"],
2155 FieldSetDefinitions.PEER_REVIEW["name"],
2156 FieldSetDefinitions.PLAGIARISM["name"],
2157 FieldSetDefinitions.EDITORIAL["name"],
2158 FieldSetDefinitions.APC["name"],
2159 FieldSetDefinitions.APC_WAIVERS["name"],
2160 FieldSetDefinitions.OTHER_FEES["name"],
2161 FieldSetDefinitions.ARCHIVING_POLICY["name"],
2162 FieldSetDefinitions.REPOSITORY_POLICY["name"],
2163 FieldSetDefinitions.UNIQUE_IDENTIFIERS["name"]
2164 ],
2165 "templates": {
2166 "form" : "application_form/public_application.html",
2167 "default_field" : "application_form/_field.html",
2168 "default_group" : "application_form/_group.html"
2169 },
2170 "crosswalks": {
2171 "obj2form": ApplicationFormXWalk.obj2form,
2172 "form2obj": ApplicationFormXWalk.form2obj
2173 },
2174 "processor": application_processors.NewApplication,
2175 }
2177 # ~~->$ UpdateRequest:FormContext~~
2178 # ~~^-> NewApplication:FormContext~~
2179 # ~~^-> UpdateRequest:FormProcessor~~
2180 UPDATE = deepcopy(PUBLIC)
2181 UPDATE["name"] = "update_request"
2182 UPDATE["processor"] = application_processors.PublisherUpdateRequest
2183 UPDATE["templates"]["form"] = "application_form/publisher_update_request.html"
2185 # ~~->$ ReadOnlyApplication:FormContext~~
2186 # ~~^-> NewApplication:FormContext~~
2187 READ_ONLY = deepcopy(PUBLIC)
2188 READ_ONLY["name"] = "application_read_only"
2189 READ_ONLY["processor"] = application_processors.NewApplication # FIXME: enter the real processor
2190 READ_ONLY["templates"]["form"] = "application_form/readonly_application.html"
2192 # ~~->$ AssociateEditorApplication:FormContext~~
2193 # ~~^-> NewApplication:FormContext~~
2194 # ~~^-> AssociateEditorApplication:FormProcessor~~
2195 ASSOCIATE = deepcopy(PUBLIC)
2196 ASSOCIATE["name"] = "associate_editor"
2197 ASSOCIATE["fieldsets"] += [
2198 FieldSetDefinitions.STATUS["name"],
2199 FieldSetDefinitions.SUBJECT["name"],
2200 FieldSetDefinitions.NOTES["name"]
2201 ]
2202 ASSOCIATE["processor"] = application_processors.AssociateApplication
2203 ASSOCIATE["templates"]["form"] = "application_form/assed_application.html"
2205 # ~~->$ EditorApplication:FormContext~~
2206 # ~~^-> NewApplication:FormContext~~
2207 # ~~^-> EditorApplication:FormProcessor~~
2208 EDITOR = deepcopy(PUBLIC)
2209 EDITOR["name"] = "editor"
2210 EDITOR["fieldsets"] += [
2211 FieldSetDefinitions.STATUS["name"],
2212 FieldSetDefinitions.REVIEWERS["name"],
2213 FieldSetDefinitions.SUBJECT["name"],
2214 FieldSetDefinitions.NOTES["name"]
2215 ]
2216 EDITOR["processor"] = application_processors.EditorApplication
2217 EDITOR["templates"]["form"] = "application_form/editor_application.html"
2219 # ~~->$ ManEdApplication:FormContext~~
2220 # ~~^-> NewApplication:FormContext~~
2221 # ~~^-> ManEdApplication:FormProcessor~~
2222 MANED = deepcopy(PUBLIC)
2223 MANED["name"] = "admin"
2224 MANED["fieldsets"] += [
2225 FieldSetDefinitions.SEAL["name"],
2226 FieldSetDefinitions.QUICK_REJECT["name"],
2227 FieldSetDefinitions.REASSIGN["name"],
2228 FieldSetDefinitions.STATUS["name"],
2229 FieldSetDefinitions.REVIEWERS["name"],
2230 FieldSetDefinitions.CONTINUATIONS["name"],
2231 FieldSetDefinitions.SUBJECT["name"],
2232 FieldSetDefinitions.NOTES["name"]
2233 ]
2234 MANED["processor"] = application_processors.AdminApplication
2235 MANED["templates"]["form"] = "application_form/maned_application.html"
2238class JournalContextDefinitions:
2239 # ~~->$ ReadOnlyJournal:FormContext~~
2240 # ~~^-> JournalForm:Crosswalk~~
2241 # ~~^-> ReadOnlyJournal:FormProcessor~~
2242 READ_ONLY = {
2243 "name": "readonly",
2244 "fieldsets": [
2245 FieldSetDefinitions.BASIC_COMPLIANCE["name"],
2246 FieldSetDefinitions.ABOUT_THE_JOURNAL["name"],
2247 FieldSetDefinitions.PUBLISHER["name"],
2248 FieldSetDefinitions.SOCIETY_OR_INSTITUTION["name"],
2249 FieldSetDefinitions.LICENSING["name"],
2250 FieldSetDefinitions.EMBEDDED_LICENSING["name"],
2251 FieldSetDefinitions.COPYRIGHT["name"],
2252 FieldSetDefinitions.PEER_REVIEW["name"],
2253 FieldSetDefinitions.PLAGIARISM["name"],
2254 FieldSetDefinitions.EDITORIAL["name"],
2255 FieldSetDefinitions.APC["name"],
2256 FieldSetDefinitions.APC_WAIVERS["name"],
2257 FieldSetDefinitions.OTHER_FEES["name"],
2258 FieldSetDefinitions.ARCHIVING_POLICY["name"],
2259 FieldSetDefinitions.REPOSITORY_POLICY["name"],
2260 FieldSetDefinitions.UNIQUE_IDENTIFIERS["name"]
2261 ],
2262 "templates": {
2263 "form" : "application_form/readonly_journal.html",
2264 "default_field" : "application_form/_field.html",
2265 "default_group" : "application_form/_group.html"
2266 },
2267 "crosswalks": {
2268 "obj2form": JournalFormXWalk.obj2form,
2269 "form2obj": JournalFormXWalk.form2obj
2270 },
2271 "processor": application_processors.ReadOnlyJournal
2272 }
2274 # ~~->$ AssEditorJournal:FormContext~~
2275 # ~~^-> ReadOnlyJournal:FormContext~~
2276 # ~~^-> AssEdJournal:FormProcessor~~
2277 ASSOCIATE = deepcopy(READ_ONLY)
2278 ASSOCIATE["fieldsets"] += [
2279 FieldSetDefinitions.SUBJECT["name"],
2280 FieldSetDefinitions.NOTES["name"]
2281 ]
2282 ASSOCIATE["name"] = "associate_editor"
2283 ASSOCIATE["processor"] = application_processors.AssEdJournalReview
2284 ASSOCIATE["templates"]["form"] = "application_form/assed_journal.html"
2286 # ~~->$ EditorJournal:FormContext~~
2287 # ~~^-> AssEdJournal:FormContext~~
2288 # ~~^-> EditorJournal:FormProcessor~~
2289 EDITOR = deepcopy(ASSOCIATE)
2290 EDITOR["name"] = "editor"
2291 EDITOR["fieldsets"] += [
2292 FieldSetDefinitions.REVIEWERS["name"]
2293 ]
2294 EDITOR["processor"] = application_processors.EditorJournalReview
2295 EDITOR["templates"]["form"] = "application_form/editor_journal.html"
2297 # ~~->$ ManEdJournal:FormContext~~
2298 # ~~^-> EditorJournal:FormContext~~
2299 # ~~^-> ManEdJournal:FormProcessor~~
2300 MANED = deepcopy(EDITOR)
2301 MANED["name"] = "admin"
2302 MANED["fieldsets"] += [
2303 FieldSetDefinitions.REASSIGN["name"],
2304 FieldSetDefinitions.OPTIONAL_VALIDATION["name"],
2305 FieldSetDefinitions.SEAL["name"],
2306 FieldSetDefinitions.CONTINUATIONS["name"]
2307 ]
2308 MANED["processor"] = application_processors.ManEdJournalReview
2309 MANED["templates"]["form"] = "application_form/maned_journal.html"
2311 # ~~->$ BulkEditJournal:FormContext~~
2312 # ~~^-> JournalForm:Crosswalk~~
2313 # ~~^-> ManEdJournal:FormProcessor~~
2314 BULK_EDIT = {
2315 "name" : "bulk_edit",
2316 "fieldsets" : [
2317 FieldSetDefinitions.BULK_EDIT["name"]
2318 ],
2319 "templates": {
2320 "form" : "application_form/maned_journal_bulk_edit.html",
2321 "default_field" : "application_form/_field.html",
2322 "default_group" : "application_form/_group.html"
2323 },
2324 "crosswalks": {
2325 "obj2form": JournalFormXWalk.obj2form,
2326 "form2obj": JournalFormXWalk.form2obj
2327 },
2328 "processor": application_processors.ManEdBulkEdit
2329 }
2331#######################################################
2332# Gather all of our form information in one place
2333#######################################################
2335APPLICATION_FORMS = {
2336 "contexts": {
2337 ApplicationContextDefinitions.PUBLIC["name"]: ApplicationContextDefinitions.PUBLIC,
2338 ApplicationContextDefinitions.UPDATE["name"]: ApplicationContextDefinitions.UPDATE,
2339 ApplicationContextDefinitions.READ_ONLY["name"]: ApplicationContextDefinitions.READ_ONLY,
2340 ApplicationContextDefinitions.ASSOCIATE["name"]: ApplicationContextDefinitions.ASSOCIATE,
2341 ApplicationContextDefinitions.EDITOR["name"]: ApplicationContextDefinitions.EDITOR,
2342 ApplicationContextDefinitions.MANED["name"]: ApplicationContextDefinitions.MANED
2343 },
2344 "fieldsets": {v['name']: v for k, v in FieldSetDefinitions.__dict__.items() if not k.startswith('_')},
2345 "fields": {v['name']: v for k, v in FieldDefinitions.__dict__.items() if not k.startswith('_')}
2346}
2349JOURNAL_FORMS = {
2350 "contexts": {
2351 JournalContextDefinitions.READ_ONLY["name"]: JournalContextDefinitions.READ_ONLY,
2352 JournalContextDefinitions.BULK_EDIT["name"]: JournalContextDefinitions.BULK_EDIT,
2353 JournalContextDefinitions.ASSOCIATE["name"]: JournalContextDefinitions.ASSOCIATE,
2354 JournalContextDefinitions.EDITOR["name"]: JournalContextDefinitions.EDITOR,
2355 JournalContextDefinitions.MANED["name"]: JournalContextDefinitions.MANED
2356 },
2357 "fieldsets": APPLICATION_FORMS["fieldsets"],
2358 "fields": APPLICATION_FORMS["fields"]
2359}
2362#######################################################
2363# Options lists
2364#######################################################
2366def iso_country_list(field, formualic_context_name):
2367 #~~-> Countries:Data~~
2368 cl = [{"display" : " ", "value" : ""}]
2369 for v, d in country_options:
2370 cl.append({"display": d, "value": v})
2371 return cl
2374def iso_language_list(field, formulaic_context_name):
2375 # ~~-> Languages:Data~~
2376 cl = [{"display" : " ", "value" : ""}]
2377 for v, d in language_options:
2378 cl.append({"display": d, "value": v})
2379 return cl
2382def iso_currency_list(field, formulaic_context_name):
2383 # ~~-> Currencies:Data~~
2384 cl = [{"display" : " ", "value" : ""}]
2385 quick_pick = []
2386 for v, d in currency_options:
2387 if v in ["GBP", "USD", "EUR"]:
2388 quick_pick.append({"display": d, "value": v})
2389 cl.append({"display": d, "value": v})
2390 if len(quick_pick) > 0:
2391 cl = quick_pick + cl
2392 return cl
2395def quick_reject(field, formulaic_context_name):
2396 # ~~-> QuickReject:Feature~~
2397 return [{"display": "Other", "value" : ""}] + [{'display': v, 'value': v} for v in app.config.get('QUICK_REJECT_REASONS', [])]
2400def application_statuses(field, formulaic_context):
2401 # ~~->$ ApplicationStatus:Workflow~~
2402 _application_status_base = [ # This is all the Associate Editor sees
2403 ('', ' '),
2404 (constants.APPLICATION_STATUS_PENDING, 'Pending'),
2405 (constants.APPLICATION_STATUS_IN_PROGRESS, 'In Progress'),
2406 (constants.APPLICATION_STATUS_COMPLETED, 'Completed')
2407 ]
2409 _application_status_admin = _application_status_base + [
2410 (constants.APPLICATION_STATUS_UPDATE_REQUEST, 'Update Request'),
2411 (constants.APPLICATION_STATUS_REVISIONS_REQUIRED, 'Revisions Required'),
2412 (constants.APPLICATION_STATUS_ON_HOLD, 'On Hold'),
2413 (constants.APPLICATION_STATUS_READY, 'Ready'),
2414 (constants.APPLICATION_STATUS_REJECTED, 'Rejected'),
2415 (constants.APPLICATION_STATUS_ACCEPTED, 'Accepted')
2416 ]
2418 _application_status_editor = _application_status_base + [
2419 (constants.APPLICATION_STATUS_READY, 'Ready'),
2420 ]
2422 formulaic_context_name = None
2423 if formulaic_context is not None:
2424 formulaic_context_name = formulaic_context.name
2426 status_list = []
2427 if formulaic_context_name is None or formulaic_context_name == "admin":
2428 status_list = _application_status_admin
2429 elif formulaic_context_name == "editor":
2430 status_list = _application_status_editor
2431 elif formulaic_context_name == "accepted":
2432 status_list = [(constants.APPLICATION_STATUS_ACCEPTED, 'Accepted')] # just the one status - Accepted
2433 else:
2434 status_list = _application_status_base
2436 return [{'display': d, 'value': v} for (v, d) in status_list]
2439def editor_choices(field, formulaic_context):
2440 """
2441 Set the editor field choices from a given editor group name
2442 ~~->EditorGroup:Model~~
2443 """
2444 egf = formulaic_context.get("editor_group")
2445 wtf = egf.wtfield
2446 if wtf is None:
2447 return [{"display" : "", "value" : ""}]
2449 editor_group_name = wtf.data
2450 if editor_group_name is None:
2451 return [{"display" : "", "value" : ""}]
2452 else:
2453 eg = EditorGroup.pull_by_key("name", editor_group_name)
2454 if eg is not None:
2455 editors = [eg.editor]
2456 editors += eg.associates
2457 editors = list(set(editors))
2458 return [{"value" : "", "display" : "No editor assigned"}] + [{"value" : editor, "display" : editor} for editor in editors]
2459 else:
2460 return [{"display" : "", "value" : ""}]
2462#######################################################
2463## Conditional disableds
2464#######################################################
2466def application_status_disabled(field, formulaic_context):
2467 choices = application_statuses(field, formulaic_context)
2468 field_value = field.wtfield.data
2469 return field_value not in [c.get("value") for c in choices]
2472#######################################################
2473## Merge disabled
2474#######################################################
2476def merge_disabled_notes(notes_group, original_form):
2477 # ~~->Notes:Feature~~
2478 merged = []
2479 wtf = notes_group.wtfield
2480 for entry in wtf.entries:
2481 if entry.data.get("note") != "" or entry.data.get("note_id") != "":
2482 merged.append(entry.data)
2483 for entry in original_form.notes.entries:
2484 d = entry.data
2485 for m in merged:
2486 if m.get("note_id") != "" and m.get("note_id") == entry.data.get("note_id"):
2487 # keep = False
2488 if m.get("note") == "":
2489 m["note"] = entry.data.get("note")
2490 m["note_date"] = entry.data.get("note_date")
2491 break
2493 while True:
2494 try:
2495 wtf.pop_entry()
2496 except IndexError:
2497 break
2499 for m in merged:
2500 wtf.append_entry(m)
2502#######################################################
2503# Validation features
2504#######################################################
2506class ReservedUsernamesBuilder:
2507 """
2508 ~~->$ ReservedUsernames:FormValidator~~
2509 """
2510 @staticmethod
2511 def render(settings, html_attrs):
2512 return
2514 @staticmethod
2515 def wtforms(field, settings):
2516 return ReservedUsernames()
2519class OwnerExistsBuilder:
2520 """
2521 ~~->$ OwnerExists:FormValidator~~
2522 """
2523 @staticmethod
2524 def render(settings, html_attrs):
2525 return
2527 @staticmethod
2528 def wtforms(field, settings):
2529 return OwnerExists()
2532class RequiredBuilder:
2533 """
2534 ~~->$ Required:FormValidator~~
2535 """
2536 @staticmethod
2537 def render(settings, html_attrs):
2538 html_attrs["required"] = ""
2539 if "message" in settings:
2540 html_attrs["data-parsley-required-message"] = "<p><small>" + settings["message"] + "</small></p>"
2541 else:
2542 html_attrs["data-parsley-required-message"] = "<p><small>" + "This answer is required" + "</p></small>"
2543 html_attrs["data-parsley-validate-if-empty"] = "true"
2545 @staticmethod
2546 def wtforms(field, settings):
2547 return CustomRequired(message=settings.get("message"))
2550class IsURLBuilder:
2551 # ~~->$ IsURL:FormValidator~~
2552 msg = "<p><small>" + "Please enter a valid URL. It should start with http or https" + "</p></small>"
2554 @staticmethod
2555 def render(settings, html_attrs):
2556 html_attrs["type"] = "url"
2557 html_attrs["pattern"] = regex.HTTP_URL
2558 html_attrs["data-parsley-pattern"] = regex.HTTP_URL
2559 html_attrs["data-parsley-pattern-message"] = IsURLBuilder.msg
2561 @staticmethod
2562 def wtforms(field, settings):
2563 return HTTPURL(message=settings.get('message', IsURLBuilder.msg))
2566class IntRangeBuilder:
2567 """
2568 ~~->$ IntRange:FormValidator~~
2569 ~~^-> NumberRange:FormValidator~~
2570 """
2571 @staticmethod
2572 def render(settings, html_attrs):
2573 html_attrs["data-parsley-type"] = "digits"
2574 default_msg = ""
2575 if "gte" in settings and "lte" in settings:
2576 html_attrs["data-parsley-range"] = "[" + str(settings.get("gte")) + ", " + str(settings.get("lte")) + "]"
2577 default_msg = "This value should be between " + str(settings.get("gte")) + " and " + str(settings.get("lte"))
2578 else:
2579 if "gte" in settings:
2580 html_attrs["data-parsley-min"] = settings.get("gte")
2581 default_msg = "This value should be bigger than " + str(settings.get("gte"))
2582 if "lte" in settings:
2583 html_attrs["data-parsley-max"] = settings.get("lte")
2584 default_msg = "This value should be smaller than " + str(settings.get("gte"))
2585 html_attrs["data-parsley-range-message"] = "<p><small>" + settings.get("message", default_msg) + "</p></small>"
2587 @staticmethod
2588 def wtforms(field, settings):
2589 min = settings.get("gte")
2590 max = settings.get("lte")
2591 kwargs = {}
2592 if min is not None:
2593 kwargs["min"] = min
2594 if max is not None:
2595 kwargs["max"] = max
2596 return validators.NumberRange(**kwargs)
2599class MaxTagsBuilder:
2600 """
2601 ~~->$ MaxLen:FormValidator~~
2602 """
2603 @staticmethod
2604 def wtforms(field, settings):
2605 max = settings.get("max")
2606 message = settings.get("message") if "message" in settings else 'You can only enter up to {x} keywords.'.format(x=max)
2607 return MaxLen(max, message=message)
2610class StopWordsBuilder:
2611 """
2612 ~~->$ StopWords:FormValidator~~
2613 """
2614 @staticmethod
2615 def wtforms(field, settings):
2616 stopwords = settings.get("disallowed", [])
2617 return StopWords(stopwords)
2620class ISSNInPublicDOAJBuilder:
2621 """
2622 ~~->$ ISSNInPublicDOAJ:FormValidator~~
2623 """
2624 @staticmethod
2625 def render(settings, html_attrs):
2626 # FIXME: not yet implemented in the front end, so setting here is speculative
2627 html_attrs["data-parsley-issn-in-public-doaj"] = ""
2629 @staticmethod
2630 def wtforms(field, settings):
2631 return ISSNInPublicDOAJ(message=settings.get("message"))
2634class JournalURLInPublicDOAJBuilder:
2635 # ~~->$ JournalURLInPublicDOAJ:FormValidator~~
2636 @staticmethod
2637 def render(settings, html_attrs):
2638 # FIXME: not yet implemented in the front end, so setting here is speculative
2639 html_attrs["data-parsley-journal-url-in-public-doaj"] = ""
2641 @staticmethod
2642 def wtforms(field, settings):
2643 return JournalURLInPublicDOAJ(message=settings.get("message"))
2646class NoScriptTagBuilder:
2647 # ~~->$ NoScriptTag:FormValidator
2648 @staticmethod
2649 def render(settings, html_attrs):
2650 html_attrs["data-parsley-no-script-tag"] = ""
2651 if "message" in settings:
2652 html_attrs["data-parsley-noScriptTag-message"] = "<p><small>" + settings["message"] + "</small></p>"
2653 else:
2654 html_attrs["data-parsley-no-script-tag-message"] = "<p><small>" + "No script tags allowed" + "</p></small>"
2656 @staticmethod
2657 def wtforms(field, settings):
2658 return NoScriptTag(settings.get("message", "No script tags allowed"))
2661class OptionalIfBuilder:
2662 # ~~->$ OptionalIf:FormValidator~~
2663 @staticmethod
2664 def render(settings, html_attrs):
2665 html_attrs["data-parsley-validate-if-empty"] = "true"
2666 html_attrs["data-parsley-optional-if"] = settings.get("field")
2667 html_attrs["data-parsley-optional-if-message"] = "<p><small>" + settings.get("message") + "</p></small>"
2669 @staticmethod
2670 def wtforms(field, settings):
2671 return OptionalIf(settings.get("field") or field, settings.get("message"), settings.get("values", []))
2674class IsISSNBuilder:
2675 # ~~->$ IsISSN:FormValidator~~
2676 @staticmethod
2677 def render(settings, html_attrs):
2678 html_attrs["pattern"] = ISSN
2679 html_attrs["data-parsley-pattern"] = ISSN
2680 html_attrs["data-parsley-pattern-message"] = settings.get("message")
2682 @staticmethod
2683 def wtforms(field, settings):
2684 return validators.Regexp(regex=ISSN_COMPILED, message=settings.get("message"))
2687class IsISSNListBuilder:
2688 # ~~->$ IsISSNList:FormValidator~~
2689 @staticmethod
2690 def render(settings, html_attrs):
2691 html_attrs["data-parsley-entry-pattern"] = ISSN
2693 @staticmethod
2694 def wtforms(field, settings):
2695 return RegexpOnTagList(regex=ISSN_COMPILED, message=settings.get("message"))
2698class DifferentToBuilder:
2699 # ~~->$ DifferentTo:FormValidator~~
2700 @staticmethod
2701 def render(settings, html_attrs):
2702 html_attrs["data-parsley-different-to"] = settings.get("field")
2704 @staticmethod
2705 def wtforms(field, settings):
2706 return DifferentTo(settings.get("field") or field, settings.get("ignore_empty", True), settings.get("message"))
2709class RequiredIfBuilder:
2710 # ~~->$ RequiredIf:FormValidator~~
2711 @staticmethod
2712 def render(settings, html_attrs):
2713 val = settings.get("value")
2714 if isinstance(val, list):
2715 val = ",".join(val)
2717 html_attrs["data-parsley-validate-if-empty"] = "true"
2718 html_attrs["data-parsley-required-if"] = val
2719 html_attrs["data-parsley-required-if-field"] = settings.get("field")
2720 if "message" in settings:
2721 html_attrs["data-parsley-required-if-message"] = "<p><small>" + settings["message"] + "</small></p>"
2722 else:
2723 html_attrs["data-parsley-required-if-message"] = "<p><small>" + "This answer is required" + "</small></p>"
2725 @staticmethod
2726 def wtforms(field, settings):
2727 return RequiredIfOtherValue(settings.get("field") or field, settings.get("value"), settings.get("message"))
2730class OnlyIfBuilder:
2731 # ~~->$ OnlyIf:FormValidator~~
2732 @staticmethod
2733 def render(settings, html_attrs):
2734 html_attrs["data-parsley-only-if"] = ",".join([f["field"] for f in settings.get("fields", [])])
2735 for f in settings.get('fields'):
2736 if "value" in f:
2737 html_attrs["data-parsley-only-if-value_" + f["field"]] = f["value"]
2738 if "not" in f:
2739 html_attrs["data-parsley-only-if-not_" + f["field"]] = f["not"]
2740 if "or" in f:
2741 html_attrs["data-parsley-only-if-or_" + f["field"]] = ",".join(f["or"])
2742 html_attrs["data-parsley-only-if-message"] = settings.get("message")
2744 @staticmethod
2745 def wtforms(fields, settings):
2746 return OnlyIf(settings.get('fields') or fields, settings.get('message'))
2749class NotIfBuildier:
2750 # ~~->$ NotIf:FormValidator~~
2751 @staticmethod
2752 def render(settings, html_attrs):
2753 html_attrs["data-parsley-not-if"] = ",".join([f.get("field") for f in settings.get("fields", [])])
2754 if settings.get("message"):
2755 html_attrs["data-parsley-not-if-message"] = settings.get("message")
2757 @staticmethod
2758 def wtforms(fields, settings):
2759 return NotIf(settings.get('fields') or fields, settings.get('message'))
2762class GroupMemberBuilder:
2763 # ~~->$ GroupMember:FormValidator~~
2764 @staticmethod
2765 def render(settings, html_attrs):
2766 # FIXME: front end validator for this does not yet exist (do we have an existing one from formcontext?)
2767 html_attrs["data-parsley-group-member-field"] = settings.get("group_field")
2769 @staticmethod
2770 def wtforms(field, settings):
2771 return GroupMember(settings.get('group_field') or field)
2774class RequiredValueBuilder:
2775 # ~~->$ RequiredValue:FormValidator~~
2776 @staticmethod
2777 def render(settings, html_attrs):
2778 html_attrs["data-parsley-requiredvalue"] = settings.get("value")
2780 @staticmethod
2781 def wtforms(field, settings):
2782 return RequiredValue(settings.get("value"), settings.get("message"))
2785class BigEndDateBuilder:
2786 # ~~->$ BigEndDate:FormValidator~~
2787 @staticmethod
2788 def render(settings, html_attrs):
2789 html_attrs["data-parsley-pattern"] = "\d{4}-\d{2}-\d{2}"
2790 html_attrs["data-parsley-pattern-message"] = settings.get("message")
2792 @staticmethod
2793 def wtforms(field, settings):
2794 return BigEndDate(settings.get("message"))
2796class YearBuilder:
2797 @staticmethod
2798 def render(settings, html_attrs):
2799 html_attrs["data-parsley-year"] = app.config.get('MINIMAL_OA_START_DATE', 1900)
2800 html_attrs["data-parsley-year-message"] = "<p><small>" + settings["message"] + "</small></p>"
2802 def wtforms(field, settings):
2803 return Year(settings.get("message"))
2806#########################################################
2807# Crosswalks
2808#########################################################
2810PYTHON_FUNCTIONS = {
2811 "options": {
2812 "iso_country_list": iso_country_list,
2813 "iso_language_list": iso_language_list,
2814 "iso_currency_list": iso_currency_list,
2815 "quick_reject" : quick_reject,
2816 "application_statuses" : application_statuses,
2817 "editor_choices" : editor_choices
2818 },
2819 "disabled" : {
2820 "application_status_disabled" : application_status_disabled
2821 },
2822 "merge_disabled" : {
2823 "merge_disabled_notes" : merge_disabled_notes
2824 },
2825 "validate": {
2826 "render": {
2827 "required": RequiredBuilder.render,
2828 "is_url": IsURLBuilder.render,
2829 "int_range": IntRangeBuilder.render,
2830 "issn_in_public_doaj": ISSNInPublicDOAJBuilder.render,
2831 "journal_url_in_public_doaj" : JournalURLInPublicDOAJBuilder.render,
2832 "optional_if": OptionalIfBuilder.render,
2833 "is_issn": IsISSNBuilder.render,
2834 "is_issn_list": IsISSNListBuilder.render,
2835 "different_to": DifferentToBuilder.render,
2836 "required_if": RequiredIfBuilder.render,
2837 "only_if" : OnlyIfBuilder.render,
2838 "group_member" : GroupMemberBuilder.render,
2839 "not_if" : NotIfBuildier.render,
2840 "required_value" : RequiredValueBuilder.render,
2841 "bigenddate": BigEndDateBuilder.render,
2842 "no_script_tag": NoScriptTagBuilder.render,
2843 "year": YearBuilder.render
2844 },
2845 "wtforms": {
2846 "required": RequiredBuilder.wtforms,
2847 "is_url": IsURLBuilder.wtforms,
2848 "max_tags": MaxTagsBuilder.wtforms,
2849 "int_range": IntRangeBuilder.wtforms,
2850 "stop_words": StopWordsBuilder.wtforms,
2851 "issn_in_public_doaj": ISSNInPublicDOAJBuilder.wtforms,
2852 "journal_url_in_public_doaj" : JournalURLInPublicDOAJBuilder.wtforms,
2853 "optional_if": OptionalIfBuilder.wtforms,
2854 "is_issn": IsISSNBuilder.wtforms,
2855 "is_issn_list": IsISSNListBuilder.wtforms,
2856 "different_to": DifferentToBuilder.wtforms,
2857 "required_if": RequiredIfBuilder.wtforms,
2858 "only_if" : OnlyIfBuilder.wtforms,
2859 "group_member" : GroupMemberBuilder.wtforms,
2860 "not_if" : NotIfBuildier.wtforms,
2861 "required_value" : RequiredValueBuilder.wtforms,
2862 "bigenddate": BigEndDateBuilder.wtforms,
2863 "reserved_usernames" : ReservedUsernamesBuilder.wtforms,
2864 "owner_exists" : OwnerExistsBuilder.wtforms,
2865 "no_script_tag": NoScriptTagBuilder.wtforms,
2866 "year": YearBuilder.wtforms,
2867 }
2868 }
2869}
2871JAVASCRIPT_FUNCTIONS = {
2872 "clickable_url": "formulaic.widgets.newClickableUrl", # ~~-> ClickableURL:FormWidget~~
2873 "clickable_owner": "formulaic.widgets.newClickableOwner", # ~~-> ClickableOwner:FormWidget~~
2874 "select": "formulaic.widgets.newSelect", # ~~-> SelectBox:FormWidget~~
2875 "taglist": "formulaic.widgets.newTagList", # ~~-> TagList:FormWidget~~
2876 "tagentry" : "formulaic.widgets.newTagEntry", # ~~-> TagEntry:FormWidget~~
2877 "multiple_field": "formulaic.widgets.newMultipleField", # ~~-> MultiField:FormWidget~~
2878 "infinite_repeat": "formulaic.widgets.newInfiniteRepeat", # ~~-> InfiniteRepeat:FormWidget~~
2879 "autocomplete": "formulaic.widgets.newAutocomplete", # ~~-> Autocomplete:FormWidget~~
2880 "subject_tree" : "formulaic.widgets.newSubjectTree", # ~~-> SubjectTree:FormWidget~~
2881 "full_contents" : "formulaic.widgets.newFullContents", # ~~^->FullContents:FormWidget~~
2882 "load_editors" : "formulaic.widgets.newLoadEditors", # ~~-> LoadEditors:FormWidget~~
2883 "trim_whitespace" : "formulaic.widgets.newTrimWhitespace", # ~~-> TrimWhitespace:FormWidget~~
2884 "note_modal" : "formulaic.widgets.newNoteModal" # ~~-> NoteModal:FormWidget~~
2885}
2888##############################################################
2889# A couple of convenient widgets for WTForms rendering
2890##############################################################
2892class NumberWidget(widgets.Input):
2893 input_type = 'number'
2896class ListWidgetWithSubfields(object):
2897 """
2898 Renders a list of fields as a `ul` or `ol` list.
2900 This is used for fields which encapsulate many inner fields as subfields.
2901 The widget will try to iterate the field to get access to the subfields and
2902 call them to render them.
2904 If `prefix_label` is set, the subfield's label is printed before the field,
2905 otherwise afterwards. The latter is useful for iterating radios or
2906 checkboxes.
2907 """
2909 def __init__(self, html_tag='ul', prefix_label=False):
2910 assert html_tag in ('ol', 'ul')
2911 self.html_tag = html_tag
2912 self.prefix_label = prefix_label
2914 def __call__(self, field, **kwargs):
2915 # kwargs.setdefault('id', field.id)
2916 fl = kwargs.pop("formulaic", None)
2917 html = ['<%s %s>' % (self.html_tag, html_params(**kwargs))]
2918 for subfield in field:
2919 if self.prefix_label:
2920 html.append('<li tabindex=0>%s %s' % (subfield.label, subfield(**kwargs)))
2921 else:
2922 html.append('<li tabindex=0>%s %s' % (subfield(**kwargs), subfield.label))
2924 html.append("</li>")
2926 html.append('</%s>' % self.html_tag)
2927 return HTMLString(''.join(html))
2929##########################################################
2930# Mapping from configurations to WTForms builders
2931##########################################################
2933class RadioBuilder(WTFormsBuilder):
2934 @staticmethod
2935 def match(field):
2936 return field.get("input") == "radio"
2938 @staticmethod
2939 def wtform(formulaic_context, field, wtfargs):
2940 wtfargs["widget"] = ListWidgetWithSubfields()
2941 return UnconstrainedRadioField(**wtfargs)
2944class MultiCheckboxBuilder(WTFormsBuilder):
2945 @staticmethod
2946 def match(field):
2947 return field.get("input") == "checkbox" and \
2948 (len(field.get("options", [])) > 0 or field.get("options_fn") is not None)
2950 @staticmethod
2951 def wtform(formulaic_context, field, wtfargs):
2952 wtfargs["option_widget"] = widgets.CheckboxInput()
2953 wtfargs["widget"] = ListWidgetWithSubfields()
2954 return SelectMultipleField(**wtfargs)
2957class SingleCheckboxBuilder(WTFormsBuilder):
2958 @staticmethod
2959 def match(field):
2960 return field.get("input") == "checkbox" and len(field.get("options", [])) == 0 and field.get(
2961 "options_fn") is None
2963 @staticmethod
2964 def wtform(formulaic_context, field, wtfargs):
2965 return BooleanField(**wtfargs)
2968class SelectBuilder(WTFormsBuilder):
2969 @staticmethod
2970 def match(field):
2971 return field.get("input") == "select" and not field.get("multiple", False)
2973 @staticmethod
2974 def wtform(formulaic_context, field, wtfargs):
2975 sf = SelectField(**wtfargs)
2976 if "repeatable" in field:
2977 sf = FieldList(sf, min_entries=field.get("repeatable", {}).get("initial", 1))
2979 return sf
2982class MultiSelectBuilder(WTFormsBuilder):
2983 @staticmethod
2984 def match(field):
2985 return field.get("input") == "select" and field.get("multiple", False)
2987 @staticmethod
2988 def wtform(formulaic_context, field, wtfargs):
2989 return SelectMultipleField(**wtfargs)
2992class TextBuilder(WTFormsBuilder):
2993 @staticmethod
2994 def match(field):
2995 return field.get("input") == "text"
2997 @staticmethod
2998 def wtform(formulaic_context, field, wtfargs):
2999 if "filters" not in wtfargs:
3000 wtfargs["filters"] = (lambda x: x.strip() if x is not None else x,)
3001 sf = StringField(**wtfargs)
3002 if "repeatable" in field:
3003 sf = FieldList(sf, min_entries=field.get("repeatable", {}).get("initial", 1))
3004 return sf
3007class TextAreaBuilder(WTFormsBuilder):
3008 @staticmethod
3009 def match(field):
3010 return field.get("input") == "textarea"
3012 @staticmethod
3013 def wtform(formulaic_context, field, wtfargs):
3014 sf = TextAreaField(**wtfargs)
3015 if "repeatable" in field:
3016 sf = FieldList(sf, min_entries=field.get("repeatable", {}).get("initial", 1))
3017 return sf
3020class TagListBuilder(WTFormsBuilder):
3021 @staticmethod
3022 def match(field):
3023 return field.get("input") == "taglist"
3025 @staticmethod
3026 def wtform(formulaic_context, field, wtfargs):
3027 return TagListField(**wtfargs)
3030class IntegerBuilder(WTFormsBuilder):
3031 @staticmethod
3032 def match(field):
3033 return field.get("input") == "number" and field.get("datatype") == "integer"
3035 @staticmethod
3036 def wtform(formulaic_context, field, wtfargs):
3037 wtfargs["widget"] = NumberWidget()
3038 return IntegerField(**wtfargs)
3041# TODO: multiple group doesn't work
3042class GroupBuilder(WTFormsBuilder):
3043 @staticmethod
3044 def match(field):
3045 return field.get("input") == "group" and field.get("repeatable") is None
3047 @staticmethod
3048 def wtform(formulaic_context, field, wtfargs):
3049 fields = [formulaic_context.get(subfield) for subfield in field.get("subfields", [])]
3050 klazz = formulaic_context.make_wtform_class(fields)
3051 return NestedFormField(klazz)
3054class GroupListBuilder(WTFormsBuilder):
3055 @staticmethod
3056 def match(field):
3057 return field.get("input") == "group" and field.get("repeatable") is not None
3059 @staticmethod
3060 def wtform(formulaic_context, field, wtfargs):
3061 ff = GroupBuilder.wtform(formulaic_context, field, wtfargs)
3062 repeat_cfg = field.get("repeatable", {})
3063 return FieldList(ff, min_entries=repeat_cfg.get("initial", 1))
3066class HiddenFieldBuilder(WTFormsBuilder):
3067 @staticmethod
3068 def match(field):
3069 return field.get("input") == "hidden"
3071 @staticmethod
3072 def wtform(formulaic_context, field, wtfargs):
3073 return HiddenField(**wtfargs)
3077WTFORMS_BUILDERS = [
3078 RadioBuilder,
3079 MultiCheckboxBuilder,
3080 SingleCheckboxBuilder,
3081 SelectBuilder,
3082 MultiSelectBuilder,
3083 TextBuilder,
3084 TextAreaBuilder,
3085 TagListBuilder,
3086 IntegerBuilder,
3087 GroupBuilder,
3088 GroupListBuilder,
3089 HiddenFieldBuilder
3090]
3093ApplicationFormFactory = Formulaic(APPLICATION_FORMS, WTFORMS_BUILDERS, function_map=PYTHON_FUNCTIONS, javascript_functions=JAVASCRIPT_FUNCTIONS)
3094JournalFormFactory = Formulaic(JOURNAL_FORMS, WTFORMS_BUILDERS, function_map=PYTHON_FUNCTIONS, javascript_functions=JAVASCRIPT_FUNCTIONS)
3097if __name__ == "__main__":
3098 """
3099 Running this file from the command line enables you to output documentation for a given form context.
3101 See `docs/forms.sh` for where this is used
3103 To create the documentation you can call this file with 3 arguments:
3105 -t - the object type to output. Either 'journal' or 'application'
3106 -c - the form context. Will be one of the contexts defined elsewhere in this file, which may be specific to the
3107 object type. For example, 'admin' or 'editor'
3108 -o - the path to the file where to output the result
3110 The output is a CSV which lists the following information:
3112 * Form Position - the position in the form. Felds are listed in order
3113 * Field Name - the form field name
3114 * Label - the form field label
3115 * Input Type - what kind of input (e.g. radio, text)
3116 * Options - the express options allowed, or the name of the function which generates the options
3117 * Disabled? - is the field disabled in this context
3118 * Fieldset ID - the ID (from this file) of the fieldset that this field is part of
3119 """
3120 import argparse
3122 parser = argparse.ArgumentParser()
3123 parser.add_argument("-t", "--type", help="object type to output")
3124 parser.add_argument("-c", "--context", help="context to output")
3125 parser.add_argument("-o", "--out", help="output file path")
3126 args = parser.parse_args()
3128 if not args.out:
3129 print("Please specify an output file path with the -o option")
3130 parser.print_help()
3131 exit()
3133 if not args.context:
3134 print("Please specify a context to output")
3135 parser.print_help()
3136 exit()
3138 if not args.type:
3139 print("Please specify a type to output")
3140 parser.print_help()
3141 exit()
3143 fc = None
3144 if args.type == "journal":
3145 fc = JournalFormFactory.context(args.context)
3146 elif args.type == "application":
3147 fc = ApplicationFormFactory.context(args.context)
3149 if fc is not None:
3150 fc.to_summary_csv(args.out)
3151 else:
3152 print("You did not enter a valid type. Use one of 'journal' or 'application'")