ajax_slickgrid.py 8.49 KB
#-*- coding:utf-8 -*-

import math
from accounts.models import filtering_mode


class SlickGridQuery(object):
    def __init__(self, filters, sort_rules, mask, user):
        self.filters = filters
        self.sort_rules = sort_rules
        self.mask = mask
        self.user = user

    def filtering_mode(self):
        return filtering_mode(self.user)


class SlickGridAjax(object):
    model = None
    search_field = None
    field_translation = {}

    @classmethod
    def translate_field(cls, field):
        if field in cls.field_translation:
            return cls.field_translation[field]
        else:
            return field

    @staticmethod
    def sort_field_special_case(rule):
        return rule['field']

    @classmethod
    def get_sort_field(cls, rule):
        field = cls.sort_field_special_case(rule)
        field = cls.translate_field(field)
        if rule['order'] == 'desc':
            field = '-' + field
        return field

    @staticmethod
    def sort_queryset_special_case(queryset, field):
        return queryset

    @classmethod
    def sort_queryset(cls, queryset, sort_rules):
        order_list = []
        for rule in sort_rules:
            queryset = cls.sort_queryset_special_case(queryset, rule)
            order_list.append(cls.get_sort_field(rule))
        return queryset.extra(order_by=order_list)

    lookup_translation = {
        'eq': 'exact',
        'ne': '-exact',
        'bw': 'startswith',
        'bn': '-startswith',
        'ew': 'endswith',
        'en': '-endswith',
        'cn': 'contains',
        'nc': '-contains',
        're': 'regex',
        'nr': '-regex',
        #'se': 'surely',
        #'sd': '-maybe',
        #'me': 'maybe',
        #'md': '-surely',
        'le': 'lte',
        'ge': 'gte',
    }

    @staticmethod
    def filter_special_case(filter, lookup, negated, queryset):
        return False, filter['field'], queryset

    @classmethod
    def apply_filter(cls, queryset, filter):
        lookup = cls.lookup_translation[filter['op']]
        negated = (lookup[0] == '-')
        lookup = lookup.lstrip('-')
        data = filter['data']
        special, field, queryset = cls.filter_special_case(
            filter, lookup, negated, queryset)
        if not special:
            arg = {(field + '__' + lookup): data}
            if negated:
                queryset = queryset.exclude(**arg)
            else:
                queryset = queryset.filter(**arg).distinct()
        return queryset

    @classmethod
    def get_queryset(cls, query):
        return cls.model.objects.all()

    @classmethod
    def get_empty_queryset(cls):
        return cls.model.objects.none()

    @classmethod
    def apply_filters(cls, query):
        filters = query.filters
        queryset = cls.get_queryset(query)
        if filters:
            if filters['groupOp'] == 'AND':
                for filter in filters['rules']:
                    queryset = cls.apply_filter(queryset, filter)
            elif filters['groupOp'] == 'OR':
                new_queryset = cls.get_empty_queryset()
                for filter in filters['rules']:
                    new_queryset |= cls.apply_filter(queryset, filter)
                queryset = new_queryset
        return queryset

    @staticmethod
    def apply_mask(queryset, mask, sort_rules):
        pass # abstract

    @staticmethod
    def filter_value_special_case(queryset, rule, from_value, upward):
        return False, queryset

    # filtruje queryset według pola z reguły rule od wartości from,
    # wartości dalsze w porządku jeśli upward, w przeciwnym razie bliższe
    @classmethod
    def filter_value(cls, queryset, rule, from_value, upward):
        greater = (rule['order'] == 'asc') == upward
        special, queryset = cls.filter_value_special_case(
            queryset, rule, from_value, upward)
        if special:
            return queryset
        if greater:
            lookup = '__gte'
        else:
            lookup = '__lte'
        field = cls.translate_field(rule['field'])
        return queryset.filter(**{field + lookup: from_value})

    # id instancji z search_field rownym mask badz takiej, ktora bylaby nastepna
    # po instancji z search_field równym mask w danym sortowaniu.
    # Jezeli nie ma 'wiekszej' instancji badz reguly sortowania nie uwzgledniaja
    # search_field, metoda zwroci pierwsza instancje w danym sortowaniu
    #
    # beznadziejna nazwa metody...
    @classmethod
    def get_pk(cls, query):
        whole_queryset = cls.apply_filters(query)
        queryset = whole_queryset
        matching = cls.apply_mask(queryset, query.mask, query.sort_rules)
        if matching.count() > 0:
            matching = cls.sort_queryset(matching, query.sort_rules)
            return matching[0].pk
        else:
            #gdy nie ma pasującego
            rule = query.sort_rules[0]
            if rule['field'] == cls.search_field:
                queryset = cls.filter_value(
                    queryset, rule, from_value=query.mask, upward=True)
                if queryset.count() == 0:
                    queryset = whole_queryset
            queryset = cls.sort_queryset(queryset, query.sort_rules)
            return queryset[0].pk

    @staticmethod
    def get_field_special_case(field, instance):
        return False, None

    @classmethod
    def get_field(cls, field, instance):
        special, value = cls.get_field_special_case(field, instance)
        if not special:
            value = getattr(instance, field)
        return cls.translate_field(field), value

    # indeks wiersza w danym sortowaniu, w którym
    # znajdzie się instancja o danym id
    @classmethod
    def row_index(cls, pk, query):
        selected = cls.model.objects.get(pk=pk)
        queryset = cls.apply_filters(query)
        if query.filtering_mode():
            queryset = cls.apply_mask(queryset, query.mask, query.sort_rules)
        count = queryset.count()
        if count == 0:
            return 0, 0
        preceding = None
        assert len(query.sort_rules) > 0
        for rule in query.sort_rules:
            field = rule['field']
            field, data = cls.get_field(field, selected)
            preceding = cls.filter_value(
                queryset, rule, from_value=data, upward=False)
        return preceding.count(), count

    # też beznadziejna nazwa
    @classmethod
    def find_id(cls, selected_pk, query):
        index, count = cls.row_index(selected_pk, query)
        return {
            'rowIndex': index,
            'records': count,
        }

    @classmethod
    def get_location(cls, query):
        queryset = cls.apply_filters(query)
        count = queryset.count()
        # nie wiem, czy ma sens - wzorów i tak jest mało, a leksemy są keszowane
        if count > 0 and query.mask == '':
            return {
                'rowIndex': 0,
                'selected_id': queryset[0].pk,
                'records': count,
            }
        if query.filtering_mode():
            queryset = cls.apply_mask(queryset, query.mask, query.sort_rules)
        if queryset.count() > 0:
            selected_pk = cls.get_pk(query)
            index, _count = cls.row_index(selected_pk, query)
        else:
            index = None
            selected_pk = None
        return {
            'rowIndex': index,
            'selected_id': selected_pk,
            'records': count,
        }

    @classmethod
    def get_sorted_queryset(cls, query):
        queryset = cls.apply_filters(query)
        if query.filtering_mode():
            queryset = cls.apply_mask(queryset, query.mask, query.sort_rules)
        return cls.sort_queryset(queryset, query.sort_rules)

    @staticmethod
    def count_pages(count, from_page, to_page, limit):
        start = limit * from_page
        pages = to_page - from_page + 1
        response_rowcount = min(limit * pages, count - start)
        return start, response_rowcount

    @staticmethod
    def response_row(instance):
        pass # abstract

    @classmethod
    def make_response(cls, response_qs, count, page):
        rows = [cls.response_row(instance) for instance in response_qs]
        return {
            'page': page,
            'records': count,
            'rows': rows,
        }

    @classmethod
    def get_page(cls, from_page, to_page, limit, query):
        queryset = cls.get_sorted_queryset(query)
        count = queryset.count()
        start, response_rowcount = cls.count_pages(
            count, from_page, to_page, limit)
        response_qs = queryset[start:start + response_rowcount]
        return cls.make_response(response_qs, count, from_page)