diff --git a/common/templates/base.html b/common/templates/base.html index b041c547547c78fcabd6f7a31f3eb00164a63f62..93efcc377dae48300077730bc7a173c8bfd7b629 100644 --- a/common/templates/base.html +++ b/common/templates/base.html @@ -24,7 +24,9 @@ <script type="text/javascript" src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script type="text/javascript"> window.STATIC_URL = '{% static '' %}'; + window.USER_USERNAME = '{{ request.user.username }}'; </script> + {{ request.user.get_all_permissions|dictsort:0|json_script:"user-permissions" }} <script type="text/javascript" src="{% static 'common/js/utils.js' %}"></script> <script type="text/javascript" src="{% static 'common/js/init.js' %}"></script> <!-- translations: https://docs.djangoproject.com/en/2.2/topics/i18n/translation/#using-the-javascript-translation-catalog --> diff --git a/connections/models.py b/connections/models.py index 9b9ffb4d5198fe70e340fca5162bf556d55f8cc3..4ba398495d997792b0c1ece6d6087283125c9a9b 100644 --- a/connections/models.py +++ b/connections/models.py @@ -1,3 +1,4 @@ +from django.contrib.contenttypes.fields import GenericRelation from django.db import models from examples.models import Example @@ -17,6 +18,7 @@ class Entry(models.Model): frames_count = models.PositiveIntegerField(null=False, default=0) lexical_units_count = models.PositiveIntegerField(null=False, default=0) import_error = models.BooleanField(default=False) + assignments = GenericRelation("users.Assignment", content_type_field="subject_ct", object_id_field="subject_id") class Meta: ordering = ['name'] diff --git a/entries/static/entries/js/entries.js b/entries/static/entries/js/entries.js index ce013f844713dd648d7e59953e72f9588b11815b..aedb6d79d9b629e65904121da88a67c10b5a6714 100644 --- a/entries/static/entries/js/entries.js +++ b/entries/static/entries/js/entries.js @@ -914,9 +914,9 @@ function get_show_reals_desc() { return $('#show-realisation-descriptions').prop('checked') === true; } -function update_entries() { - - $('#entries-table').DataTable({ +function setup_datatable(options) { + + var datatable = $('#entries-table').DataTable({ // https://datatables.net/manual/tech-notes/3 destroy: true, //paging: false, @@ -926,16 +926,12 @@ function update_entries() { scrollY: $('#entries-list-div').height() - $('#entries-table_filter').outerHeight() - $('.dataTables_scrollHead').outerHeight() - $('#entries-table_info').outerHeight(), serverSide: true, ajax: { - url: '/' + lang + '/entries/get_entries/', + url: options.url, type: 'POST', }, // https://datatables.net/reference/option/dom dom: 'ftri', - columns: [ - { data: 'lemma' }, - { data: 'status' }, - { data: 'POS' }, - ], + columns: options.columns, orderMulti: false, // show processing indicator when sorting etc. processing: true, @@ -949,9 +945,10 @@ function update_entries() { $(td).prop('scope', 'row'); } }, + // add a padding to every cell { className: 'p-1', - targets: [0, 1, 2], + targets: options.columns.map(function (column, index) { return index; } ), }, // make only the lemma searchable { @@ -966,14 +963,6 @@ function update_entries() { if (related) { $(row).addClass('text-muted'); } - $(row).click(function() { - var selected_entry = $(this).data('entry'); - if (selected_entry !== curr_entry) { - $('.entry[data-entry="' + curr_entry + '"]').removeClass('table-primary'); - get_entry(selected_entry, related); - $(this).addClass('table-primary'); - } - }); }, initComplete: function(settings, json) { // display the first entry once it’s loaded @@ -993,8 +982,10 @@ function update_entries() { } } }); - + curr_entry = null; + + return datatable; } function clear_results() { @@ -1073,13 +1064,13 @@ $(document).ready(function() { }, }); - Split(['#semantics-top-pane', '#semantics-examples-pane'], { + $('#semantics-top-pane').length && Split(['#semantics-top-pane', '#semantics-examples-pane'], { direction: 'vertical', sizes: [75, 25], gutterSize: 4, }); - Split(['#semantics-frames-pane', '#semantics-schemata-pane'], { + $('#semantics-frames-pane').length && Split(['#semantics-frames-pane', '#semantics-schemata-pane'], { sizes: [40, 60], minSize: 400, gutterSize: 4, @@ -1090,7 +1081,7 @@ $(document).ready(function() { }, }); - Split(['#syntax-schemata-pane', '#syntax-examples-pane'], { + $('#semantics-schemata-pane').length && Split(['#syntax-schemata-pane', '#syntax-examples-pane'], { direction: 'vertical', sizes: [75, 25], gutterSize: 4, diff --git a/entries/static/entries/js/entries_list.js b/entries/static/entries/js/entries_list.js new file mode 100644 index 0000000000000000000000000000000000000000..d5e8579b7b12a66097f2c527d8e8281486b5365e --- /dev/null +++ b/entries/static/entries/js/entries_list.js @@ -0,0 +1,21 @@ +function update_entries() { + var datatable = setup_datatable({ + url: '/' + lang + '/entries/get_entries/', + columns: [ + { data: 'lemma' }, + { data: 'status' }, + { data: 'POS' }, + ] + }); + datatable.on('click', 'tr.entry', function () { + var selected_entry = $(this).data('entry'); + var data = datatable.row(this).data(); + if (!data) return; + var related = data.related === true; + if (selected_entry !== curr_entry) { + $('.entry[data-entry="' + curr_entry + '"]').removeClass('table-primary'); + get_entry(selected_entry, related); + $(this).addClass('table-primary'); + } + }); +} diff --git a/entries/static/entries/js/unification_entries_list.js b/entries/static/entries/js/unification_entries_list.js new file mode 100644 index 0000000000000000000000000000000000000000..e419a5005a0da8376702fe28c376a34dfd80e10f --- /dev/null +++ b/entries/static/entries/js/unification_entries_list.js @@ -0,0 +1,76 @@ +function update_entries() { + const can_see_assignees = has_permission("auth.view_user"); + + function is_assigned_to_user_renderer (data) { + return ( + data + && data.lexical_units + && data.lexical_units.some(lu => lu.assignee_username === window.USER_USERNAME && lu.status == 'O') + ) ? gettext("tak") : gettext("nie"); + } + + var datatable = setup_datatable({ + url: '/' + lang + '/entries/get_entries/?with_lexical_units=true', + columns: [ + { data: 'lemma' }, + { data: 'POS' }, + can_see_assignees ? { data: 'assignee_username' } : { render: is_assigned_to_user_renderer }, + ] + }); + datatable.on('click', 'tr.entry', function () { + var row = datatable.row(this); + var has_drilldown = row.child.isShown(); + $('.drilldown:visible').each(function () { datatable.row($(this).data("row")).child.hide(); }); + if (!has_drilldown) { + if (!row.data()) return; + var drilldown = $("<div>").addClass("drilldown").data("row", this); + row.child(drilldown).show(); + setup_lexical_units_table(drilldown, row.data().lexical_units, can_see_assignees); + drilldown.closest("td").addClass("p-0 pl-4"); + } + }); +} + +function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees) { + if (!lexical_units.length) { + return ''; + } + + function get_lexical_unit_row(lexical_unit) { + const is_assigned_to_user = lexical_unit.assignee_username === window.USER_USERNAME; + return $(` + <tr class="lexical-unit"> + <td class="p-1">${lexical_unit.display}</td> + <td class="p-1">${lexical_unit.status}</td> + ` + ( + can_see_assignees + ? `<td class="p-1">${lexical_unit.assignee_username || ""}</td>` + : `<td class="p-1">${is_assigned_to_user ? gettext("tak") : gettext("nie")}</td>` + ) + ` + </tr> + `).click(function () { + $(this).addClass('table-primary').siblings().removeClass("table-primary"); + get_entry($(drilldown.data("row")).data("entry"), false); // TODO replace with loading LU details view + }); + } + var table = $(` + <table class="table"> + <thead> + <tr> + <th class="p-1">${gettext("Jednostka Leksykalna")}</th> + <th class="p-1">${gettext("Status")}</th> + ` + ( + can_see_assignees + ? `<th class="p-1">${gettext("Leksykograf")}</th>` + : `<th class="p-1">${gettext("Moje")}</th>` + ) + ` + </tr> + </thead> + <tbody></tbody> + </table> + `); + drilldown.append(table); + lexical_units.map(function (lexical_unit) { + $("tbody", table).append(get_lexical_unit_row(lexical_unit)); + }); +} diff --git a/entries/static/entries/js/utils.js b/entries/static/entries/js/utils.js index a1cdcbd1dde3eb3fc18d9200e4ec21852ae4b330..9bc705dcc1a502c615aa7ed6b351232fdddc8733 100644 --- a/entries/static/entries/js/utils.js +++ b/entries/static/entries/js/utils.js @@ -12,3 +12,9 @@ function show_entry_spinners() { $('#syntax-schemata').append(spinner); $('#unmatched-examples').append(spinner); } + +var permissions = JSON.parse(document.getElementById('user-permissions').textContent); + +function has_permission(permission) { + return permissions.indexOf(permission) !== -1; +} diff --git a/entries/templates/entries.html b/entries/templates/entries.html index 674d39f56e74127a3feea3b78d9cd0c209f63077..0148c03c9aad6250841d17dcd48e5ff209394373 100644 --- a/entries/templates/entries.html +++ b/entries/templates/entries.html @@ -1,147 +1,15 @@ -{% extends "base.html" %} +{% extends "entries_base.html" %} {% load i18n %} {% load static %} -{% load crispy_forms_tags %} {% block title %}{% trans "HasÅ‚a" %}{% endblock %} -{% block styles %} - <!-- for autocomplete --> - <link rel="stylesheet" type="text/css" href="//code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css"> - <!-- https://datatables.net/ --> - <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.10.22/sc-2.0.3/datatables.min.css"/> - <!--link rel="stylesheet" type="text/css" href="{% static 'entries/css/panels.css' %}"--> - <link rel="stylesheet" type="text/css" href="{% static 'entries/css/entries.css' %}"> - <link rel="stylesheet" type="text/css" href="{% static 'common/css/role_colours.css' %}"> -{% endblock %} - {% block scripts %} - <!-- https://www.cssscript.com/split-view/ --> - <script src="https://unpkg.com/split.js/dist/split.min.js"></script> - <!-- https://datatables.net/ --> - <script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.22/sc-2.0.3/datatables.min.js"></script> - <script src="{% static 'common/js/csrf.js' %}"></script> - <!--script src="{% static 'entries/js/panels.js' %}"></script--> - <script src="{% static 'entries/js/forms.js' %}"></script> - <script src="{% static 'entries/js/utils.js' %}"></script> - <script src="{% static 'entries/js/entries.js' %}"></script> -{% endblock %} - -{% block additional-nav-items %} -<li class="nav-item dropdown"> - <a class="nav-link dropdown-toggle text-light" href="#" id="nav-filters" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - {% trans "Filtrowanie" %} - </a> - <div class="dropdown-menu" id="filters-visited-dropdown" aria-labelledby="nav-filters"> - <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-button" data-toggle="modal" data-target="#entry-filters"> - {% trans "HasÅ‚a" %} - </a> - <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-frames-button" data-toggle="modal" data-target="#frame-filters"> - {% trans "Ramy" %} - </a> - <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-schemata-button" data-toggle="modal" data-target="#schema-filters"> - {% trans "Schematy" %} - </a> - </div> -</li> -<li class="nav-item dropdown"> - <a class="nav-link dropdown-toggle text-light" href="#" id="nav-last" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - {% trans "Ostatnio oglÄ…dane" %} - </a> - <div class="dropdown-menu" id="last-visited-dropdown" aria-labelledby="nav-last"> - {% for lemma, eid in request.session.last_visited|slice:":-1" %} - <a class="dropdown-item font-weight-bold text-dark text-uppercase last-visited" data-entry="{{ eid }}" href="#">{{ lemma }}</a> - {% endfor %} - </div> -</li> -<li class="nav-item dropdown"> - <a class="nav-link dropdown-toggle text-light" href="#" id="nav-options" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - {% trans "Opcje" %} - </a> - <div class="dropdown-menu px-1" id="options-dropdown" aria-labelledby="nav-options"> - <div class="form-check custom-control custom-checkbox"> - <input type="checkbox" class="custom-control-input" id="show-realisation-descriptions"{% if request.session.show_reals_desc %} checked{% endif %}> - <label class="custom-control-label" for="show-realisation-descriptions"> - {% trans "WyÅ›wietlaj opisy realizacji" %} <span data-toggle="tooltip" data-placement="bottom" title="{% trans "Po wybraniu ramy wyÅ›wietlaj opisy jej realizacji skÅ‚adniowych. Opisy (dla caÅ‚ej realizacji i dla poszczególnych fraz) sÄ… wyÅ›wietlane wewnÄ…trz schematów." %}"><img src="{% static 'common/img/info.svg' %}" alt="info" width="10" height="10"/></span> - </label> - </div> - <div class="form-check custom-control custom-checkbox"> - <input type="checkbox" class="custom-control-input" id="show-linked-entries"{% if request.session.show_linked_entries %} checked{% endif %}> - <label class="custom-control-label" for="show-linked-entries"> - {% trans "WyÅ›wietlaj powiÄ…zane hasÅ‚a" %} <span data-toggle="tooltip" data-placement="bottom" data-html="true" title="{% trans "Przy filtrowaniu haseÅ‚ wyÅ›wietlaj, oprócz haseÅ‚ speÅ‚niajÄ…cych kryteria filtrowania, hasÅ‚a powiÄ…zane z nimi znaczeniowo (np. <i>podarować</i> – <i>podarunek</i> – <i>podarek</i>). HasÅ‚a powiÄ…zane niespeÅ‚niajÄ…ce kryteriów filtrowania sÄ… wyróżnione jaÅ›niejszym kolorem na liÅ›cie oraz nie podlegajÄ… filtrowaniu schematów i ram (sÄ… zawsze wyÅ›wietlane w caÅ‚oÅ›ci niezależnie od użytych filtrów dla schematów/ram)." %}"><img src="{% static 'common/img/info.svg' %}" alt="info" width="10" height="10"/></span> - </label> - </div> - </div> -</li> -{% endblock %} - -{% block content %} - -<div class="row h-100 m-0 p-0 bg-secondary"> - <!-- left panel: list of entries --> - <div id="entries-list" class="col h-100 w-100 px-0"> - <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-auto"> - {% include "entries_list.html" %} - </div> - </div> - - <!-- right panel: entry display (syntax, semantics, examples) --> - <div id="entry-display" class="col h-100 p-0"> - {% include "entry_display.html" %} - </div> -</div> - + {{ block.super }} + <script src="{% static 'entries/js/entries_list.js' %}"></script> {% endblock %} -{% block modals %} - -<div class="modal fade" id="entry-filters" tabindex="-1" role="dialog" aria-labelledby="entry-filtersLabel" aria-hidden="true"> - <div class="modal-dialog modal-xl" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="entry-filtersLabel">{% trans "Filtrowanie haseÅ‚" %}</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <div class="modal-body text-dark"> - {% crispy entries_form %} - </div> - </div> - </div> -</div> +{% block left_pane %}{% include "entries_list.html" %}{% endblock %} -<div class="modal fade" id="frame-filters" tabindex="-1" role="dialog" aria-labelledby="frame-filtersLabel" aria-hidden="true"> - <div class="modal-dialog modal-xl" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="frame-filtersLabel">{% trans "Filtrowanie ram" %}</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <div class="modal-body text-dark"> - {% crispy frames_form %} - </div> - </div> - </div> -</div> - -<div class="modal fade" id="schema-filters" tabindex="-1" role="dialog" aria-labelledby="schema-filtersLabel" aria-hidden="true"> - <div class="modal-dialog modal-xl" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="schema-filtersLabel">{% trans "Filtrowanie schematów" %}</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <div class="modal-body text-dark"> - {% crispy schemata_form %} - </div> - </div> - </div> -</div> - -{% endblock %} +{% block right_pane %}{% include "entry_display.html" with show_tabs=True %}{% endblock %} diff --git a/entries/templates/entries_base.html b/entries/templates/entries_base.html new file mode 100644 index 0000000000000000000000000000000000000000..7b43abc79fc7cf411d31e6b6b93c332483f6a376 --- /dev/null +++ b/entries/templates/entries_base.html @@ -0,0 +1,148 @@ +{% extends "base.html" %} + +{% load i18n %} +{% load static %} +{% load crispy_forms_tags %} + +{% block styles %} + <!-- for autocomplete --> + <link rel="stylesheet" type="text/css" href="//code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css"> + <!-- https://datatables.net/ --> + <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.10.22/sc-2.0.3/datatables.min.css"/> + <!--link rel="stylesheet" type="text/css" href="{% static 'entries/css/panels.css' %}"--> + <link rel="stylesheet" type="text/css" href="{% static 'entries/css/entries.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'common/css/role_colours.css' %}"> +{% endblock %} + +{% block scripts %} + <!-- https://www.cssscript.com/split-view/ --> + <script src="https://unpkg.com/split.js/dist/split.min.js"></script> + <!-- https://datatables.net/ --> + <script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.22/sc-2.0.3/datatables.min.js"></script> + <script src="{% static 'common/js/csrf.js' %}"></script> + <!--script src="{% static 'entries/js/panels.js' %}"></script--> + <script src="{% static 'entries/js/forms.js' %}"></script> + <script src="{% static 'entries/js/utils.js' %}"></script> + <script src="{% static 'entries/js/entries.js' %}"></script> +{% endblock %} + +{% 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> +{% 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"> + {% trans "Filtrowanie" %} + </a> + <div class="dropdown-menu" id="filters-visited-dropdown" aria-labelledby="nav-filters"> + <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-button" data-toggle="modal" data-target="#entry-filters"> + {% trans "HasÅ‚a" %} + </a> + <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-frames-button" data-toggle="modal" data-target="#frame-filters"> + {% trans "Ramy" %} + </a> + <a href="#" class="dropdown-item font-weight-bold text-dark text-uppercase" id="filter-schemata-button" data-toggle="modal" data-target="#schema-filters"> + {% trans "Schematy" %} + </a> + </div> +</li> +<li class="nav-item dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="nav-last" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + {% trans "Ostatnio oglÄ…dane" %} + </a> + <div class="dropdown-menu" id="last-visited-dropdown" aria-labelledby="nav-last"> + {% for lemma, eid in request.session.last_visited|slice:":-1" %} + <a class="dropdown-item font-weight-bold text-dark text-uppercase last-visited" data-entry="{{ eid }}" href="#">{{ lemma }}</a> + {% endfor %} + </div> +</li> +<li class="nav-item dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="nav-options" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + {% trans "Opcje" %} + </a> + <div class="dropdown-menu px-1" id="options-dropdown" aria-labelledby="nav-options"> + <div class="form-check custom-control custom-checkbox"> + <input type="checkbox" class="custom-control-input" id="show-realisation-descriptions"{% if request.session.show_reals_desc %} checked{% endif %}> + <label class="custom-control-label" for="show-realisation-descriptions"> + {% trans "WyÅ›wietlaj opisy realizacji" %} <span data-toggle="tooltip" data-placement="bottom" title="{% trans "Po wybraniu ramy wyÅ›wietlaj opisy jej realizacji skÅ‚adniowych. Opisy (dla caÅ‚ej realizacji i dla poszczególnych fraz) sÄ… wyÅ›wietlane wewnÄ…trz schematów." %}"><img src="{% static 'common/img/info.svg' %}" alt="info" width="10" height="10"/></span> + </label> + </div> + <div class="form-check custom-control custom-checkbox"> + <input type="checkbox" class="custom-control-input" id="show-linked-entries"{% if request.session.show_linked_entries %} checked{% endif %}> + <label class="custom-control-label" for="show-linked-entries"> + {% trans "WyÅ›wietlaj powiÄ…zane hasÅ‚a" %} <span data-toggle="tooltip" data-placement="bottom" data-html="true" title="{% trans "Przy filtrowaniu haseÅ‚ wyÅ›wietlaj, oprócz haseÅ‚ speÅ‚niajÄ…cych kryteria filtrowania, hasÅ‚a powiÄ…zane z nimi znaczeniowo (np. <i>podarować</i> – <i>podarunek</i> – <i>podarek</i>). HasÅ‚a powiÄ…zane niespeÅ‚niajÄ…ce kryteriów filtrowania sÄ… wyróżnione jaÅ›niejszym kolorem na liÅ›cie oraz nie podlegajÄ… filtrowaniu schematów i ram (sÄ… zawsze wyÅ›wietlane w caÅ‚oÅ›ci niezależnie od użytych filtrów dla schematów/ram)." %}"><img src="{% static 'common/img/info.svg' %}" alt="info" width="10" height="10"/></span> + </label> + </div> + </div> +</li> +{% endblock %} + +{% block content %} + +<div class="row h-100 m-0 p-0 bg-secondary"> + <!-- left panel: list of entries --> + <div id="entries-list" class="col h-100 w-100 px-0"> + <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-auto"> + {% block left_pane %}{% endblock %} + </div> + </div> + + <!-- right panel: entry display (syntax, semantics, examples) --> + <div id="entry-display" class="col h-100 p-0"> + {% block right_pane %}{% endblock %} + </div> +</div> + +{% endblock %} + +{% block modals %} + +<div class="modal fade" id="entry-filters" tabindex="-1" role="dialog" aria-labelledby="entry-filtersLabel" aria-hidden="true"> + <div class="modal-dialog modal-xl" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="entry-filtersLabel">{% trans "Filtrowanie haseÅ‚" %}</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body text-dark"> + {% crispy entries_form %} + </div> + </div> + </div> +</div> + +<div class="modal fade" id="frame-filters" tabindex="-1" role="dialog" aria-labelledby="frame-filtersLabel" aria-hidden="true"> + <div class="modal-dialog modal-xl" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="frame-filtersLabel">{% trans "Filtrowanie ram" %}</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body text-dark"> + {% crispy frames_form %} + </div> + </div> + </div> +</div> + +<div class="modal fade" id="schema-filters" tabindex="-1" role="dialog" aria-labelledby="schema-filtersLabel" aria-hidden="true"> + <div class="modal-dialog modal-xl" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="schema-filtersLabel">{% trans "Filtrowanie schematów" %}</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body text-dark"> + {% crispy schemata_form %} + </div> + </div> + </div> +</div> + +{% endblock %} diff --git a/entries/templates/entry_display.html b/entries/templates/entry_display.html index cf620b880ec55094d84ddca819c75a16fe9bd4fa..42d07fa913653e02cf583d9a1347383bc7bb3c21 100644 --- a/entries/templates/entry_display.html +++ b/entries/templates/entry_display.html @@ -1,5 +1,6 @@ {% load i18n %} +{% if show_tabs %} <ul class="nav nav-pills nav-justified p-1" id="entryTabs" role="tablist"> <li class="nav-item mr-1"> <a class="btn btn-sm btn-outline-dark nav-link active" id="semantics-tab" data-toggle="tab" href="#semantics" role="tab" aria-controls="semantics" aria-selected="true"> @@ -17,6 +18,7 @@ </a> </li> </ul> +{% endif %} <div class="tab-content h-100 w-100 p-0" id="entryTabsContent"> <div class="col h-100 w-100 p-0 tab-pane show active" id="semantics" role="tabpanel" aria-labelledby="semantics-tab"> diff --git a/entries/templates/unification.html b/entries/templates/unification.html new file mode 100644 index 0000000000000000000000000000000000000000..a0bf18d6cf4a5f6f5b4220ec0dbfc2cdb4ee37b3 --- /dev/null +++ b/entries/templates/unification.html @@ -0,0 +1,15 @@ +{% extends "entries_base.html" %} + +{% load i18n %} +{% load static %} + +{% block title %}{% trans "HasÅ‚a" %}{% endblock %} + +{% block scripts %} + {{ block.super }} + <script src="{% static 'entries/js/unification_entries_list.js' %}"></script> +{% endblock %} + +{% block left_pane %}{% include "unification_entries_list.html" %}{% endblock %} + +{% block right_pane %}{% include "entry_display.html" %}{% endblock %} diff --git a/entries/templates/unification_entries_list.html b/entries/templates/unification_entries_list.html new file mode 100644 index 0000000000000000000000000000000000000000..48b737359c71740f56d4fc6838528a859613a25a --- /dev/null +++ b/entries/templates/unification_entries_list.html @@ -0,0 +1,17 @@ +{% load i18n %} + +<table id="entries-table" class="table table-sm table-hover text-dark"> + <thead> + <tr> + <th class="p-1">{% trans "Lemat" %}</th> + <th class="p-1">{% trans "Część mowy" %}</th> + {% if perms.users.view_user %} + <th class="p-1">{% trans "Semantyk" %}</th> + {% else %} + <th class="p-1">{% trans "Moje (w opracowaniu)" %}</th> + {% endif %} + </tr> + </thead> + <tbody id="entries"> + </tbody> +</table> diff --git a/entries/urls.py b/entries/urls.py index 3c497647957859d31f5ec8638ba9793a15ad802f..37deeedf2ec924cfa3ee768bff8629b90dea18f9 100644 --- a/entries/urls.py +++ b/entries/urls.py @@ -14,9 +14,10 @@ urlpatterns = [ path('get_subform/', views.get_subform, name='get_subform'), path('change_show_reals_desc/', views.change_show_reals_desc, name='change_show_reals_desc'), path('change_show_linked_entries/', views.change_show_linked_entries, name='change_show_linked_entries'), - + path('unification', views.unification, name='unification'), + path('autocomplete/', autocompletes.autocomplete, name='autocomplete'), - + # TODO remove! #path('test/', views.test, name='test'), path('', views.entries, name='entries'), diff --git a/entries/views.py b/entries/views.py index fa20fa6e88807248aa3b06a9514ce1a135e10c6a..7d026a7f233d6ab62bd88db468e8165e4dcb2439 100644 --- a/entries/views.py +++ b/entries/views.py @@ -7,7 +7,8 @@ from itertools import chain, product import simplejson -from django.db.models import Q +from django.contrib.auth.decorators import login_required +from django.db.models import Prefetch, Q from django.http import JsonResponse, QueryDict from django.shortcuts import render from django.template.context_processors import csrf @@ -65,9 +66,22 @@ def entries(request): 'schemata_form' : SchemaFormFactory.get_form(as_subform=False) }) + +@login_required +def unification(request): + return render( + request, + 'unification.html', + { + 'entries_form' : EntryForm(), + 'frames_form': FrameFormFactory.get_form(as_subform=False), + 'schemata_form': SchemaFormFactory.get_form(as_subform=False) + }, + ) + + FORM_TYPES = { 'entry' : EntryForm, - } FORM_FACTORY_TYPES = { @@ -336,6 +350,7 @@ def get_entries(request): # form should already be validated if it passed through send_form assert(not errors_dict) scroller_params = get_scroller_params(request.POST) + with_lexical_units = request.GET.get('with_lexical_units') == 'true' entries = get_filtered_objects(forms).filter(import_error=False) # TODO restrictions for testing – remove!!! @@ -351,10 +366,10 @@ def get_entries(request): linked_ids = set() if request.session['show_linked_entries']: entries_linked = Entry.objects.filter(subentries__schema_hooks__argument_connections__schema_connections__subentry__entry__in=entries).distinct().exclude(id__in=entries) - entries = entries.union(entries_linked) + entries = entries | entries_linked linked_ids = set(e.id for e in entries_linked) - i, j = scroller_params['start'], scroller_params['start'] + scroller_params['length'] + first_index, last_index = scroller_params['start'], scroller_params['start'] + scroller_params['length'] order_field, order_dir = scroller_params['order'] if order_field == 0: order_field = 'name' @@ -364,22 +379,38 @@ def get_entries(request): order_field = 'pos__tag' if order_dir == 'desc': order_field = '-' + order_field - entries = entries.order_by(order_field) + entries = entries.order_by(order_field).only('id', 'name', 'status__key', 'pos__tag') + if with_lexical_units: + entries = entries.prefetch_related("lexical_units") status_names = STATUS() POS_names = POS() - entries_list = list(entries.values('id', 'name', 'status__key', 'pos__tag')) result = { 'draw' : scroller_params['draw'], 'recordsTotal': total, 'recordsFiltered': filtered, 'data': [ { - 'id' : e['id'], - 'lemma' : e['name'], - 'status' : status_names[e['status__key']], - 'POS' : POS_names[e['pos__tag']], - 'related' : e['id'] in linked_ids, - } for e in entries_list[i:j] + 'id' : e.id, + 'lemma' : e.name, + 'status' : status_names[e.status.key], + 'POS' : POS_names[e.pos.tag], + 'related' : e.id in linked_ids, + 'assignee_username': assignment.user.username if (assignment := e.assignments.first()) else None, + **( + { + 'lexical_units': [ + { + 'display': str(lu), + 'assignee_username': ( + assignment.user.username if (assignment := lu.assignments.first()) else None + ), + 'status': frame.status if (frame := lu.frames.first()) else "" + } for lu in e.lexical_units.all() + ] + } + if with_lexical_units else {} + ), + } for e in list(entries)[first_index:last_index] ], } return JsonResponse(result) diff --git a/meanings/models.py b/meanings/models.py index 7a46b0a189ca129a82609ed3c9e6f893d1b0f00f..3eaaabf2fc0b4eb360b98091e11248ec4975bebb 100644 --- a/meanings/models.py +++ b/meanings/models.py @@ -1,3 +1,4 @@ +from django.contrib.contenttypes.fields import GenericRelation from django.db import models @@ -12,6 +13,7 @@ class LexicalUnit(models.Model): definition = models.TextField(default='') gloss = models.TextField(default='') text_rep = models.TextField() + assignments = GenericRelation("users.Assignment", content_type_field="subject_ct", object_id_field="subject_id") class Meta: unique_together = ('base', 'sense', 'pos',) diff --git a/semantics/choices.py b/semantics/choices.py new file mode 100644 index 0000000000000000000000000000000000000000..d248493f6ee99ce0ad2417636c1fbe7f2b593730 --- /dev/null +++ b/semantics/choices.py @@ -0,0 +1,8 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class LexicalUnitStatus(models.TextChoices): + PROCESSING = "O", _("w obróbce") + READY = "G", _("gotowe") + VERIFIED = "S", _("sprawdzone") diff --git a/semantics/models.py b/semantics/models.py index 83273004ef2c1287cb5735b8ddb5bd4626fdb09c..17b8cee078e6d62083d07b0eaefa3f10862674d8 100644 --- a/semantics/models.py +++ b/semantics/models.py @@ -2,11 +2,18 @@ from django.db import models from meanings.models import LexicalUnit, Synset +from . import choices + class Frame(models.Model): lexical_units = models.ManyToManyField(LexicalUnit, related_name='frames') opinion = models.ForeignKey('FrameOpinion', on_delete=models.PROTECT) arguments_count = models.PositiveIntegerField(null=False, default=0) + status = models.TextField( + max_length=10, + choices=choices.LexicalUnitStatus.choices, + default=choices.LexicalUnitStatus.PROCESSING, + ) def sorted_arguments(self): # TODO: zaimplementowac wlasciwe sortowanie return Argument.objects.filter(frame=self) diff --git a/users/models.py b/users/models.py new file mode 100644 index 0000000000000000000000000000000000000000..91ce143a6a157f17210b3cf026423d24da8b65d6 --- /dev/null +++ b/users/models.py @@ -0,0 +1,15 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.db import models + + +class Assignment(models.Model): + user = models.ForeignKey("auth.User", on_delete=models.PROTECT) + subject_ct = models.ForeignKey(ContentType, on_delete=models.PROTECT) + subject_id = models.PositiveIntegerField() + subject = GenericForeignKey('subject_ct', 'subject_id') + + class Meta: + unique_together = [ + ("subject_ct", "subject_id"), + ]