From 4885e5453f470fc36513919d849a82f2856480f4 Mon Sep 17 00:00:00 2001 From: dcz <dcz@ipipan.waw.pl> Date: Fri, 27 May 2022 09:12:49 +0200 Subject: [PATCH] first stage of unified frame edit view --- entries/static/entries/js/entries.js | 12 +- entries/static/entries/js/unification.js | 168 ++++++++++++++++++ .../entries/js/unification_frames_list.js | 3 +- entries/templates/unification_frames.html | 3 +- .../templates/unified_frame_edit_display.html | 72 ++++++++ unifier/models.py | 12 +- unifier/urls.py | 1 + unifier/views.py | 137 ++++++++++---- 8 files changed, 361 insertions(+), 47 deletions(-) create mode 100644 entries/static/entries/js/unification.js create mode 100644 entries/templates/unified_frame_edit_display.html diff --git a/entries/static/entries/js/entries.js b/entries/static/entries/js/entries.js index 19231a0..8781a38 100644 --- a/entries/static/entries/js/entries.js +++ b/entries/static/entries/js/entries.js @@ -86,10 +86,12 @@ function subentries2dom(subentries) { var subentry = subentries[i]; var subentry_div = document.createElement('div'); subentry_div.className = 'subentry'; - var subentry_header = document.createElement('div'); - subentry_header.className = 'mb-1 sticky-top'; - subentry_header.innerHTML = '<h5 class="bg-dark text-light p-1">' + subentry.str + '</h5>'; - subentry_div.append(subentry_header) + if(subentry.str != null) { + var subentry_header = document.createElement('div'); + subentry_header.className = 'mb-1 sticky-top'; + subentry_header.innerHTML = '<h5 class="bg-dark text-light p-1">' + subentry.str + '</h5>'; + subentry_div.append(subentry_header) + } for (var j in subentry.schemata) { subentry_div.append(schema2dom(subentry.schemata[j])); } @@ -344,8 +346,6 @@ function show_semantics(frames, subentries) { $('#semantics-schemata').append($(schemata_dom)); var frames_dom = frames2dom(frames); - $('#semantics-frames').append('<button type="button" onclick="addSelectivePreference(frame_content[0], 1)" id="add_preference">Dodaj preferencje (przykład użycia - do wstawienia w odpowiednie miejsce)</button>'); - $('#semantics-frames').append($(frames_dom)); $('.frame').mouseenter(function() { diff --git a/entries/static/entries/js/unification.js b/entries/static/entries/js/unification.js new file mode 100644 index 0000000..0778742 --- /dev/null +++ b/entries/static/entries/js/unification.js @@ -0,0 +1,168 @@ +"use strict"; + + +function get_unified_frame(unified_frame_id, related) { + check_import_status(); + clear_entry(); + show_entry_spinners(); + //var data = { 'forms' : serialize_forms($('#main-form')), 'entry' : entry_id }; + var data = { 'unified_frame_id' : unified_frame_id, 'no_filters' : related }; + $.ajax({ + type : 'post', + url : '/' + lang + '/unifier/get_unified_frame/', + dataType : 'json', + data : data, + timeout : 60000, + success : function(response) { + frame_content = response.frames; + curr_entry = unified_frame_id; + curr_no_filters = related; + clear_info(); + curr_alternations = response.alternations; + curr_realisation_phrases = response.realisation_phrases; + curr_realisation_descriptions = response.realisation_descriptions; + curr_examples = response.examples; + curr_examples_by_id = Object(); + for (var i in curr_examples) { + curr_examples_by_id[curr_examples[i].id] = curr_examples[i]; + } + // show_syntax(response.subentries); + show_unified_frame(response.unified_frame) + show_unified_frame_lexical_units(response.frames) + show_semantics(response.frames, response.subentries); + show_unmatched_examples(); + + // if current tab is empty, switch to an active tab + var active_tab = $('#entryTabs').find('.nav-link.active').attr('id'); + if (active_tab === 'semantics-tab' && $('.frame').length === 0) { + // current tab is semantics -> show syntax + $('#syntax-tab').tab('show'); + } else if (active_tab === 'examples-tab') { + if ($('.frame').length > 0) { + // current tab is examples, semantics nonempty -> show semantics + $('#semantics-tab').tab('show'); + } else { + // current tab is examples, semantics empty -> show syntax + $('#syntax-tab').tab('show'); + } + } + + // tooltips with meaning gloss + activate_tooltips($('.frame')); + // tooltips with phrase descriptions + activate_tooltips($('.schema')); + update_last_visited(response.last_visited); + }, + error: function(request, errorType, errorMessage) { + show_error(errorType + ' (' + errorMessage + ')'); + } + }); +} + + +function unifiedFrame2dom(frame) { + var div = document.createElement('div'); + div.className = 'frame mb-3'; + div.setAttribute('data-frame_id', frame.id); + + var p = document.createElement('p'); + p.className = 'mb-1' + + p.innerHTML = '<span class="lexical-unit' + cls + '">' + frame.title + '</span>'; + div.append(p) + + var table = document.createElement('table'); + table.className = 'table m-0 table-borderless border border-secondary text-dark'; + var tbody = document.createElement('tbody'); + + // roles and selectional preferences + var roles_row = document.createElement('tr'); + roles_row.innerHTML = '<th scope="row" class="py-2 px-1 text-secondary">' + gettext('Rola') + '</td>'; + var preferences_row = document.createElement('tr'); + var preferences_html = '<th scope="row" class="py-0 px-1 text-secondary">' + gettext('Preferencje selekcyjne') + '</td>'; + for (var i in frame.arguments) { + var argument = frame.arguments[i]; + var cls = ''; + var data = ' data-argument_id="' + argument.id + '" data-role="' + argument.role + '"'; + roles_row.innerHTML += '<td class="argument py-2 px-1 border-top border-left border-secondary' + cls + ' ' + argument.role + '"' + data + '>' + argument.str + '</td>'; + + // don’t append to preferences_html.innerHTML since it automatically closes tags *** (?) + preferences_html += '<td class="preferences py-0 px-0 border-top border-left border-secondary' + cls + '"' + data + '>' + for (var j in argument.preferences) { + var preference = argument.preferences[j]; + var cls = j > 0 ? ' border-top' : ''; + if (preference.str.length > 12) { + cls += ' text-break'; + } + preferences_html += '<div class="preference py-2 px-1' + cls + '">'; + if (preference.url) { +// preferences_html += ' <a class="synset-plwn" href="' + preference.url + '" target="_blank"><img src="' + window.STATIC_URL + 'common/img/ext-link.svg" alt="external link" height="14"/></a>'; + preferences_html += ' <a class="synset-plwn" href="' + preference.url + '" target="_blank">' + preference.str + '</a>'; + } else { + preferences_html += preference.str; + } + if (preference.info) { + preferences_html += ' ' + tooltipped_info(preference.info); + } + preferences_html += '</div>'; + } + // *** and we want to close the <td> here + preferences_html += '</td>'; + } + preferences_row.innerHTML += preferences_html; + + tbody.append(roles_row); + tbody.append(preferences_row); + table.append(tbody); + div.append(table); + + return div; +} + + +function frames2lexical_units(frames) { + var div = document.createElement('div'); + div.className = 'frame mb-3'; + var lexical_units = [] + for (var i in frames) { + var frame = frames[i]; + for (var j in frame.lexical_units) { + var lexical_unit = frame.lexical_units[j]; + lexical_unit.opinion = frame.opinion; + lexical_units.push(lexical_unit); + } + } + var table = document.createElement('table'); + table.className = 'table m-0 table-borderless border border-secondary text-dark'; + var tbody = document.createElement('tbody'); + + // roles and selectional preferences + var header_row = document.createElement('tr'); + header_row.innerHTML += '<th scope="row" class="py-2 px-1 text-secondary">' + gettext('Jednostka leksykalna') + '</td>'; + header_row.innerHTML += '<th scope="row" class="py-2 px-1 text-secondary">' + gettext('Opinia') + '</td>'; + tbody.append(header_row); + + for (var j in lexical_units) { + let lexical_unit = lexical_units[j]; + let lexical_unit_row = document.createElement('tr'); + lexical_unit_row.innerHTML += '<td class="argument py-2 px-1 border-top border-left border-secondary">' + gettext(lexical_unit.str) + '</td>'; + lexical_unit_row.innerHTML += '<td class="argument py-2 px-1 border-top border-left border-secondary">' + gettext(lexical_unit.opinion) + '</td>'; + tbody.append(lexical_unit_row); + } + table.append(tbody); + div.append(table); + + return div; +} + +function show_unified_frame(unified_frame) { + var unified_frame_dom = unifiedFrame2dom(unified_frame); + $('#unified-frame').empty(); + $('#unified-frame').append($(unified_frame_dom)); +} + +function show_unified_frame_lexical_units(frames) { + var lexical_units_dom = frames2lexical_units(frames); + $('#unified-frame-lexical-units').empty(); + $('#unified-frame-lexical-units').append($(lexical_units_dom)); +} diff --git a/entries/static/entries/js/unification_frames_list.js b/entries/static/entries/js/unification_frames_list.js index a85b04b..b8066ea 100644 --- a/entries/static/entries/js/unification_frames_list.js +++ b/entries/static/entries/js/unification_frames_list.js @@ -26,7 +26,8 @@ function update_entries() { if (selected_entry !== curr_entry) { $('.entry[data-entry="' + curr_entry + '"]').removeClass('table-primary'); //TODO: zastapic etoda pobierajaca rame za po jej id - get_entry(selected_entry, related); + get_unified_frame(data.id) + // get_entry(selected_entry, related); $(this).addClass('table-primary'); } }); diff --git a/entries/templates/unification_frames.html b/entries/templates/unification_frames.html index cb4fd55..1399310 100644 --- a/entries/templates/unification_frames.html +++ b/entries/templates/unification_frames.html @@ -7,9 +7,10 @@ {% block scripts %} {{ block.super }} + <script src="{% static 'entries/js/unification.js' %}"></script> <script src="{% static 'entries/js/unification_frames_list.js' %}"></script> {% endblock %} {% block left_pane %}{% include "unification_frames_list.html" %}{% endblock %} -{% block right_pane %}{% include "entry_display.html" %}{% endblock %} +{% block right_pane %}{% include "unified_frame_edit_display.html" %}{% endblock %} diff --git a/entries/templates/unified_frame_edit_display.html b/entries/templates/unified_frame_edit_display.html new file mode 100644 index 0000000..ccd8c69 --- /dev/null +++ b/entries/templates/unified_frame_edit_display.html @@ -0,0 +1,72 @@ +{% load i18n %} + +<div class="tab-content h-100 w-100 p-0" id="entryTabsContent"> + <div class="col h-100 w-100 p-0 tab-pane show active" id="semantics" role="tabpanel" aria-labelledby="semantics-tab"> + + <div class="row m-0 p-0 overflow-auto"> + <div class="tab-content h-300 w-100 p-0" id="unified-frame-content"> + <div class="col h-100 w-100 p-0 tab-pane show active" id="unified-frame-content-col" role="tabpanel" aria-labelledby="semantics-tab"> + <div class="row m-0 p-0"> + <button type="button" onclick="addSelectivePreference(frame_content[0], 1)" id="add_preference">Dodaj preferencje (przykład użycia - do wstawienia w odpowiednie miejsce)</button> + </div> + <div class="row m-0 p-0" id="semantics-top-pane-1"> + <div id="unified-frame"></div> + </div> + <div class="row m-0 p-0" id="semantics-top-pane-2"> + <div id="unified-frame-lexical-units"></div> + </div> + <div class="row m-0 p-0" id="semantics-top-pane-3"> + <div id="semantics-frames"></div> + </div> + </div> + </div> + </div> + <div class="row m-0 p-0 overflow-auto" id="semantics-examples-pane"> + <table id="semantics-examples" class="table table-sm table-hover"> + <thead> + <tr> + <th scope="col">{% trans "Przykład" %}<i id="examples-argument"></i><i id="examples-lu"></i><i id="examples-schema"></i></th> + <th scope="col">{% trans "Źródło" %}</th> + <th scope="col">{% trans "Opinia" %}</th> + </tr> + </thead> + <tbody id="semantics-examples-list"> + </tbody> + </table> + <p class="mx-1 my-1"id="semantics-no-examples">{% trans "Brak przykładów" %}</p> + </div> + </div> + <div class="col h-100 w-100 p-0 tab-pane" id="syntax" role="tabpanel" aria-labelledby="syntax-tab"> + <div class="col w-100 px-1 pt-0 pb-0 overflow-auto" id="syntax-schemata-pane"> + <div id="syntax-schemata"></div> + </div> + <div class="col w-100 p-0 overflow-auto" id="syntax-examples-pane"> + <table id="syntax-examples" class="table table-sm table-hover"> + <thead> + <tr> + <th scope="col">{% trans "Przykład" %}</th> + <th scope="col">{% trans "Źródło" %}</th> + <th scope="col">{% trans "Opinia" %}</th> + </tr> + </thead> + <tbody id="syntax-examples-list"> + </tbody> + </table> + <p class="mx-1 my-1"id="syntax-no-examples">{% trans "Brak przykładów" %}</p> + </div> + </div> + <div class="col h-100 w-100 p-0 tab-pane" id="examples" role="tabpanel" aria-labelledby="examples-tab"> + <table id="unmatched-examples" class="table table-sm table-hover"> + <thead> + <tr> + <th scope="col">{% trans "Przykład" %}</th> + <th scope="col">{% trans "Źródło" %}</th> + <th scope="col">{% trans "Opinia" %}</th> + </tr> + </thead> + <tbody id="unmatched-examples-list"> + </tbody> + </table> + <p class="mx-1 my-1"id="unmatched-no-examples">{% trans "Brak przykładów" %}</p> + </div> +</div> diff --git a/unifier/models.py b/unifier/models.py index 1cfa92c..25e7914 100644 --- a/unifier/models.py +++ b/unifier/models.py @@ -41,20 +41,20 @@ class UnifiedFrameArgument(models.Model): class UnifiedRelationalSelectionalPreference(models.Model): relation = models.ForeignKey(SelectionalPreferenceRelation, on_delete=models.PROTECT) - to = models.ForeignKey(UnifiedFrameArgument, on_delete=models.PROTECT) + to = models.ForeignKey(UnifiedFrameArgument,on_delete=models.PROTECT) def __str__(self): return '%s -> %s' % (self.relation, self.to) class UnifiedFrame2SlowalFrameMapping(models.Model): - unified_frame = models.ForeignKey(UnifiedFrame, related_name='unified_frame', on_delete=models.PROTECT) - slowal_frame = models.ForeignKey(Frame, related_name='slowal_frame', on_delete=models.PROTECT) + unified_frame = models.ForeignKey(UnifiedFrame, related_name='unified_frame_2_slowal_frame', on_delete=models.PROTECT) + slowal_frame = models.ForeignKey(Frame, related_name='slowal_frame_2_unified_frame', on_delete=models.PROTECT) class UnifiedFrameArgumentSlowalFrameMapping(models.Model): - unified_agrument = models.ForeignKey(UnifiedFrameArgument, related_name='unified_agrument', on_delete=models.PROTECT) - slowal_agrument = models.ForeignKey(Argument, related_name='slowal_agrument', on_delete=models.PROTECT) - unified_frame_mapping = models.ForeignKey(UnifiedFrame2SlowalFrameMapping, related_name='unified_frame_mapping', on_delete=models.PROTECT) + unified_agrument = models.ForeignKey(UnifiedFrameArgument, related_name='unified_agrument_mapping', on_delete=models.PROTECT) + slowal_agrument = models.ForeignKey(Argument, related_name='slowal_agrument_mapping', on_delete=models.PROTECT) + unified_frame_mapping = models.ForeignKey(UnifiedFrame2SlowalFrameMapping, related_name='unified_frame_argument_mapping', on_delete=models.PROTECT) diff --git a/unifier/urls.py b/unifier/urls.py index 56207b1..e3b88c0 100644 --- a/unifier/urls.py +++ b/unifier/urls.py @@ -9,5 +9,6 @@ urlpatterns = [ path('save_predefined_preference/', views.save_predefined_preference, name='save_predefined_preference'), path('save_relational_selectional_preference/', views.save_relational_selectional_preference, name='save_relational_selectional_preference'), path('get_unified_frames/', views.get_unified_frames, name='get_unified_frames'), + path('get_unified_frame/', views.get_unified_frame, name='get_unified_frame'), ] diff --git a/unifier/views.py b/unifier/views.py index 82714d9..370a1ea 100644 --- a/unifier/views.py +++ b/unifier/views.py @@ -1,8 +1,9 @@ from django.http import JsonResponse from common.decorators import ajax_required -from entries.views import get_scroller_params -from semantics.models import RelationalSelectionalPreference, Frame +from entries.polish_strings import EXAMPLE_SOURCE, EXAMPLE_OPINION +from entries.views import get_scroller_params, get_alternations, get_prefs_list, schema2dict, frame2dict +from syntax.models import Schema from unifier.models import UnifiedFrameArgument, UnifiedRelationalSelectionalPreference, UnifiedFrame, \ UnifiedFrame2SlowalFrameMapping @@ -50,8 +51,6 @@ def save_relational_selectional_preference(request): @ajax_required def get_unified_frames(request): if request.method == 'POST': - errors_dict = dict() - scroller_params = get_scroller_params(request.POST) unifiedFrame2SlowalFrameMappings = UnifiedFrame2SlowalFrameMapping.objects.all(); @@ -84,32 +83,104 @@ def get_unified_frames(request): return JsonResponse({}) -# @ajax_required -# def get_unified_frame(request): -# if request.method == 'POST': -# #TODO (*) -# #form = EntryForm(request.POST) -# unified_frame_id = request.POST['unified_frame_id'] -# #TODO (*) -# if unified_frame_id.isdigit():# and form.is_valid(): -# unified_frame_id = int(unified_frame_id) -# # TODO check that Entry has no import errors -# unifiedFrame = UnifiedFrame.objects.get(id=unified_frame_id) -# -# frame_objects = Frame.objects.filter(arguments__argument_connections__schema_connections__subentry__entry=entry).distinct() -# # filter out frames by frame properties -# if filter_frames: -# frame_objects = get_filtered_objects2(frame_forms, frame_objects) -# if local_frame_form: -# frame_objects = get_filtered_objects(local_frame_form, frame_objects) -# frames = [frame2dict(frame, entry.lexical_units.all()) for frame in frame_objects] -# alternations, realisation_phrases, realisation_descriptions = get_alternations(all_schema_objects, frame_objects) -# examples = get_examples(entry) -# # https://docs.djangoproject.com/en/2.2/topics/http/sessions/#when-sessions-are-saved -# if [entry.name, entry.id] in request.session['last_visited']: -# request.session['last_visited'].remove([entry.name, entry.id]) -# request.session['last_visited'].insert(0, (entry.name, entry.id)) -# request.session['last_visited'] = request.session['last_visited'][:(MAX_LAST_VISITED + 1)] -# request.session.modified = True -# return JsonResponse({ 'subentries' : subentries, 'frames' : frames, 'alternations' : alternations, 'realisation_phrases' : realisation_phrases, 'realisation_descriptions' : realisation_descriptions, 'examples' : examples, 'last_visited' : request.session['last_visited'] }) -# return JsonResponse({}) + +def unifiedFrame2dict(frame): + return { + 'id' : str(frame.id), + 'title' : str(frame.title), + 'status' : str(frame.status), + 'arguments' : [ + { + 'str' : str(a), + 'id' : '{}-{}'.format(frame.id, a.id), + 'role' : '{}{}'.format(a.role.role.role.lower(), ' ' + a.role.attribute.attribute.lower() if a.role.attribute else '') if a.role is not None else None, + 'role_type' : a.role_type.type.lower(), + 'preferences' : get_prefs_list(a), + 'proposed_roles': [{ + 'role' : '{}{}'.format(r.role.role.lower(), ' ' + r.attribute.attribute.lower() if r.attribute else ''), + } for r in a.proposed_roles.all()], + } for a in frame.unified_arguments.all() + ], + + 'slowal_frame_mapping': [ + { + 'slowal_frame_id': slowal_frame_mapping.slowal_frame.id, + 'slowal_frame_argument_mapping': [ + { + 'unified_frame_agrument.id': slowal_frame_arg_mapping.unified_agrument.id, + 'slowal_frame_agrument.id': slowal_frame_arg_mapping.slowal_agrument.id, + } for slowal_frame_arg_mapping in slowal_frame_mapping.unified_frame_argument_mapping.all() + ] + } for slowal_frame_mapping in frame.unified_frame_2_slowal_frame.all() + ] + + } + +def get_examples(frames): + examples = [] + for frame in frames: + for argument in frame.arguments.all(): + for connection in argument.example_connections.all(): + example = connection.example + frame_ids, argument_ids, lu_ids, schema_ids, phrases, phrases_syntax, positions = set(), set(), set(), set(), set(), set(), set() + for connection in example.example_connections.all(): + for argument in connection.arguments.all(): + frame_ids.add(argument.frame.id) + argument_ids.add('{}-{}'.format(argument.frame.id, argument.id)) + if connection.lexical_unit: + lu_ids.add(connection.lexical_unit.id) + for hook in connection.schema_connections.all(): + schema_ids.add(hook.schema.id); + phrases.add('{}-{}-{}-{}'.format(hook.schema.id, hook.position.id, hook.phrase_type.id, hook.alternation - 1)) + phrases_syntax.add('{}-{}-{}'.format(hook.schema.id, hook.position.id, hook.phrase_type.id)) + positions.add('{}-{}'.format(hook.schema.id, hook.position.id)) + elem = { + 'id' : str(example.id), + 'sentence' : example.sentence, + 'source' : EXAMPLE_SOURCE()[example.source.key], + 'opinion' : EXAMPLE_OPINION()[example.opinion.key], + 'note' : example.note, + 'frame_ids' : sorted(frame_ids), + 'argument_ids' : sorted(argument_ids), + 'lu_ids' : sorted(lu_ids), + 'schema_ids' : sorted(schema_ids), + 'phrases' : sorted(phrases), + 'phrases_syntax' : sorted(phrases_syntax), + 'positions' : sorted(positions), + } + if elem not in examples: + examples.append(elem) + return sorted(examples, key=lambda x: x['sentence']) + +@ajax_required +def get_unified_frame(request): + if request.method == 'POST': + #TODO (*) + #form = EntryForm(request.POST) + unified_frame_id = request.POST['unified_frame_id'] + #TODO (*) + if unified_frame_id.isdigit():# and form.is_valid(): + unified_frame_id = int(unified_frame_id) + # TODO check that Entry has no import errors + unifiedFrame = UnifiedFrame.objects.get(id=unified_frame_id) + + slowal_frames = [connection.slowal_frame for connection in unifiedFrame.unified_frame_2_slowal_frame.all()] + + all_schema_objects = Schema.objects.filter(schema_hooks__argument_connections__argument__frame__in=slowal_frames).distinct() + + slowal_frames_dict = [frame2dict(slowal_frame, slowal_frame.lexical_units.all()) for slowal_frame in slowal_frames] + alternations, realisation_phrases, realisation_descriptions = get_alternations(all_schema_objects, slowal_frames) + examples = get_examples(slowal_frames) + + all_schema_objects_dict = [schema2dict(schema, schema.subentries.all()[0].negativity, request.LANGUAGE_CODE) for schema in all_schema_objects] + + subentries = [{ + 'str': None, + 'schemata': all_schema_objects_dict + }] + + unifiedFrame_dict = unifiedFrame2dict(unifiedFrame) + + request.session.modified = True + return JsonResponse({ 'unified_frame_id': unified_frame_id, 'unified_frame': unifiedFrame_dict, 'subentries': subentries, 'frames' : slowal_frames_dict, 'alternations' : alternations, 'realisation_phrases' : realisation_phrases, 'realisation_descriptions' : realisation_descriptions, 'examples' : examples, 'last_visited' : request.session['last_visited'] }) + return JsonResponse({}) -- GitLab