Merge branch 'develop' into website_theme_hook

This commit is contained in:
barredterra 2020-09-21 14:02:50 +02:00
commit f06807daab
27 changed files with 485 additions and 230 deletions

View file

@ -59,15 +59,18 @@ context('Recorder', () => {
cy.get('.title-text').should('contain', 'DocType');
cy.get('.list-count').should('contain', '20 of ');
cy.visit('/desk#recorder');
// temporarily commenting out theses tests as they seem to be
// randomly failing maybe due a backround event
cy.get('.list-row-container span').contains('/api/method/frappe').click();
// cy.visit('/desk#recorder');
cy.location('hash').should('contain', '#recorder/request/');
cy.get('form').should('contain', '/api/method/frappe');
// cy.get('.list-row-container span').contains('/api/method/frappe').click();
cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
cy.location('hash').should('eq', '#recorder');
// cy.location('hash').should('contain', '#recorder/request/');
// cy.get('form').should('contain', '/api/method/frappe');
// cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
// cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
// cy.location('hash').should('eq', '#recorder');
});
});

View file

@ -2,7 +2,7 @@
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"allow_rename": 1,
"autoname": "field:salutation",
"beta": 0,
"creation": "2017-04-10 12:17:58.071915",
@ -53,7 +53,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-04-10 12:55:18.855578",
"modified": "2020-09-14 12:55:18.855578",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Salutation",
@ -129,4 +129,4 @@
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
}

View file

@ -636,13 +636,15 @@ class DocType(Document):
if not name:
name = self.name
flags = {"flags": re.ASCII} if six.PY3 else {}
# a DocType name should not start or end with an empty space
if re.match("^[ \t\n\r]+|[ \t\n\r]+$", name, **flags):
frappe.throw(_("DocType's name should not start or end with whitespace"), frappe.NameError)
# a DocType's name should not start with a number or underscore
# and should only contain letters, numbers and underscore
if six.PY2:
is_a_valid_name = re.match("^(?![\W])[^\d_\s][\w ]+$", name)
else:
is_a_valid_name = re.match("^(?![\W])[^\d_\s][\w ]+$", name, flags = re.ASCII)
if not is_a_valid_name:
if not re.match("^(?![\W])[^\d_\s][\w ]+$", name, **flags):
frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError)

View file

@ -0,0 +1,23 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Document Naming Rule', {
refresh: function(frm) {
frm.trigger('document_type');
},
document_type: (frm) => {
// update the select field options with fieldnames
if (frm.doc.document_type) {
frappe.model.with_doctype(frm.doc.document_type, () => {
let fieldnames = frappe.get_meta(frm.doc.document_type).fields
.filter((d) => {
return frappe.model.no_value_type.indexOf(d.fieldtype) === -1;
}).map((d) => {
return {label: `${d.label} (${d.fieldname})`, value: d.fieldname};
});
frappe.meta.get_docfield('Document Naming Rule Condition', 'field', frm.doc.name).options = fieldnames;
frm.refresh_field('conditions');
});
}
}
});

View file

@ -0,0 +1,104 @@
{
"actions": [],
"creation": "2020-09-07 12:48:48.334318",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type",
"disabled",
"priority",
"section_break_3",
"conditions",
"naming_section",
"prefix",
"prefix_digits",
"counter"
],
"fields": [
{
"fieldname": "document_type",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Document Type",
"options": "DocType"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "prefix",
"fieldtype": "Data",
"label": "Prefix",
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\""
},
{
"fieldname": "counter",
"fieldtype": "Int",
"label": "Counter",
"read_only": 1
},
{
"default": "5",
"description": "Example: 00001",
"fieldname": "prefix_digits",
"fieldtype": "Int",
"label": "Digits",
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\""
},
{
"fieldname": "naming_section",
"fieldtype": "Section Break",
"label": "Naming"
},
{
"collapsible": 1,
"collapsible_depends_on": "conditions",
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"label": "Rule Conditions"
},
{
"fieldname": "conditions",
"fieldtype": "Table",
"label": "Conditions",
"options": "Document Naming Rule Condition"
},
{
"description": "Rules with higher priority will be applied first.",
"fieldname": "priority",
"fieldtype": "Int",
"label": "Priority"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-09-21 10:23:34.401539",
"modified_by": "Administrator",
"module": "Core",
"name": "Document Naming Rule",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "document_type",
"track_changes": 1
}

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils.data import evaluate_filters
class DocumentNamingRule(Document):
def apply(self, doc):
'''
Apply naming rules for the given document. Will set `name` if the rule is matched.
'''
if self.conditions:
if not evaluate_filters(doc, [(d.field, d.condition, d.value) for d in self.conditions]):
return
counter = frappe.db.get_value(self.doctype, self.name, 'counter', for_update=True) or 0
doc.name = self.prefix + ('%0'+str(self.prefix_digits)+'d') % (counter + 1)
frappe.db.set_value(self.doctype, self.name, 'counter', counter + 1)

View file

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestDocumentNamingRule(unittest.TestCase):
def test_naming_rule_by_series(self):
naming_rule = frappe.get_doc(dict(
doctype = 'Document Naming Rule',
document_type = 'ToDo',
prefix = 'test-todo-',
prefix_digits = 5
)).insert()
todo = frappe.get_doc(dict(
doctype = 'ToDo',
description = 'Is this my name ' + frappe.generate_hash()
)).insert()
self.assertEqual(todo.name, 'test-todo-00001')
naming_rule.delete()
todo.delete()
def test_naming_rule_by_condition(self):
naming_rule = frappe.get_doc(dict(
doctype = 'Document Naming Rule',
document_type = 'ToDo',
prefix = 'test-high-',
prefix_digits = 5,
priority = 10,
conditions = [dict(
field = 'priority',
condition = '=',
value = 'High'
)]
)).insert()
# another rule
naming_rule_1 = frappe.copy_doc(naming_rule)
naming_rule_1.prefix = 'test-medium-'
naming_rule_1.conditions[0].value = 'Medium'
naming_rule_1.insert()
# default rule with low priority - should not get applied for rules
# with higher priority
naming_rule_2 = frappe.copy_doc(naming_rule)
naming_rule_2.prefix = 'test-low-'
naming_rule_2.priority = 0
naming_rule_2.conditions = []
naming_rule_2.insert()
todo = frappe.get_doc(dict(
doctype = 'ToDo',
priority = 'High',
description = 'Is this my name ' + frappe.generate_hash()
)).insert()
todo_1 = frappe.get_doc(dict(
doctype = 'ToDo',
priority = 'Medium',
description = 'Is this my name ' + frappe.generate_hash()
)).insert()
todo_2 = frappe.get_doc(dict(
doctype = 'ToDo',
priority = 'Low',
description = 'Is this my name ' + frappe.generate_hash()
)).insert()
try:
self.assertEqual(todo.name, 'test-high-00001')
self.assertEqual(todo_1.name, 'test-medium-00001')
self.assertEqual(todo_2.name, 'test-low-00001')
finally:
naming_rule.delete()
naming_rule_1.delete()
naming_rule_2.delete()
todo.delete()
todo_1.delete()
todo_2.delete()

View file

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Document Naming Rule Condition', {
// refresh: function(frm) {
// }
});

View file

@ -0,0 +1,49 @@
{
"actions": [],
"creation": "2020-09-08 10:17:54.366279",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"field",
"condition",
"value"
],
"fields": [
{
"fieldname": "field",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Field",
"reqd": 1
},
{
"fieldname": "condition",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Condition",
"options": "=\n!=\n>\n<\n>=\n<=",
"reqd": 1
},
{
"fieldname": "value",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Value",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-09-08 10:19:56.192949",
"modified_by": "Administrator",
"module": "Core",
"name": "Document Naming Rule Condition",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class DocumentNamingRuleCondition(Document):
pass

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestDocumentNamingRuleCondition(unittest.TestCase):
pass

View file

@ -169,16 +169,14 @@ def get_comments(doctype, doc_name, frequency, user):
return timeline
def is_document_followed(doctype, doc_name, user):
docs = frappe.get_all(
return frappe.db.exists(
"Document Follow",
filters={
{
"ref_doctype": doctype,
"ref_docname": doc_name,
"user": user
},
limit=1
}
)
return len(docs)
@frappe.whitelist()
def get_follow_users(doctype, doc_name):

View file

@ -23,6 +23,8 @@ def savedocs(doc, action):
# update recent documents
run_onload(doc)
send_updated_docs(doc)
frappe.msgprint(frappe._("Saved"), indicator='green', alert=True)
except Exception:
frappe.errprint(frappe.utils.get_traceback())
raise
@ -36,6 +38,7 @@ def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_stat
doc.set(workflow_state_fieldname, workflow_state)
doc.cancel()
send_updated_docs(doc)
frappe.msgprint(frappe._("Cancelled"), indicator='red', alert=True)
except Exception:
frappe.errprint(frappe.utils.get_traceback())

View file

@ -384,12 +384,14 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation):
if isinstance(row, dict) and row:
for idx in range(len(data.columns)):
label = columns[idx]["label"]
fieldname = columns[idx]["fieldname"]
cell_value = row.get(fieldname, row.get(label, ""))
if cint(include_indentation) and 'indent' in row and idx == 0:
cell_value = (' ' * cint(row['indent'])) + cell_value
row_data.append(cell_value)
# check if column is not hidden
if not columns[idx].get("hidden"):
label = columns[idx]["label"]
fieldname = columns[idx]["fieldname"]
cell_value = row.get(fieldname, row.get(label, ""))
if cint(include_indentation) and 'indent' in row and idx == 0:
cell_value = (' ' * cint(row['indent'])) + cell_value
row_data.append(cell_value)
else:
row_data = row

View file

@ -1,181 +1,78 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-01-09 16:39:23.746535",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"creation": "2019-01-09 16:39:23.746535",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"ref_doctype",
"ref_docname",
"user"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ref_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Doctype",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "ref_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Doctype",
"options": "DocType",
"reqd": 1,
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ref_docname",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Document Name",
"length": 0,
"no_copy": 0,
"options": "ref_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "ref_docname",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Document Name",
"options": "ref_doctype",
"reqd": 1,
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User",
"reqd": 1,
"search_index": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-02-26 15:43:44.330348",
"modified_by": "Administrator",
"module": "Email",
"name": "Document Follow",
"name_case": "",
"owner": "Administrator",
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-09-17 09:19:28.496453",
"modified_by": "Administrator",
"module": "Email",
"name": "Document Follow",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC"
}

View file

@ -335,6 +335,9 @@ class BaseDocument(object):
if frappe.db.is_primary_key_violation(e):
if self.meta.autoname=="hash":
# hash collision? try again
frappe.flags.retry_count = (frappe.flags.retry_count or 0) + 1
if frappe.flags.retry_count > 5:
raise
self.name = None
self.db_insert()
return

View file

@ -7,6 +7,7 @@ from frappe import _
from frappe.utils import now_datetime, cint, cstr
import re
from six import string_types
from frappe.model import log_types
def set_new_name(doc):
@ -35,7 +36,13 @@ def set_new_name(doc):
elif getattr(doc.meta, "issingle", False):
doc.name = doc.doctype
else:
elif getattr(doc.meta, "istable", False):
doc.name = make_autoname("hash", doc.doctype)
if not doc.name:
set_naming_from_document_naming_rule(doc)
if not doc.name:
doc.run_method("autoname")
if not doc.name and autoname:
@ -43,12 +50,15 @@ def set_new_name(doc):
# if the autoname option is 'field:' and no name was derived, we need to
# notify
if autoname.startswith("field:") and not doc.name:
if not doc.name and autoname.startswith("field:"):
fieldname = autoname[6:]
frappe.throw(_("{0} is required").format(doc.meta.get_label(fieldname)))
# at this point, we fall back to name generation with the hash option
if not doc.name or autoname == "hash":
if not doc.name and autoname == "hash":
doc.name = make_autoname("hash", doc.doctype)
if not doc.name:
doc.name = make_autoname("hash", doc.doctype)
doc.name = validate_name(
@ -76,6 +86,23 @@ def set_name_from_naming_options(autoname, doc):
elif "#" in autoname:
doc.name = make_autoname(autoname, doc=doc)
def set_naming_from_document_naming_rule(doc):
'''
Evaluate rules based on "Document Naming Series" doctype
'''
if doc.doctype in log_types:
return
try:
for d in frappe.get_all('Document Naming Rule',
dict(document_type=doc.doctype, disabled=0), order_by='priority desc'):
frappe.get_cached_doc('Document Naming Rule', d.name).apply(doc)
if doc.name:
break
except frappe.db.TableMissingError: # noqa: E722
# not yet bootstrapped
pass
def set_name_by_naming_series(doc):
"""Sets name by the `naming_series` property"""
if not doc.naming_series:

View file

@ -306,6 +306,7 @@ frappe.patches.v13_0.add_toggle_width_in_navbar_settings
frappe.patches.v13_0.rename_notification_fields
frappe.patches.v13_0.remove_duplicate_navbar_items
frappe.patches.v12_0.set_default_password_reset_limit
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True)
frappe.patches.v13_0.set_route_for_blog_category
frappe.patches.v13_0.enable_custom_script
frappe.patches.v13_0.update_newsletter_content_type

View file

@ -243,6 +243,7 @@
"public/js/frappe/utils/energy_point_utils.js",
"public/js/frappe/utils/dashboard_utils.js",
"public/js/frappe/ui/chart.js",
"public/js/frappe/ui/datatable.js",
"public/js/frappe/ui/driver.js",
"public/js/frappe/barcode_scanner/index.js"
],

View file

@ -1265,7 +1265,7 @@ frappe.ui.form.Form = class FrappeForm {
set_df_property(fieldname, property, value, docname, table_field) {
var df;
if (!docname && !table_field){
if (!docname && !table_field) {
df = this.get_docfield(fieldname);
} else {
var grid = this.fields_dict[table_field].grid,
@ -1273,7 +1273,7 @@ frappe.ui.form.Form = class FrappeForm {
if (fname && fname.length)
df = frappe.meta.get_docfield(fname[0].parent, fieldname, docname);
}
if(df && df[property] != value) {
if (df && df[property] != value) {
df[property] = value;
refresh_field(fieldname, table_field);
}

View file

@ -215,10 +215,6 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
$(btn).prop("disabled", false);
frappe.ui.form.is_saving = false;
if (!r.exc) {
frappe.show_alert({message: __('Saved'), indicator: 'green'});
}
if (r) {
var doc = r.docs && r.docs[0];
if (doc) {

View file

@ -1,6 +1,6 @@
frappe.provide('frappe.route');
frappe.route_history_queue = [];
const routes_to_skip = ['Form', 'social', 'setup-wizard'];
const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder'];
const save_routes = frappe.utils.debounce(() => {
const routes = frappe.route_history_queue;
@ -30,7 +30,6 @@ function is_route_useful(route) {
if (!route[1]) {
return false;
} else if ((route[0] === 'List' && !route[2]) || routes_to_skip.includes(route[0])) {
return false;
} else {
return true;

View file

@ -0,0 +1,3 @@
import DataTable from "frappe-datatable";
frappe.DataTable = DataTable;

View file

@ -621,28 +621,6 @@ def parse_json(val):
val = frappe._dict(val)
return val
def cast_fieldtype(fieldtype, value):
if fieldtype in ("Currency", "Float", "Percent"):
value = flt(value)
elif fieldtype in ("Int", "Check"):
value = cint(value)
elif fieldtype in ("Data", "Text", "Small Text", "Long Text",
"Text Editor", "Select", "Link", "Dynamic Link"):
value = cstr(value)
elif fieldtype == "Date":
value = getdate(value)
elif fieldtype == "Datetime":
value = get_datetime(value)
elif fieldtype == "Time":
value = to_timedelta(value)
return value
def get_db_count(*args):
"""
Pass a doctype or a series of doctypes to get the count of docs in them

View file

@ -411,6 +411,28 @@ def has_common(l1, l2):
"""Returns truthy value if there are common elements in lists l1 and l2"""
return set(l1) & set(l2)
def cast_fieldtype(fieldtype, value):
if fieldtype in ("Currency", "Float", "Percent"):
value = flt(value)
elif fieldtype in ("Int", "Check"):
value = cint(value)
elif fieldtype in ("Data", "Text", "Small Text", "Long Text",
"Text Editor", "Select", "Link", "Dynamic Link"):
value = cstr(value)
elif fieldtype == "Date":
value = getdate(value)
elif fieldtype == "Datetime":
value = get_datetime(value)
elif fieldtype == "Time":
value = to_timedelta(value)
return value
def flt(s, precision=None):
"""Convert to float (ignore commas)"""
if isinstance(s, string_types):
@ -1017,20 +1039,22 @@ def evaluate_filters(doc, filters):
if isinstance(filters, dict):
for key, value in iteritems(filters):
f = get_filter(None, {key:value})
if not compare(doc.get(f.fieldname), f.operator, f.value):
if not compare(doc.get(f.fieldname), f.operator, f.value, f.fieldtype):
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):
if not compare(doc.get(f.fieldname), f.operator, f.value, f.fieldtype):
return False
return True
def compare(val1, condition, val2):
def compare(val1, condition, val2, fieldtype=None):
ret = False
if fieldtype:
val2 = cast_fieldtype(fieldtype, val2)
if condition in operator_map:
ret = operator_map[condition](val1, val2)
@ -1044,6 +1068,7 @@ def get_filter(doctype, f, filters_config=None):
"fieldname":
"operator":
"value":
"fieldtype":
}
"""
from frappe.model import default_fields, optional_fields
@ -1095,6 +1120,13 @@ def get_filter(doctype, f, filters_config=None):
f.doctype = df.options
break
try:
df = frappe.get_meta(f.doctype).get_field(f.fieldname)
except frappe.exceptions.DoesNotExistError:
df = None
f.fieldtype = df.fieldtype if df else None
return f
def make_filter_tuple(doctype, key, value):