From c0207e34104b532a65c2b07a4e30471574b47304 Mon Sep 17 00:00:00 2001 From: susanodd Date: Mon, 24 Jun 2024 22:22:52 +0200 Subject: [PATCH] #1268: Update lemma and annotation fields batch edit add to revision history. Give feedback when update not possible --- media/js/glosses_toggle_edit.js | 52 +++++- signbank/dictionary/adminviews.py | 5 +- signbank/dictionary/batch_edit.py | 158 +++++++++++++++++- .../dictionary/admin_batch_edit_view.html | 72 +++++--- signbank/dictionary/update.py | 14 ++ signbank/dictionary/urls.py | 4 + signbank/dictionary/views.py | 24 ++- 7 files changed, 291 insertions(+), 38 deletions(-) diff --git a/media/js/glosses_toggle_edit.js b/media/js/glosses_toggle_edit.js index ab546b22a..1ed23b0e4 100644 --- a/media/js/glosses_toggle_edit.js +++ b/media/js/glosses_toggle_edit.js @@ -112,6 +112,23 @@ function toggle_locprim(data) { var cell = ""+locprim+""; hCell.html(cell); } + +function toggle_language_fields(data) { + if ($.isEmptyObject(data)) { + return; + }; + var glossid = data.glossid; + var errors = data.errors; + var errors_lookup = '#errors_' + glossid; + var errorsElt = $(errors_lookup); + var glossCell = "ERRORS "; + errorsElt.html(glossCell); +} + $(document).ready(function() { // setup required for Ajax POST @@ -187,7 +204,6 @@ $(document).ready(function() { $('.quick_handedness').click(function(e) { e.preventDefault(); - console.log('handedness'); var glossid = $(this).attr('value'); var handedness = $(this).attr("data-handedness"); $.ajax({ @@ -201,7 +217,6 @@ $(document).ready(function() { $('.quick_domhndsh').click(function(e) { e.preventDefault(); - console.log('domhndsh'); var glossid = $(this).attr('value'); var domhndsh = $(this).attr("data-domhndsh"); $.ajax({ @@ -215,7 +230,6 @@ $(document).ready(function() { $('.quick_subhndsh').click(function(e) { e.preventDefault(); - console.log('subhndsh'); var glossid = $(this).attr('value'); var subhndsh = $(this).attr("data-subhndsh"); $.ajax({ @@ -229,7 +243,6 @@ $(document).ready(function() { $('.quick_locprim').click(function(e) { e.preventDefault(); - console.log('locprim'); var glossid = $(this).attr('value'); var locprim = $(this).attr("data-locprim"); $.ajax({ @@ -240,4 +253,35 @@ $(document).ready(function() { success : toggle_locprim }); }); + + $('.quick_language_fields').click(function(e) + { + e.preventDefault(); + var glossid = $(this).attr('value'); + var errors_lookup = '#errors_' + glossid; + $(errors_lookup).empty() + var update = { 'csrfmiddlewaretoken': csrf_token }; + for (var i=0; i < language_2chars.length; i++) { + var lang2char = language_2chars[i]; + var lemma_field_key = 'lemma_' + glossid + '_'+ lang2char; + var lemma_field_lookup = '#'+lemma_field_key; + var lemma_field_value = $(lemma_field_lookup).val(); + if (lemma_field_value != '') { + update[lemma_field_key] = lemma_field_value; + } + var annotation_field_key = 'annotation_' + glossid + '_'+ lang2char; + var annotation_field_lookup = '#'+annotation_field_key; + var annotation_field_value = $(annotation_field_lookup).val(); + if (annotation_field_value != '') { + update[annotation_field_key] = annotation_field_value; + } + } + $.ajax({ + url : url + "/dictionary/update/toggle_language_fields/" + glossid, + type: 'POST', + data: update, + datatype: "json", + success : toggle_language_fields + }); + }); }); diff --git a/signbank/dictionary/adminviews.py b/signbank/dictionary/adminviews.py index 631e1d73d..480341f35 100755 --- a/signbank/dictionary/adminviews.py +++ b/signbank/dictionary/adminviews.py @@ -6515,10 +6515,13 @@ def get_context_data(self, **kwargs): if not selected_datasets or selected_datasets.count() > 1: dataset_languages = Language.objects.filter(id=get_default_language_id()) else: - dataset_languages = get_dataset_languages(selected_datasets).order_by('id') + dataset_languages = get_dataset_languages(selected_datasets).order_by('-id') context['dataset_languages'] = dataset_languages + language_2chars = [str(language.language_code_2char) for language in dataset_languages] + context['language_2chars'] = language_2chars + search_form = KeyMappingSearchForm(self.request.GET, languages=dataset_languages) context['searchform'] = search_form diff --git a/signbank/dictionary/batch_edit.py b/signbank/dictionary/batch_edit.py index e7f2ec7f6..864dcbb34 100644 --- a/signbank/dictionary/batch_edit.py +++ b/signbank/dictionary/batch_edit.py @@ -14,7 +14,7 @@ def internal_batch_update_fields_for_gloss(gloss): - languages = gloss.lemma.dataset.translation_languages + languages = gloss.lemma.dataset.translation_languages.all() gloss_prefix = str(gloss.id) + '_' internal_batch_fields = [] for language in languages: @@ -32,6 +32,37 @@ def internal_batch_update_fields_for_gloss(gloss): return internal_batch_fields +def get_value_dict(request, gloss): + internal_language_fields = internal_batch_update_fields_for_gloss(gloss) + value_dict = dict() + for field in internal_language_fields: + if field in request.POST.keys(): + value = request.POST.get(field, '') + value_dict[field] = value.strip() + return value_dict + + +def get_gloss_language_fields(gloss): + gloss_prefix = str(gloss.id) + '_' + dataset_languages = gloss.dataset.translation_languages.all() + language_fields_dict = dict() + for language in dataset_languages: + lemmaidglosstranslations = gloss.lemma.lemmaidglosstranslation_set.filter(language=language) + field_name = BatchEditForm.gloss_lemma_field_prefix + gloss_prefix + language.language_code_2char + if lemmaidglosstranslations.count() > 0: + language_fields_dict[field_name] = lemmaidglosstranslations.first().text + else: + language_fields_dict[field_name] = '' + for language in dataset_languages: + annotationidglosstranslation = gloss.annotationidglosstranslation_set.filter(language=language) + field_name = BatchEditForm.gloss_annotation_field_prefix + gloss_prefix + language.language_code_2char + if annotationidglosstranslation.count() > 0: + language_fields_dict[field_name] = annotationidglosstranslation.first().text + else: + language_fields_dict[field_name] = '' + return language_fields_dict + + def get_sense_numbers(gloss): senses_mapping = dict() glosssenses = GlossSense.objects.filter(gloss=gloss).order_by('order') @@ -66,6 +97,102 @@ def add_gloss_update_to_revision_history(user, gloss, field, oldvalue, newvalue) revision.save() +def update_lemma_translation(gloss, language_code_2char, new_value): + language = Language.objects.get(language_code_2char=language_code_2char) + try: + lemma_idgloss_translation = LemmaIdglossTranslation.objects.get(lemma=gloss.lemma, language=language) + except ObjectDoesNotExist: + # create an empty translation for this gloss and language + lemma_idgloss_translation = LemmaIdglossTranslation(lemma=gloss.lemma, language=language) + + try: + lemma_idgloss_translation.text = new_value + lemma_idgloss_translation.save() + except (DatabaseError, ValidationError): + pass + + +def update_annotation_translation(gloss, language_code_2char, new_value): + language = Language.objects.get(language_code_2char=language_code_2char) + try: + annotation_idgloss_translation = AnnotationIdglossTranslation.objects.get(gloss=gloss, language=language) + except ObjectDoesNotExist: + # create an empty translation for this gloss and language + annotation_idgloss_translation = AnnotationIdglossTranslation(gloss=gloss, language=language) + + try: + annotation_idgloss_translation.text = new_value + annotation_idgloss_translation.save() + except (DatabaseError, ValidationError): + pass + + +def check_constraints_on_gloss_language_fields(gloss, value_dict): + gloss_prefix = str(gloss.id) + '_' + + errors = [] + + dataset = gloss.lemma.dataset + lemmaidglosstranslations = {} + for language in dataset.translation_languages.all(): + lemma_key = 'lemma_' + gloss_prefix + language.language_code_2char + if lemma_key in value_dict.keys(): + lemmaidglosstranslations[language] = value_dict[lemma_key] + + if lemmaidglosstranslations: + # the lemma translations are being updated + lemma_group_glossset = Gloss.objects.filter(lemma=gloss.lemma) + if lemma_group_glossset.count() > 1: + more_than_one_gloss_in_lemma_group = gettext("More than one gloss in lemma group.") + errors.append(more_than_one_gloss_in_lemma_group) + + annotationidglosstranslations = {} + for language in dataset.translation_languages.all(): + annotation_key = 'annotation__' + gloss_prefix + language.language_code_2char + if annotation_key in value_dict.keys(): + annotationidglosstranslations[language] = value_dict[annotation_key] + + # check lemma translations + lemmas_per_language_translation = dict() + for language, lemmaidglosstranslation_text in lemmaidglosstranslations.items(): + lemmatranslation_for_this_text_language = LemmaIdglossTranslation.objects.filter( + lemma__dataset=dataset, language=language, text__iexact=lemmaidglosstranslation_text) + lemmas_per_language_translation[language] = lemmatranslation_for_this_text_language + + existing_lemmas = [] + for language, lemmas in lemmas_per_language_translation.items(): + if lemmas.count(): + e5 = gettext("Lemma ID Gloss") + " (" + language.name + ") " + gettext("already exists.") + errors.append(e5) + if lemmas.first().lemma.pk not in existing_lemmas: + existing_lemmas.append(lemmas.first().lemma.pk) + if len(existing_lemmas) > 1: + e6 = gettext("Lemma translations refer to different already existing lemmas.") + errors.append(e6) + + # check annotation translations + annotations_per_language_translation = dict() + for language, annotationidglosstranslation_text in annotationidglosstranslations.items(): + annotationtranslation_for_this_text_language = AnnotationIdglossTranslation.objects.filter( + gloss__lemma__dataset=dataset, language=language, text__iexact=annotationidglosstranslation_text) + annotations_per_language_translation[language] = annotationtranslation_for_this_text_language + + existing_glosses = [] + for language, annotations in annotations_per_language_translation.items(): + if annotations.count(): + this_annotation = annotations.first() + if this_annotation.id not in existing_glosses and this_annotation.id != gloss.id: + existing_glosses.append(this_annotation.id) + e7 = gettext('Annotation ID Gloss') + " (" + language.name + ') ' + gettext( + 'already exists.') + errors.append(e7) + if len(existing_glosses) > 1: + e6 = gettext("Annotation translations refer to different already existing glosses.") + errors.append(e6) + + return errors + + def batch_edit_update_gloss(request, glossid): """Update the gloss fields""" if not request.user.is_authenticated: @@ -74,4 +201,33 @@ def batch_edit_update_gloss(request, glossid): if not request.user.has_perm('dictionary.change_gloss'): return {} + result = dict() + gloss = get_object_or_404(Gloss, id=glossid) + + value_dict = get_value_dict(request, gloss) + language_fields_dict = get_gloss_language_fields(gloss) + fields_to_update = dict() + for key in value_dict.keys(): + if value_dict[key] != language_fields_dict[key]: + fields_to_update[key] = value_dict[key] + + errors = check_constraints_on_gloss_language_fields(gloss, fields_to_update) + if errors: + result['glossid'] = glossid + result['errors'] = errors + return result + + for key in fields_to_update.keys(): + original_value = language_fields_dict[key] + newvalue = fields_to_update[key] + language = key[-2:] + if key.startswith('lemma'): + update_lemma_translation(gloss, key[-2:], newvalue) + add_gloss_update_to_revision_history(request.user, gloss, 'lemma_'+language, original_value, newvalue) + elif key.startswith('annotation'): + update_annotation_translation(gloss, key[-2:], newvalue) + add_gloss_update_to_revision_history(request.user, gloss, 'annotation_'+language, original_value, newvalue) + gloss.lastUpdated = DT.datetime.now(tz=get_current_timezone()) + gloss.save() + return result diff --git a/signbank/dictionary/templates/dictionary/admin_batch_edit_view.html b/signbank/dictionary/templates/dictionary/admin_batch_edit_view.html index 019097a50..47007e6ff 100644 --- a/signbank/dictionary/templates/dictionary/admin_batch_edit_view.html +++ b/signbank/dictionary/templates/dictionary/admin_batch_edit_view.html @@ -23,6 +23,7 @@ var multiple_select_fields = {{MULTIPLE_SELECT_GLOSS_FIELDS|safe}}; var query_parameters_keys = {{query_parameters_keys|safe}}; var query_parameters_dict = {{query_parameters|safe}}; +var language_2chars = {{language_2chars|safe}}; @@ -271,7 +272,7 @@

{% trans "On initial view, the most recent query results are shown." %}

{% trans "Gloss" %} - {% trans "Fields" %} + {% trans "Fields" %} {% trans "Quick Toggles" %} {% trans "Tags" %} @@ -344,53 +345,63 @@

{% trans "On initial view, the most recent query results are shown." %}

{% csrf_token %} - {% for dataset_lang in dataset_languages %} - {% with gloss.lemma|get_lemma_idgloss_translation:dataset_lang as lemmaidglosstranslation %} - + {% endfor %} + + + {% for dataset_lang in dataset_languages %} + {% with gloss.lemma|get_lemma_idgloss_translation:dataset_lang as lemmaidglosstranslation %} - {% endwith %} {% endfor %} - {% for dataset_lang in dataset_languages %} - {% with gloss|get_annotation_idgloss_translation_no_default:dataset_lang as annotationidglosstranslation %} + - + {% endfor %} + + + {% for dataset_lang in dataset_languages %} + {% with gloss|get_annotation_idgloss_translation_no_default:dataset_lang as annotationidglosstranslation %} - {% endwith %} {% endfor %} + {% with gloss|get_senses_mapping as senses_mapping %} {% for order, translations in senses_mapping.items %} - {% for sense_lang, sensetranslation in translations.items %} - {% with sense_lang|upper as language %} - + {% endfor %} + + + {% for sense_lang, sensetranslation in translations.items %} - - {% endwith %} {% endfor %} + {% endfor %} {% endwith %} @@ -431,6 +442,25 @@

{% trans "On initial view, the most recent query results are shown." %}

- {% with gloss.semField.all as semantic_fields_list %} - - {% endwith %} diff --git a/signbank/dictionary/update.py b/signbank/dictionary/update.py index 374cdeb08..3de3d6171 100755 --- a/signbank/dictionary/update.py +++ b/signbank/dictionary/update.py @@ -40,6 +40,7 @@ mapping_toggle_wordclass, mapping_toggle_namedentity, mapping_toggle_handedness, mapping_toggle_domhndsh, mapping_toggle_subhndsh, mapping_toggle_locprim) +from signbank.dictionary.batch_edit import batch_edit_update_gloss def show_error(request, translated_message, form, dataset_languages): @@ -3573,6 +3574,19 @@ def toggle_locprim(request, glossid, locprim): return JsonResponse(result) +@permission_required('dictionary.change_gloss') +def toggle_language_fields(request, glossid): + if not request.user.is_authenticated: + return JsonResponse({}) + + if not request.user.has_perm('dictionary.change_gloss'): + return JsonResponse({}) + + result = batch_edit_update_gloss(request, glossid) + + return JsonResponse(result) + + @permission_required('dictionary.change_gloss') def add_affiliation(request, glossid): """View to add an affiliation to a gloss""" diff --git a/signbank/dictionary/urls.py b/signbank/dictionary/urls.py index 6ab5a49f4..e65b14f20 100755 --- a/signbank/dictionary/urls.py +++ b/signbank/dictionary/urls.py @@ -102,6 +102,10 @@ signbank.dictionary.update.toggle_locprim, name='toggle_locprim'), + re_path(r'^update/toggle_language_fields/(?P\d+)$', + signbank.dictionary.update.toggle_language_fields, + name='toggle_language_fields'), + # The next one does not have a permission check because it should be accessible from a cronjob re_path(r'^update_ecv/', GlossListView.as_view(only_export_ecv=True)), re_path(r'^update/variants_of_gloss/$', signbank.dictionary.update.variants_of_gloss, name='variants_of_gloss'), diff --git a/signbank/dictionary/views.py b/signbank/dictionary/views.py index be7dd0102..824c0b160 100644 --- a/signbank/dictionary/views.py +++ b/signbank/dictionary/views.py @@ -2588,6 +2588,14 @@ def gloss_revision_history(request,gloss_pk): revision_verbose_fieldname = gettext("Simultaneous Morphology") elif revision.field_name == 'blend_morphology': revision_verbose_fieldname = gettext("Blend Morphology") + elif revision.field_name.startswith('lemma'): + language_2char = revision.field_name[-2:] + language = Language.objects.get(language_code_2char=language_2char) + revision_verbose_fieldname = gettext('Lemma ID Gloss') + ' (' + language.name + ')' + elif revision.field_name.startswith('annotation'): + language_2char = revision.field_name[-2:] + language = Language.objects.get(language_code_2char=language_2char) + revision_verbose_fieldname = gettext('Annotation ID Gloss') + ' (' + language.name + ')' else: revision_verbose_fieldname = _(revision.field_name) @@ -2644,17 +2652,19 @@ def gloss_revision_history(request,gloss_pk): # this translation exists in the interface of Gloss Edit View add_command = str(_('Update')) field_name_qualification = ' (' + add_command + ')' + elif revision.field_name.startswith('lemma') or revision.field_name.startswith('annotation'): + field_name_qualification = '' else: field_name_qualification = ' (' + revision.field_name + ')' revision_dict = { 'is_tag': revision.field_name == 'Tags', - 'gloss' : revision.gloss, - 'user' : revision.user, - 'time' : revision.time, - 'field_name' : revision_verbose_fieldname, - 'field_name_qualification' : field_name_qualification, - 'old_value' : check_value_to_translated_human_value(revision.field_name, revision.old_value), - 'new_value' : check_value_to_translated_human_value(revision.field_name, revision.new_value) } + 'gloss': revision.gloss, + 'user': revision.user, + 'time': revision.time, + 'field_name': revision_verbose_fieldname, + 'field_name_qualification': field_name_qualification, + 'old_value': check_value_to_translated_human_value(revision.field_name, revision.old_value), + 'new_value': check_value_to_translated_human_value(revision.field_name, revision.new_value) } revisions.append(revision_dict) return render(request, 'dictionary/gloss_revision_history.html',
- {% trans "Lemma" %} ({{dataset_lang.language_code_2char|upper}}) + {% for dataset_lang in dataset_languages %} + + {% trans "Lemma" %} ({{dataset_lang.name}})
- {% trans "Annotation" %} ({{dataset_lang.language_code_2char|upper}}) + {% for dataset_lang in dataset_languages %} + + {% trans "Annotation" %} ({{dataset_lang.name}})
- {% trans "Sense" %} #{{order}} ({{language}}) + {% for dataset_lang in dataset_languages %} + + {% trans "Sense" %} #{{order}} ({{dataset_lang.name}})
+
+
{% trans "Language Fields" %} +
+
+
+ + + +

+ +
+
+
+
{% trans "Tags" %} @@ -531,14 +561,6 @@

{% trans "On initial view, the most recent query results are shown." %}

{% for tag in tag_list %}{{tag.name|underscore_to_space}} {% endfor %}
-
{% for sf in semantic_fields_list %}{{sf.name}} - {% if not forloop.last %}, {% endif %}{% endfor %} -
-