Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature hooks #1492

Merged
merged 33 commits into from
Apr 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d5691a2
Required or 1.3.8 plugins.
ajrbyers Feb 17, 2020
54354d5
Added migration.
ajrbyers Mar 9, 2020
2e61a68
Merged migrations
ajrbyers Mar 14, 2020
5f64f4e
Add support for args and kwargs on hooks
mauromsl Mar 18, 2020
c277869
Initial commit on hooks for plugins
ajrbyers Mar 30, 2020
20cb259
Updated the dashboard and editor block.
ajrbyers Apr 1, 2020
3f46e2f
More work towards 0.1.
ajrbyers Apr 2, 2020
1f93f31
Updates to complete some work in previous two commits.
ajrbyers Apr 2, 2020
a926990
Adds jump_url for plugin wf elements.
ajrbyers Apr 3, 2020
b32a45c
PEP8-ify copyedit.views.article_copyediting
ajrbyers Apr 6, 2020
0313bad
Merge branch 'feature-hooks' of github.com:BirkbeckCTP/janeway into f…
ajrbyers Apr 6, 2020
bb09f4c
Removed debug statement
ajrbyers Apr 6, 2020
8ba6d59
Reverts fix that is in another PR.
ajrbyers Apr 6, 2020
60cb5c6
Updated method name for clarity
ajrbyers Apr 7, 2020
428c935
Renamed current_stage_url to current_workflow_element_url for clairty…
ajrbyers Apr 7, 2020
4679b8d
Completes previous commit
ajrbyers Apr 7, 2020
9b4d553
Added reverse code to core.0034
ajrbyers Apr 7, 2020
9a87565
WorkflowElement.articles now uses self.journal instead of the global …
ajrbyers Apr 7, 2020
89ba531
Delete migrations and pushing wip!
ajrbyers Apr 8, 2020
1ce5e2d
Add stub for SomeClass
mauromsl Apr 8, 2020
127ad2e
Improves previous implementation
mauromsl Apr 8, 2020
78ec24f
Added a validation method to DynamicChoiceField
ajrbyers Apr 15, 2020
227b04c
Added better implementation of validate.
ajrbyers Apr 15, 2020
5733bd8
WorkflowElements that are plugins can expose some useful settings via…
ajrbyers Apr 15, 2020
16074a8
Changes DynamicChoiceFormField to be typed
mauromsl Apr 15, 2020
c23f4c9
Updated formfield to append dynamic_choices to choices. passed value …
ajrbyers Apr 15, 2020
aa6776a
Remove no longer needed formfield class
mauromsl Apr 15, 2020
6346595
Completed dashboard work for workflow plugins.
ajrbyers Apr 15, 2020
45cbb5d
Changed print errors to logger errors and removed pass.
ajrbyers Apr 16, 2020
2393d1c
use defaults to avoid duplicate plugins.
ajrbyers Apr 16, 2020
0e0d011
Added an implementation of DynamicChoiceField.validate that works.
ajrbyers Apr 17, 2020
3fecc25
UPdated error message
ajrbyers Apr 17, 2020
d5aa3df
core.workflow error loggers now work in production.
ajrbyers Apr 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions src/copyediting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand All @@ -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': 'article_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 = {
Expand Down
1 change: 1 addition & 0 deletions src/core/janeway_global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
'ENABLE_ENHANCED_MAILGUN_FEATURES',
'ENABLE_ORCID',
'DEBUG',
'LANGUAGE_CODE',
]

WSGI_APPLICATION = 'core.wsgi.application'
Expand Down
26 changes: 19 additions & 7 deletions src/core/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,22 +438,34 @@ 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)


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'])
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,
defaults=defaults,
)

return element_obj

Expand Down
48 changes: 48 additions & 0 deletions src/core/migrations/0034_update_handshake_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- 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

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")

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()


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 = [
('core', '0033_set_default_xml_galley_xsl'),
]

operations = [
migrations.RunPython(
update_handshake_urls,
reverse_code=reverse_handshake_urls,
),
]
34 changes: 30 additions & 4 deletions src/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -1095,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},
Expand All @@ -1115,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}
Expand All @@ -1132,13 +1135,36 @@ 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,
journal=self.journal,
)

@property
def settings(self):
from core import workflow
return workflow.workflow_plugin_settings(self)

def __str__(self):
return self.element_name

Expand Down
4 changes: 2 additions & 2 deletions src/core/plugin_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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[
Expand Down
4 changes: 2 additions & 2 deletions src/core/templatetags/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
35 changes: 33 additions & 2 deletions src/core/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,15 @@ 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


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]

Expand Down Expand Up @@ -208,3 +211,31 @@ 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:
logger.error(e)

return {}


6 changes: 6 additions & 0 deletions src/journal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/journal/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion src/review/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
1 change: 0 additions & 1 deletion src/security/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,6 @@ def wrapper(request, *args, **kwargs):
else:
deny_access(request)


return wrapper


Expand Down
Loading