@@ -677,6 +677,22 @@ def get_installed_apps(sort=False, frappe_last=False): | |||
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): | |||
"""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)) | |||
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: | |||
hooks = _dict(load_app_hooks(app_name)) | |||
@@ -724,6 +727,26 @@ def get_hooks(hook=None, default=None, app_name=None): | |||
else: | |||
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(): | |||
"""Rebuild map of all modules (internal).""" | |||
_cache = cache() | |||
@@ -8,7 +8,7 @@ import frappe, json | |||
import frappe.defaults | |||
import frappe.share | |||
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.model import optional_fields | |||
@@ -141,15 +141,9 @@ class DatabaseQuery(object): | |||
fdict = filters | |||
filters = [] | |||
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) | |||
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): | |||
"""extract tables from fields""" | |||
self.tables = ['`tab' + self.doctype + '`'] | |||
@@ -239,7 +233,7 @@ class DatabaseQuery(object): | |||
ifnull(`tabDocType`.`fieldname`, fallback) operator "value" | |||
""" | |||
f = self.get_filter(f) | |||
f = get_filter(self.doctype, f) | |||
tname = ('`tab' + f.doctype + '`') | |||
if not tname in self.tables: | |||
@@ -296,45 +290,6 @@ class DatabaseQuery(object): | |||
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): | |||
"""add match conditions if applicable""" | |||
self.match_filters = [] | |||
@@ -741,7 +741,7 @@ class Document(BaseDocument): | |||
def composer(self, *args, **kwargs): | |||
hooks = [] | |||
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, []) \ | |||
+ doc_events.get("*", {}).get(method, []): | |||
hooks.append(frappe.get_attr(handler)) | |||
@@ -266,6 +266,7 @@ def make_test_objects(doctype, test_records, verbose=None): | |||
if docstatus == 1: | |||
d.submit() | |||
except frappe.NameError: | |||
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 | |||
} | |||
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): | |||
ret = False | |||
if condition in operator_map: | |||
@@ -620,6 +637,51 @@ def compare(val1, condition, val2): | |||
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): | |||
html = expand_relative_urls(html) | |||
# encoding should be responsibility of the composer | |||