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

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

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

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

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

  @classmethod
  def get_sort_field(self, rule):
    field = rule['field']
    field = self.sort_field_special_case(rule)
    field = self.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(self, queryset, sort_rules):
    order_list = []
    for rule in sort_rules:
      queryset = self.sort_queryset_special_case(queryset, rule)
      order_list.append(self.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(self, queryset, filter):
    lookup = self.lookup_translation[filter['op']]
    negated = (lookup[0] == '-')
    lookup = lookup.lstrip('-')
    data = filter['data']
    special, field, arg, queryset = self.filter_special_case(
      filter, lookup, negated, queryset)
    if not special:
      arg = {(field + '__' + lookup): data}
    if negated:
      new_qs = queryset.exclude(**arg)
    else:
      new_qs = queryset.filter(**arg).distinct()
    return new_qs

  @classmethod
  def get_queryset(self, *args):
    return self.model.objects.all()

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

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

  @staticmethod
  def apply_mask(queryset, mask, sort_rules):
    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(self, queryset, rule, from_value, upward):
    greater = (rule['order'] == 'asc') == upward
    special, queryset = self.filter_value_special_case(
      queryset, rule, from_value, upward)
    if special:
      return queryset
    if greater:
      lookup = '__gte'
    else:
      lookup = '__lte'
    field = self.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(self, mask, filters, sort_rules, filtering_mode, *args):
    whole_queryset = self.apply_filters(filters, *args)
    queryset = whole_queryset
    matching = self.apply_mask(queryset, mask, sort_rules)
    if matching.count() > 0:
      matching = self.sort_queryset(matching, sort_rules)
      return matching[0].pk
    else:
      #gdy nie ma pasującego
      rule = sort_rules[0]
      if rule['field'] == self.search_field:
        queryset = self.filter_value(
          queryset, rule, from_value=mask, upward=True)
        if queryset.count() == 0:
          queryset = whole_queryset
      queryset = self.sort_queryset(queryset, sort_rules)
      return queryset[0].pk

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

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

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

  # też beznadziejna nazwa
  @classmethod
  def find_id(self, filtering_mode, selected_pk, filters, sort_rules, mask,
              *args):
    index, count = self.row_index(selected_pk, filters, sort_rules,
                                  filtering_mode, mask, *args)
    return {
      'rowIndex': index,
      'records': count,
    }

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

  @classmethod
  def get_sorted_queryset(self, filtering_mode, sort_rules, filters, mask, *args):
    queryset = self.apply_filters(filters, *args)
    if filtering_mode:
      queryset = self.apply_mask(queryset, mask, sort_rules)
    return self.sort_queryset(queryset, 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):
    abstract

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

  @classmethod
  def get_page(self, filtering_mode, page, limit, sort_rules, filters, mask,
               *args):
    queryset = self.get_sorted_queryset(
      filtering_mode, sort_rules, filters, mask, *args)
    count = queryset.count()
    total_pages, start, response_rowcount = self.count_pages(count, page, limit)
    response_qs = queryset[start:start + response_rowcount]
    return self.make_response(response_qs, count, page, total_pages)

@render('sort_dialog.html')
@ajax(method='post', encode_result=False)
def sort_rules(request, colModel, colNames):
  sort_rules = []
  for ui_name, model in zip(colNames, colModel):
    if model.get('sortable', True):
      rule = {}
      rule['code_name'] = model['index']
      rule['ui_name'] = ui_name
      sort_rules.append(rule)
  return {'sort_rules' : sort_rules}