diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 411d16600c..e4931a6f9c 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -201,56 +201,88 @@ class DatabaseQuery(object): """build conditions from user filters""" if isinstance(filters, dict): filters = [filters] + for f in filters: if isinstance(f, basestring): conditions.append(f) else: - f = self.get_filter_tuple(f) - - tname = ('`tab' + f[0] + '`') - if not tname in self.tables: - self.append_table(tname) - - # prepare in condition - if f[2] in ['in', 'not in']: - opts = f[3] - if not isinstance(opts, (list, tuple)): - opts = f[3].split(",") - opts = [frappe.db.escape(t.strip()) for t in opts] - f[3] = '("{0}")'.format('", "'.join(opts)) - conditions.append('ifnull({tname}.{fname}, "") {operator} {value}'.format( - tname=tname, fname=f[1], operator=f[2], value=f[3])) - else: - df = frappe.get_meta(f[0]).get("fields", {"fieldname": f[1]}) - df = df[0] if df else None - - if df and df.fieldtype=="Date": - value, default_val = '"{0}"'.format(frappe.db.escape(getdate(f[3]).strftime("%Y-%m-%d"))), \ - "'0000-00-00'" - - elif df and df.fieldtype=="Datetime": - value, default_val = '"{0}"'.format(frappe.db.escape(get_datetime(f[3]).strftime("%Y-%m-%d %H:%M:%S.%f"))), \ - "'0000-00-00 00:00:00'" - - elif df and df.fieldtype=="Time": - value, default_val = '"{0}"'.format(frappe.db.escape(get_time(f[3]).strftime("%H:%M:%S.%f"))), \ - "'00:00:00'" - - elif f[2] == "like" or (isinstance(f[3], basestring) and - (not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])): - if f[2] == "like" and isinstance(f[3], basestring): - # because "like" uses backslash (\) for escaping - f[3] = f[3].replace("\\", "\\\\") - - value, default_val = '"{0}"'.format(frappe.db.escape(f[3])), '""' - else: - value, default_val = flt(f[3]), 0 - - conditions.append('ifnull({tname}.{fname}, {default_val}) {operator} {value}'.format( - tname=tname, fname=f[1], default_val=default_val, operator=f[2], - value=value)) - - def get_filter_tuple(self, f): + conditions.append(self.prepare_filter_condition(f)) + + def prepare_filter_condition(self, f): + """Returns a filter condition in the format: + + ifnull(`tabDocType`.`fieldname`, fallback) operator "value" + """ + + f = self.get_filter(f) + + tname = ('`tab' + f.doctype + '`') + if not tname in self.tables: + self.append_table(tname) + + # prepare in condition + if f.operator in ('in', 'not in'): + values = f.value + if not isinstance(values, (list, tuple)): + values = values.split(",") + + values = (frappe.db.escape(v.strip()) for v in values) + values = '("{0}")'.format('", "'.join(values)) + + condition = 'ifnull({tname}.{fname}, "") {operator} {value}'.format( + tname=tname, fname=f.fieldname, operator=f.operator, value=values) + + else: + df = frappe.get_meta(f.doctype).get("fields", {"fieldname": f.fieldname}) + df = df[0] if df else None + + if df and df.fieldtype=="Date": + value = getdate(f.value).strftime("%Y-%m-%d") + fallback = "'0000-00-00'" + + elif df and df.fieldtype=="Datetime": + value = get_datetime(f.value).strftime("%Y-%m-%d %H:%M:%S.%f") + fallback = "'0000-00-00 00:00:00'" + + elif df and df.fieldtype=="Time": + value = get_time(f.value).strftime("%H:%M:%S.%f") + fallback = "'00:00:00'" + + elif f.operator == "like" or (isinstance(f.value, basestring) and + (not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])): + value = f.value + fallback = '""' + + if f.operator == "like" and isinstance(value, basestring): + # because "like" uses backslash (\) for escaping + value = value.replace("\\", "\\\\") + + else: + value = flt(f.value) + fallback = 0 + + # put it inside double quotes + if isinstance(value, basestring): + value = '"{0}"'.format(frappe.db.escape(value)) + + condition = 'ifnull({tname}.{fname}, {fallback}) {operator} {value}'.format( + tname=tname, fname=f.fieldname, fallback=fallback, operator=f.operator, + value=value) + + # replace % with %% to prevent python format string error + return condition.replace("%", "%%") + + 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) @@ -262,9 +294,14 @@ class DatabaseQuery(object): f = (self.doctype, f[0], f[1], f[2]) elif len(f) != 4: - frappe.throw("Filter must have 4 values (doctype, fieldname, condition, value): " + str(f)) + frappe.throw("Filter must have 4 values (doctype, fieldname, operator, value): " + str(f)) - return list(f) + 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""" @@ -313,7 +350,8 @@ class DatabaseQuery(object): conditions = "({conditions}) or ({shared_condition})".format( conditions=conditions, shared_condition=self.get_share_condition()) - return conditions + # replace % with %% to prevent python format string error + return conditions.replace("%", "%%") else: return self.match_filters