diff --git a/frappe/__init__.py b/frappe/__init__.py index fc28f0d22e..1325096ce1 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -537,7 +537,7 @@ def call(fn, *args, **kwargs): newargs[a] = kwargs.get(a) return fn(*args, **newargs) -def make_property_setter(args, ignore_validate=False): +def make_property_setter(args, ignore_validate=False, validate_fields_for_doctype=True): args = _dict(args) ps = get_doc({ 'doctype': "Property Setter", @@ -550,6 +550,7 @@ def make_property_setter(args, ignore_validate=False): '__islocal': 1 }) ps.ignore_validate = ignore_validate + ps.validate_fields_for_doctype = validate_fields_for_doctype ps.insert() def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index e68d387b38..875b376ef9 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -1,331 +1,331 @@ { - "allow_copy": 0, - "autoname": "FL.#####", - "creation": "2013-02-22 01:27:33", - "docstatus": 0, - "doctype": "DocType", + "allow_copy": 0, + "autoname": "FL.#####", + "creation": "2013-02-22 01:27:33", + "docstatus": 0, + "doctype": "DocType", "fields": [ { - "fieldname": "label_and_type", - "fieldtype": "Section Break", - "label": "Label and Type", + "fieldname": "label_and_type", + "fieldtype": "Section Break", + "label": "Label and Type", "permlevel": 0 - }, - { - "fieldname": "label", - "fieldtype": "Data", - "hidden": 0, - "in_list_view": 1, - "label": "Label", - "oldfieldname": "label", - "oldfieldtype": "Data", - "permlevel": 0, - "print_width": "163", - "reqd": 0, - "search_index": 1, + }, + { + "fieldname": "label", + "fieldtype": "Data", + "hidden": 0, + "in_list_view": 1, + "label": "Label", + "oldfieldname": "label", + "oldfieldtype": "Data", + "permlevel": 0, + "print_width": "163", + "reqd": 0, + "search_index": 1, "width": "163" - }, - { - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "hidden": 0, - "in_list_view": 1, - "label": "Type", - "oldfieldname": "fieldtype", - "oldfieldtype": "Select", - "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", - "permlevel": 0, - "reqd": 1, + }, + { + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "hidden": 0, + "in_list_view": 1, + "label": "Type", + "oldfieldname": "fieldtype", + "oldfieldtype": "Select", + "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", + "permlevel": 0, + "reqd": 1, "search_index": 1 - }, - { - "fieldname": "fieldname", - "fieldtype": "Data", - "hidden": 0, - "in_list_view": 1, - "label": "Name", - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "permlevel": 0, - "reqd": 0, + }, + { + "fieldname": "fieldname", + "fieldtype": "Data", + "hidden": 0, + "in_list_view": 1, + "label": "Name", + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "permlevel": 0, + "reqd": 0, "search_index": 1 - }, - { - "fieldname": "reqd", - "fieldtype": "Check", - "hidden": 0, - "in_list_view": 1, - "label": "Mandatory", - "oldfieldname": "reqd", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + }, + { + "fieldname": "reqd", + "fieldtype": "Check", + "hidden": 0, + "in_list_view": 1, + "label": "Mandatory", + "oldfieldname": "reqd", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, - { - "fieldname": "search_index", - "fieldtype": "Check", - "hidden": 0, - "label": "Index", - "oldfieldname": "search_index", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + }, + { + "fieldname": "search_index", + "fieldtype": "Check", + "hidden": 0, + "label": "Index", + "oldfieldname": "search_index", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, + }, { - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique", - "permlevel": 0, + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View", - "permlevel": 0, - "print_width": "70px", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View", + "permlevel": 0, + "print_width": "70px", "width": "70px" - }, + }, { - "fieldname": "column_break_6", - "fieldtype": "Column Break", + "fieldname": "column_break_6", + "fieldtype": "Column Break", "permlevel": 0 - }, - { - "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", - "description": "Set non-standard precision for a Float or Currency field", - "fieldname": "precision", - "fieldtype": "Select", - "label": "Precision", - "options": "\n1\n2\n3\n4\n5\n6", - "permlevel": 0, + }, + { + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", + "permlevel": 0, "print_hide": 1 - }, - { - "description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", - "fieldname": "options", - "fieldtype": "Text", - "hidden": 0, - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text", - "permlevel": 0, - "reqd": 0, + }, + { + "description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", + "fieldname": "options", + "fieldtype": "Text", + "hidden": 0, + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text", + "permlevel": 0, + "reqd": 0, "search_index": 0 - }, + }, { - "fieldname": "permissions", - "fieldtype": "Section Break", - "label": "Permissions", + "fieldname": "permissions", + "fieldtype": "Section Break", + "label": "Permissions", "permlevel": 0 - }, + }, { - "fieldname": "depends_on", - "fieldtype": "Data", - "label": "Depends On", - "oldfieldname": "depends_on", - "oldfieldtype": "Data", + "fieldname": "depends_on", + "fieldtype": "Data", + "label": "Depends On", + "oldfieldname": "depends_on", + "oldfieldtype": "Data", "permlevel": 0 - }, - { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "hidden": 0, - "label": "Perm Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int", - "permlevel": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + }, + { + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "hidden": 0, + "label": "Perm Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int", + "permlevel": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, - { - "fieldname": "hidden", - "fieldtype": "Check", - "hidden": 0, - "label": "Hidden", - "oldfieldname": "hidden", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + }, + { + "fieldname": "hidden", + "fieldtype": "Check", + "hidden": 0, + "label": "Hidden", + "oldfieldname": "hidden", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, + }, { - "fieldname": "read_only", - "fieldtype": "Check", - "label": "Read Only", - "permlevel": 0, - "print_width": "50px", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only", + "permlevel": 0, + "print_width": "50px", "width": "50px" - }, + }, { - "description": "Do not allow user to change after set the first time", - "fieldname": "set_only_once", - "fieldtype": "Check", - "label": "Set Only Once", + "description": "Do not allow user to change after set the first time", + "fieldname": "set_only_once", + "fieldtype": "Check", + "label": "Set Only Once", "permlevel": 0 - }, + }, { - "fieldname": "column_break_13", - "fieldtype": "Column Break", + "fieldname": "column_break_13", + "fieldtype": "Column Break", "permlevel": 0 - }, + }, { - "description": "User permissions should not apply for this Link", - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions", + "description": "User permissions should not apply for this Link", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions", "permlevel": 0 - }, - { - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", + }, + { + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", "width": "50px" - }, - { - "fieldname": "report_hide", - "fieldtype": "Check", - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", + }, + { + "fieldname": "report_hide", + "fieldtype": "Check", + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", "width": "50px" - }, + }, { - "fieldname": "display", - "fieldtype": "Section Break", - "label": "Display", + "fieldname": "display", + "fieldtype": "Section Break", + "label": "Display", "permlevel": 0 - }, - { - "fieldname": "default", - "fieldtype": "Text", - "hidden": 0, - "label": "Default", - "oldfieldname": "default", - "oldfieldtype": "Text", - "permlevel": 0, - "reqd": 0, + }, + { + "fieldname": "default", + "fieldtype": "Text", + "hidden": 0, + "label": "Default", + "oldfieldname": "default", + "oldfieldtype": "Text", + "permlevel": 0, + "reqd": 0, "search_index": 0 - }, - { - "fieldname": "in_filter", - "fieldtype": "Check", - "label": "In Filter", - "oldfieldname": "in_filter", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", + }, + { + "fieldname": "in_filter", + "fieldtype": "Check", + "label": "In Filter", + "oldfieldname": "in_filter", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", "width": "50px" - }, - { - "fieldname": "no_copy", - "fieldtype": "Check", - "label": "No Copy", - "oldfieldname": "no_copy", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", + }, + { + "fieldname": "no_copy", + "fieldtype": "Check", + "label": "No Copy", + "oldfieldname": "no_copy", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", "width": "50px" - }, + }, { - "fieldname": "column_break_22", - "fieldtype": "Column Break", + "fieldname": "column_break_22", + "fieldtype": "Column Break", "permlevel": 0 - }, - { - "fieldname": "description", - "fieldtype": "Text", - "in_list_view": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_width": "300px", + }, + { + "fieldname": "description", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "permlevel": 0, + "print_width": "300px", "width": "300px" - }, - { - "fieldname": "print_hide", - "fieldtype": "Check", - "hidden": 0, - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check", - "permlevel": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + }, + { + "fieldname": "print_hide", + "fieldtype": "Check", + "hidden": 0, + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check", + "permlevel": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, + }, { - "fieldname": "print_width", - "fieldtype": "Data", - "label": "Print Width", + "fieldname": "print_width", + "fieldtype": "Data", + "label": "Print Width", "permlevel": 0 - }, - { - "fieldname": "width", - "fieldtype": "Data", - "hidden": 0, - "label": "Width", - "oldfieldname": "width", - "oldfieldtype": "Data", - "permlevel": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + }, + { + "fieldname": "width", + "fieldtype": "Data", + "hidden": 0, + "label": "Width", + "oldfieldname": "width", + "oldfieldtype": "Data", + "permlevel": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, - { - "fieldname": "oldfieldname", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldname", - "oldfieldtype": "Data", - "permlevel": 0, - "reqd": 0, + }, + { + "fieldname": "oldfieldname", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldname", + "oldfieldtype": "Data", + "permlevel": 0, + "reqd": 0, "search_index": 0 - }, - { - "fieldname": "oldfieldtype", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldtype", - "oldfieldtype": "Data", - "permlevel": 0, - "reqd": 0, + }, + { + "fieldname": "oldfieldtype", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldtype", + "oldfieldtype": "Data", + "permlevel": 0, + "reqd": 0, "search_index": 0 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "in_dialog": 1, - "issingle": 0, - "istable": 1, - "modified": "2014-09-26 05:03:44.822570", - "modified_by": "Administrator", - "module": "Core", - "name": "DocField", - "owner": "Administrator", - "permissions": [], + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "in_dialog": 1, + "issingle": 0, + "istable": 1, + "modified": "2014-11-07 11:40:55.281141", + "modified_by": "Administrator", + "module": "Core", + "name": "DocField", + "owner": "Administrator", + "permissions": [], "read_only": 0 -} \ No newline at end of file +} diff --git a/frappe/core/page/data_import_tool/exporter.py b/frappe/core/page/data_import_tool/exporter.py index 8709f0abe2..9d3449f989 100644 --- a/frappe/core/page/data_import_tool/exporter.py +++ b/frappe/core/page/data_import_tool/exporter.py @@ -6,13 +6,32 @@ from __future__ import unicode_literals import frappe, json, os from frappe import _ import frappe.permissions +import re from frappe.utils.csvutils import UnicodeWriter from frappe.utils import cstr, cint, flt from frappe.core.page.data_import_tool.data_import_tool import get_data_keys +reflags = { + "I":re.I, + "L":re.L, + "M":re.M, + "U":re.U, + "S":re.S, + "X":re.X, + "D": re.DEBUG +} + @frappe.whitelist() def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data="No"): all_doctypes = all_doctypes=="Yes" + docs_to_export = {} + if doctype: + if isinstance(doctype, basestring): + doctype = [doctype]; + if len(doctype) > 1: + docs_to_export = doctype[1] + doctype = doctype[0] + if not parent_doctype: parent_doctype = doctype @@ -151,6 +170,28 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data # get permitted data only data = frappe.get_list(doctype, fields=["*"], limit_page_length=None) for doc in data: + op = docs_to_export.get("op") + names = docs_to_export.get("name") + + if names and op: + if op == '=' and doc.name not in names: + continue + elif op == '!=' and doc.name in names: + continue + elif names: + try: + sflags = docs_to_export.get("flags", "I,U").upper() + flags = 0 + for a in re.split('\W+',sflags): + flags = flags | reflags.get(a,0) + + c = re.compile(names, flags) + m = c.match(doc.name) + if not m: + continue + except: + if doc.name not in names: + continue # add main table row_group = [] diff --git a/frappe/core/page/data_import_tool/test_exporter_fixtures.py b/frappe/core/page/data_import_tool/test_exporter_fixtures.py new file mode 100644 index 0000000000..862268b00f --- /dev/null +++ b/frappe/core/page/data_import_tool/test_exporter_fixtures.py @@ -0,0 +1,257 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe +import frappe.defaults +from frappe.core.page.data_import_tool.data_import_tool import export_csv +import unittest + +class TestDataImportFixtures(unittest.TestCase): + + def setUp(self): + print "\nTeste for export explicit fixtures" + print "see fixtures csv test files in sites folder" + + #start test for Custom Script + def test_Custom_Script_fixture_simple(self): + fixture = "Custom Script" + path = frappe.scrub(fixture) + "_original_style.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_simple_name_equal_default(self): + fixture = ["Custom Script", {"name":["Item-Client"]}] + path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_simple_name_equal(self): + fixture = ["Custom Script", {"name":["Item-Client"],"op":"="}] + path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_simple_name_not_equal(self): + fixture = ["Custom Script", {"name":["Item-Client"],"op":"!="}] + path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + #without [] around the name... + def test_Custom_Script_fixture_simple_name_at_least_equal(self): + fixture = ["Custom Script", {"name":"Item-Cli"}] + path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_multi_name_equal(self): + fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"="}] + path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_multi_name_not_equal(self): + fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"!="}] + path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_empty_object(self): + fixture = ["Custom Script", {}] + path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_just_list(self): + fixture = ["Custom Script"] + path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + # Custom Script regular expression + def test_Custom_Script_fixture_rex_no_flags(self): + fixture = ["Custom Script", {"name":r"^[i|A]"}] + path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Script_fixture_rex_with_flags(self): + fixture = ["Custom Script", {"name":r"^[i|A]", "flags":"L,M"}] + path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + + #start test for Custom Field + def test_Custom_Field_fixture_simple(self): + fixture = "Custom Field" + path = frappe.scrub(fixture) + "_original_style.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_simple_name_equal_default(self): + fixture = ["Custom Field", {"name":["Item-vat"]}] + path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_simple_name_equal(self): + fixture = ["Custom Field", {"name":["Item-vat"],"op":"="}] + path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_simple_name_not_equal(self): + fixture = ["Custom Field", {"name":["Item-vat"],"op":"!="}] + path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + #without [] around the name... + def test_Custom_Field_fixture_simple_name_at_least_equal(self): + fixture = ["Custom Field", {"name":"Item-va"}] + path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_multi_name_equal(self): + fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"="}] + path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_multi_name_not_equal(self): + fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"!="}] + path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_empty_object(self): + fixture = ["Custom Field", {}] + path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_just_list(self): + fixture = ["Custom Field"] + path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + # Custom Field regular expression + def test_Custom_Field_fixture_rex_no_flags(self): + fixture = ["Custom Field", {"name":r"^[r|L]"}] + path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Custom_Field_fixture_rex_with_flags(self): + fixture = ["Custom Field", {"name":r"^[i|A]", "flags":"L,M"}] + path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + + #start test for Doctype + def test_Doctype_fixture_simple(self): + fixture = "ToDo" + path = "Doctype_" + frappe.scrub(fixture) + "_original_style_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_fixture_simple_name_equal_default(self): + fixture = ["ToDo", {"name":["TDI00000008"]}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_fixture_simple_name_equal(self): + fixture = ["ToDo", {"name":["TDI00000002"],"op":"="}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_simple_name_not_equal(self): + fixture = ["ToDo", {"name":["TDI00000002"],"op":"!="}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + #without [] around the name... + def test_Doctype_fixture_simple_name_at_least_equal(self): + fixture = ["ToDo", {"name":"TDI"}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_multi_name_equal(self): + fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"="}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_multi_name_not_equal(self): + fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"!="}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_fixture_empty_object(self): + fixture = ["ToDo", {}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_fixture_just_list(self): + fixture = ["ToDo"] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + # Doctype regular expression + def test_Doctype_fixture_rex_no_flags(self): + fixture = ["ToDo", {"name":r"^TDi"}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_no_flags_should_be_all.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + def test_Doctype_fixture_rex_with_flags(self): + fixture = ["ToDo", {"name":r"^TDi", "flags":"L,M"}] + path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_with_flags_should_be_none.csv" + print "teste done {}".format(path) + export_csv(fixture, path) + self.assertTrue(True) + + \ No newline at end of file diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index ac43c81db8..d8070df3f4 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -68,7 +68,7 @@ "fieldname": "precision", "fieldtype": "Select", "label": "Precision", - "options": "\n1\n2\n3\n4\n5\n6", + "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", "permlevel": 0, "precision": "" }, @@ -274,7 +274,7 @@ ], "icon": "icon-glass", "idx": 1, - "modified": "2014-09-26 05:04:49.148737", + "modified": "2014-09-26 05:04:49.148738", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 852786f914..2c2adb92bf 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, cstr +from frappe.utils import cstr from frappe import _ from frappe.model.document import Document @@ -30,12 +30,11 @@ class CustomField(Document): frappe.throw(_("Fieldname not set for Custom Field")) def on_update(self): - # validate field - from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype - frappe.clear_cache(doctype=self.dt) - - validate_fields_for_doctype(self.dt) + if not getattr(self, "ignore_validate", False): + # validate field + from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype + validate_fields_for_doctype(self.dt) # create property setter to emulate insert after self.create_property_setter() @@ -74,7 +73,7 @@ class CustomField(Document): "fieldname": self.fieldname, "property": "previous_field", "value": self.insert_after - }) + }, validate_fields_for_doctype=False) @frappe.whitelist() def get_fields_label(doctype=None): diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 5be88baffc..f7a12ea9d6 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -8,6 +8,7 @@ from __future__ import unicode_literals """ import frappe, json from frappe import _ +from frappe.utils import cint from frappe.model.document import Document from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype @@ -104,6 +105,7 @@ class CustomizeForm(Document): self.make_property_setter(property=property, value=self.get(property), property_type=self.doctype_properties[property]) + update_db = False for df in self.get("customize_form_fields"): if df.get("__islocal"): continue @@ -123,9 +125,17 @@ class CustomizeForm(Document): .format(df.idx)) continue + elif property == "precision" and cint(df.get("precision")) > 6 \ + and cint(df.get("precision")) > cint(meta_df[0].get("precision")): + update_db = True + self.make_property_setter(property=property, value=df.get(property), property_type=self.docfield_properties[property], fieldname=df.fieldname) + if update_db: + from frappe.model.db_schema import updatedb + updatedb(self.doc_type) + def update_custom_fields(self): for df in self.get("customize_form_fields"): if df.get("__islocal"): @@ -160,6 +170,7 @@ class CustomizeForm(Document): changed = True if changed: + custom_field.ignore_validate = True custom_field.save() def delete_custom_fields(self): diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 719d4ab76e..66af546982 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -93,7 +93,7 @@ "fieldname": "precision", "fieldtype": "Select", "label": "Precision", - "options": "\n1\n2\n3\n4\n5\n6", + "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", "permlevel": 0, "precision": "" }, @@ -285,7 +285,7 @@ "idx": 1, "issingle": 0, "istable": 1, - "modified": "2014-09-26 05:06:37.372435", + "modified": "2014-09-26 05:06:37.372436", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/hooks.py b/frappe/hooks.py index 3368879b06..4d8323cd90 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -4,7 +4,6 @@ app_publisher = "Web Notes Technologies Pvt. Ltd." app_description = "Full Stack Web Application Framework in Python" app_icon = "assets/frappe/images/frappe.svg" app_version = "5.0.0-alpha" - app_color = "#3498db" app_email = "support@frappe.io" diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index 1cce322131..3aabaf8ae3 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -11,7 +11,7 @@ Syncs a database table to the `DocType` (metadata) import os import frappe from frappe import _ -from frappe.utils import cstr +from frappe.utils import cstr, cint type_map = { 'Currency': ('decimal', '18,6') @@ -134,20 +134,23 @@ class DbTable: get columns from docfields and custom fields """ fl = frappe.db.sql("SELECT * FROM tabDocField WHERE parent = %s", self.doctype, as_dict = 1) + precisions = {} - try: + if not frappe.flags.in_install_app: custom_fl = frappe.db.sql("""\ SELECT * FROM `tabCustom Field` WHERE dt = %s AND docstatus < 2""", (self.doctype,), as_dict=1) if custom_fl: fl += custom_fl - except Exception, e: - if e.args[0]!=1146: # ignore no custom field - raise + + # get precision from property setters + for ps in frappe.get_all("Property Setter", fields=["field_name", "value"], + filters={"doc_type": self.doctype, "doctype_or_field": "DocField", "property": "precision"}): + precisions[ps.field_name] = ps.value for f in fl: self.columns[f['fieldname']] = DbColumn(self, f['fieldname'], - f['fieldtype'], f.get('length'), f.get('default'), - f.get('search_index'), f.get('options'), f.get('unique')) + f['fieldtype'], f.get('length'), f.get('default'), f.get('search_index'), + f.get('options'), f.get('unique'), precisions.get(f['fieldname']) or f.get('precision')) def get_columns_from_db(self): self.show_columns = frappe.db.sql("desc `%s`" % self.name) @@ -229,7 +232,7 @@ class DbTable: class DbColumn: def __init__(self, table, fieldname, fieldtype, length, default, - set_index, options, unique): + set_index, options, unique, precision): self.table = table self.fieldname = fieldname self.fieldtype = fieldtype @@ -238,9 +241,10 @@ class DbColumn: self.default = default self.options = options self.unique = unique + self.precision = precision - def get_definition(self): - column_def = get_definition(self.fieldtype) + def get_definition(self, with_default=1): + column_def = get_definition(self.fieldtype, self.precision) if self.default and (self.default not in default_shortcuts) \ and not self.default.startswith(":") and column_def not in ['text', 'longtext']: @@ -418,7 +422,7 @@ def remove_all_foreign_keys(): for f in fklist: frappe.db.sql("alter table `tab%s` drop foreign key `%s`" % (t[0], f[1])) -def get_definition(fieldtype): +def get_definition(fieldtype, precision=None): d = type_map.get(fieldtype) if not d: @@ -426,12 +430,15 @@ def get_definition(fieldtype): ret = d[0] if d[1]: - ret += '(' + d[1] + ')' + length = d[1] + if fieldtype in ["Float", "Currency", "Percent"] and cint(precision) > 6: + length = '18,9' + ret += '(' + length + ')' + return ret -def add_column(doctype, column_name, fieldtype): - """Add a column to the database""" +def add_column(doctype, column_name, fieldtype, precision=None): frappe.db.commit() frappe.db.sql("alter table `tab%s` add column %s %s" % (doctype, - column_name, get_definition(fieldtype))) + column_name, get_definition(fieldtype, precision))) diff --git a/frappe/public/css/font/notosans-400.ttf b/frappe/public/css/font/notosans-400.ttf new file mode 100644 index 0000000000..9dd10199bc Binary files /dev/null and b/frappe/public/css/font/notosans-400.ttf differ diff --git a/frappe/public/css/font/notosans-700.ttf b/frappe/public/css/font/notosans-700.ttf new file mode 100644 index 0000000000..6e00cdce1d Binary files /dev/null and b/frappe/public/css/font/notosans-700.ttf differ diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index f331a47e3d..f1322149f9 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -229,19 +229,21 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ } else { // inline var value = me.get_value(); - if(me.parse) { - value = me.parse(value); + var parsed = me.parse ? me.parse(value) : value; + var set_input = function(before, after) { + if(before !== after) { + me.set_input(after); + } else { + me.set_mandatory && me.set_mandatory(before); + } } if(me.validate) { - me.validate(value, function(value1) { - if(value !== value1) { - me.set_input(value1) - } else { - me.set_mandatory && me.set_mandatory(value); - } + me.validate(parsed, function(validated) { + set_input(value, validated); }); } else { - me.set_mandatory && me.set_mandatory(value); + set_input(value, parsed); + } } }); @@ -380,14 +382,17 @@ frappe.ui.form.ControlInt = frappe.ui.form.ControlData.extend({ return false; }) }, + parse: function(value) { + return cint(value, null); + }, validate: function(value, callback) { - return callback(cint(value, null)); + return callback(value); } }); frappe.ui.form.ControlFloat = frappe.ui.form.ControlInt.extend({ - validate: function(value, callback) { - return callback(isNaN(parseFloat(value)) ? null : flt(value)); + parse: function(value) { + return isNaN(parseFloat(value)) ? null : flt(value); }, format_for_input: function(value) { var number_format; diff --git a/frappe/public/js/frappe/misc/datetime.js b/frappe/public/js/frappe/misc/datetime.js index 90de7eb489..855ca1ee3a 100644 --- a/frappe/public/js/frappe/misc/datetime.js +++ b/frappe/public/js/frappe/misc/datetime.js @@ -9,7 +9,7 @@ frappe.provide("frappe.datetime"); $.extend(frappe.datetime, { str_to_obj: function(d) { - return moment(d)._d; + return moment(d, "YYYY-MM-DD HH:mm:ss")._d; }, obj_to_str: function(d) { diff --git a/frappe/public/js/frappe/ui/filters.js b/frappe/public/js/frappe/ui/filters.js index 7f667a3026..1973e93f14 100644 --- a/frappe/public/js/frappe/ui/filters.js +++ b/frappe/public/js/frappe/ui/filters.js @@ -365,12 +365,12 @@ frappe.ui.Filter = Class.extend({ if(this.field.df.fieldname==="docstatus") { value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value; - } - - if(this.field.df.original_type==="Check") { + } else if(this.field.df.original_type==="Check") { value = {0:"No", 1:"Yes"}[cint(value)]; } else if (in_list(["Date", "Datetime"], this.field.df.fieldtype)) { value = frappe.datetime.str_to_user(value); + } else { + value = this.field.get_value(); } this.$btn_group.find(".toggle-filter") diff --git a/frappe/templates/pages/list.py b/frappe/templates/pages/list.py index 6f00acfcd7..3ec8e92672 100644 --- a/frappe/templates/pages/list.py +++ b/frappe/templates/pages/list.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe, os from frappe.modules import get_doc_path, load_doctype_module +from jinja2 import Template no_cache = 1 no_sitemap = 1 diff --git a/frappe/templates/styles/standard.css b/frappe/templates/styles/standard.css index 0ba413b3f0..73edf744db 100644 --- a/frappe/templates/styles/standard.css +++ b/frappe/templates/styles/standard.css @@ -1,4 +1,15 @@ -@import url(https://fonts.googleapis.com/css?family=Noto+Sans:400,700); +@font-face { + font-family: 'Noto Sans'; + font-style: normal; + font-weight: 400; + src: local('Noto Sans'), local('NotoSans'), url({{ frappe.get_url("/assets/frappe/css/font/notosans-400.ttf") }}) format('truetype'); +} +@font-face { + font-family: 'Noto Sans'; + font-style: normal; + font-weight: 700; + src: local('Noto Sans Bold'), local('NotoSans-Bold'), url({{ frappe.get_url("/assets/frappe/css/font/notosans-700.ttf")}}) format('truetype'); +} @media screen { .print-format-gutter { diff --git a/frappe/utils/fixtures.py b/frappe/utils/fixtures.py index 6062c9ec38..bfcf0ae91c 100644 --- a/frappe/utils/fixtures.py +++ b/frappe/utils/fixtures.py @@ -22,10 +22,12 @@ def sync_fixtures(app=None): def export_fixtures(): for app in frappe.get_installed_apps(): for fixture in frappe.get_hooks("fixtures", app_name=app): - print "Exporting " + fixture + print "Exporting {0}".format(fixture) if not os.path.exists(frappe.get_app_path(app, "fixtures")): os.mkdir(frappe.get_app_path(app, "fixtures")) - if frappe.db.get_value("DocType", fixture, "issingle"): - export_fixture(fixture, fixture, app) + if isinstance(fixture, basestring): + fixture = [fixture]; + if frappe.db.get_value("DocType", fixture[0], "issingle"): + export_fixture(fixture[0], fixture[0], app) else: - export_csv(fixture, frappe.get_app_path(app, "fixtures", frappe.scrub(fixture) + ".csv")) + export_csv(fixture, frappe.get_app_path(app, "fixtures", frappe.scrub(fixture[0]) + ".csv")) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 766bdf72f2..0fa896c704 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -47,6 +47,7 @@ def get_allowed_functions_for_jenv(): # make available limited methods of frappe "frappe": { "_": frappe._, + "get_url": frappe.utils.get_url, "format_value": frappe.format_value, "format_date": frappe.utils.data.global_date_format, "form_dict": frappe.local.form_dict, diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 34ed1ac6e4..985f9fedcf 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils import now from frappe.model.document import Document from frappe.model.naming import append_number_if_name_exists from frappe.website.utils import cleanup_page_name, get_home_page @@ -26,9 +27,16 @@ class WebsiteGenerator(Document): def validate(self): self.set_parent_website_route() - if self.website_published() and self.meta.get_field("page_name") and not self.page_name: - self.page_name = self.get_page_name() - self.update_routes_of_descendants() + + if self.meta.get_field("page_name") and not self.get("__islocal"): + current_route = self.get_route() + current_page_name = self.page_name + + self.page_name = self.make_page_name() + + # page name changed, rename everything + if current_page_name and current_page_name != self.page_name: + self.update_routes_of_descendants(current_route) def on_update(self): clear_cache(self.get_route()) @@ -36,9 +44,6 @@ class WebsiteGenerator(Document): frappe.add_version(self) def get_route(self, doc = None): - if not self.website_published(): - return None - self.get_page_name() return make_route(self) @@ -51,11 +56,14 @@ class WebsiteGenerator(Document): def get_or_make_page_name(self): page_name = self.get("page_name") if not page_name: - page_name = cleanup_page_name(self.get(self.page_title_field)) + page_name = self.make_page_name() self.set("page_name", page_name) return page_name + def make_page_name(self): + return cleanup_page_name(self.get(self.page_title_field)) + def before_rename(self, oldname, name, merge): self._local = self.get_route() self.clear_cache() @@ -88,10 +96,20 @@ class WebsiteGenerator(Document): old_route = frappe.get_doc(self.doctype, self.name).get_route() if old_route and old_route != self.get_route(): - frappe.db.sql("""update `tab{0}` set - parent_website_route = replace(parent_website_route, %s, %s) - where parent_website_route like %s""".format(self.doctype), - (old_route, self.get_route(), old_route + "%")) + # clear cache of old routes + old_routes = frappe.get_all(self.doctype, fields=["parent_website_route", "page_name"], + filters={"parent_website_route": ("like", old_route + "%")}) + + if old_routes: + for old_route in old_routes: + clear_cache(old_route) + + frappe.db.sql("""update `tab{0}` set + parent_website_route = replace(parent_website_route, %s, %s), + modified = %s + modified_by = %s + where parent_website_route like %s""".format(self.doctype), + (old_route, self.get_route(), now(), frappe.session.user, old_route + "%")) def get_website_route(self): route = frappe._dict() diff --git a/setup.py b/setup.py index 3ff684a504..9c51d60a7f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ from setuptools import setup, find_packages -import os version = "5.0.0-alpha" diff --git a/test_sites/test_site/site_config.json b/test_sites/test_site/site_config.json index 608c42ad16..562fdc8599 100644 --- a/test_sites/test_site/site_config.json +++ b/test_sites/test_site/site_config.json @@ -5,5 +5,6 @@ "mail_server": "smtp.example.com", "mail_login": "test@example.com", "mail_password": "test", - "admin_password": "admin" + "admin_password": "admin", + "mute_emails": 1 }