ajax_jqgrid.py 7.81 KB
#-*- coding:utf-8 -*-

import math
from accounts.models import filtering_mode
from common.decorators import render, ajax

class JqGridQuery(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 JqGridAjax(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, page, limit):
    total_pages = int(math.ceil(float(count) / limit))
    if limit < 0:
      limit = 0
    page = min(page, total_pages)
    start = limit * (page - 1)
    start = max(start, 0)
    response_rowcount = min(limit, count - start)
    return total_pages, start, response_rowcount

  @staticmethod
  def response_row(instance):
    pass # abstract

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

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