From 2a2e5ee1e7c3aa2d837380385e213316e1796535 Mon Sep 17 00:00:00 2001
From: dcz <dcz@ipipan.waw.pl>
Date: Thu, 15 Dec 2022 11:36:43 +0100
Subject: [PATCH] First version of unified frame filtering - enable filtering
 of entry list based on frame filtering - creating filtering form for unified
 frame filtering - enable filtering of unified frame list based on unified
 frame filtering

---
 entries/forms.py                              | 48 +++++++++++++++++++
 entries/static/entries/js/entries.js          |  2 +
 entries/static/entries/js/forms.js            |  7 +++
 .../checkboxselectmultiple_with_tooltips.html |  2 +-
 entries/templates/entries_base.html           | 19 ++++++++
 .../role_checkboxselectmultiple_inner.html    |  2 +-
 entries/urls.py                               |  1 +
 entries/views.py                              | 44 +++++++++++++----
 .../unification/Entries/EntriesList.vue       |  2 +-
 .../Unification/UnificationFramesList.vue     | 13 +++++
 frontend/src/main.js                          |  2 +-
 unifier/views.py                              | 11 ++++-
 12 files changed, 139 insertions(+), 14 deletions(-)

diff --git a/entries/forms.py b/entries/forms.py
index c30edf0..2384da9 100644
--- a/entries/forms.py
+++ b/entries/forms.py
@@ -27,6 +27,7 @@ from semantics.models import (
 )
 
 from meanings.models import Synset
+from unifier.models import UnifiedFrame
 
 from .form_fields.generic_fields import (
     RangeFilter,
@@ -601,6 +602,53 @@ class FrameFormFactory(FormFactory):
         raise KeyError(type(child_form))
 
 
+
+class UnifiedFrameFormFactory(FormFactory):
+
+    form_class_name = 'UnifiedFrameForm'
+    form_model = UnifiedFrame
+    form_formtype = 'unifiedframe'
+    form_header = _('Zunifikowana rama semantyczna')
+
+    field_makers = (
+        (
+            'opinion',
+            lambda: ModelMultipleChoiceFilter(
+                label=_('Opinia'),
+                queryset=FrameOpinion.objects.exclude(key='unk').filter(frame__isnull=False).distinct(),
+                key='key',
+                human_values=polish_strings.FRAME_OPINION(),
+                lookup='opinion',
+            ), None,
+        ),
+        (
+            'num_arguments',
+            lambda: RangeFilter(
+                label=_('Liczba argumentów'),
+                lookup='arguments_count',
+            ), None
+        ),
+        (
+            'name',
+            lambda: RegexFilter(
+                label=_('Nazwa'),
+                max_length=200,
+                lookup='title',
+            ), None,
+        ),
+        (
+            None, None,
+            lambda n, cls: and_or_form_creator(_('UnifiedFrameArgument'), 'add-argument-{}'.format(n), data_add='argument'),
+        ),
+    )
+
+    @staticmethod
+    def get_child_form_prefix(child_form):
+        if child_form.model_class == Argument:
+            return 'arguments__in'
+        raise KeyError(type(child_form))
+
+
 class ArgumentFormFactory(FormFactory):
     
     form_class_name = 'ArgumentForm'
diff --git a/entries/static/entries/js/entries.js b/entries/static/entries/js/entries.js
index a218b24..cc480ad 100644
--- a/entries/static/entries/js/entries.js
+++ b/entries/static/entries/js/entries.js
@@ -1128,6 +1128,8 @@ $(document).ready(function() {
     initialize_frames_form();
 
     initialize_schemata_form();
+
+    initialize_unified_frames_form();
     
     //$('.local-filter').hide();
 
diff --git a/entries/static/entries/js/forms.js b/entries/static/entries/js/forms.js
index b123492..cacca0d 100644
--- a/entries/static/entries/js/forms.js
+++ b/entries/static/entries/js/forms.js
@@ -427,6 +427,7 @@ function initialize_main_form() {
                 if (response.errors) {
                     show_form_errors(main_form, response.errors);
                 } else if (response.success) {
+                    //ponizsza funkcja prowadzi do metody osadzonej w main.js, gdzie nastepuje odpowiednie odswiezenie komponentu z lista elementow Vue
                     update_entries();
                     $('#entry-filters').modal('hide');
                 }
@@ -476,6 +477,7 @@ function initialize_local_form(selector, url) {
                 if (response.errors) {
                     show_form_errors(form, response.errors);
                 } else if (response.success) {
+                    //ponizsza funkcja prowadzi do metody osadzonej w main.js, gdzie nastepuje odpowiednie odswiezenie komponentu z lista elementow Vue
                     update_entries();
                     selector.modal('hide');
                 }
@@ -494,6 +496,11 @@ function initialize_frames_form() {
     initialize_local_form($('#frame-filters'), '/' + lang + '/entries/send_frames_form/');
 }
 
+function initialize_unified_frames_form() {
+    initialize_local_form($('#unified-frame-filters'), '/' + lang + '/entries/send_unified_frames_form/');
+}
+
+
 function initialize_schemata_form() {
 
     initialize_local_form($('#schema-filters'), '/' + lang + '/entries/send_schemata_form/');
diff --git a/entries/templates/checkboxselectmultiple_with_tooltips.html b/entries/templates/checkboxselectmultiple_with_tooltips.html
index b0cbaef..9ef9c8a 100644
--- a/entries/templates/checkboxselectmultiple_with_tooltips.html
+++ b/entries/templates/checkboxselectmultiple_with_tooltips.html
@@ -9,7 +9,7 @@
         <input type="checkbox" class="{%if use_custom_control%}custom-control-input{% else %}form-check-input{% endif %}{%if is_bound %} is-{% if field.errors %}in{%endif%}valid{% endif %}"{% if choice.0 in field.value or choice.0|stringformat:"s" in field.value or choice.0|stringformat:"s" == field.value|default_if_none:""|stringformat:"s" %} checked="checked"{% endif %} name="{{ field.html_name }}" id="id_{{ field.html_name }}_{{ forloop.counter }}" value="{{ choice.0|unlocalize }}" {{ field.field.widget.attrs|flatatt }}>
         <label class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}" for="id_{{ field.html_name }}_{{ forloop.counter }}">
             {% if choice.1.1 %}
-            {{ choice.1.0|unlocalize }} <span data-toggle="tooltip" data-placement="bottom" title="{{ choice.1.1 }}"><img src="{% static 'common/img/info.svg' %}" alt="info" width="14" height="14"/></span>
+            {{ choice.1.0|unlocalize }} <span data-toggle="tooltip" data-placement="bottom" title="{{ choice.1.1 }}"><img src="common/img/info.svg" alt="info" width="14" height="14"/></span>
             {% else %}
             {{ choice.1.0|unlocalize }}
             {% endif %}
diff --git a/entries/templates/entries_base.html b/entries/templates/entries_base.html
index b46559a..546dee8 100644
--- a/entries/templates/entries_base.html
+++ b/entries/templates/entries_base.html
@@ -42,6 +42,9 @@
         <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-frames-button" data-toggle="modal" data-target="#frame-filters">
             {% trans "Ramy" %}
         </a>
+        <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-unified-frames-button" data-toggle="modal" data-target="#unified-frame-filters">
+            {% trans "Ramy zunifikowane" %}
+        </a>
         <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-schemata-button" data-toggle="modal" data-target="#schema-filters">
             {% trans "Schematy" %}
         </a>
@@ -115,6 +118,22 @@
   </div>
 </div>
 
+<div class="modal fade" id="unified-frame-filters" tabindex="-1" role="dialog" aria-labelledby="unified-frame-filtersLabel" aria-hidden="true">
+    <div class="modal-dialog modal-xl" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="unified-frame-filtersLabel">{% trans "Filtrowanie zunifikowanych ram" %}</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body text-dark">
+                {% crispy unified_frames_form %}
+            </div>
+        </div>
+    </div>
+</div>
+
 <div class="modal fade" id="schema-filters" tabindex="-1" role="dialog" aria-labelledby="schema-filtersLabel" aria-hidden="true">
   <div class="modal-dialog modal-xl" role="document">
     <div class="modal-content">
diff --git a/entries/templates/role_checkboxselectmultiple_inner.html b/entries/templates/role_checkboxselectmultiple_inner.html
index 6310fe0..bc10a42 100644
--- a/entries/templates/role_checkboxselectmultiple_inner.html
+++ b/entries/templates/role_checkboxselectmultiple_inner.html
@@ -26,7 +26,7 @@
         <input type="checkbox" class="{%if use_custom_control%}custom-control-input{% else %}form-check-input{% endif %}{%if is_bound %} is-{% if field.errors %}in{%endif%}valid{% endif %}"{% if choice.0 in field.value or choice.0|stringformat:"s" in field.value or choice.0|stringformat:"s" == field.value|default_if_none:""|stringformat:"s" %} checked="checked"{% endif %} name="{{ field.html_name }}" id="id_{{ field.html_name }}_{{ forloop.parentloop.parentloop.counter }}_{{ forloop.parentloop.counter }}_{{ forloop.counter }}" value="{{ choice.0|unlocalize }}" {{ field.field.widget.attrs|flatatt }}>
         <label class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %} text-dark" for="id_{{ field.html_name }}_{{ forloop.parentloop.parentloop.counter }}_{{ forloop.parentloop.counter }}_{{ forloop.counter }}">
             {% if choice.1.1 %}
-            {{ choice.1.0|unlocalize }} <span data-toggle="tooltip" data-placement="bottom" title="{{ choice.1.1 }}"><img src="{% static 'common/img/info.svg' %}" alt="info" width="14" height="14"/></span>
+            {{ choice.1.0|unlocalize }} <span data-toggle="tooltip" data-placement="bottom" title="{{ choice.1.1 }}"><img src="common/img/info.sv" alt="info" width="14" height="14"/></span>
             {% else %}
             {{ choice.1.0|unlocalize }}
             {% endif %}
diff --git a/entries/urls.py b/entries/urls.py
index d735f33..3b4bd7c 100644
--- a/entries/urls.py
+++ b/entries/urls.py
@@ -10,6 +10,7 @@ urlpatterns = [
     path('send_form/', views.send_form, name='send_form'),
     path('send_schemata_form/', views.send_schemata_form, name='send_schemata_form'),
     path('send_frames_form/', views.send_frames_form, name='send_frames_form'),
+    path('send_unified_frames_form/', views.send_unified_frames_form, name='send_unified_frames_form'),
     #path('filter_schemata/', views.filter_schemata, name='filter_schemata'),
     path('get_entries/', views.get_entries, name='get_entries'),
     path('get_entry/', views.get_entry, name='get_entry'),
diff --git a/entries/views.py b/entries/views.py
index 5430b01..8cacf58 100644
--- a/entries/views.py
+++ b/entries/views.py
@@ -38,7 +38,7 @@ from .forms import (
     ArgumentFormFactory,
     PredefinedPreferenceFormFactory,
     RelationalPreferenceFormFactory,
-    SynsetPreferenceFormFactory,
+    SynsetPreferenceFormFactory, UnifiedFrameFormFactory,
 )
 
 from .polish_strings import STATUS, POS, SCHEMA_OPINION, FRAME_OPINION, EXAMPLE_SOURCE, EXAMPLE_OPINION, RELATION
@@ -66,7 +66,8 @@ def entries(request):
             'is_vue_app': True,
             'entries_form' : EntryForm(),
             'frames_form' : FrameFormFactory.get_form(as_subform=False),
-            'schemata_form' : SchemaFormFactory.get_form(as_subform=False)
+            'schemata_form' : SchemaFormFactory.get_form(as_subform=False),
+            'unified_frames_form': UnifiedFrameFormFactory.get_form(as_subform=False),
         })
 
 
@@ -80,7 +81,8 @@ def unification(request):
             'unified_frame_id': request.GET.get("unified_frame_id"),
             'entries_form' : EntryForm(),
             'frames_form': FrameFormFactory.get_form(as_subform=False),
-            'schemata_form': SchemaFormFactory.get_form(as_subform=False)
+            'schemata_form': SchemaFormFactory.get_form(as_subform=False),
+            'unified_frames_form': UnifiedFrameFormFactory.get_form(as_subform=False),
         },
     )
 
@@ -95,6 +97,7 @@ FORM_FACTORY_TYPES = {
     'phrase_lex' : LexFormFactory,
     'lemma'      : LemmaFormFactory,
     'frame'      : FrameFormFactory,
+    'unifiedframe'      : UnifiedFrameFormFactory,
     'argument'   : ArgumentFormFactory,
     'predefined' : PredefinedPreferenceFormFactory,
     'relational' : RelationalPreferenceFormFactory,
@@ -334,6 +337,21 @@ def send_frames_form(request):
     return JsonResponse({})
 
 
+@ajax_required
+def send_unified_frames_form(request):
+    if request.method == 'POST':
+        errors_dict = dict()
+        forms = collect_forms(request.POST['forms[]'], errors_dict)
+        eid = request.POST['entry']
+        if errors_dict:
+            del request.session['unified_frame_form']
+            return JsonResponse({ 'success' : 0, 'errors' : errors_dict })
+        else:
+            request.session['unified_frame_form'] = request.POST['forms[]']
+            return JsonResponse({ 'success' : 1 })
+    return JsonResponse({})
+
+
 def get_scroller_params(POST_data):
     order = (int(POST_data['order[0][column]']), POST_data['order[0][dir]'])
     return {
@@ -371,6 +389,12 @@ def get_entries(request):
         if scroller_params['filter']:
             entries = entries.filter(name__startswith=scroller_params['filter'])
         filtered = entries.count()
+
+        local_frame_form = None
+        if 'frame_form' in request.session:
+            errors_dict = dict()
+            local_frame_form = collect_forms(request.session['frame_form'], errors_dict)
+            assert(not errors_dict)
         
         linked_ids = set()
         if request.session['show_linked_entries']:
@@ -402,28 +426,32 @@ def get_entries(request):
             .select_related('status', 'pos')
             .prefetch_related(Prefetch("assignments", to_attr="_assignments"))
         )
+
         if with_lexical_units:
+            frameQueryset = Frame.objects.select_related("slowal_frame_2_unified_frame").prefetch_related(Prefetch("assignments", to_attr="_assignments"));
             entries = entries.prefetch_related(
                 Prefetch(
                     "lexical_units",
                     LexicalUnit.objects.prefetch_related(
                         Prefetch(
                             "frames",
-                            Frame.objects
-                            .select_related("slowal_frame_2_unified_frame")
-                            .prefetch_related(Prefetch("assignments", to_attr="_assignments")),
+                            queryset=get_filtered_objects(local_frame_form, frameQueryset) if local_frame_form is not None else frameQueryset,
                             to_attr="_frames",
                         )
                     )
                 )
             )
+
         status_names = STATUS()
         POS_names = POS()
 
         def iter_lexical_units(e):
             for lu in e.lexical_units.all():
-                lu._frame = lu._frames[0] if lu._frames else None
-                yield lu
+                lu._frame = lu._frames[0] if lu._frames and len(lu._frames) > 0 else None
+                if lu._frame is None:
+                    continue
+                else:
+                    yield lu
 
         result = {
             'draw' : scroller_params['draw'],
diff --git a/frontend/src/components/unification/Entries/EntriesList.vue b/frontend/src/components/unification/Entries/EntriesList.vue
index 40e4b3c..e72897d 100644
--- a/frontend/src/components/unification/Entries/EntriesList.vue
+++ b/frontend/src/components/unification/Entries/EntriesList.vue
@@ -24,7 +24,7 @@
       }
     },
     methods: {
-      reset_entry_list() {
+      reset_list() {
         this.$emit('lexicalUnitSelected', null, null);
         setup_entries_list({
           table: this.$refs.table,
diff --git a/frontend/src/components/unification/Unification/UnificationFramesList.vue b/frontend/src/components/unification/Unification/UnificationFramesList.vue
index b4e1bb4..933bae2 100644
--- a/frontend/src/components/unification/Unification/UnificationFramesList.vue
+++ b/frontend/src/components/unification/Unification/UnificationFramesList.vue
@@ -10,6 +10,18 @@ export default {
       canViewAssignment: has_permission("users.view_assignment")
     }
   },
+  methods: {
+    reset_list() {
+      this.$emit('unifiedFrameSelected', null);
+      setup_frames_list({
+        table: this.$refs.table,
+        unifiedFrameSelected: (unifiedFrameId) => {
+          this.$emit('unifiedFrameSelected', unifiedFrameId);
+        },
+        selectEntryId: this.initialUnifiedFrameId,
+      });
+    }
+  },
   watch: {
     unificationEntriesListRefreshKey() {
       // TODO: reload data and click in selected row
@@ -25,6 +37,7 @@ export default {
   },
   emits: ['unifiedFrameSelected'],
   mounted() {
+    this.$.appContext.config.globalProperties.$entries_list = this;
     setup_frames_list({
       table: this.$refs.table,
       unifiedFrameSelected: (unifiedFrameId) => {
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 3e8ecc4..ffa91da 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -25,6 +25,6 @@ window.update_entries = function () {
     mounted = true;
   }
   if(app._context.config.globalProperties.$entries_list) {
-    app._context.config.globalProperties.$entries_list.reset_entry_list();
+    app._context.config.globalProperties.$entries_list.reset_list();
   }
 }
diff --git a/unifier/views.py b/unifier/views.py
index 036dc00..8de7272 100644
--- a/unifier/views.py
+++ b/unifier/views.py
@@ -8,7 +8,8 @@ from django.views.decorators.csrf import csrf_exempt
 
 from common.decorators import ajax_required, ajax
 from entries.polish_strings import EXAMPLE_SOURCE, EXAMPLE_OPINION
-from entries.views import get_scroller_params, get_alternations, get_prefs_list, schema2dict, frame2dict
+from entries.views import get_scroller_params, get_alternations, get_prefs_list, schema2dict, frame2dict, collect_forms, \
+    get_filtered_objects
 from importer.unification.UnificationPreprocessXML import UnificationPreprocessHandler
 from meanings.models import LexicalUnit
 from semantics.choices import FrameStatus
@@ -72,8 +73,14 @@ def get_unified_frames(request):
         exclude_status = request.GET.get('exclude_status')
         restrict_to_user = request.GET.get('restrict_to_user')
 
+        errors_dict = dict()
+        forms = collect_forms(request.session['unified_frame_form'], errors_dict)
+
         res = {}
-        unifiedFrames = UnifiedFrame.objects.all();
+        # unifiedFrames = UnifiedFrame.objects.all();
+
+        unifiedFrames = get_filtered_objects(forms).filter()
+
         for unifiedFrame in unifiedFrames:
             res[unifiedFrame.id] = {
                 'id': str(unifiedFrame.id),
-- 
GitLab