@@ -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() | ||||
@@ -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 = [] | ||||
@@ -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)) | ||||
@@ -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 | ||||
@@ -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)})) |
@@ -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 | ||||