From d5691a2a122993fbde26bf7c555a6645d9a145e5 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Mon, 17 Feb 2020 14:51:26 +0000 Subject: [PATCH 01/32] Required or 1.3.8 plugins. --- src/core/janeway_global_settings.py | 1 + src/core/models.py | 3 ++ .../migrations/0041_auto_20200203_1547.py | 20 ++++++++ src/utils/install.py | 6 ++- .../management/commands/install_plugins.py | 21 +++++++- src/utils/models.py | 4 +- src/utils/plugins.py | 49 +++++++++++++++++++ 7 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 src/submission/migrations/0041_auto_20200203_1547.py create mode 100644 src/utils/plugins.py diff --git a/src/core/janeway_global_settings.py b/src/core/janeway_global_settings.py index 646d20073a..a3be3d8d82 100755 --- a/src/core/janeway_global_settings.py +++ b/src/core/janeway_global_settings.py @@ -170,6 +170,7 @@ 'ENABLE_ENHANCED_MAILGUN_FEATURES', 'ENABLE_ORCID', 'DEBUG', + 'LANGUAGE_CODE', ] WSGI_APPLICATION = 'core.wsgi.application' diff --git a/src/core/models.py b/src/core/models.py index f75c2e1aae..5fcd35a784 100755 --- a/src/core/models.py +++ b/src/core/models.py @@ -846,6 +846,9 @@ def render(self): return files.render_xml(self.file, self.article, xsl_file=self.xsl_file) def has_missing_image_files(self, show_all=False): + if not self.file.mime_type in files.MIMETYPES_WITH_FIGURES: + return [] + xml_file_contents = self.file.get_file(self.article) souped_xml = BeautifulSoup(xml_file_contents, 'lxml') diff --git a/src/submission/migrations/0041_auto_20200203_1547.py b/src/submission/migrations/0041_auto_20200203_1547.py new file mode 100644 index 0000000000..4703e3ac94 --- /dev/null +++ b/src/submission/migrations/0041_auto_20200203_1547.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-03 15:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submission', '0040_article_projected_issue'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='stage', + field=models.CharField(choices=[('Unsubmitted', 'Unsubmitted'), ('Unassigned', 'Unassigned'), ('Assigned', 'Assigned to Editor'), ('Under Review', 'Peer Review'), ('Under Revision', 'Revision'), ('Rejected', 'Rejected'), ('Accepted', 'Accepted'), ('Editor Copyediting', 'Editor Copyediting'), ('Author Copyediting', 'Author Copyediting'), ('Final Copyediting', 'Final Copyediting'), ('Typesetting', 'Typesetting'), ('Proofing', 'Proofing'), ('pre_publication', 'Pre Publication'), ('Published', 'Published'), ('preprint_review', 'Preprint Review'), ('preprint_published', 'Preprint Published'), ('Back Content', 'Back Content Plugin'), ('typesetting_plugin', 'Typesetting Plugin')], default='Unsubmitted', max_length=200), + ), + ] diff --git a/src/utils/install.py b/src/utils/install.py index af3a9cd6cf..d75e18b9f4 100755 --- a/src/utils/install.py +++ b/src/utils/install.py @@ -15,7 +15,9 @@ from submission import models as submission_models -def update_settings(journal_object=None, management_command=False, overwrite_with_defaults=False): +def update_settings(journal_object=None, management_command=False, + overwrite_with_defaults=False, + file_path='utils/install/journal_defaults.json'): """ Updates or creates the settings for a journal from journal_defaults.json. :param journal_object: the journal object to update or None to set the @@ -23,7 +25,7 @@ def update_settings(journal_object=None, management_command=False, overwrite_wit :param management_command: whether or not to print output to the console :return: None """ - with codecs.open(os.path.join(settings.BASE_DIR, 'utils/install/journal_defaults.json'), encoding='utf-8') as json_data: + with codecs.open(os.path.join(settings.BASE_DIR, file_path), encoding='utf-8') as json_data: default_data = json.load(json_data) diff --git a/src/utils/management/commands/install_plugins.py b/src/utils/management/commands/install_plugins.py index 34c408c4b0..7a8725e987 100755 --- a/src/utils/management/commands/install_plugins.py +++ b/src/utils/management/commands/install_plugins.py @@ -12,6 +12,14 @@ class Command(BaseCommand): help = "Checks for new plugins and installs them." + def add_arguments(self, parser): + """ Adds arguments to Django's management command-line parser. + + :param parser: the parser to which the required arguments will be added + :return: None + """ + parser.add_argument('plugin_name', nargs='?', default=None) + def handle(self, *args, **options): """ Checks for new plugins and installs them. @@ -19,8 +27,17 @@ def handle(self, *args, **options): :param options: None :return: None """ - plugin_dirs = plugin_loader.get_dirs('plugins') - homepage_dirs = plugin_loader.get_dirs(os.path.join('core', 'homepage_elements')) + + plugin_name = options.get('plugin_name', None) + + if plugin_name: + plugin_dirs = [plugin_name] + homepage_dirs = [] + else: + plugin_dirs = plugin_loader.get_dirs('plugins') + homepage_dirs = plugin_loader.get_dirs( + os.path.join('core', 'homepage_elements'), + ) for plugin in plugin_dirs: print('Checking plugin {0}'.format(plugin)) diff --git a/src/utils/models.py b/src/utils/models.py index a740e74876..52b4f96bbb 100755 --- a/src/utils/models.py +++ b/src/utils/models.py @@ -138,9 +138,9 @@ def __repr__(self): def best_name(self): if self.display_name: - return self.display_name + return self.display_name.lower() - return self.name + return self.name.lower() setting_types = ( diff --git a/src/utils/plugins.py b/src/utils/plugins.py new file mode 100644 index 0000000000..a35676bd2e --- /dev/null +++ b/src/utils/plugins.py @@ -0,0 +1,49 @@ +from utils import models + + +class Plugin: + plugin_name = None + display_name = None + description = None + author = None + short_name = None + stage = None + + manager_url = None + + version = None + janeway_version = None + + is_workflow_plugin = False + handshake_url = None + article_pk_in_handshake_url = False + press_wide = False + + kanban_card = '{plugin_name}/kanban_card.html'.format( + plugin_name=plugin_name, + ) + + @classmethod + def install(cls): + plugin, created = cls.get_or_create_plugin_object() + + if not created and plugin.version != cls.version: + plugin.version = cls.version + plugin.save() + + return plugin, created + + @classmethod + def hook_registry(cls): + pass + + @classmethod + def get_or_create_plugin_object(cls): + plugin, created = models.Plugin.objects.get_or_create( + name=cls.short_name, + display_name=cls.display_name, + press_wide=cls.press_wide, + defaults={'version': cls.version, 'enabled': True}, + ) + + return plugin, created From 54354d51ed7c1046d16768f3aa5ab28eef59b391 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Mon, 9 Mar 2020 20:48:58 +0000 Subject: [PATCH 02/32] Added migration. --- .../migrations/0042_auto_20200227_1051.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/submission/migrations/0042_auto_20200227_1051.py diff --git a/src/submission/migrations/0042_auto_20200227_1051.py b/src/submission/migrations/0042_auto_20200227_1051.py new file mode 100644 index 0000000000..dc0dc0ff35 --- /dev/null +++ b/src/submission/migrations/0042_auto_20200227_1051.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-02-27 10:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submission', '0040_article_projected_issue'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='stage', + field=models.CharField(choices=[('Unsubmitted', 'Unsubmitted'), ('Unassigned', 'Unassigned'), ('Assigned', 'Assigned to Editor'), ('Under Review', 'Peer Review'), ('Under Revision', 'Revision'), ('Rejected', 'Rejected'), ('Accepted', 'Accepted'), ('Editor Copyediting', 'Editor Copyediting'), ('Author Copyediting', 'Author Copyediting'), ('Final Copyediting', 'Final Copyediting'), ('Typesetting', 'Typesetting'), ('Proofing', 'Proofing'), ('pre_publication', 'Pre Publication'), ('Published', 'Published'), ('preprint_review', 'Preprint Review'), ('preprint_published', 'Preprint Published'), ('typesetting_plugin', 'Typesetting Plugin')], default='Unsubmitted', max_length=200), + ), + ] From 2e61a685353d6a1669ee3555d39d9c19221d45d4 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Sat, 14 Mar 2020 13:36:40 +0000 Subject: [PATCH 03/32] Merged migrations --- src/security/decorators.py | 1 - .../migrations/0043_merge_20200314_1336.py | 16 ++++++++++++++++ .../admin/proofing/proofing_article.html | 2 +- .../OLH/templates/preprints/submit_start.html | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/submission/migrations/0043_merge_20200314_1336.py diff --git a/src/security/decorators.py b/src/security/decorators.py index 6098e746ed..6e7681d132 100755 --- a/src/security/decorators.py +++ b/src/security/decorators.py @@ -930,7 +930,6 @@ def wrapper(request, *args, **kwargs): else: deny_access(request) - return wrapper diff --git a/src/submission/migrations/0043_merge_20200314_1336.py b/src/submission/migrations/0043_merge_20200314_1336.py new file mode 100644 index 0000000000..ec9a217016 --- /dev/null +++ b/src/submission/migrations/0043_merge_20200314_1336.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-03-14 13:36 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submission', '0041_auto_20200203_1547'), + ('submission', '0042_auto_20200227_1051'), + ] + + operations = [ + ] diff --git a/src/templates/admin/proofing/proofing_article.html b/src/templates/admin/proofing/proofing_article.html index dc9bbb69cc..56132de12c 100644 --- a/src/templates/admin/proofing/proofing_article.html +++ b/src/templates/admin/proofing/proofing_article.html @@ -86,7 +86,7 @@

Proofreading Tasks

Awaiting
proofreader {% endif %} {% if request.user.is_admin %} -  Edit in Admin +  Edit in Admin {% endif %} diff --git a/src/themes/OLH/templates/preprints/submit_start.html b/src/themes/OLH/templates/preprints/submit_start.html index 32142797cb..778ee222c6 100644 --- a/src/themes/OLH/templates/preprints/submit_start.html +++ b/src/themes/OLH/templates/preprints/submit_start.html @@ -1,4 +1,4 @@ -{% extends "/core/base.html" %} +{% extends "core/base.html" %} {% load static from staticfiles %} {% load i18n %} {% load foundation %} From 5f64f4e9e422510c2f4a6fbb72789e4efa6cf877 Mon Sep 17 00:00:00 2001 From: Mauro MSL Date: Wed, 18 Mar 2020 09:40:06 +0000 Subject: [PATCH 04/32] Add support for args and kwargs on hooks --- src/core/templatetags/hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/templatetags/hooks.py b/src/core/templatetags/hooks.py index 120886d267..0c8d2c33b9 100755 --- a/src/core/templatetags/hooks.py +++ b/src/core/templatetags/hooks.py @@ -8,13 +8,13 @@ @register.simple_tag(takes_context=True) -def hook(context, hook_name): +def hook(context, hook_name, *args, **kwargs): try: html = '' for hook in settings.PLUGIN_HOOKS.get(hook_name, []): hook_module = import_module(hook.get('module')) function = getattr(hook_module, hook.get('function')) - html = html + function(context) + html = html + function(context, *args, **kwargs) return mark_safe(html) except BaseException as e: From c277869bc9d0e4d0dbc108ea842ecf59cbb11d4c Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Mon, 30 Mar 2020 13:33:19 +0100 Subject: [PATCH 05/32] Initial commit on hooks for plugins --- src/copyediting/views.py | 2 +- .../migrations/0034_update_handshake_urls.py | 37 +++++++++++++++++++ src/core/models.py | 6 +-- src/events/logic.py | 6 +++ src/journal/views.py | 2 +- src/review/views.py | 2 +- src/submission/models.py | 7 +++- src/templates/admin/core/nav.html | 22 +++++------ 8 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 src/core/migrations/0034_update_handshake_urls.py diff --git a/src/copyediting/views.py b/src/copyediting/views.py index 0991463c69..84b14038e2 100755 --- a/src/copyediting/views.py +++ b/src/copyediting/views.py @@ -70,7 +70,7 @@ def article_copyediting(request, article_id): if request.POST and 'complete' in request.POST: event_logic.Events.raise_event(event_logic.Events.ON_COPYEDIT_COMPLETE, task_object=article, **message_kwargs) - workflow_kwargs = {'handshake_url': 'article_copyediting', 'request': request, 'article': article, + workflow_kwargs = {'handshake_url': 'copyediting', 'request': request, 'article': article, 'switch_stage': True} return event_logic.Events.raise_event(event_logic.Events.ON_WORKFLOW_ELEMENT_COMPLETE, task_object=article, **workflow_kwargs) diff --git a/src/core/migrations/0034_update_handshake_urls.py b/src/core/migrations/0034_update_handshake_urls.py new file mode 100644 index 0000000000..2c93c4dee0 --- /dev/null +++ b/src/core/migrations/0034_update_handshake_urls.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-03-30 10:18 +from __future__ import unicode_literals + +from django.db import migrations + + +def update_handshake_urls(apps, schema_editor): + WorkflowElement = apps.get_model("core", "WorkflowElement") + + updates = [ + {'from': 'review_unassigned_article', 'to': 'review_home'}, + {'from': 'publish_article', 'to': 'publish'}, + {'from': 'article_copyediting', 'to': 'copyediting'}, + ] + + for update in updates: + elements_to_update = WorkflowElement.objects.filter( + handshake_url=update.get('from') + ) + for element_to_update in elements_to_update: + element_to_update.handshake_url = update.get('to') + element_to_update.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0033_set_default_xml_galley_xsl'), + ] + + operations = [ + migrations.RunPython( + update_handshake_urls, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 5fcd35a784..5434885b66 100755 --- a/src/core/models.py +++ b/src/core/models.py @@ -1098,12 +1098,12 @@ def save(self, *args, **kwargs): BASE_ELEMENTS = [ {'name': 'review', - 'handshake_url': 'review_unassigned_article', + 'handshake_url': 'review_home', 'jump_url': 'review_in_review', 'stage': submission_models.STAGE_UNASSIGNED, 'article_url': True}, {'name': 'copyediting', - 'handshake_url': 'article_copyediting', + 'handshake_url': 'copyediting', 'jump_url': 'article_copyediting', 'stage': submission_models.STAGE_EDITOR_COPYEDITING, 'article_url': True}, @@ -1118,7 +1118,7 @@ def save(self, *args, **kwargs): 'stage': submission_models.STAGE_PROOFING, 'article_url': False}, {'name': 'prepublication', - 'handshake_url': 'publish_article', + 'handshake_url': 'publish', 'jump_url': 'publish_article', 'stage': submission_models.STAGE_READY_FOR_PUBLICATION, 'article_url': True} diff --git a/src/events/logic.py b/src/events/logic.py index fb759cef74..026bd54faf 100755 --- a/src/events/logic.py +++ b/src/events/logic.py @@ -242,6 +242,12 @@ def raise_event(event_name, task_object=None, **kwargs): Events.raise_event('destroy_tasks', **kwargs) # fire hooked functions + + print('\n\nFiring event: {}\n\n'.format(event_name)) + + import time + time.sleep(2) + if event_name not in Events._hooks: return else: diff --git a/src/journal/views.py b/src/journal/views.py index edde859dee..9282e86d2f 100755 --- a/src/journal/views.py +++ b/src/journal/views.py @@ -883,7 +883,7 @@ def publish_article(request, article_id): messages.add_message(request, messages.SUCCESS, 'Article set for publication.') if request.journal.element_in_workflow(element_name='prepublication'): - workflow_kwargs = {'handshake_url': 'publish_article', + workflow_kwargs = {'handshake_url': 'publish', 'request': request, 'article': article, 'switch_stage': True} diff --git a/src/review/views.py b/src/review/views.py index 06a9bf9351..84ba1811be 100755 --- a/src/review/views.py +++ b/src/review/views.py @@ -1419,7 +1419,7 @@ def review_decision(request, article_id, decision): article.snapshot_authors(article) event_logic.Events.raise_event(event_logic.Events.ON_ARTICLE_ACCEPTED, task_object=article, **kwargs) - workflow_kwargs = {'handshake_url': 'review_unassigned_article', + workflow_kwargs = {'handshake_url': 'review_home', 'request': request, 'article': article, 'switch_stage': True} diff --git a/src/submission/models.py b/src/submission/models.py index 347aa859a7..6ad450c706 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -480,8 +480,11 @@ def get_render_galley(self): if self.render_galley: return self.render_galley - ret = self.galley_set.filter(file__mime_type="application/xml").order_by( - "sequence") + ret = self.galley_set.filter( + file__mime_type__in=files.XML_MIMETYPES + ).order_by( + "sequence", + ) if len(ret) > 0: return ret[0] diff --git a/src/templates/admin/core/nav.html b/src/templates/admin/core/nav.html index aa0fa3aefc..678c61993b 100644 --- a/src/templates/admin/core/nav.html +++ b/src/templates/admin/core/nav.html @@ -20,18 +20,16 @@
  • Search Submissions
  • Workflow
  • -
  •   Unassigned -
  • -
  •   Review -
  • -
  •   Editing -
  • -
  •   - Production
  • -
  •   - Proofing
  • -
  •   - Pre Publication
  • +
  • +   Unassigned +
  • + + {% for element in request.journal.workflow.elements.all %} +
  • +   {{ element|capfirst }} +
  • + {% endfor %} +
  • Back Content
  •   Issues
  • From 20cb25954905f48c3dd11474c5e8ec7b0fff7abd Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Wed, 1 Apr 2020 15:21:33 +0100 Subject: [PATCH 06/32] Updated the dashboard and editor block. --- src/templates/admin/core/dashboard.html | 67 ++----------------- .../admin/elements/core/editor_dashboard.html | 44 ++++++++++++ 2 files changed, 50 insertions(+), 61 deletions(-) create mode 100644 src/templates/admin/elements/core/editor_dashboard.html diff --git a/src/templates/admin/core/dashboard.html b/src/templates/admin/core/dashboard.html index bd81726493..72f7dd25c6 100644 --- a/src/templates/admin/core/dashboard.html +++ b/src/templates/admin/core/dashboard.html @@ -9,65 +9,8 @@ {% block body %}
    - {% if is_editor %} -
    -
    -
    -

    Editor

    -
    -
    -
    -
    -
    - {{ unassigned_articles_count }} - Unassigned - -
    -
    - {{ assigned_articles_count }} - Review - -
    -
    -
    -
    -
    - {{ editing_articles_count }} - Copyediting - -
    -
    -
    - {{ production_articles_count }} - Production - -
    - -
    -
    -
    -
    -
    - {{ proofing_articles_count }} - Proofing - -
    -
    - {{ prepub_articles_count }} - Pre Publication - -
    -
    -
    - -
    -
    -
    - {% endif %} - + {% include "elements/core/editor_dashboard.html" %} +
    {% if is_reviewer %}
    @@ -143,7 +86,7 @@

    Production

    {% endif %} {% user_has_role request 'production' as production %} - {% if production %} + {% if production and 'production' in workflow_elements %}
    @@ -160,7 +103,8 @@

    Production Manager

    {% endif %} {% user_has_role request 'proofreader' as proofreader %} - {% if proofreader or is_author and 'proofing' in workflow_elements %} + {% if 'proofing' in workflow_elements %} + {% if proofreader or is_author %}
    @@ -184,6 +128,7 @@

    Proofreader

    {% endif %} + {% endif %} {% if typesetter and 'proofing' in workflow_elements %}
    diff --git a/src/templates/admin/elements/core/editor_dashboard.html b/src/templates/admin/elements/core/editor_dashboard.html new file mode 100644 index 0000000000..07ba7d8a14 --- /dev/null +++ b/src/templates/admin/elements/core/editor_dashboard.html @@ -0,0 +1,44 @@ +{% if is_editor %} +
    +
    +
    +

    Editor

    +
    + +
    +
    +
    +
    + {{ unassigned_articles_count }} + Unassigned + +
    + {% for element in request.journal.workflow.elements.all %} +
    + {{ element.articles_in_stage }} + {{ element|capfirst }} + +
    + {% if forloop.counter == 1 or forloop.counter|divisibleby:3 and not forloop.last %} +
    +
    +
    +
    + {% elif forloop.last %} +
    +
    + {% endif %} + {% endfor %} +
    + + +
    + + +{% endif %} \ No newline at end of file From 3f46e2f2549cdc3c6e8f74b9c68fe4aad64a8724 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Thu, 2 Apr 2020 10:52:20 +0100 Subject: [PATCH 07/32] More work towards 0.1. --- src/core/models.py | 19 ++++++++++++++++++- src/events/logic.py | 3 --- src/submission/models.py | 2 +- .../admin/elements/core/editor_dashboard.html | 2 +- src/workflow/views.py | 3 --- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/core/models.py b/src/core/models.py index 5434885b66..43f4c80ca9 100755 --- a/src/core/models.py +++ b/src/core/models.py @@ -1135,13 +1135,30 @@ class WorkflowElement(models.Model): element_name = models.CharField(max_length=255) handshake_url = models.CharField(max_length=255) jump_url = models.CharField(max_length=255) - stage = models.CharField(max_length=255, default=submission_models.STAGE_UNASSIGNED) + stage = models.CharField( + max_length=255, + default=submission_models.STAGE_UNASSIGNED, + ) article_url = models.BooleanField(default=True) order = models.PositiveIntegerField(default=20) class Meta: ordering = ('order', 'element_name') + @property + def stages(self): + from core import workflow + try: + return workflow.ELEMENT_STAGES[self.element_name] + except KeyError: + return [self.stage] + + @property + def articles(self): + return submission_models.Article.objects.filter( + stage__in=self.stages, + ) + def __str__(self): return self.element_name diff --git a/src/events/logic.py b/src/events/logic.py index 026bd54faf..83c9a098e4 100755 --- a/src/events/logic.py +++ b/src/events/logic.py @@ -245,9 +245,6 @@ def raise_event(event_name, task_object=None, **kwargs): print('\n\nFiring event: {}\n\n'.format(event_name)) - import time - time.sleep(2) - if event_name not in Events._hooks: return else: diff --git a/src/submission/models.py b/src/submission/models.py index 6ad450c706..96aacf803c 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -481,7 +481,7 @@ def get_render_galley(self): return self.render_galley ret = self.galley_set.filter( - file__mime_type__in=files.XML_MIMETYPES + file__mime_type="application/xml" ).order_by( "sequence", ) diff --git a/src/templates/admin/elements/core/editor_dashboard.html b/src/templates/admin/elements/core/editor_dashboard.html index 07ba7d8a14..31d99ff483 100644 --- a/src/templates/admin/elements/core/editor_dashboard.html +++ b/src/templates/admin/elements/core/editor_dashboard.html @@ -34,7 +34,7 @@

    Editor

    diff --git a/src/workflow/views.py b/src/workflow/views.py index 951f345184..9f54362150 100644 --- a/src/workflow/views.py +++ b/src/workflow/views.py @@ -52,6 +52,3 @@ def manage_article_workflow(request, article_id): } return render(request, template, context) - - - From 1f93f311705ea9ec1f5207ef1f06d2d43a1ee08e Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Thu, 2 Apr 2020 17:11:21 +0100 Subject: [PATCH 08/32] Updates to complete some work in previous two commits. --- src/core/models.py | 3 +++ src/submission/models.py | 11 +++++++++++ .../admin/elements/core/editor_dashboard.html | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/core/models.py b/src/core/models.py index 43f4c80ca9..4ec050d2f0 100755 --- a/src/core/models.py +++ b/src/core/models.py @@ -1155,8 +1155,11 @@ def stages(self): @property def articles(self): + from core.middleware import GlobalRequestMiddleware + request = GlobalRequestMiddleware.get_current_request() return submission_models.Article.objects.filter( stage__in=self.stages, + journal=request.journal, ) def __str__(self): diff --git a/src/submission/models.py b/src/submission/models.py index 96aacf803c..7b1dfcb258 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -952,6 +952,8 @@ def current_stage_url(self): return reverse('proofing_article', kwargs=kwargs) elif self.stage == STAGE_READY_FOR_PUBLICATION: return reverse('publish_article', kwargs=kwargs) + else: + return reverse(self.current_stage.jump_url, kwargs=kwargs) @property def custom_fields(self): @@ -1025,6 +1027,15 @@ def workflow_stages(self): from core import models as core_models return core_models.WorkflowLog.objects.filter(article=self) + @property + def current_stage(self): + from core import models as core_models + logs = core_models.WorkflowLog.objects.filter( + article=self, + ) + element = logs.reverse().first().element + return element + @cache(600) def render_sample_doi(self): return id_logic.render_doi_from_pattern(self) diff --git a/src/templates/admin/elements/core/editor_dashboard.html b/src/templates/admin/elements/core/editor_dashboard.html index 31d99ff483..b9e9274e3f 100644 --- a/src/templates/admin/elements/core/editor_dashboard.html +++ b/src/templates/admin/elements/core/editor_dashboard.html @@ -15,7 +15,7 @@

    Editor

    {% for element in request.journal.workflow.elements.all %}
    - {{ element.articles_in_stage }} + {{ element.articles.count }} {{ element|capfirst }}
    From a926990407f13abb3119f2c3551ac92fa46995b2 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Fri, 3 Apr 2020 22:38:37 +0100 Subject: [PATCH 09/32] Adds jump_url for plugin wf elements. --- src/core/logic.py | 22 +++++++++++++++------- src/utils/plugins.py | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/core/logic.py b/src/core/logic.py index 237654911a..33335b05a0 100755 --- a/src/core/logic.py +++ b/src/core/logic.py @@ -438,11 +438,16 @@ def get_available_elements(workflow): module_name = "{0}.plugin_settings".format(plugin) plugin_settings = import_module(module_name) - if hasattr(plugin_settings, 'IS_WORKFLOW_PLUGIN') and hasattr(plugin_settings, 'HANDSHAKE_URL'): + if hasattr(plugin_settings, 'IS_WORKFLOW_PLUGIN') and hasattr( + plugin_settings, 'HANDSHAKE_URL'): if plugin_settings.IS_WORKFLOW_PLUGIN: our_elements.append( - {'name': plugin_settings.PLUGIN_NAME, 'handshake_url': plugin_settings.HANDSHAKE_URL, - 'stage': plugin_settings.STAGE, 'article_url': plugin_settings.ARTICLE_PK_IN_HANDSHAKE_URL} + {'name': plugin_settings.PLUGIN_NAME, + 'handshake_url': plugin_settings.HANDSHAKE_URL, + 'stage': plugin_settings.STAGE, + 'article_url': plugin_settings.ARTICLE_PK_IN_HANDSHAKE_URL, + 'jump_url': plugin_settings.JUMP_URL if hasattr(plugin_settings, 'JUMP_URL') else '', + } ) return clear_active_elements(our_elements, workflow, plugins) @@ -450,10 +455,13 @@ def get_available_elements(workflow): def handle_element_post(workflow, element_name, request): for element in get_available_elements(workflow): if element['name'] == element_name: - element_obj, created = models.WorkflowElement.objects.get_or_create(journal=request.journal, - element_name=element_name, - handshake_url=element['handshake_url'], - stage=element['stage']) + element_obj, created = models.WorkflowElement.objects.get_or_create( + journal=request.journal, + element_name=element_name, + handshake_url=element['handshake_url'], + stage=element['stage'], + jump_url=element.get('jump_url', ''), + ) return element_obj diff --git a/src/utils/plugins.py b/src/utils/plugins.py index a35676bd2e..ead68502c2 100644 --- a/src/utils/plugins.py +++ b/src/utils/plugins.py @@ -15,6 +15,7 @@ class Plugin: janeway_version = None is_workflow_plugin = False + jump_url = None handshake_url = None article_pk_in_handshake_url = False press_wide = False From b32a45c214b75c35eba8a4105939fcb9f0dc2658 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Mon, 6 Apr 2020 09:32:07 +0100 Subject: [PATCH 10/32] PEP8-ify copyedit.views.article_copyediting --- src/copyediting/views.py | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/copyediting/views.py b/src/copyediting/views.py index 84b14038e2..2200cba426 100755 --- a/src/copyediting/views.py +++ b/src/copyediting/views.py @@ -45,14 +45,16 @@ def copyediting(request): @editor_user_required def article_copyediting(request, article_id): """ - View allows Editor to view and assign copyeditors and author reviews of articles. + View allows Editor to view and assign copyeditors and author reviews. :param request: django request object :param article_id: PK of an Article :return: a contextualised template """ article = get_object_or_404(submission_models.Article, pk=article_id) - copyeditor_assignments = models.CopyeditAssignment.objects.filter(article=article) + copyeditor_assignments = models.CopyeditAssignment.objects.filter( + article=article, + ) message_kwargs = {'request': request, 'article': article} @@ -63,17 +65,37 @@ def article_copyediting(request, article_id): pk=assignment_id, decision__isnull=True) message_kwargs['copyedit_assignment'] = assignment - messages.add_message(request, messages.SUCCESS, 'Assignment #{0} delete'.format(assignment_id)) - event_logic.Events.raise_event(event_logic.Events.ON_COPYEDIT_DELETED, **message_kwargs) + messages.add_message( + request, + messages.SUCCESS, + 'Assignment #{0} delete'.format(assignment_id), + ) + event_logic.Events.raise_event( + event_logic.Events.ON_COPYEDIT_DELETED, + **message_kwargs, + ) assignment.delete() - return redirect(reverse('article_copyediting', kwargs={'article_id': article.pk})) + return redirect( + reverse('article_copyediting', kwargs={'article_id': article.pk}) + ) if request.POST and 'complete' in request.POST: - event_logic.Events.raise_event(event_logic.Events.ON_COPYEDIT_COMPLETE, task_object=article, **message_kwargs) - workflow_kwargs = {'handshake_url': 'copyediting', 'request': request, 'article': article, - 'switch_stage': True} - return event_logic.Events.raise_event(event_logic.Events.ON_WORKFLOW_ELEMENT_COMPLETE, task_object=article, - **workflow_kwargs) + event_logic.Events.raise_event( + event_logic.Events.ON_COPYEDIT_COMPLETE, + task_object=article, + **message_kwargs, + ) + workflow_kwargs = { + 'handshake_url': 'copyediting', + 'request': request, + 'article': article, + 'switch_stage': True, + } + return event_logic.Events.raise_event( + event_logic.Events.ON_WORKFLOW_ELEMENT_COMPLETE, + task_object=article, + **workflow_kwargs, + ) template = 'copyediting/article_copyediting.html' context = { From bb09f4ce4422df67f4823a93988781caa7087b82 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Mon, 6 Apr 2020 09:40:01 +0100 Subject: [PATCH 11/32] Removed debug statement --- src/events/logic.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/events/logic.py b/src/events/logic.py index 83c9a098e4..fb759cef74 100755 --- a/src/events/logic.py +++ b/src/events/logic.py @@ -242,9 +242,6 @@ def raise_event(event_name, task_object=None, **kwargs): Events.raise_event('destroy_tasks', **kwargs) # fire hooked functions - - print('\n\nFiring event: {}\n\n'.format(event_name)) - if event_name not in Events._hooks: return else: From 8ba6d59d7efa412069aa662ec32aa7655e0cf630 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Mon, 6 Apr 2020 09:43:24 +0100 Subject: [PATCH 12/32] Reverts fix that is in another PR. --- src/themes/OLH/templates/preprints/submit_start.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/OLH/templates/preprints/submit_start.html b/src/themes/OLH/templates/preprints/submit_start.html index 778ee222c6..32142797cb 100644 --- a/src/themes/OLH/templates/preprints/submit_start.html +++ b/src/themes/OLH/templates/preprints/submit_start.html @@ -1,4 +1,4 @@ -{% extends "core/base.html" %} +{% extends "/core/base.html" %} {% load static from staticfiles %} {% load i18n %} {% load foundation %} From 60cb5c6293f74e5df9bcb862245c0d3113725908 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Tue, 7 Apr 2020 13:36:03 +0100 Subject: [PATCH 13/32] Updated method name for clarity --- src/submission/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index 7b1dfcb258..657b2c0d68 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -953,7 +953,7 @@ def current_stage_url(self): elif self.stage == STAGE_READY_FOR_PUBLICATION: return reverse('publish_article', kwargs=kwargs) else: - return reverse(self.current_stage.jump_url, kwargs=kwargs) + return reverse(self.current_workflow_element.jump_url, kwargs=kwargs) @property def custom_fields(self): @@ -1028,7 +1028,7 @@ def workflow_stages(self): return core_models.WorkflowLog.objects.filter(article=self) @property - def current_stage(self): + def current_workflow_element(self): from core import models as core_models logs = core_models.WorkflowLog.objects.filter( article=self, From 428c935d8dfdfe85dafa16a96d6629b1abd44805 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Tue, 7 Apr 2020 13:38:45 +0100 Subject: [PATCH 14/32] Renamed current_stage_url to current_workflow_element_url for clairty. Removed most branches except unassigned. --- src/submission/models.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index 657b2c0d68..9d6e5033b1 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -940,18 +940,9 @@ def current_stage_url(self): kwargs = {'article_id': self.pk} + # STAGE_UNASSIGNED isn't a workflow element so is hardcoded here. if self.stage == STAGE_UNASSIGNED: return reverse('review_unassigned_article', kwargs=kwargs) - elif self.stage in REVIEW_STAGES: - return reverse('review_in_review', kwargs=kwargs) - elif self.stage in COPYEDITING_STAGES: - return reverse('article_copyediting', kwargs=kwargs) - elif self.stage == STAGE_TYPESETTING: - return reverse('production_article', kwargs=kwargs) - elif self.stage == STAGE_PROOFING: - return reverse('proofing_article', kwargs=kwargs) - elif self.stage == STAGE_READY_FOR_PUBLICATION: - return reverse('publish_article', kwargs=kwargs) else: return reverse(self.current_workflow_element.jump_url, kwargs=kwargs) From 4679b8d0bb022de2f1e6f7e50fa37ffe3e151f3c Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Tue, 7 Apr 2020 13:43:56 +0100 Subject: [PATCH 15/32] Completes previous commit --- src/submission/models.py | 2 +- src/templates/admin/core/dashboard.html | 2 +- src/templates/admin/elements/core/submission_list_element.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index 9d6e5033b1..16aaecded9 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -936,7 +936,7 @@ def active_author_copyedits(self): return author_copyedits @property - def current_stage_url(self): + def current_workflow_element_url(self): kwargs = {'article_id': self.pk} diff --git a/src/templates/admin/core/dashboard.html b/src/templates/admin/core/dashboard.html index 72f7dd25c6..fee1dce29f 100644 --- a/src/templates/admin/core/dashboard.html +++ b/src/templates/admin/core/dashboard.html @@ -176,7 +176,7 @@

    Section Editor

    {% for assignment in section_editor_articles %} - {{ assignment.article.title }} + {{ assignment.article.title }} {{ assignment.article.author_list }} {{ assignment.article.date_submitted }} diff --git a/src/templates/admin/elements/core/submission_list_element.html b/src/templates/admin/elements/core/submission_list_element.html index 8e869fee99..524513e86a 100644 --- a/src/templates/admin/elements/core/submission_list_element.html +++ b/src/templates/admin/elements/core/submission_list_element.html @@ -8,7 +8,7 @@ {% for editor in article.editors %}{% if forloop.first %}Editors: {% endif %}{{ editor.editor.full_name }} ( {% if editor.editor_type == 'section-editor' %}SE{% else %}E {% endif %}){% if not forloop.last %}, {% endif %}{% endfor %} - + View Article From 9b4d553affa89e05174ea5d7fd41ea136d035af5 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Tue, 7 Apr 2020 13:44:10 +0100 Subject: [PATCH 16/32] Added reverse code to core.0034 --- .../migrations/0034_update_handshake_urls.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/core/migrations/0034_update_handshake_urls.py b/src/core/migrations/0034_update_handshake_urls.py index 2c93c4dee0..19bbefe319 100644 --- a/src/core/migrations/0034_update_handshake_urls.py +++ b/src/core/migrations/0034_update_handshake_urls.py @@ -4,16 +4,15 @@ from django.db import migrations +updates = [ + {'from': 'review_unassigned_article', 'to': 'review_home'}, + {'from': 'publish_article', 'to': 'publish'}, + {'from': 'article_copyediting', 'to': 'copyediting'}, +] def update_handshake_urls(apps, schema_editor): WorkflowElement = apps.get_model("core", "WorkflowElement") - updates = [ - {'from': 'review_unassigned_article', 'to': 'review_home'}, - {'from': 'publish_article', 'to': 'publish'}, - {'from': 'article_copyediting', 'to': 'copyediting'}, - ] - for update in updates: elements_to_update = WorkflowElement.objects.filter( handshake_url=update.get('from') @@ -23,6 +22,18 @@ def update_handshake_urls(apps, schema_editor): element_to_update.save() +def reverse_handshake_urls(apps, schema_editor): + WorkflowElement = apps.get_model("core", "WorkflowElement") + + for update in updates: + elements_to_update = WorkflowElement.objects.filter( + handshake_url=update.get('to') + ) + for element_to_update in elements_to_update: + element_to_update.handshake_url = update.get('from') + element_to_update.save() + + class Migration(migrations.Migration): dependencies = [ @@ -32,6 +43,6 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython( update_handshake_urls, - reverse_code=migrations.RunPython.noop + reverse_code=reverse_handshake_urls, ), ] From 9a87565cdb4554a626759cfa02c207aa27eb924c Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Tue, 7 Apr 2020 13:45:33 +0100 Subject: [PATCH 17/32] WorkflowElement.articles now uses self.journal instead of the global request object. --- src/core/models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/models.py b/src/core/models.py index 4ec050d2f0..4801b3415f 100755 --- a/src/core/models.py +++ b/src/core/models.py @@ -1155,11 +1155,9 @@ def stages(self): @property def articles(self): - from core.middleware import GlobalRequestMiddleware - request = GlobalRequestMiddleware.get_current_request() return submission_models.Article.objects.filter( stage__in=self.stages, - journal=request.journal, + journal=self.journal, ) def __str__(self): From 89ba531b30e2bb18021181a03e5147cff7e01435 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Wed, 8 Apr 2020 16:22:36 +0100 Subject: [PATCH 18/32] Delete migrations and pushing wip! --- .../migrations/0041_auto_20200203_1547.py | 20 ------------------- .../migrations/0042_auto_20200227_1051.py | 20 ------------------- .../migrations/0043_merge_20200314_1336.py | 16 --------------- src/submission/models.py | 19 ++++++++++++++++-- 4 files changed, 17 insertions(+), 58 deletions(-) delete mode 100644 src/submission/migrations/0041_auto_20200203_1547.py delete mode 100644 src/submission/migrations/0042_auto_20200227_1051.py delete mode 100644 src/submission/migrations/0043_merge_20200314_1336.py diff --git a/src/submission/migrations/0041_auto_20200203_1547.py b/src/submission/migrations/0041_auto_20200203_1547.py deleted file mode 100644 index 4703e3ac94..0000000000 --- a/src/submission/migrations/0041_auto_20200203_1547.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-03 15:47 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('submission', '0040_article_projected_issue'), - ] - - operations = [ - migrations.AlterField( - model_name='article', - name='stage', - field=models.CharField(choices=[('Unsubmitted', 'Unsubmitted'), ('Unassigned', 'Unassigned'), ('Assigned', 'Assigned to Editor'), ('Under Review', 'Peer Review'), ('Under Revision', 'Revision'), ('Rejected', 'Rejected'), ('Accepted', 'Accepted'), ('Editor Copyediting', 'Editor Copyediting'), ('Author Copyediting', 'Author Copyediting'), ('Final Copyediting', 'Final Copyediting'), ('Typesetting', 'Typesetting'), ('Proofing', 'Proofing'), ('pre_publication', 'Pre Publication'), ('Published', 'Published'), ('preprint_review', 'Preprint Review'), ('preprint_published', 'Preprint Published'), ('Back Content', 'Back Content Plugin'), ('typesetting_plugin', 'Typesetting Plugin')], default='Unsubmitted', max_length=200), - ), - ] diff --git a/src/submission/migrations/0042_auto_20200227_1051.py b/src/submission/migrations/0042_auto_20200227_1051.py deleted file mode 100644 index dc0dc0ff35..0000000000 --- a/src/submission/migrations/0042_auto_20200227_1051.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.27 on 2020-02-27 10:51 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('submission', '0040_article_projected_issue'), - ] - - operations = [ - migrations.AlterField( - model_name='article', - name='stage', - field=models.CharField(choices=[('Unsubmitted', 'Unsubmitted'), ('Unassigned', 'Unassigned'), ('Assigned', 'Assigned to Editor'), ('Under Review', 'Peer Review'), ('Under Revision', 'Revision'), ('Rejected', 'Rejected'), ('Accepted', 'Accepted'), ('Editor Copyediting', 'Editor Copyediting'), ('Author Copyediting', 'Author Copyediting'), ('Final Copyediting', 'Final Copyediting'), ('Typesetting', 'Typesetting'), ('Proofing', 'Proofing'), ('pre_publication', 'Pre Publication'), ('Published', 'Published'), ('preprint_review', 'Preprint Review'), ('preprint_published', 'Preprint Published'), ('typesetting_plugin', 'Typesetting Plugin')], default='Unsubmitted', max_length=200), - ), - ] diff --git a/src/submission/migrations/0043_merge_20200314_1336.py b/src/submission/migrations/0043_merge_20200314_1336.py deleted file mode 100644 index ec9a217016..0000000000 --- a/src/submission/migrations/0043_merge_20200314_1336.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.27 on 2020-03-14 13:36 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('submission', '0041_auto_20200203_1547'), - ('submission', '0042_auto_20200227_1051'), - ] - - operations = [ - ] diff --git a/src/submission/models.py b/src/submission/models.py index 16aaecded9..ea1309f793 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -24,7 +24,6 @@ from metrics.logic import ArticleMetrics from preprint import models as preprint_models from review import models as review_models -from utils import logic from utils.function_cache import cache fs = JanewayFileSystemStorage() @@ -298,6 +297,16 @@ def get_queryset(self): return super(PreprintManager, self).get_queryset().filter(is_preprint=True) +class SomeClass: + pass + + +class DynamicChoiceField(models.CharField): + def formfield(self, *args, **kwargs): + kwargs["choices_form_class"] = SomeClass + super().formfield(**kwargs) + + class Article(models.Model): journal = models.ForeignKey('journal.Journal', blank=True, null=True) # Metadata @@ -367,7 +376,13 @@ class Article(models.Model): page_numbers = models.CharField(max_length=20, blank=True, null=True) # Stage - stage = models.CharField(max_length=200, blank=False, null=False, default='Unsubmitted', choices=STAGE_CHOICES) + stage = DynamicChoiceField( + max_length=200, + blank=True, + null=False, + default=STAGE_UNSUBMITTED, + choices=STAGE_CHOICES, + ) # Agreements publication_fees = models.BooleanField(default=False) From 1ce5e2d4d05f651a86b447e5174b1df320e30b10 Mon Sep 17 00:00:00 2001 From: Mauro MSL Date: Wed, 8 Apr 2020 17:26:43 +0100 Subject: [PATCH 19/32] Add stub for SomeClass --- src/submission/models.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index ea1309f793..138d871a8d 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -297,8 +297,11 @@ def get_queryset(self): return super(PreprintManager, self).get_queryset().filter(is_preprint=True) -class SomeClass: - pass +class SomeClass(ChoiceField): + def valid_value(self): + valid = super().valid_value() + if valid is False: +# check plugin stages class DynamicChoiceField(models.CharField): @@ -416,15 +419,15 @@ class Article(models.Model): # Primary issue, allows the Editor to set the Submission's primary Issue primary_issue = models.ForeignKey( - 'journal.Issue', + 'journal.Issue', blank=True, - null=True, + null=True, on_delete=models.SET_NULL, ) projected_issue = models.ForeignKey( - 'journal.Issue', - blank=True, - null=True, + 'journal.Issue', + blank=True, + null=True, on_delete=models.SET_NULL, related_name='projected_issue', ) From 127ad2ef55c0e44e7176f4c2701f130d4b60fa97 Mon Sep 17 00:00:00 2001 From: Mauro MSL Date: Wed, 8 Apr 2020 18:00:11 +0100 Subject: [PATCH 20/32] Improves previous implementation --- src/submission/models.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index 138d871a8d..615ebd3bfc 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -18,6 +18,7 @@ from django.db.models.signals import pre_delete from django.dispatch import receiver from django.core.exceptions import ObjectDoesNotExist +from django.forms.fields import ChoiceField from core.file_system import JanewayFileSystemStorage from identifiers import logic as id_logic @@ -297,17 +298,38 @@ def get_queryset(self): return super(PreprintManager, self).get_queryset().filter(is_preprint=True) -class SomeClass(ChoiceField): - def valid_value(self): +class DynamicChoiceFormField(ChoiceField): + """ + Allows adding choices dynamically without requiring a migration + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.dynamic_choices = [] + + def _get_choices(self): + choices = super()._get_choices() + return choices + self.dynamic_choices + + def valid_value(self, value): valid = super().valid_value() if valid is False: -# check plugin stages + return value in self.dynamic_choices + return valid class DynamicChoiceField(models.CharField): + def __init__(self, dynamic_choices=(), *args, **kwargs): + super().__init__(*args, **kwargs) + self.dynamic_choices = dynamic_choices + def formfield(self, *args, **kwargs): - kwargs["choices_form_class"] = SomeClass - super().formfield(**kwargs) + kwargs["choices_form_class"] = DynamicChoiceFormField + form_element = super().formfield(**kwargs) + for choice in self.dynamic_choices: + form_element.dynamic_choices.append(choice) + return form_element + class Article(models.Model): @@ -385,6 +407,7 @@ class Article(models.Model): null=False, default=STAGE_UNSUBMITTED, choices=STAGE_CHOICES, + dynamic_choices=(), ) # Agreements From 16074a8dc37b31aea2b53219894a7851234d70a0 Mon Sep 17 00:00:00 2001 From: Mauro MSL Date: Wed, 15 Apr 2020 12:08:28 +0100 Subject: [PATCH 21/32] Changes DynamicChoiceFormField to be typed --- src/submission/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index 615ebd3bfc..fb5187a4ce 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -18,7 +18,7 @@ from django.db.models.signals import pre_delete from django.dispatch import receiver from django.core.exceptions import ObjectDoesNotExist -from django.forms.fields import ChoiceField +from django.forms.fields import TypedChoiceField from core.file_system import JanewayFileSystemStorage from identifiers import logic as id_logic @@ -298,7 +298,7 @@ def get_queryset(self): return super(PreprintManager, self).get_queryset().filter(is_preprint=True) -class DynamicChoiceFormField(ChoiceField): +class DynamicChoiceFormField(TypedChoiceField): """ Allows adding choices dynamically without requiring a migration """ From c23f4c98fe7b02418707574b8fc393623cc60423 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Wed, 15 Apr 2020 11:16:50 +0100 Subject: [PATCH 22/32] Updated formfield to append dynamic_choices to choices. passed value to super().valid_value() --- src/core/plugin_loader.py | 4 ++-- src/submission/models.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/plugin_loader.py b/src/core/plugin_loader.py index 5ceff77d7f..882f46b034 100755 --- a/src/core/plugin_loader.py +++ b/src/core/plugin_loader.py @@ -11,7 +11,7 @@ from django.db.utils import OperationalError, ProgrammingError from core.workflow import ELEMENT_STAGES -from submission.models import STAGE_CHOICES +from submission.models import PLUGIN_WORKFLOW_STAGES from utils import models from utils.logic import get_janeway_version @@ -49,7 +49,7 @@ def load(directory="plugins", prefix="plugins", permissive=False): workflow_check = check_plugin_workflow(plugin_settings) if workflow_check: settings.WORKFLOW_PLUGINS[workflow_check] = module_name - STAGE_CHOICES.append( + PLUGIN_WORKFLOW_STAGES.append( (plugin_settings.STAGE, plugin_settings.PLUGIN_NAME) ) ELEMENT_STAGES[ diff --git a/src/submission/models.py b/src/submission/models.py index fb5187a4ce..ede1acd198 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -244,6 +244,8 @@ def article_media_upload(instance, filename): (STAGE_PREPRINT_PUBLISHED, 'Preprint Published') ] +PLUGIN_WORKFLOW_STAGES = [] + class ArticleStageLog(models.Model): article = models.ForeignKey('Article') @@ -312,7 +314,8 @@ def _get_choices(self): return choices + self.dynamic_choices def valid_value(self, value): - valid = super().valid_value() + valid = super().valid_value(value) + if valid is False: return value in self.dynamic_choices return valid @@ -327,11 +330,10 @@ def formfield(self, *args, **kwargs): kwargs["choices_form_class"] = DynamicChoiceFormField form_element = super().formfield(**kwargs) for choice in self.dynamic_choices: - form_element.dynamic_choices.append(choice) + form_element.choices.append(choice) return form_element - class Article(models.Model): journal = models.ForeignKey('journal.Journal', blank=True, null=True) # Metadata @@ -407,7 +409,7 @@ class Article(models.Model): null=False, default=STAGE_UNSUBMITTED, choices=STAGE_CHOICES, - dynamic_choices=(), + dynamic_choices=PLUGIN_WORKFLOW_STAGES, ) # Agreements From 78ec24f38dde6c4cc4f8b130a566b90ae5e16f51 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Wed, 15 Apr 2020 11:50:28 +0100 Subject: [PATCH 23/32] Added a validation method to DynamicChoiceField --- src/submission/models.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index ede1acd198..b2d5467408 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -17,7 +17,7 @@ from hvad.models import TranslatableModel, TranslatedFields from django.db.models.signals import pre_delete from django.dispatch import receiver -from django.core.exceptions import ObjectDoesNotExist +from django.core import exceptions from django.forms.fields import TypedChoiceField from core.file_system import JanewayFileSystemStorage @@ -333,6 +333,38 @@ def formfield(self, *args, **kwargs): form_element.choices.append(choice) return form_element + def validate(self, value, model_instance): + """ + Validates value and throws ValidationError. + """ + if not self.editable: + # Skip validation for non-editable fields. + return + + choices = self.choices + self.dynamic_choices + + if choices and value not in self.empty_values: + for option_key, option_value in choices: + if isinstance(option_value, (list, tuple)): + # This is an optgroup, so look inside the group for + # options. + for optgroup_key, optgroup_value in option_value: + if value == optgroup_key: + return + elif value == option_key: + return + raise exceptions.ValidationError( + self.error_messages['invalid_choice'], + code='invalid_choice', + params={'value': value}, + ) + + if value is None and not self.null: + raise exceptions.ValidationError(self.error_messages['null'], code='null') + + if not self.blank and value in self.empty_values: + raise exceptions.ValidationError(self.error_messages['blank'], code='blank') + class Article(models.Model): journal = models.ForeignKey('journal.Journal', blank=True, null=True) @@ -1102,7 +1134,7 @@ def close_core_workflow_objects(self): def production_assignment_or_none(self): try: return self.productionassignment - except ObjectDoesNotExist: + except exceptions.ObjectDoesNotExist: return None @property From 227b04c0d92e25d1437d839f620e871c53bed744 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Wed, 15 Apr 2020 12:04:26 +0100 Subject: [PATCH 24/32] Added better implementation of validate. --- src/submission/models.py | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index b2d5467408..332c918446 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -337,33 +337,13 @@ def validate(self, value, model_instance): """ Validates value and throws ValidationError. """ - if not self.editable: - # Skip validation for non-editable fields. - return - - choices = self.choices + self.dynamic_choices - - if choices and value not in self.empty_values: - for option_key, option_value in choices: - if isinstance(option_value, (list, tuple)): - # This is an optgroup, so look inside the group for - # options. - for optgroup_key, optgroup_value in option_value: - if value == optgroup_key: - return - elif value == option_key: - return - raise exceptions.ValidationError( - self.error_messages['invalid_choice'], - code='invalid_choice', - params={'value': value}, - ) - - if value is None and not self.null: - raise exceptions.ValidationError(self.error_messages['null'], code='null') - - if not self.blank and value in self.empty_values: - raise exceptions.ValidationError(self.error_messages['blank'], code='blank') + try: + super().validate(value, model_instance) + except exceptions.ValidationError: + # Check if the value is in dynamic choices and remove the + # error message if it is + if 'invalid_choice' in self.error_messages and value in self.dynamic_choices: + self.error_messages.pop('invalid_choice') class Article(models.Model): From 5733bd84d3ccd926323287d6a20ecd299cae9e6b Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Wed, 15 Apr 2020 12:04:50 +0100 Subject: [PATCH 25/32] WorkflowElements that are plugins can expose some useful settings via a new method. --- src/core/models.py | 5 +++++ src/core/workflow.py | 34 ++++++++++++++++++++++++++++++++++ src/journal/models.py | 6 ++++++ 3 files changed, 45 insertions(+) diff --git a/src/core/models.py b/src/core/models.py index 4801b3415f..62cc318144 100755 --- a/src/core/models.py +++ b/src/core/models.py @@ -1160,6 +1160,11 @@ def articles(self): journal=self.journal, ) + @property + def settings(self): + from core import workflow + return workflow.workflow_plugin_settings(self) + def __str__(self): return self.element_name diff --git a/src/core/workflow.py b/src/core/workflow.py index 455a8222e9..6d0b117b8f 100755 --- a/src/core/workflow.py +++ b/src/core/workflow.py @@ -174,6 +174,10 @@ def articles_in_workflow_stages(request): return workflow_list +def core_workflow_element_names(): + return [element.get('name') for element in models.BASE_ELEMENTS] + + def element_names(elements): return [element.element_name for element in elements] @@ -208,3 +212,33 @@ def remove_element(request, journal_workflow, element): messages.SUCCESS, 'Element removed from workflow.' ) + + +def workflow_plugin_settings(element): + """ + Gets the plugin settings module for a plugin and returns useful settings + :param element: a WorkflowElement object + :return: dict of useful settings + """ + try: + settings_module = import_module( + settings.WORKFLOW_PLUGINS[element.element_name], + ) + + return { + 'display_name': getattr(settings_module, 'DISPLAY_NAME', ''), + 'description': getattr(settings_module, 'DESCRIPTION', ''), + 'kanban_card': getattr(settings_module, 'KANBAN_CARD', ''), + 'dashboard_template': getattr( + settings_module, 'DASHBOARD_TEMPLATE', '' + ) + } + + except (ImportError, KeyError) as e: + if settings.DEBUG: + print(e) + pass + + return {} + + diff --git a/src/journal/models.py b/src/journal/models.py index e004ef0087..945322f0c4 100755 --- a/src/journal/models.py +++ b/src/journal/models.py @@ -407,6 +407,12 @@ def article_keywords(self): article__in=self.published_articles ).order_by('word') + @property + def workflow_plugin_elements(self): + return self.workflowelement_set.exclude( + element_name__in=workflow.core_workflow_element_names() + ) + class PinnedArticle(models.Model): journal = models.ForeignKey(Journal) From aa6776a8db7982b4ba5d63bc22140257ca65b80b Mon Sep 17 00:00:00 2001 From: Mauro MSL Date: Wed, 15 Apr 2020 13:13:48 +0100 Subject: [PATCH 26/32] Remove no longer needed formfield class --- src/submission/models.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index 332c918446..f96c24b736 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -18,7 +18,6 @@ from django.db.models.signals import pre_delete from django.dispatch import receiver from django.core import exceptions -from django.forms.fields import TypedChoiceField from core.file_system import JanewayFileSystemStorage from identifiers import logic as id_logic @@ -300,26 +299,6 @@ def get_queryset(self): return super(PreprintManager, self).get_queryset().filter(is_preprint=True) -class DynamicChoiceFormField(TypedChoiceField): - """ - Allows adding choices dynamically without requiring a migration - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.dynamic_choices = [] - - def _get_choices(self): - choices = super()._get_choices() - return choices + self.dynamic_choices - - def valid_value(self, value): - valid = super().valid_value(value) - - if valid is False: - return value in self.dynamic_choices - return valid - class DynamicChoiceField(models.CharField): def __init__(self, dynamic_choices=(), *args, **kwargs): @@ -327,7 +306,6 @@ def __init__(self, dynamic_choices=(), *args, **kwargs): self.dynamic_choices = dynamic_choices def formfield(self, *args, **kwargs): - kwargs["choices_form_class"] = DynamicChoiceFormField form_element = super().formfield(**kwargs) for choice in self.dynamic_choices: form_element.choices.append(choice) From 634659590d744ee3fc6f7497a02dafa9b831dcde Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Wed, 15 Apr 2020 16:48:50 +0100 Subject: [PATCH 27/32] Completed dashboard work for workflow plugins. --- src/submission/models.py | 1 - src/templates/admin/core/dashboard.html | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/submission/models.py b/src/submission/models.py index f96c24b736..d5830f628b 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -299,7 +299,6 @@ def get_queryset(self): return super(PreprintManager, self).get_queryset().filter(is_preprint=True) - class DynamicChoiceField(models.CharField): def __init__(self, dynamic_choices=(), *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/src/templates/admin/core/dashboard.html b/src/templates/admin/core/dashboard.html index fee1dce29f..a0018068a2 100644 --- a/src/templates/admin/core/dashboard.html +++ b/src/templates/admin/core/dashboard.html @@ -155,6 +155,10 @@

    Proofing Corrections

    {% endif %} + {% for element in request.journal.workflow_plugin_elements %} + {% include element.settings.dashboard_template %} + {% endfor %} + {% user_has_role request 'section-editor' as sectioneditor %} {% if sectioneditor %} From 45cbb5d95bf8c5f2f31401a5ef14875a6fb58f45 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Thu, 16 Apr 2020 17:39:52 +0100 Subject: [PATCH 28/32] Changed print errors to logger errors and removed pass. --- src/core/workflow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/workflow.py b/src/core/workflow.py index 6d0b117b8f..2804a23f45 100755 --- a/src/core/workflow.py +++ b/src/core/workflow.py @@ -169,7 +169,7 @@ def articles_in_workflow_stages(request): workflow_list[element.element_name] = element_dict except (KeyError, AttributeError) as e: if settings.DEBUG: - print(e) + logger.error(e) return workflow_list @@ -236,8 +236,7 @@ def workflow_plugin_settings(element): except (ImportError, KeyError) as e: if settings.DEBUG: - print(e) - pass + logger.error(e) return {} From 2393d1c59a79684f97860866f1dace24bac3b6ad Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Thu, 16 Apr 2020 21:14:24 +0100 Subject: [PATCH 29/32] use defaults to avoid duplicate plugins. --- src/core/logic.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/logic.py b/src/core/logic.py index 33335b05a0..b6d8c0c2cd 100755 --- a/src/core/logic.py +++ b/src/core/logic.py @@ -455,12 +455,16 @@ def get_available_elements(workflow): def handle_element_post(workflow, element_name, request): for element in get_available_elements(workflow): if element['name'] == element_name: + defaults = { + 'jump_url': element.get('jump_url', ''), + 'stage': element['stage'], + 'handshake_url': element['handshake_url'], + + } element_obj, created = models.WorkflowElement.objects.get_or_create( journal=request.journal, element_name=element_name, - handshake_url=element['handshake_url'], - stage=element['stage'], - jump_url=element.get('jump_url', ''), + defaults=defaults, ) return element_obj From 0e0d0115addf013f8a8001ead8ee920815735317 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Fri, 17 Apr 2020 10:36:28 +0100 Subject: [PATCH 30/32] Added an implementation of DynamicChoiceField.validate that works. --- src/submission/models.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index d5830f628b..480152acb5 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -316,11 +316,15 @@ def validate(self, value, model_instance): """ try: super().validate(value, model_instance) - except exceptions.ValidationError: + except exceptions.ValidationError as e: # Check if the value is in dynamic choices and remove the # error message if it is - if 'invalid_choice' in self.error_messages and value in self.dynamic_choices: - self.error_messages.pop('invalid_choice') + if e.code == 'invalid_choice': + potential_values = set( + item[0] for item in self.dynamic_choices + ) + if value not in potential_values: + raise class Article(models.Model): From 3fecc2550ae403c1e3bf2586652568abb90225b6 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Fri, 17 Apr 2020 12:36:57 +0100 Subject: [PATCH 31/32] UPdated error message --- src/submission/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/submission/models.py b/src/submission/models.py index 480152acb5..4e504a6273 100755 --- a/src/submission/models.py +++ b/src/submission/models.py @@ -317,8 +317,8 @@ def validate(self, value, model_instance): try: super().validate(value, model_instance) except exceptions.ValidationError as e: - # Check if the value is in dynamic choices and remove the - # error message if it is + # If the raised exception is for invalid choice we check if the + # choice is in dynamic choices. if e.code == 'invalid_choice': potential_values = set( item[0] for item in self.dynamic_choices From d5aa3dfcccda8d2d2c36d18956f2e67ca1309a81 Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Fri, 17 Apr 2020 12:38:31 +0100 Subject: [PATCH 32/32] core.workflow error loggers now work in production. --- src/core/workflow.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/workflow.py b/src/core/workflow.py index 2804a23f45..7664aaaae6 100755 --- a/src/core/workflow.py +++ b/src/core/workflow.py @@ -168,8 +168,7 @@ def articles_in_workflow_stages(request): workflow_list[element.element_name] = element_dict except (KeyError, AttributeError) as e: - if settings.DEBUG: - logger.error(e) + logger.error(e) return workflow_list @@ -235,8 +234,7 @@ def workflow_plugin_settings(element): } except (ImportError, KeyError) as e: - if settings.DEBUG: - logger.error(e) + logger.error(e) return {}