瀏覽代碼

[enhancements] added frappe.utils.evalute_filter(doc, filter) and ability to hook multiple doc events at the same time

version-14
Rushabh Mehta 9 年之前
父節點
當前提交
9d8c2b5eff
共有 6 個文件被更改,包括 139 次插入62 次删除
  1. +36
    -13
      frappe/__init__.py
  2. +3
    -48
      frappe/model/db_query.py
  3. +1
    -1
      frappe/model/document.py
  4. +1
    -0
      frappe/test_runner.py
  5. +36
    -0
      frappe/tests/test_utils.py
  6. +62
    -0
      frappe/utils/data.py

+ 36
- 13
frappe/__init__.py 查看文件

@@ -677,6 +677,22 @@ def get_installed_apps(sort=False, frappe_last=False):


return installed return installed


def get_doc_hooks():
'''Returns hooked methods for given doc. It will expand the dict tuple if required.'''
if not hasattr(local, 'doc_events_hooks'):
hooks = get_hooks('doc_events', {})
out = {}
for key, value in hooks.iteritems():
if isinstance(key, tuple):
for doctype in key:
append_hook(out, doctype, value)
else:
append_hook(out, key, value)

local.doc_events_hooks = out

return local.doc_events_hooks

def get_hooks(hook=None, default=None, app_name=None): def get_hooks(hook=None, default=None, app_name=None):
"""Get hooks via `app/hooks.py` """Get hooks via `app/hooks.py`


@@ -700,19 +716,6 @@ def get_hooks(hook=None, default=None, app_name=None):
append_hook(hooks, key, getattr(app_hooks, key)) append_hook(hooks, key, getattr(app_hooks, key))
return hooks return hooks


def append_hook(target, key, value):
if isinstance(value, dict):
target.setdefault(key, {})
for inkey in value:
append_hook(target[key], inkey, value[inkey])
else:
append_to_list(target, key, value)

def append_to_list(target, key, value):
target.setdefault(key, [])
if not isinstance(value, list):
value = [value]
target[key].extend(value)


if app_name: if app_name:
hooks = _dict(load_app_hooks(app_name)) hooks = _dict(load_app_hooks(app_name))
@@ -724,6 +727,26 @@ def get_hooks(hook=None, default=None, app_name=None):
else: else:
return hooks return hooks


def append_hook(target, key, value):
'''appends a hook to the the target dict.

If the hook key, exists, it will make it a key.

If the hook value is a dict, like doc_events, it will
listify the values against the key.
'''
if isinstance(value, dict):
# dict? make a list of values against each key
target.setdefault(key, {})
for inkey in value:
append_hook(target[key], inkey, value[inkey])
else:
# make a list
target.setdefault(key, [])
if not isinstance(value, list):
value = [value]
target[key].extend(value)

def setup_module_map(): def setup_module_map():
"""Rebuild map of all modules (internal).""" """Rebuild map of all modules (internal)."""
_cache = cache() _cache = cache()


+ 3
- 48
frappe/model/db_query.py 查看文件

@@ -8,7 +8,7 @@ import frappe, json
import frappe.defaults import frappe.defaults
import frappe.share import frappe.share
import frappe.permissions import frappe.permissions
from frappe.utils import flt, cint, getdate, get_datetime, get_time
from frappe.utils import flt, cint, getdate, get_datetime, get_time, make_filter_tuple, get_filter
from frappe import _ from frappe import _
from frappe.model import optional_fields from frappe.model import optional_fields


@@ -141,15 +141,9 @@ class DatabaseQuery(object):
fdict = filters fdict = filters
filters = [] filters = []
for key, value in fdict.iteritems(): for key, value in fdict.iteritems():
filters.append(self.make_filter_tuple(key, value))
filters.append(make_filter_tuple(self.doctype, key, value))
setattr(self, filter_name, filters) setattr(self, filter_name, filters)


def make_filter_tuple(self, key, value):
if isinstance(value, (list, tuple)):
return [self.doctype, key, value[0], value[1]]
else:
return [self.doctype, key, "=", value]

def extract_tables(self): def extract_tables(self):
"""extract tables from fields""" """extract tables from fields"""
self.tables = ['`tab' + self.doctype + '`'] self.tables = ['`tab' + self.doctype + '`']
@@ -239,7 +233,7 @@ class DatabaseQuery(object):
ifnull(`tabDocType`.`fieldname`, fallback) operator "value" ifnull(`tabDocType`.`fieldname`, fallback) operator "value"
""" """


f = self.get_filter(f)
f = get_filter(self.doctype, f)


tname = ('`tab' + f.doctype + '`') tname = ('`tab' + f.doctype + '`')
if not tname in self.tables: if not tname in self.tables:
@@ -296,45 +290,6 @@ class DatabaseQuery(object):


return condition return condition


def get_filter(self, f):
"""Returns a _dict like

{
"doctype": "DocType",
"fieldname": "fieldname",
"operator": "=",
"value": "value"
}

"""
if isinstance(f, dict):
key, value = f.items()[0]
f = self.make_filter_tuple(key, value)

if not isinstance(f, (list, tuple)):
frappe.throw("Filter must be a tuple or list (in a list)")

if len(f) == 3:
f = (self.doctype, f[0], f[1], f[2])

elif len(f) != 4:
frappe.throw("Filter must have 4 values (doctype, fieldname, operator, value): {0}".format(str(f)))

if not f[2]:
# if operator is missing
f[2] = "="

valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in")
if f[2] not in valid_operators:
frappe.throw("Operator must be one of {0}".format(", ".join(valid_operators)))

return frappe._dict({
"doctype": f[0],
"fieldname": f[1],
"operator": f[2],
"value": f[3]
})

def build_match_conditions(self, as_condition=True): def build_match_conditions(self, as_condition=True):
"""add match conditions if applicable""" """add match conditions if applicable"""
self.match_filters = [] self.match_filters = []


+ 1
- 1
frappe/model/document.py 查看文件

@@ -741,7 +741,7 @@ class Document(BaseDocument):
def composer(self, *args, **kwargs): def composer(self, *args, **kwargs):
hooks = [] hooks = []
method = f.__name__ method = f.__name__
doc_events = frappe.get_hooks("doc_events", {})
doc_events = frappe.get_doc_hooks()
for handler in doc_events.get(self.doctype, {}).get(method, []) \ for handler in doc_events.get(self.doctype, {}).get(method, []) \
+ doc_events.get("*", {}).get(method, []): + doc_events.get("*", {}).get(method, []):
hooks.append(frappe.get_attr(handler)) hooks.append(frappe.get_attr(handler))


+ 1
- 0
frappe/test_runner.py 查看文件

@@ -266,6 +266,7 @@ def make_test_objects(doctype, test_records, verbose=None):


if docstatus == 1: if docstatus == 1:
d.submit() d.submit()

except frappe.NameError: except frappe.NameError:
pass pass




+ 36
- 0
frappe/tests/test_utils.py 查看文件

@@ -0,0 +1,36 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals

import unittest

from frappe.utils import evaluate_filters

class TestFilters(unittest.TestCase):
def test_simple_dict(self):
self.assertTrue(evaluate_filters({'doctype': 'User', 'status': 'Open'}, {'status': 'Open'}))
self.assertFalse(evaluate_filters({'doctype': 'User', 'status': 'Open'}, {'status': 'Closed'}))

def test_multiple_dict(self):
self.assertTrue(evaluate_filters({'doctype': 'User', 'status': 'Open', 'name': 'Test 1'},
{'status': 'Open', 'name':'Test 1'}))
self.assertFalse(evaluate_filters({'doctype': 'User', 'status': 'Open', 'name': 'Test 1'},
{'status': 'Closed', 'name': 'Test 1'}))

def test_list_filters(self):
self.assertTrue(evaluate_filters({'doctype': 'User', 'status': 'Open', 'name': 'Test 1'},
[{'status': 'Open'}, {'name':'Test 1'}]))
self.assertFalse(evaluate_filters({'doctype': 'User', 'status': 'Open', 'name': 'Test 1'},
[{'status': 'Open'}, {'name':'Test 2'}]))

def test_list_filters_as_list(self):
self.assertTrue(evaluate_filters({'doctype': 'User', 'status': 'Open', 'name': 'Test 1'},
[['status', '=', 'Open'], ['name', '=', 'Test 1']]))
self.assertFalse(evaluate_filters({'doctype': 'User', 'status': 'Open', 'name': 'Test 1'},
[['status', '=', 'Open'], ['name', '=', 'Test 2']]))

def test_lt_gt(self):
self.assertTrue(evaluate_filters({'doctype': 'User', 'status': 'Open', 'age': 20},
{'status': 'Open', 'age': ('>', 10)}))
self.assertFalse(evaluate_filters({'doctype': 'User', 'status': 'Open', 'age': 20},
{'status': 'Open', 'age': ('>', 30)}))

+ 62
- 0
frappe/utils/data.py 查看文件

@@ -613,6 +613,23 @@ operator_map = {
"None": lambda (a, b): (not a) and True or False "None": lambda (a, b): (not a) and True or False
} }


def evaluate_filters(doc, filters):
'''Returns true if doc matches filters'''
if isinstance(filters, dict):
for key, value in filters.iteritems():
f = get_filter(None, {key:value})
if not compare(doc.get(f.fieldname), f.operator, f.value):
return False

elif isinstance(filters, (list, tuple)):
for d in filters:
f = get_filter(None, d)
if not compare(doc.get(f.fieldname), f.operator, f.value):
return False

return True


def compare(val1, condition, val2): def compare(val1, condition, val2):
ret = False ret = False
if condition in operator_map: if condition in operator_map:
@@ -620,6 +637,51 @@ def compare(val1, condition, val2):


return ret return ret


def get_filter(doctype, f):
"""Returns a _dict like

{
"doctype":
"fieldname":
"operator":
"value":
}
"""
if isinstance(f, dict):
key, value = f.items()[0]
f = make_filter_tuple(doctype, key, value)

if not isinstance(f, (list, tuple)):
frappe.throw("Filter must be a tuple or list (in a list)")

if len(f) == 3:
f = (doctype, f[0], f[1], f[2])

elif len(f) != 4:
frappe.throw("Filter must have 4 values (doctype, fieldname, operator, value): {0}".format(str(f)))

if not f[2]:
# if operator is missing
f[2] = "="

valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in")
if f[2] not in valid_operators:
frappe.throw("Operator must be one of {0}".format(", ".join(valid_operators)))

return frappe._dict({
"doctype": f[0],
"fieldname": f[1],
"operator": f[2],
"value": f[3]
})

def make_filter_tuple(doctype, key, value):
'''return a filter tuple like [doctype, key, operator, value]'''
if isinstance(value, (list, tuple)):
return [doctype, key, value[0], value[1]]
else:
return [doctype, key, "=", value]

def scrub_urls(html): def scrub_urls(html):
html = expand_relative_urls(html) html = expand_relative_urls(html)
# encoding should be responsibility of the composer # encoding should be responsibility of the composer


Loading…
取消
儲存