Skip to content
Snippets Groups Projects
Select Git revision
  • 49ed26bea2e00a61c0bd9d64d08466f265e612f6
  • master default protected
  • vertical_relations
  • lu_without_semantic_frames
  • hierarchy
  • additional-unification-filters
  • v0.1.1
  • v0.1.0
  • v0.0.9
  • v0.0.8
  • v0.0.7
  • v0.0.6
  • v0.0.5
  • v0.0.4
  • v0.0.3
  • v0.0.2
  • v0.0.1
17 results

tests.py

Blame
  • views.py NaN GiB
    import operator
    import time
    
    from collections import defaultdict
    from functools import reduce
    from itertools import chain, product
    
    import simplejson
    
    from django.contrib.auth.models import User
    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
    from django.utils.translation import gettext as _
    
    from crispy_forms.utils import render_crispy_form
    
    from connections.models import Entry, Subentry, ArgumentConnection, RealisationDescription
    from meanings.models import LexicalUnit
    from syntax.models import NaturalLanguageDescription, Schema
    from semantics.models import Frame, PredefinedSelectionalPreference, SelectivePreferenceRelations, SemanticRole, \
        RoleAttribute
    
    from common.decorators import ajax_required, ajax
    from unifier.models import UnifiedFrame
    from users.models import Assignment
    
    from .forms import (
        EntryForm,
        SchemaFormFactory,
        FrameFormFactory,
        PositionFormFactory,
        PhraseAttributesFormFactory,
        LexFormFactory,
        LemmaFormFactory,
        ArgumentFormFactory,
        PredefinedPreferenceFormFactory,
        RelationalPreferenceFormFactory,
        SynsetPreferenceFormFactory, UnifiedFrameFormFactory, FrameOpinionFormFactory, UnifiedArgumentFormFactory,
    )
    
    from .polish_strings import STATUS, POS, SCHEMA_OPINION, FRAME_OPINION, EXAMPLE_SOURCE, EXAMPLE_OPINION, RELATION
    
    from .phrase_descriptions.descriptions import position_prop_description
    
    MAX_LAST_VISITED = 10
    
    @login_required
    def entries(request):
        # TODO make this automatic by subclassing/configuring session object
        if 'last_visited' not in request.session:
            request.session['last_visited'] = []
        if 'show_reals_desc' not in request.session:
            request.session['show_reals_desc'] = True
        if 'show_linked_entries' not in request.session:
            request.session['show_linked_entries'] = True
        # TODO retrieve the form from the request session – keep forms between page refreshes,
        # keep search history, allow saving searches?
        # if so, don’t delete local forms on main form submit in send_form
        return render(
            request,
            'entries.html',
            {
                'is_vue_app': True,
                'entries_form' : EntryForm(),
                'frames_form' : FrameFormFactory.get_form(as_subform=False),
                'schemata_form' : SchemaFormFactory.get_form(as_subform=False),
                'unified_frames_form': UnifiedFrameFormFactory.get_form(as_subform=False),
            })
    
    
    @login_required
    def unification(request):
        return render(
            request,
            'unification.html',
            {
                'is_vue_app': True,
                'unified_frame_id': request.GET.get("unified_frame_id"),
                'entries_form' : EntryForm(),
                'frames_form': FrameFormFactory.get_form(as_subform=False),
                'schemata_form': SchemaFormFactory.get_form(as_subform=False),
                'unified_frames_form': UnifiedFrameFormFactory.get_form(as_subform=False),
            },
        )
    
    
    @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': unified_frame_id,
                'entries_form': EntryForm(),
                'frames_form': FrameFormFactory.get_form(as_subform=False),
                'schemata_form': SchemaFormFactory.get_form(as_subform=False),
                'unified_frames_form': UnifiedFrameFormFactory.get_form(as_subform=False),
            },
        )
    
    
    FORM_TYPES = {
        'entry'      : EntryForm,
    }
    
    
    FORM_FACTORY_TYPES = {
        'schema'     : SchemaFormFactory,
        'position'   : PositionFormFactory,
        'phrase_lex' : LexFormFactory,
        'lemma'      : LemmaFormFactory,
        'frame'      : FrameFormFactory,
        'unifiedframe'      : UnifiedFrameFormFactory,
        'FrameOpinion'      : FrameOpinionFormFactory,
        'argument'   : ArgumentFormFactory,
        'UnifiedArgument'   : UnifiedArgumentFormFactory,
        'predefined' : PredefinedPreferenceFormFactory,
        'relational' : RelationalPreferenceFormFactory,
        'synset'     : SynsetPreferenceFormFactory,
    }
    
    
    def make_form(form_type, data=None, unique_number=None):
        if form_type in FORM_FACTORY_TYPES:
            return FORM_FACTORY_TYPES[form_type].get_form(data=data, unique_number=unique_number)
        if form_type in FORM_TYPES:
            return FORM_TYPES[form_type](data=data)
        elif form_type.startswith('phrase_'):
            phrase_type = form_type[7:]
            return PhraseAttributesFormFactory.get_form(phrase_type, data=data, unique_number=unique_number)
        return None
    
    
    @ajax_required
    def get_subform(request):
        if request.method == 'GET':
            ctx = {}
            ctx.update(csrf(request))
            form_type = request.GET['subform_type']
            form = make_form(form_type)
            try:
                form_html = render_crispy_form(form, context=ctx)
            except:
                print('******************', form_type)
                raise
            return JsonResponse({'form_html' : form_html})
    
    #TODO clean this code bordello up
    
    def filter_objects(objects, queries, tab=''):
        #print(tab + '===================================================================')
        for query in queries:
            #print(tab + '***', query)
            objects = objects.filter(query).distinct()
            #print(tab + '---------------------------------------------------------------')
            #print(tab, objects)
            #print('\n')
        #print(tab + '===================================================================')
        return objects.distinct()
    
    
    def collect_forms(forms_json, errors_collector_dict, tab='   '):
        data = simplejson.loads(forms_json)
        form_type = data['formtype']
        form_number = data.get('formnumber', 0)
        if form_type in ('or', 'other'):
            return form_type
        else:
            #print(tab, 'FORM:', data['form'])
            #print(tab, 'TYPE:', form_type, 'NUMBER:', form_number)
            #print(tab, 'DATA:', data)
            query_params = QueryDict(data['form'])
            #print(tab, 'PARAMS:', query_params)
            form = make_form(form_type, data=query_params, unique_number=form_number)
            #print(tab, 'FORM TYPE:', type(form))
            if not form.is_valid():
                errors_collector_dict.update(form.errors)
            #print(tab, '{} CHILDREN GROUP(S)'.format(len(data['children'])))
            # a form may have one or more children forms, organised into and-or
            # (e.g. an entry form has child schema forms, frame forms etc.)
            subform_groups = []
            for subforms_json in data['children']:
                subform_group = simplejson.loads(subforms_json)
                subform_type, subforms = subform_group['formtype'], subform_group['subforms']
                children = [[]]
                conjunctions = set()
                for child in subforms:
                    child_form = collect_forms(child, errors_collector_dict, tab + '    ')
                    if child_form in ('or', 'other'):
                        children.append([])
                        conjunctions.add(child_form)
                    else:
                        children[-1].append(child_form)
                assert(len(conjunctions) <= 1)
                conjunction = 'or' if not conjunctions else conjunctions.pop()
                subforms = list(filter(None, children))
                if subforms:
                    subform_groups.append((subform_type, conjunction, subforms))
            return (form, data['negated'], subform_groups)
    
    
    def reduce_ids_and(ids):
        # sum the negated ids
        ids[False] = reduce(lambda x, y: x.union(y), ids[False], set())
        if ids[True]:
            # intersect the non-negated and subtract the sum of negated
            return (True, reduce(lambda x, y: x.intersection(y), ids[True]) - ids[False])
        else:
            # negate the sum of negated
            return (False, ids[False])
    
    
    def other_operator(tab, form, children_group, parent_objects):
        name, conjunction, children = children_group
        print(tab, '==============', name, conjunction)
        print(tab, 'PARENT OBJECTS:', parent_objects)
        prefixes = set()
        # matches[id][j] -> set of child ids of ‹id› parent satisfying ‹j›th specification
        matches = defaultdict(lambda: defaultdict(set))
        for i, conj_children in enumerate(children):
            print(tab, '    ', conjunction)
            child_ids_and = { True: [], False: [] }
            for child in conj_children:
                child_form = child[0]
                child_objects = get_filtered_objects(child, tab=tab + '        ')
                prefix = form.get_child_form_prefix(child_form)
                prefixes.add(prefix)
                child_ids = set(co.id for co in child_objects)
                child_ids_and[not child_form.is_negated()].append(child_ids)
            child_ids_and = reduce_ids_and(child_ids_and)
            print(tab, '    ===>', '[]'.format(i), child_ids_and[0], len(child_ids_and[1]), sorted(child_ids_and[1])[:10])
            # TODO enable negations?
            assert(child_ids_and[0] == True)
            for child_id in child_ids_and[1]:
                assert(prefix.endswith('__in'))
                for parent in parent_objects.filter(Q((prefix, [child_id]))):
                    matches[parent.id][i].add(child_id)
        assert(len(prefixes) == 1)
        N = len(children)
        matching_parent_ids = set()
        for parent_id, mtchs in matches.items():
            print(tab, parent_id, mtchs)
            if len(mtchs) < len(children):
                print(tab, 'not all matched')
                continue
            for x in product(*mtchs.values()):
                if len(x) == len(set(x)):
                    # found N different children objects, each satisfying different specification
                    matching_parent_ids.add(parent_id)
                    print(tab, 'MATCH:', x)
                    continue
            if parent_id not in matching_parent_ids:
                print(tab, 'no match')
        return parent_objects.filter(id__in=matching_parent_ids)
    
    
    def get_filtered_objects(forms, initial_objects=None, tab='   '):
        form, negated_attrs, children = forms
        objects = form.model_class.objects.all() if initial_objects is None else initial_objects.all()
        queries = form.get_queries(negated_attrs)
        #print(tab, type(form), 'FOR FILTERING:', form.model_class)
        #print(tab, queries)
        objects = filter_objects(objects, queries, tab=tab)
        #print(tab, 'OK')
        for children_group in children:
            if children_group[1] == 'other':
                objects = other_operator(tab, form, children_group, objects)
                continue
            #print(tab, 'CHILD FORMS')
            object_ids_or = []
            prefixes = set()
            for or_children in children_group[2]:
                objects_and = form.model_class.objects.all() if initial_objects is None else initial_objects.all()
                for child in or_children:
                    child_form = child[0]
                    child_objects = get_filtered_objects(child, tab=tab + '        ')
                    prefix = form.get_child_form_prefix(child_form)
                    prefixes.add(prefix)
                    child_ids = [co.id for co in child_objects]
                    q = Q((prefix, child_ids))
                    if child_form.is_negated():
                        objects_and = objects_and.exclude(q)
                    else:
                        objects_and = objects_and.filter(q)
                object_ids_or.append({o.id for o in objects_and})
            assert(len(prefixes) == 1)
            object_ids = reduce(operator.or_, object_ids_or)
            objects = objects.filter(id__in=object_ids)
        objects = objects.distinct()
        #print(tab, 'FILTERED:', form.model_class)
        return objects
    
    
    # forms – an ‘or’ list of ‘and’ lists of forms, the forms are flattened and treated as one ‘or’ list.
    # The function is used for filtering out schemata/frames. E.g. if the user chooses entries with a schema
    # safisfying X AND a schema satisfying Y, schemata satisfying X OR Y should be displayed (and all other
    # schemata should be hidden).
    def get_filtered_objects2(forms, objects):
        #print(forms)
        filtered_ids = [{ schema.id for schema in get_filtered_objects(form, initial_objects=objects) } for form in chain.from_iterable(forms)]
        filtered_ids = reduce(operator.or_, filtered_ids)
        return objects.filter(id__in=filtered_ids)
    
    
    @ajax_required
    def send_form(request):
        if request.method == 'POST':
            errors_dict = dict()
            forms = collect_forms(request.POST['forms[]'], errors_dict)
            if errors_dict:
                del request.session['forms']
                return JsonResponse({ 'success' : 0, 'errors' : errors_dict })
            else:
                request.session['forms'] = request.POST['forms[]']
                if 'schema_form' in request.session:
                    del request.session['schema_form']
                if 'frame_form' in request.session:
                    del request.session['frame_form']
                return JsonResponse({ 'success' : 1 })
        return JsonResponse({})
    
    
    @ajax_required
    def send_schemata_form(request):
        if request.method == 'POST':
            errors_dict = dict()
            forms = collect_forms(request.POST['forms[]'], errors_dict)
            eid = request.POST['entry']
            if errors_dict:
                del request.session['schema_form']
                return JsonResponse({ 'success' : 0, 'errors' : errors_dict })
            else:
                request.session['schema_form'] = request.POST['forms[]']
                return JsonResponse({ 'success' : 1 })
        return JsonResponse({})
    
    
    @ajax_required
    def send_frames_form(request):
        if request.method == 'POST':
            errors_dict = dict()
            forms = collect_forms(request.POST['forms[]'], errors_dict)
            eid = request.POST['entry']
            if errors_dict:
                del request.session['frame_form']
                return JsonResponse({ 'success' : 0, 'errors' : errors_dict })
            else:
                request.session['frame_form'] = request.POST['forms[]']
                return JsonResponse({ 'success' : 1 })
        return JsonResponse({})
    
    
    @ajax_required
    def send_unified_frames_form(request):
        if request.method == 'POST':
            errors_dict = dict()
            forms = collect_forms(request.POST['forms[]'], errors_dict)
            eid = request.POST['entry']
            if errors_dict:
                del request.session['unified_frame_form']
                return JsonResponse({ 'success' : 0, 'errors' : errors_dict })
            else:
                request.session['unified_frame_form'] = request.POST['forms[]']
                return JsonResponse({ 'success' : 1 })
        return JsonResponse({})
    
    
    def get_scroller_params(POST_data):
        order = (int(POST_data['order[0][column]']), POST_data['order[0][dir]'])
        return {
            'draw'   : int(POST_data['draw']),
            'start'  : int(POST_data['start']),
            'length' : int(POST_data['length']),
            'order'  : order,
            'filter' : POST_data['search[value]']
        }
    
    # TODO restriction to >1 subentries for testing css – remove!!!
    #from django.db.models import Count
    
    @ajax_required
    @login_required
    def get_entries(request):
        if request.method == 'POST':
            errors_dict = dict()
            forms = collect_forms(request.session['forms'], errors_dict)
            # 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'
            exclude_status = request.GET.get('exclude_status')
            restrict_to_user = request.GET.get('restrict_to_user')
            has_unified_frame = request.GET.get('has_unified_frame')
            entries = get_filtered_objects(forms).filter(import_error=False)
            
            # TODO restrictions for testing – remove!!!
            #entries = entries.annotate(nsub=Count('subentries')).filter(nsub__gt=1)
            #entries = entries.filter(subentries__schemata__opinion__key__in=('vul', 'col')).filter(status__key__in=('(S) gotowe', '(S) sprawdzone'))
            #entries = entries.filter(subentries__schema_hooks__alternation=2)
            
            total = entries.count()
            if scroller_params['filter']:
                entries = entries.filter(name__startswith=scroller_params['filter'])
            filtered = entries.count()
    
            local_frame_form = None
            if 'frame_form' in request.session:
                errors_dict = dict()
                local_frame_form = collect_forms(request.session['frame_form'], errors_dict)
                assert(not errors_dict)
            
            linked_ids = set()
            if request.session['show_linked_entries']:
                entries_linked = Entry.objects.filter(pk__in=(
                    Entry.objects
                    .filter(subentries__schema_hooks__argument_connections__schema_connections__subentry__entry__in=entries)
                    .exclude(id__in=entries)
                )).distinct()
                entries = entries | entries_linked
                linked_ids = set(entries_linked.values_list('id', flat=True))
            
            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'
            elif order_field == 1:
                order_field = 'status__key'
            elif order_field == 2:
                order_field = 'pos__tag'
            if order_dir == 'desc':
                order_field = '-' + order_field
    
            if restrict_to_user:
                # pre-filtering entries for performance reasons
                entries = entries.filter(lexical_units__frames__assignments__user=User.objects.get(username=restrict_to_user))
            entries = (
                entries
                .order_by(order_field)
                .select_related('status', 'pos')
                .prefetch_related(Prefetch("assignments", to_attr="_assignments"))
            )
    
            if with_lexical_units:
                frameQueryset = Frame.objects.select_related("slowal_frame_2_unified_frame").prefetch_related(Prefetch("assignments", to_attr="_assignments"));
                entries = entries.prefetch_related(
                    Prefetch(
                        "lexical_units",
                        LexicalUnit.objects.prefetch_related(
                            Prefetch(
                                "frames",
                                queryset=get_filtered_objects(local_frame_form, frameQueryset) if local_frame_form is not None else frameQueryset,
                                to_attr="_frames",
                            )
                        )
                    )
                )
    
            status_names = STATUS()
            POS_names = POS()
    
            def iter_lexical_units(e):
                for lu in e.lexical_units.all():
                    lu._frame = lu._frames[0] if lu._frames and len(lu._frames) > 0 else None
                    if lu._frame is None:
                        continue
                    else:
                        yield lu
    
            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,
                        'assignee_username': e._assignments[0].user.username if e._assignments else None,
                        **(
                            {
                                'lexical_units': [
                                    {
                                        'pk': lu.pk,
                                        'display': str(lu),
                                        'assignee_username': (
                                            lu._frame._assignments[0].user.username if lu._frame and lu._frame._assignments else None
                                        ),
                                        'status': lu._frame.status if lu._frame else "",
                                        'unified_frame_id': lu._frame.slowal_frame_2_unified_frame.unified_frame_id if lu._frame and hasattr(lu._frame, 'slowal_frame_2_unified_frame') else -1,
                                    } for lu in iter_lexical_units(e)
                                ]
                            }
                            if with_lexical_units else {}
                        ),
                    } for e in entries[first_index:last_index]
                ],
            }
    
            if with_lexical_units:
                filteredData = []
                for entry in result['data']:
                    lexicalUnits = entry['lexical_units']
                    filteredLexicalUnits = []
                    for lexicalUnit in lexicalUnits:
                        if (exclude_status == None or lexicalUnit['status'] != exclude_status) and \
                                (restrict_to_user == None or lexicalUnit['assignee_username'] == restrict_to_user) and \
                                    (has_unified_frame != 'true' or lexicalUnit['unified_frame_id'] != -1):
                            filteredLexicalUnits.append(lexicalUnit)
                    entry['lexical_units'] = filteredLexicalUnits
                    if len(filteredLexicalUnits) > 0:
                        filteredData.append(entry)
    
                result['data'] = filteredData
    
            return JsonResponse(result)
        return JsonResponse({})
    
    
    def subentry2str(subentry):
        ret = subentry.entry.name
        if subentry.inherent_sie.name == 'true':
            ret += ' się'
        elems = []
        if subentry.aspect:
            elems.append(subentry.aspect.name)
        if subentry.negativity:
            elems.append(subentry.negativity.name)
        if elems:
            ret += ' ({})'.format(', '.join(elems))
        if subentry.predicativity.name == 'true':
            ret += ' pred.'
        return ret
    
    
    def position_prop2dict(prop):
        return {
            'str'  : prop.name,
            'desc' : position_prop_description(prop.name),
        } if prop else {
            'str'  : '',
            'desc' : '',
        }
    
    
    def get_phrase_desc(phrase, position, negativity, lang):
        return NaturalLanguageDescription.objects.get(
                phrase_str=phrase.text_rep,
                function=position.function,
                control=position.control,
                pred_control=position.pred_control,
                negativity=negativity,
                lang=lang).description
    
    
    def schema2dict(schema, negativity, lang):
        return {
            'opinion'     : SCHEMA_OPINION()[schema.opinion.key],
            'opinion_key' : schema.opinion.key,
            'id'          : str(schema.id),
            'positions'   : [
                {
                    'func'      : position_prop2dict(p.function),
                    'control'   : position_prop2dict(p.control),
                    'p_control' : position_prop2dict(p.pred_control),
                    'id'        : '{}-{}'.format(schema.id, p.id),
                    'phrases' : [
                        {
                            'str'       : str(pt),
                            'id'        : '{}-{}-{}'.format(schema.id, p.id, pt.id),
                            'desc'      : get_phrase_desc(pt, p, negativity, lang),
                        } for pt in p.sorted_phrase_types()
                    ],
                } for p in schema.sorted_positions()
             ],
        }
    
    
    def get_rel_pref_desc(pref):
        relation = pref.relation.key
        if relation == 'RELAT':
            desc = _('Realizacja tego argumentu w zdaniu powinna być powiązana jakąkolwiek relacją')
        else:
            desc = _('Realizacja tego argumentu w zdaniu powinna być powiązana relacją <i>{}</i>').format(RELATION()[relation])
        return desc + ' ' + _('z realizacją argumentu <i>{}</i>.').format(pref.to)
    
    
    def make_ul(items):
        return '<ul>{}</ul>'.format(''.join(map('<li>{}</li>'.format, items)))
    
    def get_synset_def(synset):
        ret = []
        if synset.definition:
            ret.append(_('definicja:') + make_ul([synset.definition]))
        #ret.append(_('jednostki leksykalne: ') + ', '.join(map(str, synset.lexical_units.all())))
        hypernyms = list(synset.hypernyms.all())
        if hypernyms:
            ret.append(_('hiperonimy:') + make_ul(map(str, hypernyms)))
        return ' '.join(ret)
    
    
    def get_prefs_list(argument):
        return sorted(
            ({
                'id'   : p.pk,
                'type' : p._meta.label,
                'str'  : str(p),
            } for p in argument.predefined.all()),
            key=lambda x: x['str']
        ) + sorted(
            ({
                'id'   : s.pk,
                'type' : s._meta.label,
                'str'  : str(s),
                # can be a new synset
                'url'  : None if s.id < 0 else 'http://plwordnet21.clarin-pl.eu/synset/{}'.format(s.id),
                'info' : s.lexical_units.all()[0].gloss if s.id < 0 else get_synset_def(s),
            } for s in argument.synsets.all()),
            key=lambda x: x['str']
        ) + sorted(
            ({
                'id'   : r.pk,
                'type' : r._meta.label,
                'str'  : str(r),
                'info' : get_rel_pref_desc(r),
            } for r in argument.relations.all()),
            key=lambda x: x['str']
        )
    
    
    def frame2dict(frame, entry_meanings):
        return {
            'opinion'       : FRAME_OPINION()[frame.opinion.key],
            'opinion_key'   : frame.opinion.key,
            'id'            : frame.id,
            'status'        : frame.status,
            'lexical_units' : [
                {
                    'str'           : lu.text_rep,
                    'id'            : lu.id,
                    'entry_meaning' : lu in entry_meanings,
                    'definition'    : lu.definition,
                    'gloss'         : lu.gloss,
                    'url'           : None if lu.luid is None else 'http://plwordnet21.clarin-pl.eu/lemma/{}/{}'.format(lu.base, lu.sense)
                } for lu in frame.lexical_units.all()
            ],
            'arguments'     : [
                {
                    'str'         : str(a),
                    'argument_id' : a.id,
                    'id'          : '{}-{}'.format(frame.id, a.id),
                    'role'        : '{}{}'.format(a.role.role.role.lower(), ' ' + a.role.attribute.attribute.lower() if a.role.attribute else ''),
                    'preferences' : get_prefs_list(a),
                } for a in sorted(frame.arguments.all(), key=lambda a: a.role.role.priority + (a.role.attribute.priority if a.role.attribute else 2))
            ],
        }
    
    # returns two dicts:
    # (1) {
    #     frame_id_1 : {
    #         schema_id_1 : { [alt_1*, ..., alt_l] },
    #         schema_id_k : {...}
    #     }
    #     ...
    #     frame_id_n : {...}
    # }
    # *alternation is a dict: {
    #    key: extended argument id (frame_id-arg_id)
    #    val: list of extended phrase ids (schema_id-position_id-phr_id)
    # (2) {
    #     extended_arg_id_1 : {
    #         alt_1 : {
    #             extended_phr_id_1 : psedo_natural_language_phrase_1_1_1,
    #             extended_phr_id_l : psedo_natural_language_phrase_1_1_l,
    #         }
    #         ...
    #         alt_k : {...}
    #     }
    #     ...
    #     extended_arg_idn : {...}
    # }
    def get_alternations(schemata, frames):
        # TODO czy alternacja może być podpięta do całej pozycji, bez konkretnej frazy?
        alternations = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(list))))
        phrases = defaultdict(lambda: defaultdict(dict))
        realisation_descriptions = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
        for schema in schemata:
            for hook in schema.schema_hooks.all():
                arg_conns = hook.argument_connections.all()
                assert (len(arg_conns) < 2)
                if (arg_conns):
                    argument = arg_conns[0].argument
                    frame = argument.frame
                    if frame not in frames:
                        continue
                    phr_id = '{}-{}-{}'.format(schema.id, hook.position.id, hook.phrase_type.id)
                    arg_id = '{}-{}'.format(frame.id, argument.id)
                    alternations[frame.id][schema.id][hook.alternation][arg_id].append(phr_id)
                    phrases[arg_id][hook.alternation - 1][phr_id] = hook.description
        alt_dict = defaultdict(lambda: defaultdict(list))
        
        for frame_id, frame_schema_alternations in alternations.items():
            for schema_id, schema_alternations in frame_schema_alternations.items():
                for alt_no in sorted(schema_alternations.keys()):
                    alt_dict[frame_id][schema_id].append(schema_alternations[alt_no])
                    realisation_descriptions[frame_id][schema_id][alt_no - 1] = RealisationDescription.objects.get(frame__id=frame_id, schema__id=schema_id, alternation=alt_no).description
        return alt_dict, phrases, realisation_descriptions
    
    
    def get_examples(entry):
        examples = []
        for example in entry.examples.all():
            frame_ids, argument_ids, lu_ids, schema_ids, phrases, phrases_syntax, positions = set(), set(), set(), set(), set(), set(), set()
            for connection in example.example_connections.all():
                for argument in connection.arguments.all():
                    frame_ids.add(argument.frame.id)
                    argument_ids.add('{}-{}'.format(argument.frame.id, argument.id))
                if connection.lexical_unit:
                    lu_ids.add(connection.lexical_unit.id)
                for hook in connection.schema_connections.all():
                    schema_ids.add(hook.schema.id);
                    phrases.add('{}-{}-{}-{}'.format(hook.schema.id, hook.position.id, hook.phrase_type.id, hook.alternation - 1))
                    phrases_syntax.add('{}-{}-{}'.format(hook.schema.id, hook.position.id, hook.phrase_type.id))
                    positions.add('{}-{}'.format(hook.schema.id, hook.position.id))
            examples.append({
                '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),
            })
        return sorted(examples, key=lambda x: x['sentence'])
    
    # [[POS1], [POS2, POS3]]
    # =>
    # [
    #     [
    #         (<SchemaForm>, [], [('position', 'or', [[POS1]])])
    #     ],
    #     [
    #         (<SchemaForm>, [], [('position', 'or', [[POS2]])]),
    #         (<SchemaForm>, [], [('position', 'or', [[POS3]])])
    #     ]
    # ]
    def position_forms2schema_forms(forms):
        dummy_schema_form = make_form('schema', data={})
        # validate the dummy to ensure access to cleaned_data
        assert(dummy_schema_form.is_valid())
        return [
            [(dummy_schema_form, [], [('position', 'or', [[posf]])]) for posf in posfs]
            for posfs in forms
        ]
    
    # [[ATR1, ATR2], [ATR3]]
    # =>
    # [
    #     [
    #         (<SchemaForm>, [], [('position', 'or', [[(<PositionForm>, [], [('switch', 'or', [[ATR]])])]])]),
    #         (<SchemaForm>, [], [('position', 'or', [[(<PositionForm>, [], [('switch', 'or', [[ATR]])])]])])
    #     ],
    #     [
    #         (<SchemaForm>, [], [('position', 'or', [[(<PositionForm>, [], [('switch', 'or', [[ATR]])])]])])
    #     ]
    # ]
    
    def phrase_forms2schema_forms(forms):
        dummy_schema_form = make_form('schema', data={})
        dummy_position_form = make_form('position', data={})
        # validate the dummies to ensure access to cleaned_data
        assert(dummy_schema_form.is_valid())
        assert(dummy_position_form.is_valid())
        return [
            [(dummy_schema_form, [], [('position', 'or', [[(dummy_position_form, [], [('switch', 'or', [[phrf]])])]])]) for phrf in phrfs]
            for phrfs in forms
        ]
    
    # [[ARG1, ARG2], [ARG3]]
    # =>
    # [
    #     [
    #         (<FrameForm>, [], [('argument', 'or', [[ARG1]])]),
    #         (<FrameForm>, [], [('argument', 'or', [[ARG2]])])
    #     ],
    #     [
    #         (<FrameForm>, [], [('argument', 'or', [[ARG3]])])
    #     ]
    # ]
    
    def argument_forms2frame_forms(forms):
        dummy_frame_form = make_form('frame', data={})
        # validate the dummy to ensure access to cleaned_data
        assert(dummy_frame_form.is_valid())
        return [
            [(dummy_frame_form, [], [('argument', 'or', [[argf]])]) for argf in argfs]
            for argfs in forms
        ]
    
    #TODO test (*) changes
    
    @ajax_required
    def get_entry(request):
        if request.method == 'POST':
            #TODO (*)
            #form = EntryForm(request.POST)
            eid = request.POST['entry']
            #TODO (*)
            if eid.isdigit():# and form.is_valid():
                eid = int(eid)
                # TODO check that Entry has no import errors
                entry = Entry.objects.get(id=eid)
                errors_dict = dict()
                #TODO (*)
                #entry_form, _, children_forms = collect_forms(request.POST['forms[]'], errors_dict)
                entry_form, _, children_forms = collect_forms(request.session['forms'], errors_dict)
                # form should already be validated if it passed through send_form
                assert(not errors_dict)
                
                # dont’ do schema/frame filtering for related entries
                apply_filters = not simplejson.loads(request.POST['no_filters'])
                filter_schemata = apply_filters and entry_form.cleaned_data['filter_schemata']
                filter_frames = apply_filters and entry_form.cleaned_data['filter_frames']
                lexical_unit = LexicalUnit.objects.get(pk=lu_id) if (lu_id := request.POST.get("lexical_unit_id")) else None
                if filter_schemata:
                    schema_forms = []
                    # e.g. entry has schema that satisfies X & entry has schema that satisfies Y
                    # => filtering schemata shows only schemata that contributed to the match
                    # => leave schemata that satisfies X or satisfy Y
                    schema_forms1 = [frms[2] for frms in children_forms if frms[0] == 'schema']
                    assert (len(schema_forms1) <= 1)
                    if schema_forms1:
                        schema_forms = schema_forms1[0]
                    # e.g. entry has position that satisfies X & entry has position that satisfies Y
                    # => entry has schema that has position that satisfies X
                    # & entry has schema that has position that satisfies Y
                    # => leave schemata that have position that satisfies X or have position that satisfies Y
                    position_forms = [frms[2] for frms in children_forms if frms[0] == 'position']
                    assert (len(position_forms) <= 1)
                    if position_forms:
                        position_forms = position_forms[0]
                        schema_forms += position_forms2schema_forms(position_forms)
                    phrase_forms = [frms[2] for frms in children_forms if frms[0] == 'switch']
                    assert (len(phrase_forms) <= 1)
                    if phrase_forms:
                        phrase_forms = phrase_forms[0]
                        schema_forms += phrase_forms2schema_forms(phrase_forms)
                    filter_schemata = len(schema_forms) > 0
                
                if filter_frames:
                    frame_forms = []
                    frame_forms1 = [frms[2] for frms in children_forms if frms[0] == 'frame']
                    assert (len(frame_forms1) <= 1)
                    if frame_forms1:
                        frame_forms = frame_forms1[0]
                    argument_forms = [frms[2] for frms in children_forms if frms[0] == 'argument']
                    assert (len(argument_forms) <= 1)
                    if argument_forms:
                        argument_forms = argument_forms[0]
                        frame_forms += argument_forms2frame_forms(argument_forms)
                    filter_frames = len(frame_forms) > 0
    
                local_schema_filter_form = get_local_schema_filter_form(apply_filters, request)
    
                local_frame_filter_form = get_local_frame_filter_form(apply_filters, request)
    
                subentries = []
                all_schema_objects = []
                for subentry in entry.subentries.all():
                    schemata = []
                    schema_objects = subentry.schemata.all()
                    # filter out schemata by schema properties
                    if filter_schemata:
                        schema_objects = get_filtered_objects2(schema_forms, schema_objects)
                    if local_schema_filter_form:
                        schema_objects = get_filtered_objects(local_schema_filter_form, schema_objects)
                    for schema in schema_objects:
                        schemata.append(schema2dict(schema, subentry.negativity, request.LANGUAGE_CODE))
                    if schemata:
                        all_schema_objects += list(schema_objects)
                        subentries.append({ 'str' : subentry2str(subentry), 'schemata' : schemata })
                frame_objects = Frame.objects.filter(arguments__argument_connections__schema_connections__subentry__entry=entry).distinct()
                # filter out frames by frame properties
                if filter_frames:
                    frame_objects = get_filtered_objects2(frame_forms, frame_objects)
                if lexical_unit:
                    frame_objects = frame_objects.filter(lexical_units=lexical_unit)
                if local_frame_filter_form:
                    frame_objects = get_filtered_objects(local_frame_filter_form, frame_objects)
                frames = [frame2dict(frame, entry.lexical_units.all()) for frame in frame_objects]
                alternations, realisation_phrases, realisation_descriptions = get_alternations(all_schema_objects, frame_objects)
                examples = get_examples(entry)
                unified_frame = None
                if lexical_unit and (unified_frame := UnifiedFrame.objects.all().for_lexical_unit(lexical_unit).first()):
                    unified_frame = {
                        'pk': unified_frame.pk,
                        'status': unified_frame.status,
                        'assignee_username' : assignment.user.username if (assignment := unified_frame.assignments.first()) else None,
                    }
                # https://docs.djangoproject.com/en/2.2/topics/http/sessions/#when-sessions-are-saved
                if [entry.name, entry.id] in request.session['last_visited']:
                    request.session['last_visited'].remove([entry.name, entry.id])
                request.session['last_visited'].insert(0, (entry.name, entry.id))
                request.session['last_visited'] = request.session['last_visited'][:(MAX_LAST_VISITED + 1)]
                request.session.modified = True
                return JsonResponse({ 'subentries' : subentries, 'frames' : frames, 'alternations' : alternations, 'realisation_phrases' : realisation_phrases, 'realisation_descriptions' : realisation_descriptions, 'examples' : examples, 'unified_frame': unified_frame, 'last_visited' : request.session['last_visited'] })
        return JsonResponse({})
    
    
    def get_local_frame_filter_form(apply_filters, request):
        local_frame_form = None
        if apply_filters and 'frame_form' in request.session:
            errors_dict = dict()
            local_frame_form = collect_forms(request.session['frame_form'], errors_dict)
            assert (not errors_dict)
        return local_frame_form
    
    
    def get_local_schema_filter_form(apply_filters, request):
        local_schema_form = None
        if apply_filters and 'schema_form' in request.session:
            errors_dict = dict()
            local_schema_form = collect_forms(request.session['schema_form'], errors_dict)
            assert (not errors_dict)
        return local_schema_form
    
    
    '''
    @ajax_required
    def filter_schemata(request):
        if request.method == 'POST':
            print(request.POST['forms[]'])
            errors_dict = dict()
            forms = collect_forms(request.POST['forms[]'], errors_dict)
            eid = request.POST['entry']
            print(eid)
            print(forms)
            if errors_dict:
                return JsonResponse({ 'success' : 0, 'errors' : errors_dict })
            
            # TODO check that Entry has no import errors
            entry = Entry.objects.get(id=eid)
            schema_ids = []
            for subentry in entry.subentries.all():
                for schema in get_filtered_objects(forms, subentry.schemata.all()):
                    schema_ids.append(schema.id)
            return JsonResponse({ 'success' : 1, 'schema_ids' : schema_ids })
        return JsonResponse({})
    '''
    
    @ajax_required
    def change_show_reals_desc(request):
        if request.method == 'POST':
            val = simplejson.loads(request.POST['val'])
            request.session['show_reals_desc'] = val
            return JsonResponse({ 'success' : 1 })
        return JsonResponse({})
    
    @ajax_required
    def change_show_linked_entries(request):
        if request.method == 'POST':
            val = simplejson.loads(request.POST['val'])
            request.session['show_linked_entries'] = val
            return JsonResponse({ 'success' : 1 })
        return JsonResponse({})
    
    @ajax(method='get', encode_result=True)
    def ajax_plWN_context_lookup(request, term):
        results = []
        # term = term.encode('utf8')
        if len(term) > 0:
            obj_results = LexicalUnit.objects.filter(base__startswith=term)
            results = get_ordered_lexical_units_bases(obj_results)
        return {'result': results}
    
    
    def get_ordered_lexical_units_bases(lexical_units_query):
        last_unit_base = ''
        lexical_unit_bases = []
        ordered_lexical_units = lexical_units_query.order_by('base')
        for lexical_unit in ordered_lexical_units:
            if lexical_unit.base != last_unit_base:
                lexical_unit_bases.append(lexical_unit.base)
            last_unit_base = lexical_unit.base
        return lexical_unit_bases
    
    
    @ajax(method='get', encode_result=True)
    def ajax_predefined_preferences(request):
        predefined = []
        for preference in PredefinedSelectionalPreference.objects.order_by('name'):
            if preference.members:
                members = [member.name for member in preference.members.generals.order_by('name')]
                members.extend([str(synset) for synset in preference.members.synsets.all()])
                content = '%s: (%s)' % (preference.name, ', '.join(members))
            else:
                content = '%s' % (preference.name)
            predefined.append({"id": preference.id, "content": content})
    
        context = {
            'predefined': predefined,
        }
    
        return context
    
    @ajax(method='get', encode_result=True)
    def ajax_roles(request):
        roles = []
        for role in SemanticRole.objects.order_by('priority'):
           roles.append({"id": role.id, "role": role.role, "priority": role.priority})
    
        context = {
            'roles': roles,
        }
    
        return context
    
    @ajax(method='get', encode_result=True)
    def ajax_role_attributes(request):
        roleAttributes = []
        for roleAttribute in RoleAttribute.objects.order_by('priority'):
            roleAttributes.append({"id": roleAttribute.id, "attribute": roleAttribute.attribute, "priority": roleAttribute.priority})
    
        context = {
            'role_attributes': roleAttributes,
        }
    
        return context
    
    # @render('relations.json')
    @ajax(method='get', encode_result=True)
    def ajax_relations(request):
    
        relations = [{"id": relation.plwn_id, "content": relation.name} for relation in SelectivePreferenceRelations.objects.all()]
    
        context = {
            'relations': relations,
        }
    
        return context
    
    @ajax(method='get', encode_result=True)
    def ajax_synsets(request, base, pos):
    
        if pos == '_':
            lexical_units = LexicalUnit.objects.filter(base=base).order_by('pos', 'base', 'sense')
        else:
            lexical_units = LexicalUnit.objects.filter(base=base, pos=pos).order_by('pos', 'base', 'sense')
    
        synsets = []
        for representative in lexical_units:
            synset = [{"id": lu.id, "luid": lu.luid, "base": lu.base, "sense": lu.sense, "pos": lu.pos, "glossa": lu.definition} for lu in LexicalUnit.objects.filter(synset=representative.synset)]
            synsets.append({"id": representative.synset.id, "content": synset})
    
        context = {
            'synsets': synsets,
        }
    
        return context