diff --git a/media/js/glosses_toggle_edit.js b/media/js/glosses_toggle_edit.js index c4c6d3351..397815586 100644 --- a/media/js/glosses_toggle_edit.js +++ b/media/js/glosses_toggle_edit.js @@ -65,6 +65,30 @@ function toggle_namedentity(data) { neCell.html(cell); } +function toggle_handedness(data) { + if ($.isEmptyObject(data)) { + return; + }; + var glossid = data.glossid; + var handedness = data.handedness; + var hCell = $("#handedness_cell_"+glossid); + $(hCell).empty(); + var cell = ""+handedness+""; + hCell.html(cell); +} + +function toggle_domhndsh(data) { + if ($.isEmptyObject(data)) { + return; + }; + var glossid = data.glossid; + var domhndsh = data.domhndsh; + var hCell = $("#domhndsh_cell_"+glossid); + $(hCell).empty(); + var cell = ""+domhndsh+""; + hCell.html(cell); +} + $(document).ready(function() { // setup required for Ajax POST @@ -137,4 +161,33 @@ $(document).ready(function() { success : toggle_namedentity }); }); + $('.quick_handedness').click(function(e) + { + e.preventDefault(); + console.log('handedness'); + var glossid = $(this).attr('value'); + var handedness = $(this).attr("data-handedness"); + $.ajax({ + url : url + "/dictionary/update/toggle_handedness/" + glossid + "/" + handedness, + type: 'POST', + data: { 'csrfmiddlewaretoken': csrf_token }, + datatype: "json", + success : toggle_handedness + }); + }); + $('.quick_domhndsh').click(function(e) + { + e.preventDefault(); + console.log('domhndsh'); + var glossid = $(this).attr('value'); + var domhndsh = $(this).attr("data-domhndsh"); + $.ajax({ + url : url + "/dictionary/update/toggle_domhndsh/" + glossid + "/" + domhndsh, + type: 'POST', + data: { 'csrfmiddlewaretoken': csrf_token }, + datatype: "json", + success : toggle_domhndsh + }); + }); + }); diff --git a/signbank/dictionary/adminviews.py b/signbank/dictionary/adminviews.py index 948c6d13d..5d102bb88 100755 --- a/signbank/dictionary/adminviews.py +++ b/signbank/dictionary/adminviews.py @@ -6495,6 +6495,129 @@ def get_queryset(self): return glossesXsenses +class BatchEditView(ListView): + + model = Gloss + template_name = 'dictionary/admin_batch_edit_view.html' + paginate_by = 25 + search_type = 'sign' + query_parameters = dict() + + def get(self, request, *args, **kwargs): + return super(BatchEditView, self).get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(BatchEditView, self).get_context_data(**kwargs) + + selected_datasets = get_selected_datasets_for_user(self.request.user) + context['selected_datasets'] = selected_datasets + + 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') + + context['dataset_languages'] = dataset_languages + + search_form = KeyMappingSearchForm(self.request.GET, languages=dataset_languages) + + context['searchform'] = search_form + + multiple_select_gloss_fields = ['tags'] + context['MULTIPLE_SELECT_GLOSS_FIELDS'] = multiple_select_gloss_fields + + context['available_tags'] = [tag for tag in Tag.objects.all()] + + context['available_semanticfields'] = [semfield for semfield in SemanticField.objects.filter( + machine_value__gt=1).order_by('name')] + + context['available_handedness'] = [h for h in FieldChoice.objects.filter( + field='Handedness', machine_value__gt=1).order_by('name')] + + context['available_handshape'] = [wc for wc in Handshape.objects.filter( + machine_value__gt=1).order_by('name')] + + # data structures to store the query parameters in order to keep them in the form + context['query_parameters'] = json.dumps(self.query_parameters) + query_parameters_keys = list(self.query_parameters.keys()) + context['query_parameters_keys'] = json.dumps(query_parameters_keys) + + context['SHOW_DATASET_INTERFACE_OPTIONS'] = getattr(settings, 'SHOW_DATASET_INTERFACE_OPTIONS', False) + context['USE_REGULAR_EXPRESSIONS'] = getattr(settings, 'USE_REGULAR_EXPRESSIONS', False) + + # construct scroll bar + # the following retrieves language code for English (or DEFAULT LANGUAGE) + # so the sorting of the scroll bar matches the default sorting of the results in Gloss List View + + list_of_objects = self.object_list + + (interface_language, interface_language_code, + default_language, default_language_code) = get_interface_language_and_default_language_codes(self.request) + + dataset_display_languages = [] + for lang in dataset_languages: + dataset_display_languages.append(lang.language_code_2char) + if interface_language_code in dataset_display_languages: + lang_attr_name = interface_language_code + else: + lang_attr_name = default_language_code + + items = construct_scrollbar(list_of_objects, self.search_type, lang_attr_name) + self.request.session['search_results'] = items + + return context + + def get_queryset(self): + # this is a ListView for a complicated data structure + + selected_datasets = get_selected_datasets_for_user(self.request.user) + + if not selected_datasets or selected_datasets.count() > 1: + feedback_message = _('Please select a single dataset to use Batch Edit.') + messages.add_message(self.request, messages.ERROR, feedback_message) + # the query set is a list of tuples (gloss, keyword_translations, senses_groups) + return [] + + get = self.request.GET + + # multilingual + # this needs to be sorted for jquery purposes + dataset_languages = get_dataset_languages(selected_datasets).order_by('id') + + if get: + glosses_of_datasets = Gloss.none_morpheme_objects().filter(lemma__dataset__in=selected_datasets) + else: + recently_added_signs_since_date = DT.datetime.now(tz=get_current_timezone()) - RECENTLY_ADDED_SIGNS_PERIOD + glosses_of_datasets = Gloss.objects.filter(morpheme=None, lemma__dataset__in=selected_datasets).filter( + creationDate__range=[recently_added_signs_since_date, DT.datetime.now(tz=get_current_timezone())]).order_by( + 'creationDate') + + # data structure to store the query parameters in order to keep them in the form + query_parameters = dict() + + if 'tags[]' in get: + vals = get.getlist('tags[]') + if vals: + query_parameters['tags[]'] = vals + glosses_with_tag = list( + TaggedItem.objects.filter(tag__id__in=vals).values_list('object_id', flat=True)) + glosses_of_datasets = glosses_of_datasets.filter(id__in=glosses_with_tag) + if 'createdBy' in get and get['createdBy']: + get_value = get['createdBy'] + query_parameters['createdBy'] = get_value.strip() + created_by_search_string = ' '.join(get_value.strip().split()) # remove redundant spaces + glosses_of_datasets = glosses_of_datasets.annotate( + created_by=Concat('creator__first_name', V(' '), 'creator__last_name', output_field=CharField())) \ + .filter(created_by__icontains=created_by_search_string) + + # save the query parameters to a session variable + self.request.session['query_parameters'] = json.dumps(query_parameters) + self.request.session.modified = True + self.query_parameters = query_parameters + + return glosses_of_datasets + + class ToggleListView(ListView): model = Gloss diff --git a/signbank/dictionary/batch_edit.py b/signbank/dictionary/batch_edit.py new file mode 100644 index 000000000..d130c8ab5 --- /dev/null +++ b/signbank/dictionary/batch_edit.py @@ -0,0 +1,71 @@ +from django.core.exceptions import ObjectDoesNotExist + +from django.shortcuts import get_object_or_404 + +from django.contrib.auth.decorators import permission_required +from django.db import DatabaseError, IntegrityError +from django.db.transaction import TransactionManagementError + +from tagging.models import TaggedItem, Tag + +from signbank.dictionary.models import * +from signbank.dictionary.forms import * + + +def internal_batch_update_fields_for_gloss(gloss): + + languages = gloss.lemma.dataset.translation_languages + gloss_prefix = str(gloss.id) + '_' + gloss_suffix = '_' + str(gloss.id) + internal_batch_fields = [] + for language in languages: + gloss_lemma_field_name = BatchEditForm.gloss_lemma_field_prefix + gloss_prefix + language.language_code_2char + internal_batch_fields.append(gloss_lemma_field_name) + + for language in languages: + gloss_annotation_field_name = BatchEditForm.gloss_annotation_field_prefix + gloss_prefix + language.language_code_2char + internal_batch_fields.append(gloss_annotation_field_name) + + for language in languages: + gloss_sense_field_name = self.gloss_sense_field_prefix + gloss_prefix + language.language_code_2char + internal_batch_fields.append(gloss_sense_field_name) + + internal_batch_fields.append('handedness' + gloss_suffix) + internal_batch_fields.append('domhndsh' + gloss_suffix) + internal_batch_fields.append('subhndsh' + gloss_suffix) + + return internal_batch_fields + + +def get_sense_numbers(gloss): + senses_mapping = dict() + glosssenses = GlossSense.objects.filter(gloss=gloss).order_by('order') + languages = gloss.lemma.dataset.translation_languages.all() + if not glosssenses: + return [] + gloss_senses = dict() + for gs in glosssenses: + order = gs.order + sense = gs.sense + if order in gloss_senses.keys(): + continue + gloss_senses[order] = sense + for order, sense in gloss_senses.items(): + senses_mapping[order] = dict() + for language in languages: + sensetranslation = sense.senseTranslations.filter(language=language).first() + translations = sensetranslation.translations.all().order_by('index') if sensetranslation else [] + keywords_list = [translation.translation.text for translation in translations] + senses_mapping[order][language.language_code_2char] = ', '.join(keywords_list) + return senses_mapping + + +def batch_edit_update_gloss(request, glossid): + """Update the gloss fields""" + if not request.user.is_authenticated: + return {} + + if not request.user.has_perm('dictionary.change_gloss'): + return {} + + gloss = get_object_or_404(Gloss, id=glossid) diff --git a/signbank/dictionary/forms.py b/signbank/dictionary/forms.py index ad244a6dc..22415086c 100755 --- a/signbank/dictionary/forms.py +++ b/signbank/dictionary/forms.py @@ -1,17 +1,19 @@ from colorfield.fields import ColorWidget from django import forms -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError, ObjectDoesNotExist + from django.utils.translation import override, gettext_lazy as _, get_language from django.db import OperationalError, ProgrammingError from django.db.transaction import atomic from signbank.video.fields import VideoUploadToFLVField -from signbank.dictionary.models import Dialect, Gloss, Morpheme, Definition, Relation, RelationToForeignSign, \ - MorphologyDefinition, OtherMedia, Handshape, SemanticField, DerivationHistory, \ - AnnotationIdglossTranslation, Dataset, FieldChoice, LemmaIdgloss, \ - LemmaIdglossTranslation, Translation, Keyword, Language, SignLanguage, \ - QueryParameterFieldChoice, SearchHistory, QueryParameter, \ - QueryParameterMultilingual, QueryParameterHandshape, SemanticFieldTranslation, \ - ExampleSentence, Affiliation, AffiliatedUser, AffiliatedGloss +from signbank.dictionary.models import (Dialect, Gloss, Morpheme, Definition, Relation, RelationToForeignSign, + MorphologyDefinition, OtherMedia, Handshape, SemanticField, DerivationHistory, + AnnotationIdglossTranslation, Dataset, FieldChoice, LemmaIdgloss, + LemmaIdglossTranslation, Translation, Keyword, Language, SignLanguage, + QueryParameterFieldChoice, SearchHistory, QueryParameter, + QueryParameterMultilingual, QueryParameterHandshape, SemanticFieldTranslation, + ExampleSentence, Affiliation, AffiliatedUser, AffiliatedGloss, GlossSense, + SenseTranslation) from signbank.dictionary.field_choices import fields_to_fieldcategory_dict from django.conf import settings from tagging.models import Tag @@ -52,7 +54,7 @@ class GlossCreateForm(forms.ModelForm): """Form for creating a new gloss from scratch""" gloss_create_field_prefix = "glosscreate_" - languages = None # Languages to use for annotation idgloss translations + languages = None # Languages to use for annotation idgloss translations user = None last_used_dataset = None @@ -1420,3 +1422,73 @@ def __init__(self, *args, **kwargs): choices=[(0, '-')], required=False, widget=Select2) self.fields['negative'].choices = [('0', '-'), ('yes', _('Yes')), ('no', _('No'))] + + +class BatchEditForm(forms.Form): + """Specify updates for Gloss fields""" + + languages = None # Languages to use for lemma, annotation translations + user = None + gloss = None + gloss_lemma_field_prefix = "lemma_" + gloss_annotation_field_prefix = "annotation_" + gloss_sense_field_prefix = "sense_" + + def __init__(self, queryDict, *args, **kwargs): + self.gloss = kwargs.pop('gloss') + self.languages = kwargs.pop('languages') + self.user = kwargs.pop('user') + + super(BatchEditForm, self).__init__(queryDict, *args, **kwargs) + + for language in self.languages: + gloss_lemma_field_name = self.gloss_lemma_field_prefix + language.language_code_2char + self.fields[gloss_lemma_field_name] = forms.CharField(label=_("Lemma")+(" (%s)" % language.name)) + if gloss_lemma_field_name in queryDict: + self.fields[gloss_lemma_field_name].value = queryDict[gloss_lemma_field_name] + + for language in self.languages: + gloss_annotation_field_name = self.gloss_annotation_field_prefix + language.language_code_2char + self.fields[gloss_annotation_field_name] = forms.CharField(label=_("Gloss")+(" (%s)" % language.name)) + if gloss_annotation_field_name in queryDict: + self.fields[gloss_annotation_field_name].value = queryDict[gloss_annotation_field_name] + + gloss_senses = GlossSense.objects.filter(gloss=self.gloss).order_by('order') + current_sense_numbers = [gs.order for gs in gloss_senses] + current_trans = [] + current_indices = [] + for gs in gloss_senses: + sense = gs.sense + for language in self.languages: + try: + sense_trans = sense.senseTranslations.get(language=language) + except ObjectDoesNotExist: + # there should only be one + sense_trans = SenseTranslation(language=language) + sense_trans.save() + sense.senseTranslations.add(sense_trans) + # the new sense translation object is empty + continue + for trans in sense_trans.translations.all().order_by('index'): + current_indices.append(trans.index) + current_trans.append(trans) + current_keywords = dict() + for order in current_sense_numbers: + current_keywords[order] = dict() + for language in self.languages: + current_keywords[order][language.id] = [t.translation.text for t in current_trans + if t.orderIndex == order and t.language == language] + + for language in self.languages: + gloss_sense_field_name = self.gloss_sense_field_prefix + language.language_code_2char + self.fields[gloss_sense_field_name] = forms.CharField(label=_("Sense") + (" (%s)" % language.name)) + if gloss_sense_field_name in queryDict: + self.fields[gloss_sense_field_name].value = queryDict[gloss_sense_field_name] + + @atomic + def save(self, commit=True): + + for field_key in self.instance.fields.keys(): + if self.instance.fields[field_key]: + print(self.instance.fields[field_key]) + diff --git a/signbank/dictionary/models.py b/signbank/dictionary/models.py index f4948da56..00623a6a4 100755 --- a/signbank/dictionary/models.py +++ b/signbank/dictionary/models.py @@ -1260,6 +1260,10 @@ def idgloss(self): except: return str(self.id) + def num_senses(self): + senses_for_this_gloss = GlossSense.objects.filter(gloss_pk=self.pk).count() + return senses_for_this_gloss + def reorder_senses(self): """when a sense is deleted, the senses should be reordered""" glosssenses_of_this_gloss = GlossSense.objects.filter(gloss=self).order_by('order') diff --git a/signbank/dictionary/templates/dictionary/admin_batch_edit_view.html b/signbank/dictionary/templates/dictionary/admin_batch_edit_view.html new file mode 100644 index 000000000..1cdfeda17 --- /dev/null +++ b/signbank/dictionary/templates/dictionary/admin_batch_edit_view.html @@ -0,0 +1,556 @@ +{% extends "baselayout.html" %} +{% load i18n %} +{% load stylesheet %} +{% load bootstrap3 %} +{% load tagging_tags %} + +{% load guardian_tags %} +{% load annotation_idgloss_translation %} + +{% block bootstrap3_title %} +{% blocktrans %}Signbank: Annotation Toggle View{% endblocktrans %} +{% endblock %} + +{% block extrajs %} + + + + + + + + + + + + +{% endblock %} + +{% block extrahead %} + + + +{% endblock %} + +{% block content %} + +
+ | {% trans "Gloss" %} | +{% trans "Fields" %} | +{% trans "Quick Toggles" %} | +{% trans "Tags" %} | +|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
{% if gloss.get_image_path %} + {% url 'dictionary:protected_media' '' as protected_media_url %} + + {% endif %} + | +
+
|
+
+
|
+
+
+
+
+
+ {% trans "Tags" %}
+
+
+
+
+ {% trans "Handedness" %}
+
+
+
+
+
+ + {% for wc in available_handedness %} + + {% endfor %} + +
+
+ {% trans "Sterke Hand" %}
+
+
+
+
+
+ + {% for wc in available_handshape %} + + {% endfor %} + + |
+ {% tags_for_object gloss as tag_list %}
+ + | + {% with gloss.semField.all as semantic_fields_list %} + +
+ {% for sf in semantic_fields_list %}{{sf.name}}
+ {% if not forloop.last %}, {% endif %}{% endfor %}
+
+ |
+ {% endwith %}
+ ||||||||||||||
+ |
+
|
+