浏览代码

Merge pull request #1351 from anandpdoshi/customize-title-field

[fix] Customize Title Field using Customize Form. Validate title field customization. Fixes frappe/erpnext#4177
version-14
Anand Doshi 9 年前
父节点
当前提交
a6fa910452
共有 7 个文件被更改,包括 114 次插入29 次删除
  1. +34
    -13
      frappe/core/doctype/doctype/doctype.py
  2. +6
    -0
      frappe/core/doctype/file/test_file.py
  3. +38
    -15
      frappe/custom/doctype/customize_form/customize_form.json
  4. +1
    -0
      frappe/custom/doctype/customize_form/customize_form.py
  5. +28
    -0
      frappe/custom/doctype/customize_form/test_customize_form.py
  6. +5
    -0
      frappe/email/doctype/email_account/test_email_account.py
  7. +2
    -1
      frappe/utils/file_manager.py

+ 34
- 13
frappe/core/doctype/doctype/doctype.py 查看文件

@@ -3,6 +3,7 @@


from __future__ import unicode_literals from __future__ import unicode_literals


import re
import frappe import frappe
from frappe import _ from frappe import _


@@ -12,6 +13,9 @@ from frappe.model.document import Document
from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.notifications import delete_notification_count_for from frappe.desk.notifications import delete_notification_count_for
from frappe.modules import make_boilerplate from frappe.modules import make_boilerplate
from frappe.model.db_schema import validate_column_name

class InvalidFieldNameError(frappe.ValidationError): pass


form_grid_templates = { form_grid_templates = {
"fields": "templates/form_grid/fields.html" "fields": "templates/form_grid/fields.html"
@@ -36,7 +40,6 @@ class DocType(Document):
frappe.throw(_("{0} not allowed in name").format(c)) frappe.throw(_("{0} not allowed in name").format(c))
self.validate_series() self.validate_series()
self.scrub_field_names() self.scrub_field_names()
self.validate_title_field()
self.validate_document_type() self.validate_document_type()
validate_fields(self) validate_fields(self)


@@ -77,13 +80,6 @@ class DocType(Document):
else: else:
d.fieldname = d.fieldtype.lower().replace(" ","_") + "_" + str(d.idx) d.fieldname = d.fieldtype.lower().replace(" ","_") + "_" + str(d.idx)



def validate_title_field(self):
"""Throw exception if `title_field` is not a valid field."""
if self.title_field and \
self.title_field not in [d.fieldname for d in self.get("fields")]:
frappe.throw(_("Title field must be a valid fieldname"))

def validate_series(self, autoname=None, name=None): def validate_series(self, autoname=None, name=None):
"""Validate if `autoname` property is correctly set.""" """Validate if `autoname` property is correctly set."""
if not autoname: autoname = self.autoname if not autoname: autoname = self.autoname
@@ -207,7 +203,7 @@ class DocType(Document):
return max_idx and max_idx[0][0] or 0 return max_idx and max_idx[0][0] or 0


def validate_fields_for_doctype(doctype): def validate_fields_for_doctype(doctype):
validate_fields(frappe.get_meta(doctype))
validate_fields(frappe.get_meta(doctype, cached=False))


# this is separate because it is also called via custom field # this is separate because it is also called via custom field
def validate_fields(meta): def validate_fields(meta):
@@ -223,13 +219,11 @@ def validate_fields(meta):
9. Precision is set in numeric fields and is between 1 & 6. 9. Precision is set in numeric fields and is between 1 & 6.
10. Fold is not at the end (if set). 10. Fold is not at the end (if set).
11. `search_fields` are valid. 11. `search_fields` are valid.
12. `title_field` and title field pattern are valid.


:param meta: `frappe.model.meta.Meta` object to check.""" :param meta: `frappe.model.meta.Meta` object to check."""
def check_illegal_characters(fieldname): def check_illegal_characters(fieldname):
for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$',
'(', ')', '[', ']', '/']:
if c in fieldname:
frappe.throw(_("{0} not allowed in fieldname {1}").format(c, fieldname))
validate_column_name(fieldname)


def check_unique_fieldname(fieldname): def check_unique_fieldname(fieldname):
duplicates = filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields)) duplicates = filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))
@@ -305,6 +299,7 @@ def validate_fields(meta):
frappe.throw(_("Fold can not be at the end of the form")) frappe.throw(_("Fold can not be at the end of the form"))


def check_search_fields(meta): def check_search_fields(meta):
"""Throw exception if `search_fields` don't contain valid fields."""
if not meta.search_fields: if not meta.search_fields:
return return


@@ -314,7 +309,32 @@ def validate_fields(meta):
if fieldname not in fieldname_list: if fieldname not in fieldname_list:
frappe.throw(_("Search Fields should contain valid fieldnames")) frappe.throw(_("Search Fields should contain valid fieldnames"))


def check_title_field(meta):
"""Throw exception if `title_field` isn't a valid fieldname."""
if not meta.title_field:
return

fieldname_list = [d.fieldname for d in fields]

if meta.title_field not in fieldname_list:
frappe.throw(_("Title field must be a valid fieldname"), InvalidFieldNameError)

def _validate_title_field_pattern(pattern):
if not pattern:
return

for fieldname in re.findall("{(.*?)}", pattern, re.UNICODE):
if fieldname.startswith("{"):
# edge case when double curlies are used for escape
continue

if fieldname not in fieldname_list:
frappe.throw(_("{{{0}}} is not a valid fieldname pattern. It should be {{field_name}}.").format(fieldname),
InvalidFieldNameError)


df = meta.get_field(meta.title_field)
_validate_title_field_pattern(df.options)
_validate_title_field_pattern(df.default)


fields = meta.get("fields") fields = meta.get("fields")
for d in fields: for d in fields:
@@ -334,6 +354,7 @@ def validate_fields(meta):


check_fold(fields) check_fold(fields)
check_search_fields(meta) check_search_fields(meta)
check_title_field(meta)


def validate_permissions_for_doctype(doctype, for_remove=False): def validate_permissions_for_doctype(doctype, for_remove=False):
"""Validates if permissions are set correctly.""" """Validates if permissions are set correctly."""


+ 6
- 0
frappe/core/doctype/file/test_file.py 查看文件

@@ -15,6 +15,12 @@ class TestFile(unittest.TestCase):
self.delete_test_data() self.delete_test_data()
self.upload_file() self.upload_file()


def tearDown(self):
try:
frappe.get_doc("File", {"file_name": "file_copy.txt"}).delete()
except frappe.DoesNotExistError:
pass

def delete_test_data(self): def delete_test_data(self):
for f in frappe.db.sql('''select name, file_name from tabFile where for f in frappe.db.sql('''select name, file_name from tabFile where
is_home_folder = 0 and is_attachments_folder = 0 order by rgt-lft asc'''): is_home_folder = 0 and is_attachments_folder = 0 order by rgt-lft asc'''):


+ 38
- 15
frappe/custom/doctype/customize_form/customize_form.json 查看文件

@@ -78,14 +78,35 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box",
"fieldname": "search_fields",
"fieldtype": "Data",
"depends_on": "",
"fieldname": "max_attachments",
"fieldtype": "Int",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 1,
"label": "Search Fields",
"in_list_view": 0,
"label": "Max Attachments",
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "allow_copy",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Hide Copy",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
@@ -121,16 +142,17 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"depends_on": "",
"fieldname": "max_attachments",
"fieldtype": "Int",
"description": "Use this fieldname to generate title",
"fieldname": "title_field",
"fieldtype": "Data",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Max Attachments",
"label": "Title Field",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0, "report_hide": 0,
@@ -143,13 +165,14 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "allow_copy",
"fieldtype": "Check",
"description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box",
"fieldname": "search_fields",
"fieldtype": "Data",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0,
"label": "Hide Copy",
"in_list_view": 1,
"label": "Search Fields",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
@@ -234,7 +257,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Sort Order",
"label": "Sort Order",
"no_copy": 0, "no_copy": 0,
"options": "ASC\nDESC", "options": "ASC\nDESC",
"permlevel": 0, "permlevel": 0,
@@ -301,7 +324,7 @@
"is_submittable": 0, "is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"modified": "2015-10-02 07:17:18.939161",
"modified": "2015-10-21 08:11:19.151364",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Custom", "module": "Custom",
"name": "Customize Form", "name": "Customize Form",


+ 1
- 0
frappe/custom/doctype/customize_form/customize_form.py 查看文件

@@ -16,6 +16,7 @@ from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
class CustomizeForm(Document): class CustomizeForm(Document):
doctype_properties = { doctype_properties = {
'search_fields': 'Data', 'search_fields': 'Data',
'title_field': 'Data',
'sort_field': 'Data', 'sort_field': 'Data',
'sort_order': 'Data', 'sort_order': 'Data',
'default_print_format': 'Data', 'default_print_format': 'Data',


+ 28
- 0
frappe/custom/doctype/customize_form/test_customize_form.py 查看文件

@@ -4,10 +4,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, unittest, json import frappe, unittest, json
from frappe.test_runner import make_test_records_for_doctype from frappe.test_runner import make_test_records_for_doctype
from frappe.core.doctype.doctype.doctype import InvalidFieldNameError


test_dependencies = ["Custom Field", "Property Setter"] test_dependencies = ["Custom Field", "Property Setter"]
class TestCustomizeForm(unittest.TestCase): class TestCustomizeForm(unittest.TestCase):
def insert_custom_field(self): def insert_custom_field(self):
frappe.delete_doc_if_exists("Custom Field", "User-test_custom_field")
frappe.get_doc({ frappe.get_doc({
"doctype": "Custom Field", "doctype": "Custom Field",
"dt": "User", "dt": "User",
@@ -174,3 +176,29 @@ class TestCustomizeForm(unittest.TestCase):


# allow for custom field # allow for custom field
self.assertEquals(d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit, 1) self.assertEquals(d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit, 1)

def test_title_field_pattern(self):
d = self.get_customize_form("Web Form")

df = d.get("fields", {"fieldname": "title"})[0]

# invalid fieldname
df.options = """{doc_type} - {introduction_test}"""
self.assertRaises(InvalidFieldNameError, d.run_method, "save_customization")

# space in formatter
df.options = """{doc_type} - {introduction text}"""
self.assertRaises(InvalidFieldNameError, d.run_method, "save_customization")

# valid fieldname
df.options = """{doc_type} - {introduction_text}"""
d.run_method("save_customization")

# valid fieldname with escaped curlies
df.options = """{{ {doc_type} }} - {introduction_text}"""
d.run_method("save_customization")

# undo
df.options = None
d.run_method("save_customization")


+ 5
- 0
frappe/email/doctype/email_account/test_email_account.py 查看文件

@@ -58,6 +58,11 @@ class TestEmailAccount(unittest.TestCase):
attachments = get_attachments(comm.doctype, comm.name) attachments = get_attachments(comm.doctype, comm.name)
self.assertTrue("erpnext-conf-14.png" in [f.file_name for f in attachments]) self.assertTrue("erpnext-conf-14.png" in [f.file_name for f in attachments])


# cleanup
existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'})
frappe.delete_doc("File", existing_file.name)
delete_file_from_filesystem(existing_file)

def test_outgoing(self): def test_outgoing(self):
frappe.flags.sent_mail = None frappe.flags.sent_mail = None
make(subject = "test-mail-000", content="test mail 000", recipients="test_receiver@example.com", make(subject = "test-mail-000", content="test mail 000", recipients="test_receiver@example.com",


+ 2
- 1
frappe/utils/file_manager.py 查看文件

@@ -164,9 +164,10 @@ def save_file(fname, content, dt, dn, folder=None, decode=False):
f = frappe.get_doc(file_data) f = frappe.get_doc(file_data)
f.flags.ignore_permissions = True f.flags.ignore_permissions = True
try: try:
f.insert();
f.insert()
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
return frappe.get_doc("File", f.duplicate_entry) return frappe.get_doc("File", f.duplicate_entry)

return f return f


def get_file_data_from_hash(content_hash): def get_file_data_from_hash(content_hash):


正在加载...
取消
保存