diff --git a/events.py b/events.py new file mode 100644 index 0000000..81d8c27 --- /dev/null +++ b/events.py @@ -0,0 +1,50 @@ +from django.core.management import call_command + +from plugins.hydra import models, events + + +def distribute_articles(**kwargs): + print('We are distributing articles') + article = kwargs.get('article') + journal = article.journal + + # Try direct access via reverse relation + try: + linked_group = journal.linked_journals + except models.LinkedJournals.DoesNotExist: + try: + linked_group = models.LinkedJournals.objects.get(links=journal) + except models.LinkedJournals.DoesNotExist: + return + + # Build set of journals: primary + linked ones + group_journals = {linked_group.journal} + group_journals.update( + link.to_journal for link in + linked_group.journal_link.select_related("to_journal") + ) + + # Exclude the current journal + group_journals.discard(journal) + + results = [] + + for journal in group_journals: + try: + call_command( + "copy_articles", + journal.code, + article=article.pk, + target_lang="en", + ) + results.append( + { + "journal": journal.code, + "status": "success", + } + ) + except Exception as e: + print(e) + + + return group_journals \ No newline at end of file diff --git a/forms.py b/forms.py new file mode 100644 index 0000000..62f2ec3 --- /dev/null +++ b/forms.py @@ -0,0 +1,44 @@ +from django import forms + + +from utils import setting_handler, models +from plugins.hydra import plugin_settings + + +class HydraAdminForm(forms.Form): + hydra_enable_sidebar = forms.BooleanField(label="Enable Hydra Sidebar", required=False) + + def __init__(self, *args, journal=None, **kwargs): + """Initialize form with current plugin settings and apply help text.""" + super().__init__(*args, **kwargs) + self.journal = journal + self.plugin = models.Plugin.objects.get( + name=plugin_settings.SHORT_NAME, + ) + + # Initialize fields with settings values and help texts + hydra_enable_sidebar_setting = setting_handler.get_plugin_setting( + self.plugin, + 'hydra_enable_sidebar', + self.journal, + create=True, + pretty='Enable Hydra Sidebar', + types='boolean' + ) + self.fields[ + 'hydra_enable_sidebar' + ].initial = hydra_enable_sidebar_setting.processed_value + + def save(self): + """Save each setting in the cleaned data to the plugin settings.""" + for setting_name, setting_value in self.cleaned_data.items(): + if setting_value: + setting_value = 'On' + else: + setting_value = '' + setting_handler.save_plugin_setting( + plugin=self.plugin, + setting_name=setting_name, + value=setting_value, + journal=self.journal + ) diff --git a/hooks.py b/hooks.py index 282c3bb..ee4185d 100644 --- a/hooks.py +++ b/hooks.py @@ -1,13 +1,15 @@ -from itertools import groupby -from operator import attrgetter +from urllib.parse import urlparse from django.db.models import Q, Subquery -from django.apps import apps +from django.http import Http404 from django.template.loader import render_to_string from django.utils.translation import get_language_info -from plugins.hydra import models +from plugins.hydra import models, utils from utils.logger import get_logger +from submission import models as submission_models + +from utils import setting_handler logger = get_logger(__name__) @@ -36,7 +38,8 @@ def sidebar_article_links(context, article): def language_header_switcher(context): """ - Provides a language switcher for linked journal sites. + Provides a language switcher for journals linked by LinkedArticle records, + or (when not on an article page) switches to equivalent paths on linked journals. """ request = context["request"] @@ -47,64 +50,183 @@ def language_header_switcher(context): if not journal: return "" - # Find the linked group this journal belongs to - try: - linked_group = journal.linked_journals - except models.LinkedJournals.DoesNotExist: + links = [] + + # Normalise path + prefix = f"/{journal.code}/" + stripped_path = path[len(prefix):] if path.startswith(prefix) else path + normalised_path = stripped_path.lstrip("/") + + if not normalised_path: + corrected_path = "/" + elif normalised_path.startswith("issue/"): + corrected_path = "/issues/" + else: + corrected_path = f"/{normalised_path}" + + if article: + # Build set of transitive linked article PKs + visited = set() + to_visit = {article.pk} + + while to_visit: + current_ids = to_visit + to_visit = set() + + linked = models.LinkedArticle.objects.filter( + Q(from_article_id__in=current_ids) | Q(to_article_id__in=current_ids) + ).values("from_article_id", "to_article_id") + + for link in linked: + from_id = link["from_article_id"] + to_id = link["to_article_id"] + + if from_id not in visited: + to_visit.add(from_id) + if to_id not in visited: + to_visit.add(to_id) + + visited.update(current_ids) + + linked_articles = submission_models.Article.objects.filter( + pk__in=visited - {article.pk}, + ).select_related("journal") + + for linked_article in linked_articles: + linked_journal = linked_article.journal + + try: + linked_language = linked_journal.get_setting( + group_name="general", + setting_name="default_journal_language", + ) + except Exception: + continue + + links.append({ + "name_local": get_language_info(linked_language)["name_local"], + "url": linked_journal.site_url(path=corrected_path), + }) + + else: + # No article: find the LinkedJournals group try: - linked_group = models.LinkedJournals.objects.get(links=journal) + linked_set = journal.linked_journals except models.LinkedJournals.DoesNotExist: - return "" + try: + journal_link = models.JournalLink.objects.get(to_journal=journal) + linked_set = journal_link.parent + except models.JournalLink.DoesNotExist: + linked_set = None + + if linked_set: + for linked_journal in linked_set.links.all(): + if linked_journal == journal: + continue + + try: + linked_language = linked_journal.get_setting( + group_name="general", + setting_name="default_journal_language", + ) + except Exception: + continue + + links.append({ + "name_local": get_language_info(linked_language)["name_local"], + "url": linked_journal.site_url(path=corrected_path), + }) + + # Add parent journal if we're not it + if linked_set.journal != journal: + try: + parent_language = linked_set.journal.get_setting( + group_name="general", + setting_name="default_journal_language", + ) + links.append({ + "name_local": get_language_info(parent_language)["name_local"], + "url": linked_set.journal.site_url(path=corrected_path), + }) + except Exception: + pass - if not linked_group.journal_link.exists(): + if not links: return "" - # Strip the journal-specific path prefix - prefix = f"/{journal.code}/" - stripped_path = path[len(prefix):] if path.startswith(prefix) else path - corrected_path = "/issues/" if stripped_path.startswith("issue/") else f"/{stripped_path}" - - # Build the full set of group journals - group_journals = {linked_group.journal} - group_journals.update( - link.to_journal for link in linked_group.journal_link.select_related("to_journal") + return render_to_string( + "hydra/elements/language_links.html", + { + "language_links": links, + } ) + + + +def editor_nav_article_switcher(context): + """ + Adds backend links to linked articles. + """ + request = context["request"] + journal = getattr(request, "journal", None) + article = context.get("article") + + sidebar_enabled = setting_handler.get_setting( + "plugin:hydra", + "hydra_enable_sidebar", + journal=journal, + ).processed_value + + if not sidebar_enabled: + return "" + + if not article or not journal: + return "" + + related = article.linked_from.select_related( + "from_article__journal", + "to_article__journal", + ).all() + related |= article.linked_to.select_related( + "from_article__journal", + "to_article__journal", + ).all() + + linked_articles = utils.get_interlinked_articles(article.pk) + linked_articles = {a for a in linked_articles if a.pk != article.pk} + + if not linked_articles: + return "" + + current_code = journal.code links = [] - for linked_journal in group_journals: - if linked_journal.pk == journal.pk: - continue + for a in linked_articles: + raw_url = a.current_workflow_element_url or "" + parsed = urlparse(raw_url) + path_parts = parsed.path.strip("/").split("/") - try: - linked_language = linked_journal.get_setting( - group_name="general", - setting_name="default_journal_language", - ) - except Exception: - continue - - if article: - related = article.linked_from.select_related("from_article__journal", "to_article__journal").all() - related |= article.linked_to.select_related("from_article__journal", "to_article__journal").all() - if not any( - a.to_article.journal_id == linked_journal.pk - or a.from_article.journal_id == linked_journal.pk - for a in related - ): - continue + # If the current journal code appears early AND this is a *different* journal + if ( + current_code in path_parts[:2] + and a.journal_id != journal.pk + ): + path_parts = [part for part in path_parts if part != current_code] + + cleaned_path = "/" + "/".join(path_parts) links.append({ - "name_local": get_language_info(linked_language)["name_local"], - "url": linked_journal.site_url(path=corrected_path), + "article": a, + "journal": a.journal.code if hasattr(a, "journal") else "", + "url": cleaned_path, }) - if not links: - return "" - return render_to_string( - "hydra/elements/language_links.html", + "hydra/elements/linked_article_admin_links.html", { - "language_links": links, + "article_links": links, } ) + + diff --git a/install/settings.json b/install/settings.json new file mode 100644 index 0000000..a331238 --- /dev/null +++ b/install/settings.json @@ -0,0 +1,17 @@ +[ + { + "group": { + "name": "plugin:hydra" + }, + "setting": { + "description": "Enable Hydra Sidebar", + "is_translatable": true, + "name": "hydra_enable_sidebar", + "pretty_name": "Enable Hydra Sidebar", + "type": "char" + }, + "value": { + "default": "" + } + } +] diff --git a/management/commands/copy_articles.py b/management/commands/copy_articles.py index 57b95ab..cf06b1c 100644 --- a/management/commands/copy_articles.py +++ b/management/commands/copy_articles.py @@ -1,5 +1,6 @@ import os import shutil +import itertools from django.core.management.base import BaseCommand from django.db import transaction @@ -41,10 +42,18 @@ def add_arguments(self, parser): type=int, help='Optional single article ID to copy', ) + parser.add_argument( + "--stage", + type=str, + help="Optional stage to copy articles into" + ) parser.add_argument( '--target-lang', type=str, - help='Language code of the target journal eg "es" or "fr"', + help=( + 'Language code of the target journal eg "es" or "fr".' + ' will use the journal default if not provided', + ), ) def handle(self, *args, **options): @@ -52,6 +61,7 @@ def handle(self, *args, **options): target_code = options['target'] issue_id = options.get('issue') article_id = options.get('article') + stage = options.get('stage') self.target_lang = options.get('target_lang') try: @@ -70,10 +80,10 @@ def handle(self, *args, **options): for article in articles: with transaction.atomic(): - new_article = self.copy_article(article, target_journal) + new_article = self.copy_article(article, target_journal, stage) self.link_articles(article, new_article) - def copy_article(self, article, target_journal): + def copy_article(self, article, target_journal, stage): # Check for existing pubid in target journal pub_id = article.pk existing = identifiers_models.Identifier.objects.filter( @@ -99,7 +109,7 @@ def copy_article(self, article, target_journal): new_article = submission_models.Article( journal=target_journal, language=article.language, - stage=article.stage, + stage=stage if stage else article.stage, is_import=True, ) @@ -124,7 +134,7 @@ def copy_article(self, article, target_journal): new_article.last_page = article.last_page new_article.page_numbers = article.page_numbers new_article.total_pages = article.total_pages - new_article.stage = article.stage + new_article.stage = stage if stage else article.stage new_article.publication_fees = article.publication_fees new_article.submission_requirements = article.submission_requirements new_article.copyright_notice = article.copyright_notice @@ -147,9 +157,12 @@ def copy_article(self, article, target_journal): setattr(new_article, f"title_{lang_code}", getattr(article, f"title_{lang_code}", None)) - if self.target_lang: - new_article.title_en = getattr(article, f"title_{self.target_lang}") - new_article.title = getattr(article, f"title_{self.target_lang}") + # TODO: Remove once outgoing feeds/apis support multilang + language = self.target_lang or get_journal_lang_code(new_article.journal) + if language: + # Set default / en language to translated language + new_article.title_en = getattr(article, f"title_{language}") + new_article.title = getattr(article, f"title_{language}") # Section if article.section: @@ -295,7 +308,11 @@ def copy_file(self, file, new_article): return new_file def copy_galleys(self, source_article, target_article): - for galley in source_article.galley_set.filter(label__icontains=self.target_lang): + language = self.target_lang or get_journal_lang_code(target_article.journal) + if not language: + self.stderr.write(self.style.ERROR("No language provided and no default found")) + return + for galley in source_article.galley_set.filter(label__icontains=f"_{language}"): new_galley = core_models.Galley.objects.get(pk=galley.pk) new_galley.pk = None new_galley.article = target_article @@ -364,14 +381,19 @@ def create_doi(self, source_article, target_article): return '{0}/{1}'.format(doi_prefix, doi_suffix) def link_articles(self, source_article, target_article): - to_link = identifiers_models.Identifier.objects.filter( - id_type="linkid", - identifier=source_article.pk, + models.LinkedArticle.objects.get_or_create( + from_article=source_article, + to_article=target_article, ) - if to_link.count() < 2: - return - for a, b in itertools.permutations(to_link, 2): - models.LinkedArticle.objects.get_or_create( - from_article=a, - to_article=b - ) + +def get_journal_lang_code(journal): + language = None + try: + language = journal.get_setting( + group_name="general", + setting_name="default_journal_language", + ) + except Exception: + pass + + return language \ No newline at end of file diff --git a/plugin_settings.py b/plugin_settings.py index bf52020..967710c 100644 --- a/plugin_settings.py +++ b/plugin_settings.py @@ -1,4 +1,7 @@ from utils import plugins +from utils.install import update_settings +from events import logic as events_logic +from plugins.hydra import events PLUGIN_NAME = 'Hydra' DISPLAY_NAME = 'Hydra' @@ -10,8 +13,12 @@ JANEWAY_VERSION = '1.7.4' # Workflow Settings -IS_WORKFLOW_PLUGIN = False # For now -# JUMP_URL = '' +IS_WORKFLOW_PLUGIN = True +HANDSHAKE_URL = 'hydra_handshake_url' +JUMP_URL = 'hydra_jump_url' +ARTICLE_PK_IN_HANDSHAKE_URL = False +STAGE = 'hydra' +KANBAN_CARD = 'hydra/elements/card.html' class HydraPlugin(plugins.Plugin): @@ -27,10 +34,18 @@ class HydraPlugin(plugins.Plugin): janeway_version = JANEWAY_VERSION is_workflow_plugin = IS_WORKFLOW_PLUGIN + handshake_url = HANDSHAKE_URL + article_pk_in_handshake_url = ARTICLE_PK_IN_HANDSHAKE_URL + jump_url = JUMP_URL def install(): HydraPlugin.install() + update_settings(file_path="plugins/hydra/install/settings.json") + +def register_for_events(): + pass + def hook_registry(): return { @@ -43,5 +58,10 @@ def hook_registry(): { 'module': 'plugins.hydra.hooks', 'function': 'language_header_switcher', + }, + 'journal_editor_nav_block': + { + 'module': 'plugins.hydra.hooks', + 'function': 'editor_nav_article_switcher', } } diff --git a/templates/hydra/elements/linked_article_admin_links.html b/templates/hydra/elements/linked_article_admin_links.html new file mode 100644 index 0000000..05189d4 --- /dev/null +++ b/templates/hydra/elements/linked_article_admin_links.html @@ -0,0 +1,8 @@ +{% for link in article_links %} +
  • + +   + {{ link.journal }}: {{ link.article.pk }} + +
  • +{% endfor %} \ No newline at end of file diff --git a/templates/hydra/handshake.html b/templates/hydra/handshake.html new file mode 100644 index 0000000..f27bfab --- /dev/null +++ b/templates/hydra/handshake.html @@ -0,0 +1,64 @@ +{% extends "admin/core/base.html" %} +{% load fqdn %} +{% block title %} + Article List - Hydra Plugin +{% endblock %} + +{% block title-section %} + Article List - Hydra Plugin +{% endblock %} + +{% block breadcrumbs %} + {{ block.super }} +
  • Hydra Article List
  • +{% endblock %} + +{% block body %} +
    +
    + {% include "admin/elements/list_filters.html" %} +
    + +
    +
    +

    Hydra Articles

    +
    + {% include "common/elements/sorting.html" with form_id=facet_form.id %} + + {% for article in article_list %} +
    +

    + {{ article.safe_title }} +

    +
    +
    + {% include "admin/elements/layout/key_value_above.html" with key="ID" value=article.pk %} + {% include "admin/elements/layout/key_value_above.html" with key="Section" value=article.section.name %} + {% include "admin/elements/layout/key_value_above.html" with key="Authors" value=article.author_list %} +
    + +
    +
    + {% include "admin/elements/layout/key_value_above.html" with key="Date Submitted" value=article.date_submitted %} + {% include "admin/elements/layout/key_value_above.html" with key="Date Accepted" value=article.date_accepted %} +
    + +
    +

    Actions

    + +
    +
    +
    +
    + {% empty %} +

    {% trans 'No articles to display.' %}

    + {% endfor %} + + {% include "common/elements/pagination.html" with form_id=facet_form.id %} +
    +{% endblock %} \ No newline at end of file diff --git a/templates/hydra/index.html b/templates/hydra/index.html index 04b0c04..9287a27 100644 --- a/templates/hydra/index.html +++ b/templates/hydra/index.html @@ -1,4 +1,5 @@ {% extends "admin/core/base.html" %} +{% load foundation %} {% block title %}Hail Hydra{% endblock title %} {% block title-section %}Hydra{{ galley.pk }}{% endblock %} @@ -8,6 +9,22 @@ {% endblock breadcrumbs %} {% block body %} -
    -

    The Hydra plugin is installed

    -{% endblock body %} + +
    +
    +
    +
    +

    The Hydra plugin is installed

    +

    Use this form to enable the Hydra navbar hook. Linked articles will have jump links rendered in the left hand menu.

    +
    + {% include "admin/elements/forms/errors.html" with form=form %} + {% csrf_token %} + {{ form|foundation }} + +
    +
    +
    +
    +
    + +{% endblock body %} \ No newline at end of file diff --git a/templates/hydra/jump.html b/templates/hydra/jump.html new file mode 100644 index 0000000..e3c92a3 --- /dev/null +++ b/templates/hydra/jump.html @@ -0,0 +1,79 @@ +{% extends "admin/core/base.html" %} +{% load fqdn %} +{% block title %} + #{{ article.pk }} - Hydra Plugin +{% endblock %} + +{% block title-section %} + #{{ article.pk }} - Hydra Plugin +{% endblock %} + +{% block breadcrumbs %} + {{ block.super }} +
  • Hydra Article List
  • +
  • Distribute Article #{{ article.pk }}
  • +{% endblock %} + +{% block body %} +
    +
    +
    +
    +

    Distribute Article #{{ article.pk }} {{ article.safe_title }}

    +
    +
    +

    {{ request.journal.name }} is linked to these journals:

    +
      + {% for journal in linked_journals %} +
    • {{ journal.name }}
    • + {% empty %} +
    • No linked journals
    • + {% endfor %} +
    +

    + You can distribute the article to one or all of the journals. +

    + +
    +
    +
    +
    + {% csrf_token %} + {% for journal in linked_journals %} + + {% endfor %} + +
    +
    +
    +
    +
    +
    +
    + +
    +{% endblock %} \ No newline at end of file diff --git a/urls.py b/urls.py index 8c4c592..f4bcd69 100644 --- a/urls.py +++ b/urls.py @@ -6,5 +6,20 @@ re_path(r'^$', views.index, name='hydra_index', - ) + ), + re_path( + r"^distribute/(?P\d+)/$", + views.distribute_articles_view, + name="distribute_articles", + ), + re_path( + r'articles/', + views.HydraHandshakeView.as_view(), + name="hydra_handshake_url", + ), + re_path( + r'article/(?P\d+)/', + views.hydra_jump_url, + name="hydra_jump_url", + ), ] diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..f456c3f --- /dev/null +++ b/utils.py @@ -0,0 +1,78 @@ +from django.core.management import call_command +from django.db.models import Q, Subquery + +from plugins.hydra import models +from utils.function_cache import cache +from submission import models as submission_models + + +def get_linked_journals(article): + journal = article.journal + + # Try direct access via reverse relation + try: + linked_group = journal.linked_journals + except models.LinkedJournals.DoesNotExist: + try: + linked_group = models.LinkedJournals.objects.get(links=journal) + except models.LinkedJournals.DoesNotExist: + return + + # Build set of journals: primary + linked ones + group_journals = {linked_group.journal} + group_journals.update( + link.to_journal for link in + linked_group.journal_link.select_related("to_journal") + ) + + # Exclude the current journal + group_journals.discard(journal) + + return group_journals + + +def distribute_articles(article, journals): + for journal in journals: + try: + call_command( + "copy_articles", + journal.code, + article=article.pk, + stage="Editor Copyediting", + target_lang="en", + ) + except Exception as e: + return e + return None + + +@cache(600) +def get_interlinked_articles(article_id): + """ + Return related articles: direct children and siblings via parent relationship. + """ + try: + article = submission_models.Article.objects.get(pk=article_id) + except submission_models.Article.DoesNotExist: + return set() + + parent_ids = models.LinkedArticle.objects.filter( + to_article=article + ).values("from_article") + + relatives = models.LinkedArticle.objects.filter( + Q(from_article=article) | Q(from_article__in=Subquery(parent_ids)) + ).select_related( + "from_article__journal", + "to_article__journal", + ).distinct() + + linked_articles = set() + + for link in relatives: + if link.from_article and link.from_article.pk != article.pk: + linked_articles.add(link.from_article) + if link.to_article and link.to_article.pk != article.pk: + linked_articles.add(link.to_article) + + return linked_articles \ No newline at end of file diff --git a/views.py b/views.py index 38770d7..3174d05 100644 --- a/views.py +++ b/views.py @@ -1,26 +1,25 @@ -from django.shortcuts import render, get_object_or_404, redirect, reverse +from django.contrib.admin.views.decorators import staff_member_required +from django.shortcuts import render, redirect, reverse, get_object_or_404 from django.contrib import messages -from django.core.files.base import ContentFile -from django.http import Http404 -from django.views.decorators.http import require_POST -from django.utils import timezone -from django.core.exceptions import PermissionDenied, ValidationError -from django.contrib.auth.decorators import login_required -from django.db.models import Q +from django.core.management import call_command +from django.utils.translation import gettext as _ try: - from plugins.typesetting import plugin_settings, models, logic, forms, security + from plugins.typesetting import plugin_settings, models, logic, security from plugins.typesetting.notifications import notify except ImportError: - from typesetting import plugin_settings, models, logic, forms, security - from typesetting.notifications import notify + pass from security import decorators -from submission import models as submission_models -from core import models as core_models, forms as core_forms, files -from production import logic as production_logic -from journal.views import article_figure -from journal import logic as journal_logic from utils.logger import get_logger +from plugins.hydra import forms, utils, events, plugin_settings +from submission import models as submission_models +from journal import models as journal_models +from submission.models import Article +from core import models as core_models, views as core_views + +from django.http import JsonResponse, HttpResponseNotFound +from django.views.decorators.http import require_GET + logger = get_logger(__name__) @@ -28,6 +27,171 @@ @decorators.has_journal @decorators.editor_user_required def index(request): + form = forms.HydraAdminForm( + journal=request.journal, + ) + if request.POST: + form = forms.HydraAdminForm( + request.POST, + journal=request.journal, + ) + if form.is_valid(): + form.save() + messages.success( + request, + 'Settings updated successfully', + ) + return redirect('hydra_index') template = 'hydra/index.html' - context = {} + context = { + 'form': form, + } return render(request, template, context) + + +class HydraHandshakeView(core_views.GenericFacetedListView): + """ + Faceted search view for articles in the Hydra plugin. + Lists only articles from the current journal and in the 'hydra' stage. + """ + + model = submission_models.Article + template_name = 'hydra/handshake.html' + + def get_facets(self): + facets = { + 'q': { + 'type': 'search', + 'field_label': _('Search'), + }, + } + return self.filter_facets_if_journal(facets) + + def get_order_by_choices(self): + return [ + ('title', _('Title A–Z')), + ('-title', _('Title Z–A')), + ('-date_submitted', _('Newest')), + ('date_submitted', _('Oldest')), + ] + + def get_queryset(self, params_querydict=None): + queryset = super().get_queryset(params_querydict) + return queryset.filter( + journal=self.request.journal, + stage=plugin_settings.STAGE, + ) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['hydra_article_search'] = True + return context + + +@decorators.editor_user_required +def hydra_jump_url(request, article_id): + article = get_object_or_404( + submission_models.Article, + pk=article_id, + journal=request.journal, + ) + linked_journals = utils.get_linked_journals( + article, + ) + linked_articles = utils.get_interlinked_articles(article.pk) + error = None + if request.POST: + code_to_distribute = request.POST.get('code_to_distribute') + if code_to_distribute == 'all': + error = utils.distribute_articles(article, linked_journals) + else: + journal = get_object_or_404( + journal_models.Journal, + code=code_to_distribute, + ) + if journal in linked_journals: + error = utils.distribute_articles(article, [journal]) + else: + messages.warning( + request, + f"Journal with code {code_to_distribute} not linked to this journal", + ) + if error: + messages.error( + request, + error, + ) + else: + stage = submission_models.STAGE_EDITOR_COPYEDITING + element = core_models.WorkflowElement.objects.get( + journal=article.journal, + stage=stage, + ) + for linked_article in linked_articles: + core_models.WorkflowLog.objects.get_or_create( + article=linked_article, + element=element, + ) + messages.success( + request, + "Articles distributed successfully", + ) + + return redirect('hydra_jump_url', article_id=article_id) + + template = 'hydra/jump.html' + context = { + 'article': article, + 'linked_journals': linked_journals, + 'linked_articles': linked_articles, + } + return render( + request, + template, + context, + ) + + + +@staff_member_required +@require_GET +def distribute_articles_view(request, article_id): + try: + article = Article.objects.get(pk=article_id) + except Article.DoesNotExist: + return HttpResponseNotFound("Article not found") + + journals = events.distribute_articles( + article=article, + ) + + if not journals: + return JsonResponse({"status": "No linked journals found"}, status=200) + + results = [] + + for journal in journals: + try: + call_command( + "copy_articles", + article.journal.code, + journal_code=journal.code, + article=article.pk, + target_lang='en', + ) + results.append({ + "journal": journal.code, + "status": "success", + }) + except Exception as e: + results.append({ + "journal": journal.code, + "status": "error", + "message": str(e), + }) + + return JsonResponse({ + "article_id": article.pk, + "source_journal": article.journal.code, + "results": results, + }) \ No newline at end of file