ajax_lexeme_slickgrid.py 9.25 KB
#-*- coding:utf-8 -*-
from hashlib import md5

from django.db.models import Count
from django.core.cache import cache

from dictionary.models import Lexeme, filter_visible, visible_vocabularies
from dictionary.ajax_slickgrid import SlickGridAjax, SlickGridQuery
from common.decorators import ajax
from common.util import bisect_left, reverse, json_encode

import locale
locale.setlocale(locale.LC_ALL, 'pl_PL.UTF-8')

class LexemeGrid(SlickGridAjax):
    model = Lexeme
    search_field = 'entry'
    field_translation = {
        'part_of_speech': 'part_of_speech__symbol',
    }

    @classmethod
    def get_sort_field(cls, rule):
        new_rule = dict(rule)
        if rule['field'] == 'entry' and rule['a_tergo']:
            new_rule['field'] = 'rev'
        return super(LexemeGrid, cls).get_sort_field(new_rule)

    @classmethod
    def sort_queryset(cls, queryset, sort_rules):
        for rule in sort_rules:
            if rule['field'] == 'entry' and rule['a_tergo']:
                queryset = queryset.extra(select={'rev': "reverse(haslo)"})
        return super(LexemeGrid, cls).sort_queryset(queryset, sort_rules)

    @classmethod
    def apply_filter(cls, queryset, filter):
        lookup = cls.lookup_translation[filter['op']]
        negated = (lookup[0] == '-')
        field, data = filter['field'], filter['data']
        new_filter = dict(filter)
        special = False
        field_translation = {
            'form': 'lexemeform__form',
            'lexeme_qualifier': 'qualifiers__pk',
            'lip_qualifier': 'lexemeinflectionpattern__qualifiers__pk',
            'classification_value': 'classificationvalue__pk',
            'pattern_name': 'lexemeinflectionpattern__pattern__name',
            'inflection_characteristic':
                'lexemeinflectionpattern__inflection_characteristic__symbol',
            'containing_vocabulary': 'vocabularies__pk',
            'owner_vocabulary': 'owner_vocabulary__pk',
            'pattern_count': 'pc',
            'ic_count': 'icc',
            'cr_type': 'refs_to__type__pk',
        }
        if field == 'pattern_count':
            queryset = queryset.annotate(
                pc=Count('lexemeinflectionpattern__pattern', distinct=True))
        elif field == 'ic_count':
            queryset = queryset.annotate(
                icc=Count('lexemeinflectionpattern__inflection_characteristic',
                    distinct=True))
        elif field == 'qualifier':
            where = '''(
                exists (
                  select * from kwalifikatory_leksemow where lexeme_id = leksemy.id and
                    qualifier_id = %s) or
                exists (
                  select * from kwalifikatory_odmieniasiow join odmieniasie o on
                    lexemeinflectionpattern_id = o.id
                  where
                    qualifier_id = %s and o.l_id = leksemy.id) or
                exists (
                  select * from
                    odmieniasie o
                    join wzory w on (o.w_id = w.id)
                    join szablony_tabel s on (w.typ = s.wtyp and o.charfl = s.charfl)
                    join klatki k on k.st_id = s.id
                    join zakonczenia z on (o.w_id = z.w_id and k.efobaz = z.efobaz)
                    join kwalifikatory_zakonczen kz on (z.id = kz.ending_id)
                  where o.l_id = leksemy.id and s.wariant = '1' and
                    kz.qualifier_id = %s)
                )'''
            if negated:
                where = 'not ' + where
            queryset = queryset.extra(where=[where], params=[data] * 3)
            special = True
        if not special:
            new_filter['field'] = field_translation.get(field, field)
            return super(LexemeGrid, cls).apply_filter(queryset, new_filter)
        else:
            return queryset

    @classmethod
    def get_queryset(cls, query):
        lexemes = super(LexemeGrid, cls).get_queryset(query)
        return filter_visible(lexemes, query.user)

    @staticmethod
    def apply_mask(lexemes, mask, sort_rules):
        if mask == '':
            return lexemes
        for rule in sort_rules:
            if rule['field'] == 'entry':
                if not rule['a_tergo']:
                    matching_lexemes = lexemes.filter(entry__istartswith=mask)
                else:
                    matching_lexemes = lexemes.filter(entry__iendswith=mask)
                break
        else:
            matching_lexemes = lexemes.filter(entry__istartswith=mask)
        return matching_lexemes

    @classmethod
    def filter_value(cls, queryset, rule, from_value, upward):
        greater = (rule['order'] == 'asc') == upward
        if rule['field'] == 'entry' and rule['a_tergo']:
            if greater:
                comp = '>='
            else:
                comp = '<='
            return queryset.extra(where=["reverse(haslo) " + comp + " %s"],
                params=[reverse(from_value)])
        else:
            return super(LexemeGrid, cls).filter_value(
                queryset, rule, from_value, upward)

    @staticmethod
    def response_row(lexeme):
        lip_data = lexeme.lip_data()
        cont_vocabs = '/'.join(v.id for v in lexeme.vocabularies.all())
        return {
            'id': lexeme.id,
            'entry': lexeme.entry,
            'pos': lexeme.part_of_speech.symbol,
            'patterns': lip_data['patterns'],
            'ics': lip_data['inflection_characteristics'],
            'vocabs': cont_vocabs,
            'owner': lexeme.owner_vocabulary.id,
            'status': dict(Lexeme.STATUS_CHOICES).get(lexeme.status),
        }

    # indeks wiersza w danym sortowaniu, w którym
    # znajdzie się instancja o danym id
    @classmethod
    def row_index(cls, lexeme_id, query):
        id_list = get_id_list(query)
        count = len(id_list)
        if count == 0:
            return 0, 0
        return id_list.index(lexeme_id), count

    @classmethod
    def search_index(cls, query):
        id_list = get_id_list(query)
        count = len(id_list)
        if count == 0:
            return 0
        first_rule = query.sort_rules[0]
        if first_rule['field'] != cls.search_field:
            return 0

        index = bisect_left(id_list, query.mask,
            cmp=make_lexeme_cmp(first_rule))
        if index == count:
            index -= 1
        return index

def make_lexeme_cmp(rule):
    def lexeme_cmp(lexeme_id, mask):
        e1 = Lexeme.objects.get(id=lexeme_id).entry
        e2 = mask
        if rule['a_tergo']:
            e1 = reverse(e1)
            e2 = reverse(e2)
        result = locale.strcoll(e1, e2)
        if rule['order'] == 'desc' and e2 != '':
            result = -result
        return result

    return lexeme_cmp

# Zapytanie o indeks wiersza o pewnym id przy danym sortowaniu
@ajax(method='get')
def find_id(request, id, sort_rules, mask, filters=None):
    query = SlickGridQuery(
        filters=filters, sort_rules=sort_rules, mask=mask, user=request.user)
    index, count = LexemeGrid.row_index(id, query)
    return {
        'index': index,
        'count': count,
    }

@ajax(method='get')
def search_index(request, sort_rules, search='', filters=None):
    query = SlickGridQuery(
        filters=filters, sort_rules=sort_rules, mask=search, user=request.user)
    return {
        'index': LexemeGrid.search_index(query)
    }


def cache_key(query):
    key = json_encode(query.sort_rules) + json_encode(query.filters)
    for vocabulary in visible_vocabularies(query.user):
        key += vocabulary.id
    if query.filtering_mode():
        key += query.mask
    return md5(key).hexdigest()

def get_cached_lexemes(query):
    key = cache_key(query)
    return cache.get(key)


def cache_lexemes(id_list, query):
    key = cache_key(query)
    cache.set(key, id_list)
    key_list = cache.get('key_list', [])
    if key not in key_list:
        key_list.append(key)
    cache.set('key_list', key_list)


def get_id_list(query, force_reload=False):
    if not force_reload:
        id_list = get_cached_lexemes(query)
    else:
        id_list = None
    if id_list is None:
        lexemes = LexemeGrid.get_sorted_queryset(query)
        if 'rev' in lexemes.query.extra_select:
            id_list = list(row[0] for row in lexemes.values_list('id', 'rev'))
        else:
            id_list = list(lexemes.values_list('id', flat=True))
        cache_lexemes(id_list, query)
    return id_list


@ajax(method='get')
def get_lexemes(request, from_page, to_page, rows, sort_rules, filters,
                mask='', force_reload=False):
    request.session['sort_rules'] = sort_rules
    request.session['filters'] = filters
    query = SlickGridQuery(
        filters=filters, sort_rules=sort_rules, mask=mask, user=request.user)
    id_list = get_id_list(query, force_reload)
    count = len(id_list)
    start, response_rowcount = LexemeGrid.count_pages(
        count, from_page, to_page, rows)
    sublist = id_list[start:start + response_rowcount]
    lexemes_qs = Lexeme.objects.filter(id__in=sublist).select_related(
        'owner_vocabulary', 'part_of_speech').prefetch_related(
        'lexemeinflectionpattern_set__pattern',
        'lexemeinflectionpattern_set__inflection_characteristic',
        'vocabularies')
    lexemes_dict = dict((l.id, l) for l in lexemes_qs)
    lexemes = [lexemes_dict[id] for id in sublist]
    return LexemeGrid.make_response(lexemes, count, from_page)