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

from accounts.models import filtering_mode

# TODO jedna klasa zamiast dwóch
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

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

    @classmethod
    def sort_queryset(cls, queryset, sort_rules):
        order_list = []
        for rule in sort_rules:
            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',
        'le': 'lte',
        'ge': 'gte',
    }

    @classmethod
    def apply_filter(cls, queryset, filter):
        lookup = cls.lookup_translation[filter['op']]
        negated = (lookup[0] == '-')
        lookup = lookup.lstrip('-')
        data = filter['data']
        arg = {(filter['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['group_op'] == 'AND':
                for filter in filters['rules']:
                    queryset = cls.apply_filter(queryset, filter)
            elif filters['group_op'] == '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

    # 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
        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

    @classmethod
    def get_field(cls, field, instance):
        return getattr(instance, field)

    # 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)
        if queryset.count() == 0:
            return 0, 0
        preceding = None
        assert len(query.sort_rules) > 0
        for rule in query.sort_rules:
            data = cls.get_field(rule['field'], selected)
            preceding = cls.filter_value(
                queryset, rule, from_value=data, upward=False)
        return preceding.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,
            'count': 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)