Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions src/core/include_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
core_views.edit_settings_group,
name="core_edit_settings_group",
),
re_path(
r"^manager/settings/journal/keywords/suggest/$",
core_views.journal_keyword_suggestions,
name="core_journal_keyword_suggestions",
),
re_path(
r"^manager/settings/(?P<plugin>[-\w.:]+)/(?P<setting_group_name>[-\w.]+)/(?P<journal>\d+)/$",
core_views.edit_plugin_settings_groups,
Expand Down
24 changes: 21 additions & 3 deletions src/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
from django.shortcuts import render, get_object_or_404, redirect, Http404
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.http import HttpResponse, QueryDict
from django.http import HttpResponse, QueryDict, JsonResponse
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.sessions.models import Session
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.conf import settings as django_settings
from django.views.decorators.http import require_POST
from django.views.decorators.http import require_GET, require_POST
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import CreateView, UpdateView, DeleteView
from django.contrib.contenttypes.models import ContentType
Expand Down Expand Up @@ -1182,7 +1182,6 @@ def edit_setting(request, setting_group, setting_name):
}
return render(request, template, context)


@staff_member_required
def edit_default_setting(request, setting_group, setting_name):
"""Proxy view for edit_setting allowing editing the default value
Expand Down Expand Up @@ -1285,6 +1284,25 @@ def edit_settings_group(request, display_group):
return render(request, template, context)


@editor_user_required
@require_GET
def journal_keyword_suggestions(request):
"""
Returns a small list of matching existing keywords for journal disciplines.
"""
search_term = request.GET.get("q", "").strip()

current_keyword_ids = request.journal.keywords.values_list("pk", flat=True)
matches = submission_models.Keyword.objects.exclude(pk__in=current_keyword_ids)

if search_term:
matches = matches.filter(word__istartswith=search_term)

matches = matches.order_by("word").values_list("word", flat=True)[:10]

return JsonResponse(list(matches), safe=False)


@editor_user_required
def edit_plugin_settings_groups(
request, plugin, setting_group_name, journal=None, title=None
Expand Down
72 changes: 72 additions & 0 deletions src/security/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -4461,6 +4461,78 @@ def test_setting_is_available_in_group(self):
)
self.assertContains(response, "Journal Name")

def test_journal_settings_uses_remote_keyword_suggestions(self):
medicine = submission_models.Keyword.objects.create(word="Medicine")
arts = submission_models.Keyword.objects.create(word="Arts")
self.journal_one.keywords.add(medicine, arts)

self.client.force_login(self.editor)
response = self.client.get(
reverse(
"core_edit_settings_group",
kwargs={
"display_group": "journal",
},
),
SERVER_NAME="journal1.localhost",
)

self.assertEqual(response.status_code, 200)
self.assertContains(
response,
reverse("core_journal_keyword_suggestions"),
)
self.assertNotContains(response, '"Medicine"')
self.assertNotContains(response, '"Arts"')

def test_journal_keyword_suggestions_returns_matching_keywords(self):
medicine = submission_models.Keyword.objects.create(word="Medicine")
mediation = submission_models.Keyword.objects.create(word="Mediation")
arts = submission_models.Keyword.objects.create(word="Arts")
self.journal_one.keywords.add(medicine)
self.journal_two.keywords.add(arts)

self.client.force_login(self.editor)
response = self.client.get(
reverse("core_journal_keyword_suggestions"),
{"q": "Me"},
SERVER_NAME="journal1.localhost",
)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), ["Mediation"])

def test_journal_keyword_suggestions_returns_initial_results_for_empty_query(self):
medicine = submission_models.Keyword.objects.create(word="Medicine")
arts = submission_models.Keyword.objects.create(word="Arts")
self.journal_one.keywords.add(medicine)
self.journal_two.keywords.add(arts)

self.client.force_login(self.editor)
response = self.client.get(
reverse("core_journal_keyword_suggestions"),
SERVER_NAME="journal1.localhost",
)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), ["Arts"])

def test_journal_settings_page_requires_three_characters_before_searching(self):
self.client.force_login(self.editor)
response = self.client.get(
reverse(
"core_edit_settings_group",
kwargs={
"display_group": "journal",
},
),
SERVER_NAME="journal1.localhost",
)

self.assertEqual(response.status_code, 200)
self.assertContains(response, "minLength: 3")
self.assertNotContains(response, "showAutocompleteOnFocus: true")

def test_setting_is_removed_from_group(self):
# set the journal_name setting to only be editable by journal managers.
journal_manager_role = core_models.Role.objects.get(
Expand Down
184 changes: 184 additions & 0 deletions src/submission/tests/test_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,190 @@ def test_submit_info_view_form_selection_author(self):
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, section.__str__())

@override_settings(URL_CONFIG="domain")
def test_submit_info_view_shows_journal_disciplines_as_keyword_suggestions(self):
author_1, _ = self.create_authors()
clear_cache()
article = models.Article.objects.create(
journal=self.journal_one,
title="Test article: keyword suggestions",
owner=author_1,
)
medicine = models.Keyword.objects.create(word="Medicine")
arts = models.Keyword.objects.create(word="Arts")
self.journal_one.keywords.add(medicine, arts)

self.client.force_login(author_1)
clear_script_prefix()
response = self.client.get(
reverse("submit_info", kwargs={"article_id": article.pk}),
SERVER_NAME="testserver",
)

self.assertEqual(response.status_code, 200)
self.assertContains(
response,
reverse(
"submission_keyword_suggestions",
kwargs={"article_id": article.pk},
),
)
self.assertNotContains(response, '"Medicine"')
self.assertNotContains(response, '"Arts"')

@override_settings(URL_CONFIG="domain")
def test_edit_metadata_view_shows_journal_disciplines_as_keyword_suggestions(self):
article = helpers.create_article(self.journal_one)
medicine = models.Keyword.objects.create(word="Medicine")
arts = models.Keyword.objects.create(word="Arts")
self.journal_one.keywords.add(medicine, arts)

self.client.force_login(self.editor)
clear_script_prefix()
response = self.client.get(
reverse("edit_metadata", kwargs={"article_id": article.pk}),
SERVER_NAME="testserver",
)

self.assertEqual(response.status_code, 200)
self.assertContains(
response,
reverse(
"submission_keyword_suggestions",
kwargs={"article_id": article.pk},
),
)
self.assertNotContains(response, '"Medicine"')
self.assertNotContains(response, '"Arts"')

@override_settings(URL_CONFIG="domain")
def test_keyword_suggestions_returns_filtered_journal_keywords(self):
author_1, _ = self.create_authors()
clear_cache()
article = models.Article.objects.create(
journal=self.journal_one,
title="Test article: keyword suggestions endpoint",
owner=author_1,
)
medicine = models.Keyword.objects.create(word="Medicine")
mediation = models.Keyword.objects.create(word="Mediation")
arts = models.Keyword.objects.create(word="Arts")
medieval = models.Keyword.objects.create(word="Medieval")
self.journal_one.keywords.add(medicine, mediation, arts)
self.journal_two.keywords.add(medieval)

self.client.force_login(author_1)
clear_script_prefix()
response = self.client.get(
reverse(
"submission_keyword_suggestions",
kwargs={"article_id": article.pk},
),
{"q": "Me"},
SERVER_NAME="testserver",
)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), ["Mediation", "Medicine"])

@override_settings(URL_CONFIG="domain")
def test_keyword_suggestions_returns_initial_results_for_empty_query(self):
author_1, _ = self.create_authors()
clear_cache()
article = models.Article.objects.create(
journal=self.journal_one,
title="Test article: empty keyword query",
owner=author_1,
)
medicine = models.Keyword.objects.create(word="Medicine")
arts = models.Keyword.objects.create(word="Arts")
self.journal_one.keywords.add(medicine)
self.journal_one.keywords.add(arts)

self.client.force_login(author_1)
clear_script_prefix()
response = self.client.get(
reverse(
"submission_keyword_suggestions",
kwargs={"article_id": article.pk},
),
SERVER_NAME="testserver",
)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), ["Arts", "Medicine"])

@override_settings(URL_CONFIG="domain")
def test_keyword_suggestions_can_filter_single_character_query(self):
author_1, _ = self.create_authors()
clear_cache()
article = models.Article.objects.create(
journal=self.journal_one,
title="Test article: short keyword query",
owner=author_1,
)
medicine = models.Keyword.objects.create(word="Medicine")
arts = models.Keyword.objects.create(word="Arts")
self.journal_one.keywords.add(medicine)
self.journal_one.keywords.add(arts)

self.client.force_login(author_1)
clear_script_prefix()
response = self.client.get(
reverse(
"submission_keyword_suggestions",
kwargs={"article_id": article.pk},
),
{"q": "M"},
SERVER_NAME="testserver",
)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), ["Medicine"])

@override_settings(URL_CONFIG="domain")
def test_submission_keyword_autocomplete_requires_three_characters_before_searching(self):
author_1, _ = self.create_authors()
clear_cache()
article = models.Article.objects.create(
journal=self.journal_one,
title="Test article: keyword autocomplete threshold",
owner=author_1,
)

self.client.force_login(author_1)
clear_script_prefix()
response = self.client.get(
reverse("submit_info", kwargs={"article_id": article.pk}),
SERVER_NAME="testserver",
)

self.assertEqual(response.status_code, 200)
self.assertContains(response, "minLength: 3")
self.assertNotContains(response, "showAutocompleteOnFocus: true")

@override_settings(URL_CONFIG="domain")
def test_keyword_suggestions_available_for_editor_on_submitted_article(self):
article = helpers.create_article(self.journal_one)
article.stage = models.STAGE_UNDER_REVIEW
article.save()
medicine = models.Keyword.objects.create(word="Medicine")
self.journal_one.keywords.add(medicine)

self.client.force_login(self.editor)
clear_script_prefix()
response = self.client.get(
reverse(
"submission_keyword_suggestions",
kwargs={"article_id": article.pk},
),
{"q": "Me"},
SERVER_NAME="testserver",
)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), ["Medicine"])

def test_article_issue_title(self):
from utils.testing import helpers

Expand Down
5 changes: 5 additions & 0 deletions src/submission/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
urlpatterns = [
re_path(r"^start/$", views.start, name="submission_start"),
re_path(r"^(?P<type>[-\w.]+)/start/$", views.start, name="submission_start"),
re_path(
r"^(?P<article_id>\d+)/keywords/suggest/$",
views.keyword_suggestions,
name="submission_keyword_suggestions",
),
re_path(r"^(?P<article_id>\d+)/info/$", views.submit_info, name="submit_info"),
re_path(
r"^(?P<article_id>\d+)/authors/$", views.submit_authors, name="submit_authors"
Expand Down
32 changes: 30 additions & 2 deletions src/submission/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.db.models import Q
from django.http import HttpResponse, Http404
from django.http import HttpResponse, Http404, JsonResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone, translation
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.views.decorators.http import require_POST
from django.views.decorators.http import require_GET, require_POST

from core import files, models as core_models
from core.logic import create_organization_name, reverse_with_next
Expand Down Expand Up @@ -175,6 +175,34 @@ def submit_funding(request, article_id):
return render(request, template, context)


@login_required
@decorators.submission_is_enabled
@user_can_edit_article
@submission_authorised
@require_GET
def keyword_suggestions(request, article_id):
"""
Returns a small list of matching journal keywords for submission forms.
"""
get_object_or_404(
models.Article,
pk=article_id,
journal=request.journal,
)
search_term = request.GET.get("q", "").strip()

matches = request.journal.keywords.all()

if search_term:
matches = matches.filter(
word__istartswith=search_term,
)

matches = matches.order_by("word").values_list("word", flat=True)[:10]

return JsonResponse(list(matches), safe=False)


@login_required
@decorators.submission_is_enabled
@article_is_not_submitted
Expand Down
Loading