Merge pull request #7305 from SaiFi0102/DocType-JSON-Field-Order

feat: DocType JSON changes for cleaner Git Diffs
This commit is contained in:
Rushabh Mehta 2019-04-24 14:21:56 +05:30 committed by GitHub
commit a6ddb424f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 204 additions and 4 deletions

View file

@ -1297,7 +1297,7 @@ def get_value(*args, **kwargs):
def as_json(obj, indent=1):
from frappe.utils.response import json_handler
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler)
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': '))
def are_emails_muted():
from frappe.utils import cint

View file

@ -17,7 +17,10 @@ from frappe.desk.notifications import delete_notification_count_for
from frappe.modules import make_boilerplate, get_doc_path
from frappe.model.db_schema import validate_column_name, validate_column_length, type_map
from frappe.model.docfield import supports_translation
from frappe.modules.import_file import get_file_path
from six import iteritems
import frappe.website.render
import json
# imports - third-party imports
import pymysql
@ -240,7 +243,8 @@ class DocType(Document):
self.update_fields_to_fetch()
from frappe import conf
if not self.custom and not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode'):
allow_doctype_export = frappe.flags.allow_doctype_export or (not frappe.flags.in_test and conf.get('developer_mode'))
if not self.custom and not frappe.flags.in_import and allow_doctype_export:
self.export_doc()
self.make_controller_template()
@ -390,6 +394,72 @@ class DocType(Document):
if naming_series[0].default:
make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False)
def before_export(self, docdict):
# remove null and empty fields
def remove_null_fields(o):
to_remove = []
for attr, value in iteritems(o):
if isinstance(value, list):
for v in value:
remove_null_fields(v)
elif not value:
to_remove.append(attr)
for attr in to_remove:
del o[attr]
remove_null_fields(docdict)
# retain order of 'fields' table and change order in 'field_order'
docdict["field_order"] = [f.fieldname for f in self.fields]
path = get_file_path(self.module, "DocType", self.name)
if os.path.exists(path):
try:
with open(path, 'r') as txtfile:
olddoc = json.loads(txtfile.read())
old_field_names = [f['fieldname'] for f in olddoc.get("fields", [])]
if old_field_names:
new_field_dicts = []
remaining_field_names = [f.fieldname for f in self.fields]
for fieldname in old_field_names:
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
if field_dict:
new_field_dicts.append(field_dict[0])
remaining_field_names.remove(fieldname)
for fieldname in remaining_field_names:
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
new_field_dicts.append(field_dict[0])
docdict['fields'] = new_field_dicts
except ValueError:
pass
@staticmethod
def prepare_for_import(docdict):
# set order of fields from field_order
if docdict.get("field_order"):
new_field_dicts = []
remaining_field_names = [f['fieldname'] for f in docdict.get('fields', [])]
for fieldname in docdict.get('field_order'):
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
if field_dict:
new_field_dicts.append(field_dict[0])
remaining_field_names.remove(fieldname)
for fieldname in remaining_field_names:
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
new_field_dicts.append(field_dict[0])
docdict['fields'] = new_field_dicts
if "field_order" in docdict:
del docdict["field_order"]
def export_doc(self):
"""Export to standard folder `[module]/doctype/[name]/[name].json`."""
from frappe.modules.export_file import export_to_files

View file

@ -104,4 +104,125 @@ class TestDocType(unittest.TestCase):
for depends_on in ["depends_on", "collapsible_depends_on"]:
condition = field.get(depends_on)
if condition:
self.assertFalse(re.match(pattern, condition))
self.assertFalse(re.match(pattern, condition))
def test_sync_field_order(self):
from frappe.modules.import_file import get_file_path
import os
# create test doctype
test_doctype = frappe.get_doc({
"doctype": "DocType",
"module": "Core",
"fields": [
{
"label": "Field 1",
"fieldname": "field_1",
"fieldtype": "Data"
},
{
"label": "Field 2",
"fieldname": "field_2",
"fieldtype": "Data"
},
{
"label": "Field 3",
"fieldname": "field_3",
"fieldtype": "Data"
},
{
"label": "Field 4",
"fieldname": "field_4",
"fieldtype": "Data"
}
],
"permissions": [{
"role": "System Manager",
"read": 1
}],
"name": "Test Field Order DocType",
"__islocal": 1
})
path = get_file_path(test_doctype.module, test_doctype.doctype, test_doctype.name)
initial_fields_order = ['field_1', 'field_2', 'field_3', 'field_4']
frappe.delete_doc_if_exists("DocType", "Test Field Order DocType")
if os.path.isfile(path):
os.remove(path)
try:
frappe.flags.allow_doctype_export = 1
test_doctype.save()
# assert that field_order list is being created with the default order
test_doctype_json = frappe.get_file_json(path)
self.assertTrue(test_doctype_json.get("field_order"))
self.assertEqual(len(test_doctype_json['fields']), len(test_doctype_json['field_order']))
self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], test_doctype_json['field_order'])
self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], initial_fields_order)
self.assertListEqual(test_doctype_json['field_order'], initial_fields_order)
# remove field_order to test reload_doc/sync/migrate is backwards compatible without field_order
del test_doctype_json['field_order']
with open(path, 'w+') as txtfile:
txtfile.write(frappe.as_json(test_doctype_json))
# assert that field_order is actually removed from the json file
test_doctype_json = frappe.get_file_json(path)
self.assertFalse(test_doctype_json.get("field_order"))
# make sure that migrate/sync is backwards compatible without field_order
frappe.reload_doctype(test_doctype.name, force=True)
test_doctype.reload()
# assert that field_order list is being created with the default order again
test_doctype.save()
test_doctype_json = frappe.get_file_json(path)
self.assertTrue(test_doctype_json.get("field_order"))
self.assertEqual(len(test_doctype_json['fields']), len(test_doctype_json['field_order']))
self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], test_doctype_json['field_order'])
self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], initial_fields_order)
self.assertListEqual(test_doctype_json['field_order'], initial_fields_order)
# reorder fields: swap row 1 and 3
test_doctype.fields[0], test_doctype.fields[2] = test_doctype.fields[2], test_doctype.fields[0]
for i, f in enumerate(test_doctype.fields):
f.idx = i + 1
# assert that reordering fields only affects `field_order` rather than `fields` attr
test_doctype.save()
test_doctype_json = frappe.get_file_json(path)
self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], initial_fields_order)
self.assertListEqual(test_doctype_json['field_order'], ['field_3', 'field_2', 'field_1', 'field_4'])
# reorder `field_order` in the json file: swap row 2 and 4
test_doctype_json['field_order'][1], test_doctype_json['field_order'][3] = test_doctype_json['field_order'][3], test_doctype_json['field_order'][1]
with open(path, 'w+') as txtfile:
txtfile.write(frappe.as_json(test_doctype_json))
# assert that reordering `field_order` from json file is reflected in DocType upon migrate/sync
frappe.reload_doctype(test_doctype.name, force=True)
test_doctype.reload()
self.assertListEqual([f.fieldname for f in test_doctype.fields], ['field_3', 'field_4', 'field_1', 'field_2'])
# insert row in the middle and remove first row (field 3)
test_doctype.append("fields", {
"label": "Field 5",
"fieldname": "field_5",
"fieldtype": "Data"
})
test_doctype.fields[4], test_doctype.fields[3] = test_doctype.fields[3], test_doctype.fields[4]
test_doctype.fields[3], test_doctype.fields[2] = test_doctype.fields[2], test_doctype.fields[3]
test_doctype.remove(test_doctype.fields[0])
for i, f in enumerate(test_doctype.fields):
f.idx = i + 1
test_doctype.save()
test_doctype_json = frappe.get_file_json(path)
self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], ['field_1', 'field_2', 'field_4', 'field_5'])
self.assertListEqual(test_doctype_json['field_order'], ['field_4', 'field_5', 'field_1', 'field_2'])
except:
raise
finally:
frappe.flags.allow_doctype_export = 0

View file

@ -23,6 +23,7 @@ def export_to_files(record_list=None, record_module=None, verbose=0, create_init
def write_document_file(doc, record_module=None, create_init=True):
newdoc = doc.as_dict(no_nulls=True)
doc.run_method("before_export", newdoc)
# strip out default fields from children
for df in doc.meta.get_table_fields():
@ -38,7 +39,7 @@ def write_document_file(doc, record_module=None, create_init=True):
# write the data file
fname = scrub(doc.name)
with open(os.path.join(folder, fname +".json"),'w+') as txtfile:
with open(os.path.join(folder, fname + ".json"), 'w+') as txtfile:
txtfile.write(frappe.as_json(newdoc))
def get_module_name(doc):

View file

@ -6,6 +6,7 @@ from __future__ import unicode_literals, print_function
import frappe, os, json
from frappe.modules import get_module_path, scrub_dt_dn
from frappe.utils import get_datetime_str
from frappe.model.base_document import get_controller
ignore_values = {
"Report": ["disabled", "prepared_report"],
@ -97,8 +98,15 @@ def import_doc(docdict, force=False, data_import=False, pre_process=None,
ignore_version=None, reset_permissions=False):
frappe.flags.in_import = True
docdict["__islocal"] = 1
controller = get_controller(docdict['doctype'])
if controller and hasattr(controller, 'prepare_for_import') and callable(getattr(controller, 'prepare_for_import')):
controller.prepare_for_import(docdict)
doc = frappe.get_doc(docdict)
doc.run_method("before_import")
doc.flags.ignore_version = ignore_version
if pre_process:
pre_process(doc)