diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f569cd..a751a36e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ UNRELEASED * [ [#353](https://github.com/digitalfabrik/lunes-cms/issues/353) ] Filter feedback by creator of related objects * [ [#468](https://github.com/digitalfabrik/lunes-cms/issues/468) ] Excel list out of existing vocabulary in cms * [ [#534](https://github.com/digitalfabrik/lunes-cms/issues/534) ] Adjust form appearance for small screens +* [ [#470](https://github.com/digitalfabrik/lunes-cms/issues/470) ] Inform users of word duplication in document form 2024.5.1 diff --git a/lunes_cms/api/utils.py b/lunes_cms/api/utils.py index 609f5613..e48e1724 100644 --- a/lunes_cms/api/utils.py +++ b/lunes_cms/api/utils.py @@ -4,10 +4,13 @@ from django.core.exceptions import PermissionDenied from django.db.models import Count, Q +from django.http import JsonResponse +from django.utils.translation import ugettext_lazy as _ + from rest_framework import routers -from ..cms.models import Discipline, GroupAPIKey -from ..cms.utils import get_child_count +from ..cms.models import Discipline, GroupAPIKey, Document +from ..cms.utils import get_child_count, document_to_string class OptionalSlashRouter(routers.DefaultRouter): @@ -151,3 +154,38 @@ def check_group_object_permissions(request, group_id): api_key_object = GroupAPIKey.get_from_token(key) if api_key_object.group_id != int(group_id): raise PermissionDenied() + + +def find_duplicates_for_word(request, word): + """ + Function to find existing words that match the input in the "word" field of document + + :param request: current request + :type request: HttpRequest + + :param group_id: input in the "word"field of document + :type group_id: str + + :return: Whether any duplicate was found, and some details of the word if found + :rtype: JsonResponse + + """ + + if duplicate := Document.objects.filter(word=word).first(): + training_sets_description = _("This word is assigned to no training set.") + if training_sets := duplicate.training_sets.all(): + training_sets_description = ", ".join( + str(training_set) for training_set in training_sets + ) + + result = { + "message": _("This word is already registered in the system."), + "word": document_to_string(duplicate) + " (" + duplicate.word_type + ")", + "definition": _("Definition: ") + duplicate.definition + if duplicate.definition + else _("Definition: ") + _("No definition is provided for this word."), + "training_sets": _("Training sets: ") + training_sets_description, + } + + return JsonResponse(result) + return JsonResponse({"message": _("This word is not yet registered in the system")}) diff --git a/lunes_cms/api/v1/urls.py b/lunes_cms/api/v1/urls.py index 9dc3ce80..e0450fd3 100644 --- a/lunes_cms/api/v1/urls.py +++ b/lunes_cms/api/v1/urls.py @@ -4,7 +4,7 @@ from django.urls import include, path from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView -from ..utils import OptionalSlashRouter +from ..utils import OptionalSlashRouter, find_duplicates_for_word from . import views #: The namespace for this URL config (see :attr:`django.urls.ResolverMatch.app_name`) @@ -59,4 +59,9 @@ ), name="swagger-ui", ), + path( + "search_duplicate/", + find_duplicates_for_word, + name="search_duplicate", + ), ] diff --git a/lunes_cms/cms/fixtures/test_data.json b/lunes_cms/cms/fixtures/test_data.json index eb7a37d3..f8350704 100644 --- a/lunes_cms/cms/fixtures/test_data.json +++ b/lunes_cms/cms/fixtures/test_data.json @@ -6891,6 +6891,7 @@ "word_type": "Nomen", "word": "Ei", "singular_article": 3, + "definition": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua", "audio": "audio/ei.mp3", "creation_date": "2021-08-09T13:52:38.131Z", "created_by": null, @@ -13411,6 +13412,20 @@ "creator_is_admin": true } }, + { + "model": "cms.document", + "pk": 2332, + "fields": { + "word_type": "Verb", + "word": "implementieren", + "singular_article": 0, + "definition": "in ein bestehendes Computersystem einsetzen, einbauen und so ein funktionsfähiges Programm erstellen", + "example_sentence": "eine neue Software implementieren", + "creation_date": "2021-04-13T15:58:10.468Z", + "created_by": null, + "creator_is_admin": true + } + }, { "model": "cms.alternativeword", "pk": 1, diff --git a/lunes_cms/locale/de/LC_MESSAGES/django.po b/lunes_cms/locale/de/LC_MESSAGES/django.po index eb9973d1..75d29862 100644 --- a/lunes_cms/locale/de/LC_MESSAGES/django.po +++ b/lunes_cms/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-06-11 15:29+0000\n" +"POT-Creation-Date: 2024-06-19 14:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,6 +21,30 @@ msgstr "" msgid "API" msgstr "API" +#: api/utils.py:175 +msgid "This word is assigned to no training set." +msgstr "Das word ist zu keinem Modul zugeordnet." + +#: api/utils.py:182 +msgid "This word is already registered in the system." +msgstr "Das Wort ist schon im System hinterlegt." + +#: api/utils.py:184 api/utils.py:186 +msgid "Definition: " +msgstr "Definition: " + +#: api/utils.py:186 +msgid "No definition is provided for this word." +msgstr "Für dieses Wort ist keine Definition hinterlegt." + +#: api/utils.py:187 +msgid "Training sets: " +msgstr "Module: " + +#: api/utils.py:191 +msgid "This word is not yet registered in the system" +msgstr "Das Wort ist noch nicht im System hinterlegt." + #: api/v1/serializers/feedback_serializer.py:22 msgid "" "The content type must be either 'discipline', 'training set' or 'document'." diff --git a/lunes_cms/static/js/toggle_plural_field.js b/lunes_cms/static/js/toggle_plural_field.js index 19e4cea8..cb110a57 100644 --- a/lunes_cms/static/js/toggle_plural_field.js +++ b/lunes_cms/static/js/toggle_plural_field.js @@ -2,6 +2,7 @@ if (!$) { $ = django.jQuery; } +//Toggle the plural field depending on the chosen word type $(document).ready(() => { $("#id_word_type").change((event) => $("#id_plural") @@ -11,6 +12,7 @@ $(document).ready(() => { $("#id_word_type").trigger("change"); }); +//Toggle the drop down for grammatical gender depending on the chosen word type $(document).ready(() => { $("#id_word_type").change((event) => $("#id_grammatical_gender") @@ -19,3 +21,77 @@ $(document).ready(() => { ); $("#id_word_type").trigger("change"); }); + +function removeDuplicationCheckMessage(){ + var existingMessage = document.getElementById("result"); + + if (existingMessage) { + existingMessage.remove(); + } +} + +function showDuplicates(data, parent) { + removeDuplicationCheckMessage(); + + result = document.createElement("div"); + result.setAttribute("id", "result"); + + + if (data["word"]) { + // If there is a duplicated word, use orange background + result.style.backgroundColor = "#ffbb4a"; + result.style.padding = "25px" + + // Show alert message + messageBox = document.createElement("div"); + message = document.createTextNode(data["message"]); + messageBox.append(message); + result.append(messageBox); + // Show the duplicated word with its word type + wordBox = document.createElement("div"); + word = document.createTextNode(data["word"]); + wordBox.style.fontWeight = "bold"; + wordBox.append(word); + result.append(wordBox); + // Show its definition too for more detail + definitionBox = document.createElement("div"); + definition = document.createTextNode(data["definition"]); + definitionBox.append(definition); + result.append(definitionBox); + // Show related training sets + trainingSetsBox = document.createElement("div"); + trainingSets = document.createTextNode(data["training_sets"]); + trainingSetsBox.append(trainingSets); + result.append(trainingSetsBox); + } else { + // If there is no duplicate, use green background + result.style.backgroundColor = "#72f399"; + result.style.padding = "25px" + // Show message that no duplicate was found + messageBox = document.createElement("div"); + message = document.createTextNode(data["message"]); + messageBox.append(message); + result.append(messageBox); + } + + parent.prepend(result); +} + + +$(document).ready(() => { + $("#id_word").change((event) => { + if ($(event.target).val().length > 0) { + $.ajax({ + type: 'GET', + url: '/api/search_duplicate/' + $(event.target).val(), + dataType: "json", + success: function(data) { + showDuplicates(data, $(event.target).closest(".card-body")); + } + }) + } else { + removeDuplicationCheckMessage(); + } + }); + $("#id_word_type").trigger("change"); +}); \ No newline at end of file diff --git a/tests/api/api_config.py b/tests/api/api_config.py index bf6f6405..d6309444 100644 --- a/tests/api/api_config.py +++ b/tests/api/api_config.py @@ -388,6 +388,26 @@ }, ] + +SEARCH_DUPLICATE_ENDPOINTS = [ + { + "endpoint": "/api/search_duplicate/implementieren", + "expected_result": "tests/api/expected-results/duplicate_implementieren.json", + }, + { + "endpoint": "/api/search_duplicate/Schere", + "expected_result": "tests/api/expected-results/duplicate_Schere.json", + }, + { + "endpoint": "/api/search_duplicate/Ei", + "expected_result": "tests/api/expected-results/duplicate_Ei.json", + }, + { + "endpoint": "/api/search_duplicate/neueswort", + "expected_result": "tests/api/expected-results/duplicate_neueswort.json", + }, +] + #: The API endpoints API_ENDPOINTS = ( DISCIPLINE_ENDPOINTS @@ -396,6 +416,7 @@ + GROUP_ENDPOINTS + FEEDBACK_ENDPOINTS + SPONSOR_ENDPOINTS + + SEARCH_DUPLICATE_ENDPOINTS ) #: Convert the dicts to tuples with a fixed length diff --git a/tests/api/expected-results/duplicate_Ei.json b/tests/api/expected-results/duplicate_Ei.json new file mode 100644 index 00000000..2ccdfaec --- /dev/null +++ b/tests/api/expected-results/duplicate_Ei.json @@ -0,0 +1,6 @@ +{ + "message": "This word is already registered in the system.", + "word": "📷 (das) Ei (Nomen)", + "definition": "Definition: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua", + "training_sets": "Training sets: Zutaten" +} \ No newline at end of file diff --git a/tests/api/expected-results/duplicate_Schere.json b/tests/api/expected-results/duplicate_Schere.json new file mode 100644 index 00000000..579f129a --- /dev/null +++ b/tests/api/expected-results/duplicate_Schere.json @@ -0,0 +1,6 @@ +{ + "message": "This word is already registered in the system.", + "word": "📷 (die) Schere (Nomen)", + "definition": "Definition: No definition is provided for this word.", + "training_sets": "Training sets: Grundlagen Werkzeuge, Grundlagen Rezeption" +} \ No newline at end of file diff --git a/tests/api/expected-results/duplicate_implementieren.json b/tests/api/expected-results/duplicate_implementieren.json new file mode 100644 index 00000000..c1109054 --- /dev/null +++ b/tests/api/expected-results/duplicate_implementieren.json @@ -0,0 +1,6 @@ +{ + "message": "This word is already registered in the system.", + "word": "⚠ (keiner) implementieren (Verb)", + "definition": "Definition: in ein bestehendes Computersystem einsetzen, einbauen und so ein funktionsfähiges Programm erstellen", + "training_sets": "Training sets: This word is assigned to no training set." +} \ No newline at end of file diff --git a/tests/api/expected-results/duplicate_neueswort.json b/tests/api/expected-results/duplicate_neueswort.json new file mode 100644 index 00000000..cf0bf45c --- /dev/null +++ b/tests/api/expected-results/duplicate_neueswort.json @@ -0,0 +1,3 @@ +{ + "message": "This word is not yet registered in the system" +} \ No newline at end of file