text: # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  # MIT License. See license.txt
  
  from __future__ import unicode_literals
  """
  	Customize Form is a Single DocType used to mask the Property Setter
  	Thus providing a better UI from user perspective
  """
  import frappe, json
  from frappe import _
  from frappe.model.document import Document
  from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
  
  class CustomizeForm(Document):
  	doctype_properties = {
  		'search_fields': 'Data',
  		'sort_field': 'Data',
  		'sort_order': 'Data',
  		'default_print_format': 'Data',
  		'read_only_onload': 'Check',
  		'allow_attach': 'Check',
  		'allow_copy': 'Check',
  		'max_attachments': 'Int'
  	}
  
  	docfield_properties = {
  		'idx': 'Int',
  		'label': 'Data',
  		'fieldtype': 'Select',
  		'options': 'Text',
  		'permlevel': 'Int',
  		'width': 'Data',
  		'print_width': 'Data',
  		'reqd': 'Check',
  		'ignore_user_permissions': 'Check',
  		'in_filter': 'Check',
  		'in_list_view': 'Check',
  		'hidden': 'Check',
  		'print_hide': 'Check',
  		'report_hide': 'Check',
  		'allow_on_submit': 'Check',
  		'depends_on': 'Data',
  		'description': 'Text',
  		'default': 'Text'
  	}
  
  	allowed_fieldtype_change = (('Currency', 'Float'), ('Small Text', 'Data'),
  		('Text', 'Text Editor', 'Code'), ('Data', 'Select'))
  
  	def on_update(self):
  		frappe.db.sql("delete from tabSingles where doctype='Customize Form'")
  		frappe.db.sql("delete from `tabCustomize Form Field`")
  
  	def fetch_to_customize(self):
  		self.clear_existing_doc()
  		if not self.doc_type:
  			return
  
  		meta = frappe.get_meta(self.doc_type)
  
  		# doctype properties
  		for property in self.doctype_properties:
  			self.set(property, meta.get(property))
  
  		for d in meta.get("fields"):
  			new_d = {"fieldname": d.fieldname, "is_custom_field": d.get("is_custom_field"), "name": d.name}
  			for property in self.docfield_properties:
  				new_d[property] = d.get(property)
  			self.append("customize_form_fields", new_d)
  
  		# NOTE doc is sent to clientside by run_method
  
  	def clear_existing_doc(self):
  		doc_type = self.doc_type
  
  		for fieldname in self.meta.get_valid_columns():
  			self.set(fieldname, None)
  
  		for df in self.meta.get_table_fields():
  			self.set(df.fieldname, [])
  
  		self.doc_type = doc_type
  		self.name = "Customize Form"
  
  	def save_customization(self):
  		if not self.doc_type:
  			return
  
  		self.set_property_setters()
  		self.update_custom_fields()
  		self.set_idx_property_setter()
  		validate_fields_for_doctype(self.doc_type)
  
  		frappe.msgprint(_("{0} updated").format(_(self.doc_type)))
  		frappe.clear_cache(doctype=self.doc_type)
  		self.fetch_to_customize()
  
  	def set_property_setters(self):
  		meta = frappe.get_meta(self.doc_type)
  		# doctype property setters
  		for property in self.doctype_properties:
  			if self.get(property) != meta.get(property):
  				self.make_property_setter(property=property, value=self.get(property),
  					property_type=self.doctype_properties[property])
  
  		for df in self.get("customize_form_fields"):
  			if df.get("__islocal"):
  				continue
  
  			meta_df = meta.get("fields", {"fieldname": df.fieldname})
  
  			if not meta_df or meta_df[0].get("is_custom_field"):
  				continue
  
  			for property in self.docfield_properties:
  				if df.get(property) != meta_df[0].get(property):
  					if property == "fieldtype":
  						self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property))
  
  					self.make_property_setter(property=property, value=df.get(property),
  						property_type=self.docfield_properties[property], fieldname=df.fieldname)
  
  	def update_custom_fields(self):
  		for df in self.get("customize_form_fields"):
  			if df.get("__islocal"):
  				self.add_custom_field(df)
  			else:
  				self.update_in_custom_field(df)
  
  		self.delete_custom_fields()
  
  	def add_custom_field(self, df):
  		d = frappe.new_doc("Custom Field")
  		d.dt = self.doc_type
  		for property in self.docfield_properties:
  			d.set(property, df.get(property))
  		d.insert()
  		df.fieldname = d.fieldname
  
  	def update_in_custom_field(self, df):
  		meta = frappe.get_meta(self.doc_type)
  		meta_df = meta.get("fields", {"fieldname": df.fieldname})
  		if not (meta_df and meta_df[0].get("is_custom_field")):
  			return
  
  		custom_field = frappe.get_doc("Custom Field", meta_df[0].name)
  		changed = False
  		for property in self.docfield_properties:
  			if df.get(property) != custom_field.get(property):
  				if property == "fieldtype":
  					self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property))
  
  				custom_field.set(property, df.get(property))
  				changed = True
  
  		if changed:
  			custom_field.save()
  
  	def delete_custom_fields(self):
  		meta = frappe.get_meta(self.doc_type)
  		fields_to_remove = (set([df.fieldname for df in meta.get("fields")])
  			- set(df.fieldname for df in self.get("customize_form_fields")))
  
  		for fieldname in fields_to_remove:
  			df = meta.get("fields", {"fieldname": fieldname})[0]
  			if df.get("is_custom_field"):
  				frappe.delete_doc("Custom Field", df.name)
  
  	def set_idx_property_setter(self):
  		meta = frappe.get_meta(self.doc_type)
  		field_order_has_changed = [df.fieldname for df in meta.get("fields")] != \
  			[d.fieldname for d in self.get("customize_form_fields")]
  
  		if field_order_has_changed:
  			_idx = []
  			for df in sorted(self.get("customize_form_fields"), key=lambda x: x.idx):
  				_idx.append(df.fieldname)
  
  			self.make_property_setter(property="_idx", value=json.dumps(_idx), property_type="Text")
  
  	def make_property_setter(self, property, value, property_type, fieldname=None):
  		self.delete_existing_property_setter(property, fieldname)
  
  		property_value = self.get_existing_property_value(property, fieldname)
  
  		if property_value==value:
  			return
  
  		# create a new property setter
  		# ignore validation becuase it will be done at end
  		frappe.make_property_setter({
  			"doctype": self.doc_type,
  			"doctype_or_field": "DocField" if fieldname else "DocType",
  			"fieldname": fieldname,
  			"property": property,
  			"value": value,
  			"property_type": property_type
  		}, ignore_validate=True)
  
  	def delete_existing_property_setter(self, property, fieldname=None):
  		# first delete existing property setter
  		existing_property_setter = frappe.db.get_value("Property Setter", {"doc_type": self.doc_type,
  			"property": property, "field_name['']": fieldname or ''})
  
  		if existing_property_setter:
  			frappe.delete_doc("Property Setter", existing_property_setter)
  
  	def get_existing_property_value(self, property_name, fieldname=None):
  		# check if there is any need to make property setter!
  		if fieldname:
  			property_value = frappe.db.get_value("DocField", {"parent": self.doc_type,
  				"fieldname": fieldname}, property_name)
  		else:
  			try:
  				property_value = frappe.db.get_value("DocType", self.doc_type, property_name)
  			except Exception, e:
  				if e.args[0]==1054:
  					property_value = None
  				else:
  					raise
  
  		return property_value
  
  	def validate_fieldtype_change(self, df, old_value, new_value):
  		allowed = False
  		for allowed_changes in self.allowed_fieldtype_change:
  			if (old_value in allowed_changes and new_value in allowed_changes):
  				allowed = True
  		if not allowed:
  			frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx))
  
  	def reset_to_defaults(self):
  		if not self.doc_type:
  			return
  
  		frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s
  			and ifnull(field_name, '')!='naming_series'""", self.doc_type)
  		frappe.clear_cache(doctype=self.doc_type)
  		self.fetch_to_customize()
  
  