From 2f751c4abc70eb936981912c4140f38352ec06ca Mon Sep 17 00:00:00 2001
From: dcz <dcz@ipipan.waw.pl>
Date: Fri, 14 Apr 2023 11:27:14 +0200
Subject: [PATCH] Coloring frames with no hierarchy Redirecting to hierarchy
 window with selected unified frame Next iteraztion of hierarchy tree
 component

---
 entries/static/entries/css/entries.css        |  12 +-
 entries/static/entries/js/entries.js          |   3 +
 .../entries/js/unification_entries_list.js    |  12 +-
 .../entries/js/unification_frames_list.js     |   1 +
 entries/templates/entries_base.html           |   5 +-
 entries/templates/hierarchy.html              |   2 +-
 entries/views.py                              |   8 +-
 .../Unification/LexicalUnitEdit.vue           |   6 +-
 .../unification/Unification/Unification.vue   |   1 +
 .../unification/hierarchy/Hierarchy.vue       |   9 +-
 .../unification/hierarchy/HierarchyEdit.vue   | 734 +++---------------
 .../hierarchy/HierarchyElement.vue            | 184 +++++
 unifier/models.py                             |  20 +-
 unifier/urls.py                               |   4 +
 unifier/views.py                              | 279 +++++--
 users/models.py                               |   1 +
 users/templates/notes.html                    |   4 +-
 users/urls.py                                 |   4 +-
 users/views.py                                |  14 +-
 19 files changed, 570 insertions(+), 733 deletions(-)
 create mode 100644 frontend/src/components/unification/hierarchy/HierarchyElement.vue

diff --git a/entries/static/entries/css/entries.css b/entries/static/entries/css/entries.css
index 4094eed..3cd0c90 100644
--- a/entries/static/entries/css/entries.css
+++ b/entries/static/entries/css/entries.css
@@ -192,7 +192,7 @@ legend {
     text-decoration: underline;
 }
 
-.lu-cursor-pointer {
+.cursor-pointer {
     cursor: pointer;
 }
 
@@ -276,3 +276,13 @@ legend {
     accent-color: green;
     pointer-events: none;
 }
+
+.hierarchy_not_exists {
+    background-color: rgba(138, 11, 17, 0.14) !important;
+}
+
+.table-primary,
+.table-primary>td,
+.table-primary>th {
+    background-color:#bfbfbf !important;
+}
diff --git a/entries/static/entries/js/entries.js b/entries/static/entries/js/entries.js
index 67bca83..f4370f3 100644
--- a/entries/static/entries/js/entries.js
+++ b/entries/static/entries/js/entries.js
@@ -1004,6 +1004,9 @@ function setup_datatable(options) {
             {
                 $(this).removeClass('tr-hover');
             });
+            if (options.setup_hierarchy_marking === true && data.hierarchy_exists !== true) {
+                $(row).addClass('hierarchy_not_exists');
+            }
         },
         initComplete: function(settings, json) {
             // display the first entry once it’s loaded
diff --git a/entries/static/entries/js/unification_entries_list.js b/entries/static/entries/js/unification_entries_list.js
index c7fc045..3ad9640 100644
--- a/entries/static/entries/js/unification_entries_list.js
+++ b/entries/static/entries/js/unification_entries_list.js
@@ -84,7 +84,7 @@ function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees,
     });
 }
 
-function show_notes_form($container, pk, model, refreshFunction, title, body) {
+function show_notes_form($container, pk, model, refreshFunction, type, title, body) {
     $('.note-form', $container).html($('#note-form-template > div', $container).clone());
     $('.hide-note-form', $container).click(function () {
         $('.note-form', $container).html('');
@@ -92,7 +92,7 @@ function show_notes_form($container, pk, model, refreshFunction, title, body) {
     $('.add-note', $container).click(function () {
         $.ajax({
             type: 'post',
-            url: $('#note-form-template').data('url').replace('MODEL', model).replace('PK', pk),
+            url: $('#note-form-template').data('url').replace('MODEL', model).replace('PK', pk).replace('TYPE', type),
             dataType: 'json',
             data: {
                 title: $('.note-form input[name=title]').val(),
@@ -117,18 +117,18 @@ function show_notes_form($container, pk, model, refreshFunction, title, body) {
     }
 }
 
-function setup_notes($container, $template, pk, model, refreshFunction, title, body) {
+function setup_notes($container, $template, pk, model, refreshFunction, type, title, body) {
     $container.html($template.children().clone());
     $('.show-note-form', $container).click(function () {
-        show_notes_form($container, pk, model, refreshFunction, title, body);
+        show_notes_form($container, pk, model, refreshFunction, type, title, body);
         return false
     });
     if(title || body) {
-        show_notes_form($container, pk, model, refreshFunction, title, body);
+        show_notes_form($container, pk, model, refreshFunction, type, title, body);
     }
     $.ajax({
         type: 'get',
-        url: $('.notes-table').data('url').replace('MODEL', model).replace('PK', pk),
+        url: $('.notes-table').data('url').replace('MODEL', model).replace('PK', pk).replace('TYPE', type),
         success: function (data) {
             data.notes.map(function (note) {
                 $('.notes-table tbody', $container).append(
diff --git a/entries/static/entries/js/unification_frames_list.js b/entries/static/entries/js/unification_frames_list.js
index 58bb40d..4582835 100644
--- a/entries/static/entries/js/unification_frames_list.js
+++ b/entries/static/entries/js/unification_frames_list.js
@@ -21,6 +21,7 @@ function setup_frames_list(options) {
         ],
         hidden_columns: can_see_assignees ? [3] : [2,3],
         selectEntryId: options.selectEntryId,
+        setup_hierarchy_marking: true
     });
     datatable.on('click', 'tr.entry', function () {
         const data = datatable.row(this).data();
diff --git a/entries/templates/entries_base.html b/entries/templates/entries_base.html
index 9745960..2d460c0 100644
--- a/entries/templates/entries_base.html
+++ b/entries/templates/entries_base.html
@@ -30,7 +30,10 @@
 {% block additional-nav-items %}
 {% if request.user.is_authenticated %}
     <li class="nav-item"><a href="{% url 'entries:unification' %}" class="nav-link">{% trans "Unifikacja" %}</a></li>
-    <li class="nav-item"><a href="{% url 'entries:hierarchy' %}" class="nav-link">{% trans "Hierarchia" %}</a></li>
+    <li class="nav-item"><a
+                            onclick='window.location.replace(window.currUnifiedFrameId ? "/pl/entries/hierarchy/?unified_frame_id="+window.currUnifiedFrameId : "/pl/entries/hierarchy")'
+                            class="nav-link cursor-pointer">{% trans "Hierarchia" %}</a></li>
+
 {% endif %}
 <li class="nav-item dropdown">
     <a class="nav-link dropdown-toggle" href="#" id="nav-filters" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
diff --git a/entries/templates/hierarchy.html b/entries/templates/hierarchy.html
index 6c75714..61a407d 100644
--- a/entries/templates/hierarchy.html
+++ b/entries/templates/hierarchy.html
@@ -14,7 +14,7 @@
     <script src="{% static 'entries/js/unification_entries_for_frames_list.js' %}"></script>
     <script src="{% static 'entries/js/jquery-impromptu.min.js' %}"></script>
     <script>
-        window.initialUnifiedFrameId = {{ unified_frame_id|default:'null' }};
+        window.currUnifiedFrameId = {{ unified_frame_id|default:'null' }};
     </script>
 {% endblock %}
 
diff --git a/entries/views.py b/entries/views.py
index 1bd5081..686d925 100644
--- a/entries/views.py
+++ b/entries/views.py
@@ -86,14 +86,20 @@ def unification(request):
         },
     )
 
+
 @login_required
 def hierarchy(request):
+
+    unified_frame_id = None
+    if "unified_frame_id" in request.GET:
+        unified_frame_id = request.GET.get("unified_frame_id")
+
     return render(
         request,
         'hierarchy.html',
         {
             'is_vue_app': True,
-            'unified_frame_id': request.GET.get("unified_frame_id"),
+            'unified_frame_id': unified_frame_id,
             'entries_form': EntryForm(),
             'frames_form': FrameFormFactory.get_form(as_subform=False),
             'schemata_form': SchemaFormFactory.get_form(as_subform=False),
diff --git a/frontend/src/components/unification/Unification/LexicalUnitEdit.vue b/frontend/src/components/unification/Unification/LexicalUnitEdit.vue
index 2afeec2..a2ea35f 100644
--- a/frontend/src/components/unification/Unification/LexicalUnitEdit.vue
+++ b/frontend/src/components/unification/Unification/LexicalUnitEdit.vue
@@ -115,16 +115,16 @@ Object.assign(LexicalUnitEdit, {
       }
     },
     setup_notes_unified_frame() {
-      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.unified_frame.id, 'unifier.UnifiedFrame', this.setup_notes_unified_frame);
+      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.unified_frame.id, 'unifier.UnifiedFrame', this.setup_notes_unified_frame, 'unified_frame');
     },
     setup_notes_slowal_frame() {
       if (this.active_slowal_frame) {
-        setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame);
+        setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame, 'semantics_frame');
       }
     },
     setup_notes_slowal_frame_with_title_and_body(title, body) {
       this.right_pane_tab = 'notes';
-      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame, title, body);
+      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame, 'semantics_frame', title, body);
     },
     unifiedFrameArgumentSelected(argument) {
       if (this.active_unified_frame_argument === argument) {
diff --git a/frontend/src/components/unification/Unification/Unification.vue b/frontend/src/components/unification/Unification/Unification.vue
index 9268241..2ab9367 100644
--- a/frontend/src/components/unification/Unification/Unification.vue
+++ b/frontend/src/components/unification/Unification/Unification.vue
@@ -22,6 +22,7 @@ export default {
       this.unifiedFrameId = unifiedFrameId;
       this.entryId = entryId;
       this.lexicalUnitId = lexicalUnitId;
+      window.currUnifiedFrameId = unifiedFrameId;
     },
     refreshEntriesList() {
       this.unificationEntriesListRefreshKey++;
diff --git a/frontend/src/components/unification/hierarchy/Hierarchy.vue b/frontend/src/components/unification/hierarchy/Hierarchy.vue
index 82876fe..af652e5 100644
--- a/frontend/src/components/unification/hierarchy/Hierarchy.vue
+++ b/frontend/src/components/unification/hierarchy/Hierarchy.vue
@@ -28,15 +28,13 @@ export default {
     }
   },
   setup() {
-    const lexicalUnit = new URL(location.href).searchParams.get('lexical_unit_id');
-    const entryId = new URL(location.href).searchParams.get('entry_id');
+    const unified_frame_id = new URL(location.href).searchParams.get('unified_frame_id');
     return {
-      initialLexicalUnitId: parseInt(lexicalUnit),
-      initialEntryId: parseInt(entryId),
+      initial_unified_frame_id: parseInt(unified_frame_id),
     };
   },
   mounted () {
-    this.unifiedFrameSelected(window.initialUnifiedFrameId);
+    this.unifiedFrameSelected(window.currUnifiedFrameId);
     $('#entries-list').length && Split(['#entries-list', '#entry-display'], {
       sizes: [20, 80],
       gutterSize: 4,
@@ -56,6 +54,7 @@ export default {
     <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-hidden">
       <unification-frames-list
               :unificationEntriesListRefreshKey="unificationEntriesListRefreshKey"
+              :initialUnifiedFrameId="initial_unified_frame_id"
               @unified-frame-selected="unifiedFrameSelected"
       />
 <!--      <unification-switchable-list-->
diff --git a/frontend/src/components/unification/hierarchy/HierarchyEdit.vue b/frontend/src/components/unification/hierarchy/HierarchyEdit.vue
index 79f174b..80b326a 100644
--- a/frontend/src/components/unification/hierarchy/HierarchyEdit.vue
+++ b/frontend/src/components/unification/hierarchy/HierarchyEdit.vue
@@ -6,12 +6,12 @@ import SlowalFrameComponent from "../shared/frame-components/SlowalFrameComponen
 import SemanticsSchemataComponent from "../shared/frame-components/SemanticsSchemataComponent.vue";
 import MeaningComponent from "../shared/frame-components/MeaningComponent.vue";
 import SelectionalPreference from "../Unification/SelectionalPreference.js";
-import UnificationFramePreview from "../Unification/UnificationFramePreview.vue";
 import { slowal_frames2selecional_preferencies } from "../shared/utils.js";
 import HierarchyPreview from "./HierarchyPreview.vue";
+import HierarchyElement from "./HierarchyElement.vue";
 
 let HierarchyEdit = {
-  components: {HierarchyPreview}
+  components: {HierarchyElement, HierarchyPreview}
 };
 
 Object.assign(HierarchyEdit, {
@@ -57,9 +57,11 @@ Object.assign(HierarchyEdit, {
       selectedSchemas: null,
       selectedExamples: null,
       hidden_frames: [],
+      hierarchy_hyponyms: null,
+      hierarchy_hyperonyms: null,
     }
   },
-  components: {InfoTooltip, Spinner, HierarchyPreview, SlowalFrameComponent, ExamplesComponent, SemanticsSchemataComponent, MeaningComponent},
+  components: {HierarchyElement, InfoTooltip, Spinner, HierarchyPreview, SlowalFrameComponent, ExamplesComponent, SemanticsSchemataComponent, MeaningComponent},
   emits: ['goToDisplay', 'refresh', 'swapFrames', 'refreshEntriesList', 'clearUnifiedFrameView'],
   watch: {
     forceRefresh(newVal, oldVal) {
@@ -69,9 +71,19 @@ Object.assign(HierarchyEdit, {
   computed: {
     selectionalPreference() {
       return new SelectionalPreference();
-    }
+    },
   },
   methods: {
+    createHierarchyTree() {
+      return {
+        'unified_frame': this.unified_frame,
+        'hyponyms': this.hierarchy_hyponyms,
+        'hypyronyms': this.hierarchy_hyperonyms
+      };
+    },
+    goToEdit (unifiedFrameId, entryId, lexicalUnitId) {
+      window.location = `/${lang}/entries/unification?unified_frame_id=${this.unified_frame.id}`;
+    },
     hasWhiteSpace(s) {
       return /\s/g.test(s);
     },
@@ -104,7 +116,6 @@ Object.assign(HierarchyEdit, {
             window.update_last_visited(response.last_visited);
             window.clear_info();
 
-            this.changeStatusButtonTitleToDefault();
             if (!this.active_slowal_frame) {
               this.setup_notes_unified_frame();
             }
@@ -113,22 +124,40 @@ Object.assign(HierarchyEdit, {
             show_error(errorType + ' (' + errorMessage + ')');
           }
         });
-
+        this.loadHierarchy();
       } catch (error) {
         console.log(error);
       }
     },
-    setup_notes_unified_frame() {
-      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.unified_frame.id, 'unifier.UnifiedFrame', this.setup_notes_unified_frame);
-    },
-    setup_notes_slowal_frame() {
-      if (this.active_slowal_frame) {
-        setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame);
-      }
+    loadHierarchy() {
+      $.ajax({
+        type: 'post',
+        url: '/' + lang + '/unifier/get_hierarchy_hyponyms/'+this.unifiedFrameId+"/",
+        dataType: 'json',
+        timeout: 60000,
+        success: function (response) {
+          this.hierarchy_hyponyms = response.hyponyms;
+        }.bind(this),
+        error: function (request, errorType, errorMessage) {
+          show_error(errorType + ' (' + errorMessage + ')');
+        }
+      });
+
+      $.ajax({
+        type: 'post',
+        url: '/' + lang + '/unifier/get_hierarchy_hyperonyms/'+this.unifiedFrameId+"/",
+        dataType: 'json',
+        timeout: 60000,
+        success: function (response) {
+          this.hierarchy_hyperonyms = response.hyperonyms;
+        }.bind(this),
+        error: function (request, errorType, errorMessage) {
+          show_error(errorType + ' (' + errorMessage + ')');
+        }
+      });
     },
-    setup_notes_slowal_frame_with_title_and_body(title, body) {
-      this.right_pane_tab = 'notes';
-      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.active_slowal_frame.id, 'semantics.Frame', this.setup_notes_slowal_frame, title, body);
+    setup_notes_unified_frame() {
+      setup_notes($('#notes-component'), $('#lexical-unit-notes-template'), this.unified_frame.id, 'unifier.UnifiedFrame', this.setup_notes_unified_frame, 'hierarchy_frame');
     },
     unifiedFrameArgumentSelected(argument) {
       if (this.active_unified_frame_argument === argument) {
@@ -154,274 +183,59 @@ Object.assign(HierarchyEdit, {
         show_info(gettext('Kliknij, aby wybrać kolumnę do edycji.'));
       }
     },
-    addSelectivePreference() {
-      if (!this.active_unified_frame_argument) {
-        alert(gettext("Zaznacz argument, do którego chcesz dodać preferencję."));
+    create_hyponym_or_hyperonym_relation(isHyperonym) {
+      if (this.currentPreviewedUnifiedFrameId === -1) {
+        alert(gettext("Wybierz w oknie podglądu ram ramę, która będzie " + (isHyperonym ? "hiperonimem" : "hiponimem") + " ramy z okna głównego."));
       } else {
-        this.selectionalPreference.addSelectivePreference(this.unified_frame, this.active_unified_frame_argument.id, this.frames, function () {
-          this.loadFrame();
-        }.bind(this));
-        //   window.addSelectivePreference(this.unified_frame, this.active_unified_frame_argument.id, this.frames, function () {
-        //   this.loadFrame();
-        // }.bind(this));
-      }
-    },
-    removeSelectionalPreference() {
-      if (!this.active_unified_frame_argument) {
-        alert(gettext("Zaznacz argument, do którego chcesz dodać preferencję."));
-      } else {
-        const existingPreferencies = function () {
-          return this.active_unified_frame_argument.preferences.map(preference => {
-            return `<label><input type="checkbox" name="preference" value="${preference.id}" /> ${preference.str}</label><br />`;
-          }).join("");
-        }.bind(this);
-
-        const remove_preference_popup = {
-          state0: {
-            title: 'Wybierz preferencje do usunięcia',
-            html: existingPreferencies,
-            buttons: {Anuluj: 0, Usuń: 1},
-            focus: -1,
-            submit: function (e, v, m, f) {
-              if (v == 0) {
-                e.preventDefault();
-                $.prompt.close();
-              }
-              if (v === 1) {
-                e.preventDefault();
-                let preference_ids = normalizeFormData(f.preference);
-                const data = {
-                  'unified_frame_id': this.unified_frame.id,
-                  'argument_id': this.active_unified_frame_argument.id,
-                  'preference_ids': JSON.stringify(preference_ids)
-                };
-                $.ajax({
-                  type: 'post',
-                  url: '/' + lang + '/unifier/remove_selectional_preference/',
-                  dataType: 'json',
-                  data: data,
-                  timeout: 60000,
-                  success: function (response) {
-                    show_info('Wybrane preferencje zostału usunięte.');
-                    this.loadFrame();
-                    $.prompt.close();
-                  }.bind(this),
-                  error: function (request, errorType, errorMessage) {
-                    show_error(errorType + ' (' + errorMessage + ')');
-                    $.prompt.close();
-                  }
-                });
-              }
-            }.bind(this)
-          }
-        }
-        $.prompt(remove_preference_popup);
-      }
-    },
-    changeTitle() {
-      let title = this.unified_frame.title != null ? this.unified_frame.title : '';
-      const change_title_popup = {
-        state0: {
-          title: 'Zmiana nazwy ramy',
-          html: '<input type="text" size="32" value="' + title + '" name="title" />',
-          buttons: {Anuluj: 0, Zapisz: 1},
-          focus: -1,
-          submit: function (e, v, m, f) {
-            if (v == 0) {
-              $.prompt.close();
-            }
-            if (v === 1) {
-              e.preventDefault();
-              const title = f.title;
-
-              if (this.hasWhiteSpace(title)) {
-                alert(gettext("Nazwa zunifikowanej ramy nie może zawierać białych znaków."));
-              } else {
-                const data = {'unified_frame_id': this.unified_frame.id, 'unified_frame_title': title};
-                $.ajax({
-                  type: 'post',
-                  url: '/' + lang + '/unifier/save_unified_frame_title/',
-                  dataType: 'json',
-                  data: data,
-                  timeout: 60000,
-                  success: function (response) {
-                    show_info('Tytuł ramy zosał zapisany');
-                    $.prompt.close();
-                    this.loadFrame();
-                  }.bind(this),
-                  error: function (request, errorType, errorMessage) {
-                    show_error(errorType + ' (' + errorMessage + ')');
-                    $.prompt.close();
-                  }
-                });
-              }
+        const data = isHyperonym ? {'hyponym_id': this.currentPreviewedUnifiedFrameId, 'hyperonym_id': this.unified_frame.id} :
+                {'hyponym_id': this.unified_frame.id, 'hyperonym_id': this.currentPreviewedUnifiedFrameId};;
+        $.ajax({
+          type: 'post',
+          url: '/' + lang + '/unifier/hierarchy_assign/',
+          dataType: 'json',
+          data: data,
+          timeout: 60000,
+          success: function (response) {
+            if(response.succ === 'true') {
+              show_info('Relacja hierarchii została ustanowiona.');
+              this.loadHierarchy();
+              this.$emit('refreshEntriesList');
+            } else if(response.exists === 'true') {
+              alert(gettext("Wybrana relacja hierarchii już występuje."));
             }
-          }.bind(this)
-        }
+          }.bind(this),
+          error: function (request, errorType, errorMessage) {
+            show_error(errorType + ' (' + errorMessage + ')');
+          }
+        });
       }
-      $.prompt(change_title_popup);
     },
-    changeRole() {
-      if (!this.active_unified_frame_argument) {
-        alert(gettext("Zaznacz argument, dla którego chcesz wybrać rolę."));
+    delete_hyponym_or_hyperonym_relation() {
+      if (this.currentPreviewedUnifiedFrameId === -1) {
+        alert(gettext("Wybierz w oknie podglądu ram ramę, dla której powiązanie ma zostać usunięte."));
       } else {
-        const existingSelect = function () {
-          let selected_unified_frame_argument = this.unified_frame_arguments.find(o => o.id === this.active_unified_frame_argument.id);
-          if (!selected_unified_frame_argument.proposed_roles) {
-            return gettext('Brak ról do wyboru.')
-          }
-          return selected_unified_frame_argument.proposed_roles.map(role => {
-            return `<label><input type="radio" name="roles" value="${role.id}" /> ${role.str}</label><br />`;
-          }).join("");
-        }.bind(this);
-
-        const newSelect = function () {
-          let rolesHTML = roles.map(role => {
-            return `<label><input type="radio" name="role" value="${role.id}" /> ${role.role}</label><br />`;
-          }).join("");
-          let attributesHTML = role_attributes.map(attribute => {
-            return `<label><input type="radio" name="attribute" value="${attribute.id}" /> ${attribute.attribute}</label><br />`;
-          }).join("");
-          const roleTypeHTML = ['role', 'modifier'].map(type => {
-            return `<label><input type="radio" name="role_type" value="${type}" /> ${type}</label><br />`;
-          }).join("");
-          return '<div class="row">' +
-              '<div class="column"><div class="role_select_header">Type</div>' + roleTypeHTML + '</div>' +
-              '<div class="column"><div class="role_select_header">Role</div>' + rolesHTML + '</div>' +
-              '<div class="column"><div class="role_select_header">Atrybuty</div>' + attributesHTML + '</div>' +
-              '</div>';
-        }.bind(this);
-
-        let change_role_popup = {
-          state0: {
-            title: 'Wybór roli',
-            html: 'Wybierz lub dodaj rolÄ™',
-            buttons: {Wybierz: 0, Dodaj: 1, Koniec: -1},
-            focus: -1,
-            submit: function (e, v, m, f) {
-              if (v == -1) {
-                e.preventDefault();
-
-                this.loadFrame();
-
-                $.prompt.close();
-              }
-              if (v === 0) {
-                e.preventDefault();
-                $.prompt.goToState('state1');
-              }
-              if (v == 1) {
-                e.preventDefault();
-                $.prompt.goToState('state2');
-              }
-            }.bind(this)
-          },
-          state1: {
-            title: 'Wybierz rolÄ™',
-            html: existingSelect(),
-            buttons: {Anuluj: -1, Zatwierdź: 1},
-            focus: -1,
-            submit: function (e, v, m, f) {
-              if (v == -1) {
-                e.preventDefault();
-                $.prompt.goToState('state0');
-              }
-              if (v == 1) {
-                e.preventDefault();
-                normalizeFormData(f.roles).map(role_id => {
-                  const data = {
-                    'unified_frame_id': this.unified_frame.id,
-                    'complement_id': this.active_unified_frame_argument.id,
-                    'role_id': role_id
-                  };
-                  $.ajax({
-                    type: 'post',
-                    url: '/' + lang + '/unifier/save_selected_role/',
-                    dataType: 'json',
-                    data: data,
-                    timeout: 60000,
-                    success: function (response) {
-                      show_info('Wybrana rola zosała zapisana');
-                      $.prompt.goToState('state0');
-                    }.bind(this),
-                    error: function (request, errorType, errorMessage) {
-                      show_error(errorType + ' (' + errorMessage + ')');
-                      $.prompt.close();
-                    }
-                  });
-                });
-              }
-            }.bind(this)
-          },
-          state2: {
-            title: 'Dodaj rolÄ™',
-            html: newSelect(),
-            buttons: {Anuluj: -1, Zatwierdź: 1},
-            focus: -1,
-            submit: function (e, v, m, f) {
-              if (v == -1) {
-                e.preventDefault();
-                $.prompt.goToState('state0');
-              }
-              if (v == 1) {
-                e.preventDefault();
-
-                const role_id = normalizeFormData(f.role)[0];
-                const role_type = normalizeFormData(f.role_type)[0];
-
-                if (role_id != null && role_type != null) {
-                  const attribute_id = normalizeFormData(f.attribute)[0];
-
-                  const data = {
-                    'unified_frame_id': this.unified_frame.id,
-                    'complement_id': this.active_unified_frame_argument.id,
-                    'role_type': role_type,
-                    'role_id': role_id,
-                    'attribute_id': attribute_id
-                  };
-                  $.ajax({
-                    type: 'post',
-                    url: '/' + lang + '/unifier/save_new_role/',
-                    dataType: 'json',
-                    data: data,
-                    timeout: 60000,
-                    success: function (response) {
-                      show_info('Nowa rola zosała zapisana');
-                      $.prompt.goToState('state0');
-                    }.bind(this),
-                    error: function (request, errorType, errorMessage) {
-                      show_error(errorType + ' (' + errorMessage + ')');
-                      $.prompt.close();
-                    }
-                  });
-                } else {
-                  alert(gettext("Musisz wybrać typ oraz rolę."));
-                }
-              }
-            }.bind(this)
+        const data = {'rel1_id': this.currentPreviewedUnifiedFrameId, 'rel2_id': this.unified_frame.id};
+        $.ajax({
+          type: 'post',
+          url: '/' + lang + '/unifier/hierarchy_unassign/',
+          dataType: 'json',
+          data: data,
+          timeout: 60000,
+          success: function (response) {
+            if(response.succ === true) {
+              show_info('Relacja hierarchii została usunięta.');
+              this.loadHierarchy();
+              this.$emit('refreshEntriesList');
+            } else {
+              alert(gettext("Wybrana relacja hierarchii nie występuje."));
+            }
+          }.bind(this),
+          error: function (request, errorType, errorMessage) {
+            show_error(errorType + ' (' + errorMessage + ')');
           }
-        };
-        $.prompt(change_role_popup);
+        });
       }
     },
-    addArgument() {
-      const data = {'unified_frame_id': this.unified_frame.id};
-      $.ajax({
-        type: 'post',
-        url: '/' + lang + '/unifier/add_argument/',
-        dataType: 'json',
-        data: data,
-        timeout: 60000,
-        success: function (response) {
-          show_info('Nowy argument zosał dodany');
-          this.loadFrame();
-        }.bind(this),
-        error: function (request, errorType, errorMessage) {
-          show_error(errorType + ' (' + errorMessage + ')');
-          $.prompt.close();
-        }
-      });
-    },
     getSlowalFrameArgumentsBy(unified_frame_argument) {
       const slowalFrameArgumentIds = [];
       for (let i in this.unified_frame.slowal_frame_mapping) {
@@ -439,163 +253,6 @@ Object.assign(HierarchyEdit, {
       }
       return slowalFrameArgumentIds;
     },
-    removeArgument() {
-      if (!this.active_unified_frame_argument) {
-        alert(gettext("Zaznacz argument, który chcesz usunąć."));
-      } else {
-        let hasSlowalFrameArgumentMapping = false;
-        for (let i in this.unified_frame.slowal_frame_mapping) {
-          const slowal_frame_mapping = this.unified_frame.slowal_frame_mapping[i];
-          for (let j in slowal_frame_mapping.slowal_frame_argument_mapping) {
-            const slowal_frame_argument_mapping = slowal_frame_mapping.slowal_frame_argument_mapping[j];
-            if (slowal_frame_argument_mapping.unified_frame_agrument_id == this.active_unified_frame_argument.id) {
-              hasSlowalFrameArgumentMapping = true;
-              break;
-            }
-          }
-        }
-        if (hasSlowalFrameArgumentMapping) {
-          alert(gettext("Zaznaczony argument nie może zostać usunięty - podpięte ramy posiadają do niego dowiązania."));
-        } else {
-          const data = {
-            'unified_frame_id': this.unified_frame.id,
-            'complement_id': this.active_unified_frame_argument.id
-          };
-          $.ajax({
-            type: 'post',
-            url: '/' + lang + '/unifier/remove_argument/',
-            dataType: 'json',
-            data: data,
-            timeout: 60000,
-            success: function (response) {
-              show_info('Wybrany argument zosał usunięty');
-              this.loadFrame();
-            }.bind(this),
-            error: function (request, errorType, errorMessage) {
-              show_error(errorType + ' (' + errorMessage + ')');
-              $.prompt.close();
-            }
-          });
-        }
-      }
-    },
-    duplicate() {
-      let title = this.unified_frame.title != null ? this.unified_frame.title : '';
-      const duplicate_popup = {
-        state0: {
-          title: 'Podaj nazwÄ™ zduplikowanej ramy',
-          html: '<input type="text" size="32" value="KOPIA_' + title + '" name="title" />',
-          buttons: {Anuluj: 0, Zapisz: 1},
-          focus: -1,
-          submit: function (e, v, m, f) {
-            if (v == 0) {
-              e.preventDefault();
-              $.prompt.close();
-            }
-            if (v === 1) {
-              e.preventDefault();
-              const title = f.title;
-
-              if (this.hasWhiteSpace(title)) {
-                alert(gettext("Nazwa zunifikowanej ramy nie może zawierać białych znaków."));
-              } else {
-                const data = {
-                  'unified_frame_id': this.unified_frame.id,
-                  'target_unified_frame_title': title
-                };
-                $.ajax({
-                  type: 'post',
-                  url: '/' + lang + '/unifier/duplicate_unified_frame/',
-                  dataType: 'json',
-                  data: data,
-                  timeout: 60000,
-                  success: function (response) {
-                    show_info('Zunifikowana rama została zduplikowana.');
-                    this.currentPreviewedUnifiedFrameId = response.unified_frame_id;
-                    $.prompt.close();
-                  }.bind(this),
-                  error: function (request, errorType, errorMessage) {
-                    show_error(errorType + ' (' + errorMessage + ')');
-                    $.prompt.close();
-                  }
-                });
-              }
-            }
-          }.bind(this)
-        }
-      }
-      $.prompt(duplicate_popup);
-    },
-    changeUnifiedFrameStatusToReadyOrVerified() {
-      if (this.isSuperLeksykograf())
-        this.changeUnifiedFrameStatusToReadyOrVerifiedByPath('change_unified_frame_status_to_verified_by_superleksykograf');
-      else
-        this.changeUnifiedFrameStatusToReadyOrVerifiedByPath('change_unified_frame_status_to_ready')
-    },
-    changeUnifiedFrameStatusToReadyOrVerifiedByPath(url_path) {
-      let foundNotVerifiedFrame = this.frames.find(frame => frame.status !== 'G' && frame.status !== 'S');
-      if (foundNotVerifiedFrame) {
-        alert(gettext("Wszystkie podpięte ramy powinny być zweryfikowane."));
-      } else {
-        let roleDict = {};
-        let hasPreferenceSelected = true;
-        for (let i in this.unified_frame_arguments) {
-          const argument = this.unified_frame_arguments[i];
-          if (argument.role) {
-            roleDict[argument.role.str] = argument.role;
-          }
-          if (!argument.preferences || argument.preferences.length == 0) {
-            hasPreferenceSelected = false;
-          }
-        }
-        if (Object.keys(roleDict).length === this.unified_frame_arguments.length && hasPreferenceSelected) {
-          //all roles are set, and are uniq
-          //TODO: aktywne preferencje w argumencie nie znajdujÄ… siÄ™ w relacji hipo-/hiperonimii.
-          const data = {'unified_frame_id': this.unified_frame.id};
-          $.ajax({
-            type: 'post',
-            url: '/' + lang + '/unifier/' + url_path + '/',
-            dataType: 'json',
-            data: data,
-            timeout: 60000,
-            success: function (response) {
-              show_info('Status ramy został zmieniony');
-              this.loadFrame();
-            }.bind(this),
-            error: function (request, errorType, errorMessage) {
-              show_error(errorType + ' (' + errorMessage + ')');
-            }
-          });
-        } else {
-          alert(gettext("Role dla wszystkich argumentów powinny być ustawione oraz unikalne. Preferencje selekcyjne dla wszystkich argumentów powinny być ustawione."));
-        }
-      }
-    },
-    change_slowal2unified_frame_argument_mapping(slowal_frame) {
-      if (slowal_frame === this.active_slowal_frame && this.selectedFrameArguments && this.selectedFrameArguments.length == 2) {
-        const data = {
-          'unified_frame_id': this.unified_frame.id,
-          'slowal_frame_id': slowal_frame.id,
-          'slowal_frame_selected_arguments': JSON.stringify(this.selectedFrameArguments.map(arg => arg.argument_id))
-        };
-        $.ajax({
-          type: 'post',
-          url: '/' + lang + '/unifier/change_slowal2unified_fram_argument_mapping/',
-          dataType: 'json',
-          data: data,
-          timeout: 60000,
-          success: function (response) {
-            show_info('Argumenty w ramie zostały zmienione');
-            this.loadFrame();
-          }.bind(this),
-          error: function (request, errorType, errorMessage) {
-            show_error(errorType + ' (' + errorMessage + ')');
-          }
-        });
-      } else {
-        alert(gettext("Zamiany pozycji argumentu w ramie wymaga zaznaczenia dokładnie 2 argumentów."));
-      }
-    },
     isSuperLeksykograf() {
       return has_permission("users.view_assignment");
     },
@@ -610,68 +267,9 @@ Object.assign(HierarchyEdit, {
         alert(gettext("Wybierz ramę, dla której chcesz zmienić status."));
       }
     },
-    change_slowal_frame_status(status) {
-      this.select_slowal_frame_req(() => {
-        let frame = this.active_slowal_frame;
-
-        if (status === 'B') {
-          this.setup_notes_slowal_frame_with_title_and_body("Niedopasowana jednostka (identyfikator jednostki)", "Do zatwierdzenia uznanie ramy Walentego jednostek (lista jednostek)" +
-              "za niedopasowana do zunifikowanej ramy (identyfikator) przez (Leksykograf).");
-        } else if (status === 'Z') {
-          this.setup_notes_slowal_frame_with_title_and_body("Błędna jednostka (identyfikator jednostki)", "Do zatwierdzenia uznanie ramy Walentego jednostek (lista jednostek)" +
-              "za niedopasowana do zunifikowanej ramy (identyfikator) przez (Leksykograf).");
-        }
-
-        const data = {
-          'unified_frame_id': this.unified_frame.id,
-          'slowal_frame_id': frame.id,
-          'status': status
-        };
-        $.ajax({
-          type: 'post',
-          url: '/' + lang + '/unifier/change_slowal_frame_status/',
-          dataType: 'json',
-          data: data,
-          timeout: 60000,
-          success: function (response) {
-            show_info('Status ramy został zmieniony');
-            this.$emit('refreshEntriesList');
-            this.loadFrame();
-          }.bind(this),
-          error: function (request, errorType, errorMessage) {
-            show_error(errorType + ' (' + errorMessage + ')');
-          }
-        });
-      });
-    },
-    slowal_frame_ready_rollback(status) {
-      this.select_slowal_frame_req(() => {
-        let frame = this.active_slowal_frame;
-        const isSuperLeksykograf = this.isSuperLeksykograf();
-        let status = null;
-        if (!isSuperLeksykograf && frame.status === 'G') {
-          //przywracamy O
-          status = 'O';
-        } else if (!isSuperLeksykograf && frame.status === 'O') {
-          //ustawiany na Gotowe
-          status = 'G';
-        } else if (isSuperLeksykograf && frame.status === 'S') {
-          //ustawiany Sprawdzone
-          status = 'G';
-        } else {
-          status = 'S';
-        }
-
-        this.change_slowal_frame_status(status);
-      });
-    },
     isFrameVisible(status) {
       return (status != 'B' && status != 'C') || this.isSuperLeksykograf();
     },
-    changeStatusButtonTitleToDefault() {
-      const isSuperLeksykograf = this.isSuperLeksykograf();
-      this.statusButtonTitle = isSuperLeksykograf ? 'Sprawdź' : 'Gotowe';
-    },
     deselectSlowalFrameSelectedElements() {
       this.subentries.forEach(subentry => {
         subentry.schemata.forEach(s => {
@@ -698,18 +296,8 @@ Object.assign(HierarchyEdit, {
       this.deselectSlowalFrameSelectedElements();
       if (this.active_slowal_frame === frame) {
         this.active_slowal_frame = null;
-        if (!this.readOnly) {
-          this.setup_notes_unified_frame();
-          this.changeStatusButtonTitleToDefault();
-        }
       } else {
         this.active_slowal_frame = frame;
-        if (!this.readOnly) {
-          this.setup_notes_slowal_frame()
-          if (this.isFrameVerified(frame)) {
-            this.statusButtonTitle = 'Przywróć';
-          }
-        }
       }
     },
     isSelectedFrame(frame) {
@@ -719,135 +307,21 @@ Object.assign(HierarchyEdit, {
         return false;
       }
     },
-    extract_frames_to_new_frame() {
-
-      const existingSelect = function () {
-        return this.frames.map(frame => {
-          return `<label><input type="checkbox" name="frames" value="${frame.id}" /> ${lexical_units2dom(frame.lexical_units)}</label><br />`;
-        }).join("");
-      }.bind(this);
-
-      const extract_frames_to_new_frame_popup = {
-        state0: {
-          title: 'Podaj nazwÄ™ zduplikowanej ramy',
-          html: existingSelect,
-          buttons: {Anuluj: 0, Wydziel: 1},
-          focus: -1,
-          submit: function (e, v, m, f) {
-            if (v == 0) {
-              e.preventDefault();
-              $.prompt.close();
-            }
-            if (v === 1) {
-              e.preventDefault();
-              let frame_ids = normalizeFormData(f.frames);
-              const data = {
-                'unified_frame_id': this.unified_frame.id, 'slowal_frame_ids': JSON.stringify(frame_ids),
-                'target_unified_frame_id': ''
-              };
-              $.ajax({
-                type: 'post',
-                url: '/' + lang + '/unifier/extract_frames_to_new_frame/',
-                dataType: 'json',
-                data: data,
-                timeout: 60000,
-                success: function (response) {
-                  show_info('Ramy zostały wydzielone do nowej ramy zunifikowanej.');
-                  this.loadFrame();
-                  let newUnifiedFrameId = response.unified_frame_id;
-                  this.currentPreviewedUnifiedFrameId = newUnifiedFrameId;
-                  $.prompt.close();
-                }.bind(this),
-                error: function (request, errorType, errorMessage) {
-                  show_error(errorType + ' (' + errorMessage + ')');
-                  $.prompt.close();
-                }
-              });
-            }
-          }.bind(this)
-        }
-      }
-      $.prompt(extract_frames_to_new_frame_popup);
-    },
-    extract_frame_to_preview_frame() {
-      if (this.currentPreviewedUnifiedFrameId !== -1 && this.currentPreviewedUnifiedFrameId !== this.unified_frame.id) {
-        let target_unified_frame_id = this.currentPreviewedUnifiedFrameId;
-        const data = {
-          'unified_frame_id': this.unified_frame.id, 'slowal_frame_ids': JSON.stringify([this.active_slowal_frame.id]),
-          'target_unified_frame_id': target_unified_frame_id
-        };
-        $.ajax({
-          type: 'post',
-          url: '/' + lang + '/unifier/extract_frames_to_new_frame/',
-          dataType: 'json',
-          data: data,
-          timeout: 60000,
-          success: function (response) {
-            show_info('Zaznaczona rama została przeniosiona.');
-            this.active_slowal_frame = null;
-            this.loadFrame();
-            this.internalForceRefresh += 1;
-          }.bind(this),
-          error: function (request, errorType, errorMessage) {
-            show_error(errorType + ' (' + errorMessage + ')');
-          }
-        });
-      }
-    },
     changePreviewedUnifiedFrameId(unifiedFrameId) {
       this.currentPreviewedUnifiedFrameId = unifiedFrameId;
     },
-    swapUnifiedFrames() {
-      if (this.currentPreviewedUnifiedFrameId !== -1 && this.unified_frame.id !== this.currentPreviewedUnifiedFrameId) {
-        this.$emit("swapFrames", this.currentPreviewedUnifiedFrameId);
-      }
-    },
-    changeShowVerifiedFrames(val) {
-      this.showVerifiedFrames = val;
-      this.hidden_frames = [];
-    },
     getArgumentCSS(argument) {
       return (argument.role ? argument.role.str + ' ' : '') + (argument == this.active_unified_frame_argument ? 'active' : '');
     },
-    meaningLuSelected(selectedLus) {
-      this.selectedLus = selectedLus;
-    },
-    insideFrameSelectionChanged(selectedFrameArguments) {
-      this.selectedFrameArguments = selectedFrameArguments;
-    },
     schemataSelected(schemas) {
       this.selectedSchemas = schemas;
     },
     exampleSelected(selectedExamples) {
       this.selectedExamples = selectedExamples;
     },
-    deleteUnifiedFrames() {
-      if (!confirm(gettext("Czy na pewno chcesz zunifikowanÄ… ramÄ™?"))) return false;
-      $.ajax({
-        type: 'post',
-        url: '/' + lang + `/unifier/delete_unified_frame/${this.unified_frame.id}/`,
-        dataType: 'json',
-        timeout: 60000,
-        success: function (response) {
-          show_info('Zunifikowana rama została usunięta.');
-          this.$emit('refreshEntriesList');
-          this.$emit("swapFrames", null);
-        }.bind(this),
-        error: function (request, errorType, errorMessage) {
-          show_error(errorType + ' (' + errorMessage + ')');
-        }
-      });
-    },
     isReadOnlyForSuperLeksykograf() {
       return (this.isSuperLeksykograf() && this.unified_frame.status === 'O') && this.unified_frame.assignee_username !== window.USER_USERNAME;
     },
-    hideFrame(frame) {
-      this.hidden_frames.push(frame);
-    },
-    getSlowalReadyFrameCnt() {
-      const readyFrames = this.frames.filter(frame => frame.status == 'G');
-      return readyFrames.length;
-    },
     frames2lexical_units(frames) {
       const lexical_units = []
       for (let i in frames) {
@@ -890,6 +364,12 @@ Object.assign(HierarchyEdit, {
           slowal_frame.arguments = new_slowal_frame_arguments;
         }
       }
+    },
+    createFrameHierarchyRepresentationHTML(unified_frame) {
+
+    },
+    createHierarchyHTML() {
+      this.unified_frame
     }
   },
   mounted() {
@@ -898,7 +378,6 @@ Object.assign(HierarchyEdit, {
       this.loadFrame();
     }
 
-    this.changeStatusButtonTitleToDefault();
     if(!this.readOnly) {
       Split(['#semantics-frames-pane', '#semantics-schemata-pane'], {
         sizes: [40, 60],
@@ -936,10 +415,10 @@ export default HierarchyEdit;
       <div :id="'semantics-unified-frame-pane' + (readOnly ? '-preview' : '')" class="col w-100 p-0 overflow-auto">
         <table v-if="!readOnly && !isReadOnlyForSuperLeksykograf()" class="table-button-menu sticky-top" cellspacing="1">
           <tr style="background-color: white;">
-            <td id="change-title" @click="changeTitle" style="padding: 10px 15px 10px 15px; color: #000000;">Nadrama</td>
-            <td id="add-arg" @click="addArgument" style="padding: 10px 15px 10px 15px; color: #000000;">Podrama</td>
-            <td style="padding: 10px 15px 10px 15px; color: #000000;" @click="addSelectivePreference">Usiń powiązanie</td>
-            <td style="padding: 10px 15px 10px 15px; color: #000000;" @click="changeUnifiedFrameStatusToReadyOrVerified">Edytuj</td>
+            <td id="change-title" @click="create_hyponym_or_hyperonym_relation(false)" style="padding: 10px 15px 10px 15px; color: #000000;">Nadrama</td>
+            <td id="add-arg" @click="create_hyponym_or_hyperonym_relation(true)" style="padding: 10px 15px 10px 15px; color: #000000;">Podrama</td>
+            <td style="padding: 10px 15px 10px 15px; color: #000000;" @click="delete_hyponym_or_hyperonym_relation">Usiń powiązanie</td>
+            <td style="padding: 10px 15px 10px 15px; color: #000000;" @click="goToEdit">Edytuj</td>
           </tr>
         </table>
         
@@ -1019,7 +498,7 @@ export default HierarchyEdit;
                   v-for='lexical_unit in lexical_units'
                 >
                   <template v-if="isFrameVisible(lexical_unit.frame)">
-                    <td class="lu-cursor-pointer argument py-2 px-1 border-top border-left border-secondary"
+                    <td class="cursor-pointer argument py-2 px-1 border-top border-left border-secondary"
                         @mouseenter="lexical_unit.frame.hover=true"
                         @mouseleave="lexical_unit.frame.hover=false"
                         :class="lexical_unit.frame === active_slowal_frame ? 'active-lu' : lexical_unit.frame.hover ? 'lu-underline' : ''"
@@ -1078,17 +557,10 @@ export default HierarchyEdit;
             :hierarchyEditComponent="HierarchyEdit"
             :initialUnifiedFrameId="currentPreviewedUnifiedFrameId" 
             @change-frame="changePreviewedUnifiedFrameId"
-            @change-preview-to-edit="swapUnifiedFrames"
             :forceRefresh="internalForceRefresh" />
       </div>
-      <div v-if="right_pane_tab === 'hierarchy'" class="overflow-hidden" style="height: calc(100% - 43px)">
-        <hierarchy-preview
-                :key="currentPreviewedUnifiedFrameId"
-                :hierarchyEditComponent="HierarchyEdit"
-                :initialUnifiedFrameId="currentPreviewedUnifiedFrameId"
-                @change-frame="changePreviewedUnifiedFrameId"
-                @change-preview-to-edit="swapUnifiedFrames"
-                :forceRefresh="internalForceRefresh" />
+      <div v-if="right_pane_tab === 'hierarchy'" class="overflow-hidden center" style="height: calc(100% - 43px); width: calc(80%)">
+          <hierarchy-element :node="createHierarchyTree()" :spacing_elem_type="'none'"/>
       </div>
       <div :class="right_pane_tab !== 'notes' && 'd-none'" id="notes-component"></div>
     </div>
diff --git a/frontend/src/components/unification/hierarchy/HierarchyElement.vue b/frontend/src/components/unification/hierarchy/HierarchyElement.vue
new file mode 100644
index 0000000..4f19b8d
--- /dev/null
+++ b/frontend/src/components/unification/hierarchy/HierarchyElement.vue
@@ -0,0 +1,184 @@
+<script>
+
+export default {
+    name: 'HierarchyElement',
+    props: {
+        node: {
+            type: Object,
+            required: true
+        },
+        spacing: {
+            type: Number,
+            default: 0
+        },
+        spacing_elem_type: String
+    },
+    data() {
+        return {
+            showHyponyms: false,
+            showHypyronyms: false
+        }
+    },
+    computed: {
+        nodeMargin() {
+            return {
+                'margin-left': `${this.spacing}px`
+            }
+        },
+        hasHyponyms() {
+            const {hyponyms} = this.node
+            return hyponyms && hyponyms.length > 0
+        },
+        hasHypyronyms() {
+            const {hypyronyms} = this.node
+            return hypyronyms && hypyronyms.length > 0
+        },
+        toggleChildrenIcon() {
+            return this.showHypyronyms ? 'fas fa-angle-down' : 'fas fa-angle-right'
+        },
+        getSpacingElem() {
+            if(this.spacing_elem_type === "std") {
+                return "&#9500;&#8594;";
+            } else if(this.spacing_elem_type === "top_corner") {
+                return "&#9484;&#8594;";
+            } else if(this.spacing_elem_type === "down_corner") {
+                return "&#9492;&#8594;";
+            } else {
+                return "";
+            }
+        },
+    },
+    methods: {
+        loadChildren (children) {
+            for (let i in children) {
+                const hypyronym = children[i];
+                if (hypyronym.hasChildrenLoaded !== true) {
+                    $.ajax({
+                        type: 'post',
+                        url: '/' + lang + '/unifier/get_hierarchy_hyperonyms/' + hypyronym.unified_frame_id + "/",
+                        dataType: 'json',
+                        timeout: 60000,
+                        success: function (response) {
+                            hypyronym.hypyronyms = response.hyperonyms;
+                        }.bind(this),
+                        error: function (request, errorType, errorMessage) {
+                            show_error(errorType + ' (' + errorMessage + ')');
+                        }
+                    });
+                    $.ajax({
+                        type: 'post',
+                        url: '/' + lang + '/unifier/get_hierarchy_hyponyms/' + hypyronym.unified_frame_id + "/",
+                        dataType: 'json',
+                        timeout: 60000,
+                        success: function (response) {
+                            hypyronym.hyponyms = response.hyponyms;
+                        }.bind(this),
+                        error: function (request, errorType, errorMessage) {
+                            show_error(errorType + ' (' + errorMessage + ')');
+                        }
+                    });
+                }
+            }
+        }, toggleHypyronyms() {
+            this.loadChildren(this.node.hypyronyms);
+            this.showHypyronyms = !this.showHypyronyms
+        },
+        toggleHyponyms() {
+            this.loadChildren(this.node.hyponyms);
+            this.showHyponyms = !this.showHyponyms
+        }
+    }
+}
+
+</script>
+
+
+<template>
+    <div :style="nodeMargin">
+        <div v-if="hasHypyronyms" v-show="showHypyronyms">
+            <hierarchy-element
+                    v-for="(child, index) in node.hypyronyms"
+                    :key="child.id"
+                    :node="child"
+                    :spacing_elem_type='index === 0 ? "top_corner" : "std"'
+                    :spacing="spacing + 10"
+            />
+        </div>
+        <div show class="d-flex justify-content-between mb-1">
+            <div class="row">
+                <div class="col" v-html="getSpacingElem"/>
+                <div class="col">
+
+                    <div align="left" style="display: table;">
+                        <div class="unifiedFrame mt-3" id="hierarchy-unified-frame-title" v-html="node.unified_frame.title"></div>
+                        <table id="hierarchy-unified-frame" class="m-0 table-borderless border border-secondary text-dark frame active">
+                            <tbody>
+                            <tr>
+                                <template v-for="argument in node.unified_frame.arguments">
+                                    <td
+                                            class="argument py-2 px-1 border-top border-left border-secondary role-column"
+                                            :class="argument.role ? argument.role.str + ' ' : ''"
+                                    >
+                                        {{ argument.role_type }}
+
+                                        <div
+                                                v-if="argument.role"
+                                        >
+                                            [{{ argument.role.str }}]
+                                        </div>
+                                        <div v-else>
+                                            <ul class="ul-role">
+                                                <li v-for="proposed_role in argument.proposed_roles">
+                                                    {{ proposed_role.str }}
+                                                </li>
+                                            </ul>
+                                        </div>
+                                    </td>
+                                </template>
+                            </tr>
+                            <tr>
+                                <td hidden class="preferences py-0 px-0 border-top border-left border-secondary role-column align-top"
+                                    v-for='argument in node.unified_frame.arguments'
+                                    :key='argument.id'
+                                >
+                                    <ul class="ul-preference" v-if="argument.preferences.length > 0">
+                                        <li v-for='preference in argument.preferences'>
+                                            <div
+                                                    v-if="preference.url != null"
+                                                    class="preference py-2 px-1 preference-bold"
+                                            >
+                                                <a class="synset-plwn" v-bind:href="preference.url" target="_blank">{{ preference.str }}</a>
+                                            </div>
+                                            <div v-else class="preference py-2 px-1 preference-bold">{{ preference.str }}</div>
+                                        </li>
+                                    </ul>
+                                </td>
+                            </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                    {{ node.label }}
+                </div>
+                <div class="col"><span
+                    v-if="hasHyponyms"
+                    :class="toggleChildrenIcon"
+                    @click="toggleHyponyms"
+                    @keypress="toggleHyponyms">V</span></div>
+                <div class="col"><span
+                    v-if="hasHypyronyms"
+                    :class="toggleChildrenIcon"
+                    @click="toggleHypyronyms"
+                    @keypress="toggleHypyronyms">A</span></div>
+            </div>
+        </div>
+        <div v-if="hasHyponyms" v-show="showHyponyms">
+            <hierarchy-element
+                    v-for="(child, index) in node.hyponyms"
+                    :key="child.id"
+                    :node="child"
+                    :spacing_elem_type='index === node.hyponyms.length - 1 ? "down_corner" : "std"'
+                    :spacing="spacing + 10"
+            />
+        </div>
+    </div>
+</template>
diff --git a/unifier/models.py b/unifier/models.py
index 53c7013..35973cf 100644
--- a/unifier/models.py
+++ b/unifier/models.py
@@ -36,6 +36,8 @@ class UnifiedFrame(models.Model):
 
     fin_statement = models.ForeignKey(FinStatement, on_delete=models.PROTECT, blank=True, default=None, null=True, related_name='unified_frame')
 
+    hasHierarchyElems = models.BooleanField(default=False)
+
     def sorted_arguments(self):  # TODO: zaimplementowac wlasciwe sortowanie
         return UnifiedFrameArgument.objects.filter(unified_frame=self)
 
@@ -79,7 +81,6 @@ class UnifiedFrame(models.Model):
                     argument_mapping.unified_agrument_id = new_unified_frame_arguments[i]
                 argument_mapping.save()
 
-
         self.update_sloval_frame_count()
         new_frame.update_sloval_frame_count()
 
@@ -120,25 +121,26 @@ class UnifiedFrame(models.Model):
 
 class UnifiedFrameArgument(models.Model):
     role_type = models.ForeignKey(RoleType, on_delete=models.PROTECT, default=None, blank=True, null=True)
-    #rola - wybrana przez użytkownika
+    # rola - wybrana przez użytkownika
     role = models.ForeignKey(ArgumentRole, on_delete=models.PROTECT, default=None, blank=True, null=True)
-    #role zaproponowane przez system unifikacyjny
+    # role zaproponowane przez system unifikacyjny
     proposed_roles = models.ManyToManyField(ArgumentRole, related_name='proposed_roles')
 
-    #3 typy preferencji - wybrane przez użytkownika
+    # 3 typy preferencji - wybrane przez użytkownika
     predefined = models.ManyToManyField(PredefinedSelectionalPreference)
     synsets = models.ManyToManyField(Synset)
     relations = models.ManyToManyField('UnifiedRelationalSelectionalPreference')
 
-    #odwołanie do ramy
+    # odwołanie do ramy
     unified_frame = models.ForeignKey(UnifiedFrame, related_name='unified_arguments', default=None, blank=True, null=True, on_delete=models.PROTECT)
 
-    #do wyszukiwania
+    # do wyszukiwania
     preferences_count = models.PositiveIntegerField(null=False, default=0)
 
     def __str__(self):
         return str(self.role)
 
+
 class UnifiedRelationalSelectionalPreference(models.Model):
     relation = models.ForeignKey(SelectionalPreferenceRelation, on_delete=models.PROTECT)
     to = models.ForeignKey(UnifiedFrameArgument,on_delete=models.PROTECT)
@@ -152,11 +154,13 @@ class UnifiedFrame2SlowalFrameMapping(models.Model):
     unified_frame = models.ForeignKey(UnifiedFrame, related_name='unified_frame_2_slowal_frame', on_delete=models.PROTECT)
     slowal_frame = models.OneToOneField(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_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)
 
 
-
-
+class HierarchyModel(models.Model):
+    hyponym = models.ForeignKey(UnifiedFrame, related_name='hyponym_mapping', on_delete=models.PROTECT)
+    hyperonym = models.ForeignKey(UnifiedFrame, related_name='hyperonym_mapping', on_delete=models.PROTECT)
diff --git a/unifier/urls.py b/unifier/urls.py
index bf8476e..9ba2f65 100644
--- a/unifier/urls.py
+++ b/unifier/urls.py
@@ -26,5 +26,9 @@ urlpatterns = [
     path('delete_unified_frame/<int:unified_frame_id>/', views.delete_unified_frame, name='delete_unified_frame'),
     path('change_unified_frame_status_to_verified_by_superleksykograf/', views.change_unified_frame_status_to_verified_by_superleksykograf, name='change_unified_frame_status_to_verified_by_superleksykograf'),
 
+    path('hierarchy_assign/', views.hierarchy_assign, name='hierarchy_assign'),
+    path('hierarchy_unassign/', views.hierarchy_unassign, name='hierarchy_unassign'),
+    path('get_hierarchy_hyponyms/<int:unified_frame_id>/', views.get_hierarchy_hyponyms, name='get_hierarchy_hyponyms'),
+    path('get_hierarchy_hyperonyms/<int:unified_frame_id>/', views.get_hierarchy_hyperonyms, name='get_hierarchy_hyperonyms'),
 
 ]
diff --git a/unifier/views.py b/unifier/views.py
index 8a45397..67f7135 100644
--- a/unifier/views.py
+++ b/unifier/views.py
@@ -18,12 +18,14 @@ from semantics.choices import FrameStatus
 from semantics.models import Frame, ArgumentRole, SemanticRole, RoleAttribute, RoleType
 from syntax.models import Schema
 from unifier.models import UnifiedFrameArgument, UnifiedRelationalSelectionalPreference, UnifiedFrame, \
-    UnifiedFrame2SlowalFrameMapping, UnifiedFrameArgumentSlowalFrameMapping
+    UnifiedFrame2SlowalFrameMapping, UnifiedFrameArgumentSlowalFrameMapping, HierarchyModel
 from users.models import Assignment
 from . import choices
 from xml.etree.ElementTree import Element, SubElement, tostring
 import io
 from xml.sax import handler, make_parser
+from django.db.models import Q
+
 
 @ajax_required
 @transaction.atomic
@@ -41,6 +43,7 @@ def save_synset_preference(request):
 
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def save_predefined_preference(request):
@@ -57,6 +60,7 @@ def save_predefined_preference(request):
 
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def save_relational_selectional_preference(request):
@@ -66,8 +70,10 @@ def save_relational_selectional_preference(request):
         complement_id_to = request.POST['complement_id_to']
         relation_id = request.POST['relation_id']
 
-        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(frame_id), id=int(complement_id_from))
-        relationalSelectionalPreference = UnifiedRelationalSelectionalPreference(to_id=complement_id_to, relation_id=relation_id)
+        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(frame_id),
+                                                                id=int(complement_id_from))
+        relationalSelectionalPreference = UnifiedRelationalSelectionalPreference(to_id=complement_id_to,
+                                                                                 relation_id=relation_id)
         relationalSelectionalPreference.save()
         unifiedFrameArgument.relations.add(relationalSelectionalPreference)
         unifiedFrameArgument.save()
@@ -90,7 +96,7 @@ def get_unified_frames(request):
             forms = collect_forms(request.session['unified_frame_form'], errors_dict)
             unifiedFrames = get_filtered_objects(forms).filter()
         else:
-            unifiedFrames = UnifiedFrame.objects.all();
+            unifiedFrames = UnifiedFrame.objects.all()
 
         if scroller_params['filter']:
             unifiedFrames = unifiedFrames.filter(title__startswith=scroller_params['filter'])
@@ -113,45 +119,48 @@ def get_unified_frames(request):
                 'id': str(unifiedFrame.id),
                 'title': unifiedFrame.title,
                 'status': unifiedFrame.status,
-                'assignee_username': assignment.user.username if (assignment := unifiedFrame.assignments.first()) else None,
+                'assignee_username': assignment.user.username if (
+                    assignment := unifiedFrame.assignments.first()) else None,
+                'hierarchy_exists': unifiedFrame.hasHierarchyElems,
             }
 
         resProcessed = []
         for key, value in res.items():
-            if (exclude_status == None or value['status'] != exclude_status) and \
-                    (restrict_to_user == None or value['assignee_username'] == restrict_to_user):
+            if (exclude_status is None or value['status'] != exclude_status) and \
+                    (restrict_to_user is None or value['assignee_username'] == restrict_to_user):
                 resProcessed.append(value)
 
         ret = {
-                'draw': scroller_params['draw'],
-                'recordsTotal': len(resProcessed),
-                'recordsFiltered': len(resProcessed),
-                'data': resProcessed
+            'draw': scroller_params['draw'],
+            'recordsTotal': len(resProcessed),
+            'recordsFiltered': len(resProcessed),
+            'data': resProcessed
         }
 
         return JsonResponse(ret)
     return JsonResponse({})
 
 
-
 def unifiedFrame2dict(frame):
     return {
-        'id'            : frame.id,
-        'title'            : frame.title,
-        'status'            : str(frame.status),
-        'assignee_username' : assignment.user.username if (assignment := frame.assignments.first()) else None,
-        'arguments'     : [
+        'id': frame.id,
+        'title': frame.title,
+        'status': str(frame.status),
+        'assignee_username': assignment.user.username if (assignment := frame.assignments.first()) else None,
+        'arguments': [
             {
-                'str'         : str(a),
-                'id'          : a.id,
-                'role'        : {
-                    'str': '{}{}'.format(a.role.role.role.lower(), ' ' + a.role.attribute.attribute.lower() if a.role.attribute else '') if a.role is not None else None,
+                'str': str(a),
+                'id': a.id,
+                'role': {
+                    'str': '{}{}'.format(a.role.role.role.lower(),
+                                         ' ' + a.role.attribute.attribute.lower() if a.role.attribute else '') if a.role is not None else None,
                     'id': str(a.role.id)
                 } if a.role is not None else None,
-                'role_type'   : a.role_type.type.lower() if a.role_type is not None else '',
-                'preferences' : get_prefs_list(a),
+                'role_type': a.role_type.type.lower() if a.role_type is not None else '',
+                'preferences': get_prefs_list(a),
                 'proposed_roles': [{
-                    'str': '{}{}'.format(r.role.role.lower(), ' ' + r.attribute.attribute.lower() if r.attribute else ''),
+                    'str': '{}{}'.format(r.role.role.lower(),
+                                         ' ' + r.attribute.attribute.lower() if r.attribute else ''),
                     'id': str(r.id)
                 } for r in a.proposed_roles.all()],
             } for a in sorted(frame.unified_arguments.all(), key=lambda x: x.id)
@@ -171,6 +180,7 @@ def unifiedFrame2dict(frame):
 
     }
 
+
 def get_examples(frames):
     examples = []
     for frame in frames:
@@ -186,41 +196,47 @@ def get_examples(frames):
                         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.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),
+                    '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'])
 
-def get_unified_frame_json(unifiedFrame, request):
 
-    apply_filters = not simplejson.loads(request.POST['no_filters'])
-    local_schema_filter_form = get_local_schema_filter_form(apply_filters, request)
-    local_frame_filter_form = get_local_frame_filter_form(apply_filters, request)
+def get_unified_frame_json(unifiedFrame, request):
+    local_schema_filter_form = None
+    local_frame_filter_form = None
+    if 'no_filters' in request.POST:
+        apply_filters = not simplejson.loads(request.POST['no_filters'])
+        local_schema_filter_form = get_local_schema_filter_form(apply_filters, request)
+        local_frame_filter_form = get_local_frame_filter_form(apply_filters, request)
 
-    slowal_frames_db = Frame.objects.filter(id__in=unifiedFrame.unified_frame_2_slowal_frame.values("slowal_frame_id")).distinct()
+    slowal_frames_db = Frame.objects.filter(
+        id__in=unifiedFrame.unified_frame_2_slowal_frame.values("slowal_frame_id")).distinct()
 
     if local_frame_filter_form:
         slowal_frames_db = get_filtered_objects(local_frame_filter_form, slowal_frames_db)
 
     slowal_frames = slowal_frames_db.all()
 
-    all_schema_objects = Schema.objects.filter(schema_hooks__argument_connections__argument__frame__in=slowal_frames).distinct()
+    all_schema_objects = Schema.objects.filter(
+        schema_hooks__argument_connections__argument__frame__in=slowal_frames).distinct()
 
     if local_schema_filter_form:
         all_schema_objects = get_filtered_objects(local_schema_filter_form, all_schema_objects)
@@ -233,7 +249,8 @@ def get_unified_frame_json(unifiedFrame, request):
     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]
+    all_schema_objects_dict = [schema2dict(schema, schema.subentries.all()[0].negativity, request.LANGUAGE_CODE) for
+                               schema in all_schema_objects]
 
     subentries = [{
         'str': None,
@@ -242,17 +259,21 @@ def get_unified_frame_json(unifiedFrame, request):
 
     unifiedFrame_dict = unifiedFrame2dict(unifiedFrame)
 
-    return { 'unified_frame_id': unifiedFrame.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 {'unified_frame_id': unifiedFrame.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']}
+
 
 @ajax_required
 @login_required
 def get_unified_frame(request):
     if request.method == 'POST':
-        #TODO (*)
-        #form = EntryForm(request.POST)
+        # TODO (*)
+        # form = EntryForm(request.POST)
         unified_frame_id = request.POST['unified_frame_id']
-        #TODO (*)
-        if unified_frame_id.isdigit():# and form.is_valid():
+        # TODO (*)
+        if unified_frame_id.isdigit():  # and form.is_valid():
             unified_frame_id = int(unified_frame_id)
             request.session.modified = True
             unifiedFrame = UnifiedFrame.objects.get(id=unified_frame_id)
@@ -260,6 +281,7 @@ def get_unified_frame(request):
 
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def extract_frames_to_new_frame(request):
@@ -273,10 +295,12 @@ def extract_frames_to_new_frame(request):
         new_unified_frame = None
         if target_unified_frame_id != '':
             new_unified_frame = UnifiedFrame.objects.get(id=target_unified_frame_id)
-        new_frame_fullfiled_and_saved = unified_frame.extract_frames_to(slowal_frames=slowal_frames, new_frame=new_unified_frame)
+        new_frame_fullfiled_and_saved = unified_frame.extract_frames_to(slowal_frames=slowal_frames,
+                                                                        new_frame=new_unified_frame)
         return JsonResponse(get_unified_frame_json(new_frame_fullfiled_and_saved, request))
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def remove_selectional_preference(request):
@@ -314,6 +338,7 @@ def duplicate_unified_frame(request):
         return JsonResponse(get_unified_frame_json(new_frame, request))
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def change_slowal2unified_fram_argument_mapping(request):
@@ -322,35 +347,38 @@ def change_slowal2unified_fram_argument_mapping(request):
         slowal_frame_id = request.POST['slowal_frame_id']
         slowal_frame_selected_arguments = json.loads(request.POST['slowal_frame_selected_arguments'])
 
-
-        unifiedFrame2SlowalFrameMapping = UnifiedFrame2SlowalFrameMapping.objects.get(unified_frame_id=unified_frame_id, slowal_frame=slowal_frame_id)
+        unifiedFrame2SlowalFrameMapping = UnifiedFrame2SlowalFrameMapping.objects.get(unified_frame_id=unified_frame_id,
+                                                                                      slowal_frame=slowal_frame_id)
 
         arg1_id = int(slowal_frame_selected_arguments[0])
         arg2_id = int(slowal_frame_selected_arguments[1])
 
-        unifiedFrameArgumentMapping1 = UnifiedFrameArgumentSlowalFrameMapping.objects.get(unified_frame_mapping=unifiedFrame2SlowalFrameMapping,
-                                                                                             slowal_agrument_id=arg1_id) if arg1_id >= 0 else None
-        unifiedFrameArgumentMapping2 = UnifiedFrameArgumentSlowalFrameMapping.objects.get(unified_frame_mapping=unifiedFrame2SlowalFrameMapping,
-                                                                                          slowal_agrument_id=arg2_id) if arg2_id >= 0 else None
+        unifiedFrameArgumentMapping1 = UnifiedFrameArgumentSlowalFrameMapping.objects.get(
+            unified_frame_mapping=unifiedFrame2SlowalFrameMapping,
+            slowal_agrument_id=arg1_id) if arg1_id >= 0 else None
+        unifiedFrameArgumentMapping2 = UnifiedFrameArgumentSlowalFrameMapping.objects.get(
+            unified_frame_mapping=unifiedFrame2SlowalFrameMapping,
+            slowal_agrument_id=arg2_id) if arg2_id >= 0 else None
 
         if unifiedFrameArgumentMapping1 is not None and unifiedFrameArgumentMapping2 is not None:
-            #mamy oba argumenty, zamieniamy miescami
+            # mamy oba argumenty, zamieniamy miescami
             unifiedFrameArgumentMapping1.slowal_agrument_id = arg2_id
             unifiedFrameArgumentMapping2.slowal_agrument_id = arg1_id
             unifiedFrameArgumentMapping1.save()
             unifiedFrameArgumentMapping2.save()
         elif unifiedFrameArgumentMapping1 is not None and unifiedFrameArgumentMapping2 is None:
-            #mamy lewy argument, prawy jest Empty
+            # mamy lewy argument, prawy jest Empty
             unifiedFrameArgumentMapping1.unified_agrument_id = -arg2_id
             unifiedFrameArgumentMapping1.save()
         elif unifiedFrameArgumentMapping1 is None and unifiedFrameArgumentMapping2 is not None:
-            #mamy prawy argument, lewy jest Empty
+            # mamy prawy argument, lewy jest Empty
             unifiedFrameArgumentMapping2.unified_agrument_id = -arg1_id
             unifiedFrameArgumentMapping2.save()
 
         return JsonResponse({})
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def change_slowal_frame_status(request):
@@ -365,6 +393,7 @@ def change_slowal_frame_status(request):
         return JsonResponse({})
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def save_unified_frame_title(request):
@@ -379,6 +408,7 @@ def save_unified_frame_title(request):
             unifiedFrame.save()
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def save_selected_role(request):
@@ -387,11 +417,13 @@ def save_selected_role(request):
         complement_id = request.POST['complement_id']
         role_id = request.POST['role_id']
 
-        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(unified_frame_id), id=int(complement_id))
+        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(unified_frame_id),
+                                                                id=int(complement_id))
         unifiedFrameArgument.role_id = role_id
         unifiedFrameArgument.save()
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def save_new_role(request):
@@ -404,7 +436,8 @@ def save_new_role(request):
 
         argumentRole = ArgumentRole.objects.filter(role_id=role_id, attribute_id=attribute_id).first()
         if argumentRole is None:
-            argumentRole = ArgumentRole(role=SemanticRole.objects.get(pk=role_id), attribute=RoleAttribute.objects.get(pk=attribute_id))
+            argumentRole = ArgumentRole(role=SemanticRole.objects.get(pk=role_id),
+                                        attribute=RoleAttribute.objects.get(pk=attribute_id))
             argumentRole.save()
 
         unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=unified_frame_id, id=complement_id)
@@ -413,6 +446,7 @@ def save_new_role(request):
         unifiedFrameArgument.save()
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def add_argument(request):
@@ -426,6 +460,7 @@ def add_argument(request):
         unifiedFrame.save()
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def remove_argument(request):
@@ -440,6 +475,7 @@ def remove_argument(request):
         unifiedFrame.save()
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def change_unified_frame_status_to_ready(request):
@@ -455,6 +491,7 @@ def change_unified_frame_status_to_ready(request):
         unifiedFrame.save()
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def change_unified_frame_status_to_verified_by_superleksykograf(request):
@@ -463,17 +500,18 @@ def change_unified_frame_status_to_verified_by_superleksykograf(request):
         unifiedFrame = UnifiedFrame.objects.get(pk=unified_frame_id)
         unifiedFrame.status = choices.UnifiedFrameStatus.VERIFIED
 
-        #save fin statement info for current active fin statement
+        # save fin statement info for current active fin statement
         fin_statements = FinStatement.objects.filter(is_active=True)
         if fin_statements.count() == 1:
             active_fin_statement = fin_statements[0]
-            unifiedFrame.fin_statement = active_fin_statement;
+            unifiedFrame.fin_statement = active_fin_statement
 
         unifiedFrame.save()
     return JsonResponse({})
 
+
 def create_unified_frame(lu_id):
-    response = requests.post('http://127.0.0.1:8000/en/unifier/build_unified_frame_xml/?lu_id='+str(lu_id))
+    response = requests.post('http://127.0.0.1:8000/en/unifier/build_unified_frame_xml/?lu_id=' + str(lu_id))
 
     parser = make_parser()
     parser.setFeature(handler.feature_external_ges, False)
@@ -489,6 +527,7 @@ def create_unified_frame(lu_id):
 
     return unified_frame_id
 
+
 @csrf_exempt
 def build_unified_frame_xml(request):
     if request.method == 'POST':
@@ -519,12 +558,14 @@ def build_unified_frame_xml(request):
 
             for id, argument in enumerate(arguments):
                 arguments_connectionElem = SubElement(arguments_connectionsElem, 'arguments_connection',
-                                                      attrib={'unifier_argument_id': str(id), 'slowal_id': str(argument.id)})
+                                                      attrib={'unifier_argument_id': str(id),
+                                                              'slowal_id': str(argument.id)})
             xml = tostring(matchingElem)
             return HttpResponse(xml)
 
     return HttpResponse()
 
+
 @ajax_required
 @transaction.atomic
 def save_unified_frame_title(request):
@@ -539,6 +580,7 @@ def save_unified_frame_title(request):
             unifiedFrame.save()
     return JsonResponse({})
 
+
 @ajax_required
 @transaction.atomic
 def frame_assign(request):
@@ -572,24 +614,125 @@ def frame_assign(request):
         return JsonResponse(ret)
     return JsonResponse({})
 
+
 def removeUnifiedFrameMappingsAndAssigments(unified_frame_id):
-    #odpianie z ramy zunifikowanej
+    # odpianie z ramy zunifikowanej
     unifiedFrame2SlowalFrameMappings = UnifiedFrame2SlowalFrameMapping.objects.filter(unified_frame_id=unified_frame_id)
     for unifiedFrame2SlowalFrameMapping in unifiedFrame2SlowalFrameMappings:
         unifiedFrame2SlowalFrameMapping.slowal_frame.status = 'N'
         unifiedFrame2SlowalFrameMapping.slowal_frame.save()
         Assignment.delete(subject_id=unifiedFrame2SlowalFrameMapping.slowal_frame.id)
-        unifiedFrameArgumentSlowalFrameMappings = UnifiedFrameArgumentSlowalFrameMapping.objects.filter(unified_frame_mapping=unifiedFrame2SlowalFrameMapping)
+        unifiedFrameArgumentSlowalFrameMappings = UnifiedFrameArgumentSlowalFrameMapping.objects.filter(
+            unified_frame_mapping=unifiedFrame2SlowalFrameMapping)
         unifiedFrameArgumentSlowalFrameMappings.delete()
         unifiedFrame2SlowalFrameMapping.delete()
     Assignment.delete(subject_id=unified_frame_id)
 
+
 @ajax(login_required=True, method='post')
 @transaction.atomic
 def delete_unified_frame(request, unified_frame_id):
-
     removeUnifiedFrameMappingsAndAssigments(unified_frame_id)
     UnifiedFrameArgument.objects.filter(unified_frame_id=unified_frame_id).delete()
     UnifiedFrame.objects.get(id=unified_frame_id).delete()
 
     return {}
+
+
+@ajax_required
+@transaction.atomic
+def hierarchy_assign(request):
+    if request.method == 'POST':
+
+        hyponym_id = request.POST['hyponym_id']
+        hyperonym_id = request.POST['hyperonym_id']
+
+        curr1 = HierarchyModel.objects.filter(hyponym_id=hyponym_id, hyperonym_id=hyperonym_id)
+        curr2 = HierarchyModel.objects.filter(hyponym_id=hyperonym_id, hyperonym_id=hyponym_id)
+
+        if curr1 or curr2:
+            return JsonResponse({'exists': 'true'})
+
+        hierarchy = HierarchyModel()
+        hierarchy.hyponym_id = hyponym_id
+        hierarchy.hyperonym_id = hyperonym_id
+        hierarchy.save()
+
+        setHierarchyExistsInfo(hyponym_id)
+        setHierarchyExistsInfo(hyperonym_id)
+
+        return JsonResponse({'succ': 'true'})
+    return JsonResponse({})
+
+
+def setHierarchyExistsInfo(unified_fram_id):
+    unified_frame = UnifiedFrame.objects.get(pk=unified_fram_id)
+    unified_frame.hasHierarchyElems = True
+    unified_frame.save()
+
+
+@ajax_required
+@transaction.atomic
+def hierarchy_unassign(request):
+    if request.method == 'POST':
+
+        rel1_id = request.POST['rel1_id']
+        rel2_id = request.POST['rel2_id']
+
+        curr1 = HierarchyModel.objects.filter(hyponym_id=rel1_id, hyperonym_id=rel2_id)
+
+        succ = False
+
+        if curr1:
+            curr1.delete()
+            succ = True
+        else:
+            curr2 = HierarchyModel.objects.filter(hyponym_id=rel2_id, hyperonym_id=rel1_id)
+            if curr2:
+                curr2.delete()
+                succ = True
+
+        # check if there is any heierarchy elements for unified frames from request
+        setNoHierarchyExistsInfo(rel1_id)
+        setNoHierarchyExistsInfo(rel2_id)
+
+        return JsonResponse({'succ': succ})
+
+
+def setNoHierarchyExistsInfo(unified_frame_id):
+    rel_1_hierarchy_count = HierarchyModel.objects.filter(Q(hyponym_id=unified_frame_id) | Q(hyperonym_id=unified_frame_id)).count()
+    if rel_1_hierarchy_count == 0:
+        # set info of no hierarchy elems in unified frame -> used to color unified frame list component
+        unified_frame = UnifiedFrame.objects.get(pk=unified_frame_id)
+        unified_frame.hasHierarchyElems = False
+        unified_frame.save()
+
+
+@ajax_required
+@transaction.atomic
+def get_hierarchy_hyponyms(request, unified_frame_id):
+    hyponyms = HierarchyModel.objects.filter(hyperonym_id=unified_frame_id)
+
+    res = []
+    for unifiedFrame in hyponyms:
+        res.append(get_unified_frame_json(unifiedFrame.hyponym, request))
+    res = {
+        'hyponyms': res
+    }
+
+    return JsonResponse(res)
+
+
+@ajax_required
+@transaction.atomic
+def get_hierarchy_hyperonyms(request, unified_frame_id):
+    hyperonyms = HierarchyModel.objects.filter(hyponym_id=unified_frame_id)
+
+    res = []
+    for unifiedFrame in hyperonyms:
+        res.append(get_unified_frame_json(unifiedFrame.hyperonym, request))
+    res = {
+        'hyperonyms': res
+    }
+
+    return JsonResponse(res)
diff --git a/users/models.py b/users/models.py
index b6dde51..2fe93d3 100644
--- a/users/models.py
+++ b/users/models.py
@@ -56,5 +56,6 @@ class Note(models.Model):
     title = models.CharField(max_length=100)
     note = models.TextField()
     created_at = models.DateTimeField(auto_now_add=True)
+    type = models.CharField(max_length=100, default=None, blank=True, null=True)
 
     objects = models.Manager.from_queryset(NoteQuerySet)()
diff --git a/users/templates/notes.html b/users/templates/notes.html
index 25240c8..0b2ba24 100644
--- a/users/templates/notes.html
+++ b/users/templates/notes.html
@@ -1,7 +1,7 @@
 {% load i18n %}
 
 <div class="border p-2">
-    <div id="note-form-template" class="d-none" data-url="{% url 'users:add_note' model='MODEL' pk='PK' %}">
+    <div id="note-form-template" class="d-none" data-url="{% url 'users:add_note' model='MODEL' pk='PK' type='TYPE' %}">
         <div class="mb-2 border-bottom pb-2">
             <div class="clearfix">
                 <h6 class="float-left">{% trans 'Dodaj notatkÄ™' %}</h6>
@@ -22,7 +22,7 @@
             <h6 class="mt-4 d-inline">{% trans 'Notatki' %}</h6>
             <a href="#" class="show-note-form btn btn-xs btn-outline-dark ml-2">{% trans 'Dodaj' %}</a>
         </div>
-        <table class="table table-sm table-striped border notes-table mb-0" data-url="{% url 'users:get_notes' model='MODEL' pk='PK' %}">
+        <table class="table table-sm table-striped border notes-table mb-0" data-url="{% url 'users:get_notes' model='MODEL' pk='PK' type='TYPE' %}">
             <tbody></tbody>
         </table>
     </div>
diff --git a/users/urls.py b/users/urls.py
index 911aa58..acc4488 100644
--- a/users/urls.py
+++ b/users/urls.py
@@ -54,7 +54,7 @@ urlpatterns = [
         ),
         name='password_reset_confirm'
     ),
-    re_path(r'^notes/(?P<model>\w+.\w+)/(?P<pk>\w+)/$', views.get_notes, name='get_notes'),
-    re_path(r'^notes/(?P<model>\w+.\w+)/(?P<pk>\w+)/add/$', views.add_note, name='add_note'),
+    re_path(r'^notes/(?P<model>\w+.\w+)/(?P<pk>\w+)/(?P<type>\w+)/$', views.get_notes, name='get_notes'),
+    re_path(r'^notes/(?P<model>\w+.\w+)/(?P<pk>\w+)/(?P<type>\w+)/add/$', views.add_note, name='add_note'),
     path('notes/<int:pk>/delete/', views.delete_note, name='delete_note'),
 ]
diff --git a/users/views.py b/users/views.py
index 63b23cf..00789ac 100644
--- a/users/views.py
+++ b/users/views.py
@@ -58,11 +58,13 @@ def user_edit(request, pk):
 
 
 @login_required
-def get_notes(request, model, pk):
+def get_notes(request, model, pk, type):
     model = apps.get_model(*model.split('.'))
     subject = get_object_or_404(model, pk=pk)
     ct = ContentType.objects.get_for_model(model)
     notes = Note.objects.filter(subject_ct=ct, subject_id=subject.pk).for_user(request.user).order_by('created_at')
+    if type:
+        notes = notes.filter(type=type)
     return JsonResponse({
         "notes": [{
             "pk": note.pk,
@@ -70,16 +72,19 @@ def get_notes(request, model, pk):
             "created_at": note.created_at,
             "title": note.title,
             "note": note.note,
+            "type": note.type,
         } for note in notes],
     })
 
 
 @require_http_methods(["POST"])
 @login_required
-def add_note(request, model, pk):
+def add_note(request, model, pk, type):
     model = apps.get_model(*model.split('.'))
     subject = get_object_or_404(model, pk=pk)
     note = Note(author=request.user, subject=subject)
+    if type:
+        note.type = type
     form = NoteForm(instance=note, data=request.POST)
     if form.is_valid():
         form.save()
@@ -89,7 +94,8 @@ def add_note(request, model, pk):
 
 @require_http_methods(["DELETE"])
 @login_required
-def delete_note(request, pk):
-    note = get_object_or_404(Note.objects.for_user(request.user), pk=pk)
+def delete_note(request, pk, type):
+    note = get_object_or_404(Note.objects.for_user(request.user), pk=pk, type=type) if type else \
+        get_object_or_404(Note.objects.for_user(request.user), pk=pk)
     note.delete()
     return JsonResponse({})
-- 
GitLab